diff --git a/samples/react-image-editor/package.json b/samples/react-image-editor/package.json index c703398855..c599d33d07 100644 --- a/samples/react-image-editor/package.json +++ b/samples/react-image-editor/package.json @@ -1,6 +1,6 @@ { "name": "react-image-editor", - "version": "1.0.0", + "version": "1.1.0", "private": true, "main": "lib/index.js", "engines": { diff --git a/samples/react-image-editor/src/components/ImageManipulation/ImageManipulation.tsx b/samples/react-image-editor/src/components/ImageManipulation/ImageManipulation.tsx index 18e4fcaaae..5ec949cc25 100644 --- a/samples/react-image-editor/src/components/ImageManipulation/ImageManipulation.tsx +++ b/samples/react-image-editor/src/components/ImageManipulation/ImageManipulation.tsx @@ -85,10 +85,9 @@ export class ImageManipulation extends React.Component { - const aspect = this.state.lockAspectResize ? this.getAspect() : undefined; - this.setResize(size.width, size.height, aspect); - }} + onChange={(size) => this.setResize(size.width, size.height, lastdata.aspect)} /> ); } - private getMaxWidth(): string { @@ -792,7 +755,23 @@ export class ImageManipulation extends React.Component= 0) { - values.sx = Math.min(sx, currentwidth - 1); + if (sx >= currentwidth) { + values.sx = currentwidth - 1; + } else { + values.sx = sx; + } + + // limit max width + if ((values.width + values.sx) > currentwidth) { + values.width = currentwidth - values.sx; + } + } if (!isNaN(sy) && sy >= 0) { - values.sy = Math.min(sy, currentheight - 1); + if (sy >= currentheight) { + values.sy = currentheight - 1; + } else { + values.sy = sy; + } + + // limit max height + if ((values.height + values.sy) > currentheight) { + values.height = currentheight - values.sy; + } } if (!isNaN(width) && width >= 0) { - values.width = Math.min(width, currentwidth - values.sx); - if (aspect) { - values.height = values.width / aspect; + if ((width + values.sx) > currentwidth) { + values.width = currentwidth - values.sx; + } else { + values.width = width; } } if (!isNaN(height) && height >= 0) { - values.height = Math.min(height, currentheight - values.sy); - if (aspect) { - values.width = values.height * aspect; + if ((height + values.sy) > currentheight) { + values.height = currentheight - values.sy; + } else { + values.height = height; } } - values.aspect = aspect; - this.addOrUpdateLastManipulation(values); - } + if (isNaN(values.aspect) && !isNaN(aspect)) { + // aspect added - private setResize(width: number, height: number, aspect: number): void { - const values: IResizeSettings = this.getResizeValues(); - - if (width && width > 0) { - values.width = width; - if (aspect) { - values.height = width / aspect; + // limit w + if ((values.width + values.sx) > currentwidth) { + values.width = currentwidth - values.sx; } - } - if (height && height > 0) { - values.height = height; - if (aspect) { - values.width = height * aspect; + + values.height = values.width / aspect; + // limit h adn recalulate w + if ((values.height + values.sy) > currentheight) { + values.height = currentheight - values.sy; + values.width = values.height * aspect; } } - - // Ensure minimum dimensions - values.width = Math.max(values.width, 1); - values.height = Math.max(values.height, 1); - + values.aspect = aspect; + if (aspect && (!isNaN(sx) || !isNaN(width))) { + values.height = values.width / aspect; + } + if (aspect && (!isNaN(sy) || !isNaN(height))) { + values.width = values.height * aspect; + } this.addOrUpdateLastManipulation(values); } - - private setRotate(value: number): void { this.addOrUpdateLastManipulation({ @@ -928,23 +923,25 @@ export class ImageManipulation extends React.Component 0 && state[state.length - 1].type === changed.type) { state[state.length - 1] = changed; } else { state.push(changed); } - - // Clear redo settings and trigger the settingsChanged callback - this.setState({ redosettings: [] }, () => { + + if (this.state.redosettings && this.state.redosettings.length > 0) { + this.setState({ redosettings: [] }, () => { + if (this.props.settingsChanged) { + this.props.settingsChanged(state); + } + }); + } else { if (this.props.settingsChanged) { - console.log('Updating settings:', state); // Debugging this.props.settingsChanged(state); } - }); + } } - private removeLastManipulation(): void { if (this.props.settings && this.props.settings.length > 0) { diff --git a/samples/react-image-editor/src/webparts/reactImageEditor/ReactImageEditorWebPart.ts b/samples/react-image-editor/src/webparts/reactImageEditor/ReactImageEditorWebPart.ts index 955d14a554..494a66a08f 100644 --- a/samples/react-image-editor/src/webparts/reactImageEditor/ReactImageEditorWebPart.ts +++ b/samples/react-image-editor/src/webparts/reactImageEditor/ReactImageEditorWebPart.ts @@ -30,16 +30,24 @@ export default class ReactImageEditorWebPart extends BaseClientSideWebPart { this.properties.title = value; }, updateUrlProperty: (value: string) => { - // tslint:disable-next-line: curly - if (this.properties.url !== value) - this.properties.url = value; + // tslint:disable-next-line: curly + if (this.properties.url !== value) + this.properties.url = value; this.properties.settings = []; this.render(); }, @@ -73,7 +81,7 @@ export default class ReactImageEditorWebPart extends BaseClientSideWebPart { const { url, settings } = this.props; @@ -56,12 +70,18 @@ export default class ReactImageEditor extends React.Component { - this.setState({ isFilePickerOpen: false }, () => this._onUrlChanged(filePickerResult.fileAbsoluteUrl)); - }} + onSave={this.handleFileSave} onCancel={() => { this.setState({ isFilePickerOpen: false }); }} @@ -71,30 +91,194 @@ export default class ReactImageEditor extends React.Component)} - {!isConfigured ? ()} + {!isConfigured && this.props.displayMode !== DisplayMode.Edit ? + :
+ } + {!isConfigured && this.props.displayMode === DisplayMode.Edit ? () : ( - - )} +
+ { + this.props.displayMode === DisplayMode.Edit ? +
+ + { this.setState({ isFilePickerOpen: true }) }} + /> +
:
+ + } +
+ ) + } ); } + private handleFileSave = async (filePickerResult: IFilePickerResult) => { + try { + if (!filePickerResult.downloadFileContent) { + this.setState( + { isFilePickerOpen: false }, + () => this._onUrlChanged(filePickerResult.fileAbsoluteUrl) + ); + return; + } + + // Get the base URL for Site Assets without duplicating paths + const siteAssetsFolderUrl = `${this.props.context.pageContext.web.serverRelativeUrl}/SiteAssets`.replace(/\/+$/, ""); + + // Ensure the folder structure exists for the current page + const pageFolderUrl = await this.ensurePageFolder(siteAssetsFolderUrl); + + // Upload the file to the folder and get its absolute URL + const uploadedFileUrl = await this.uploadFileToFolder(filePickerResult, pageFolderUrl); + + if (uploadedFileUrl) { + // Update state and trigger URL change callback + this.setState( + { isFilePickerOpen: false }, + () => this._onUrlChanged(uploadedFileUrl) + ); + } + } catch (error) { + console.error("Error handling file save:", error); + this.setState({ isFilePickerOpen: false }); + } + }; + + + /** + * Ensures the folder hierarchy exists for the current page in the Site Assets library. + * @param siteAssetsFolderUrl The base URL of the Site Assets library. + * @returns The URL of the folder for the current page. + */ + private async ensurePageFolder(siteAssetsFolderUrl: string): Promise { + try { + const sitePagesFolderUrl = `${siteAssetsFolderUrl}/SitePages`; + + // Extract the current page name from the request path + const pageName = this.getPageName(); + if (!pageName) { + throw new Error("Unable to determine the current page name."); + } + + const pageFolderUrl = `${sitePagesFolderUrl}/${pageName}`; + + // Ensure "SitePages" folder exists + await this.ensureFolder(siteAssetsFolderUrl, "SitePages"); + + // Ensure the folder for the current page exists + await this.ensureFolder(sitePagesFolderUrl, pageName); + + return pageFolderUrl; + } catch (error) { + console.error("Error ensuring page folder:", error); + throw error; + } +} + + + /** + * Extracts the page name from the server request path. + * @returns The current page name or null if not determinable. + */ + private getPageName(): string | null { + const requestPath = this.props.context.pageContext.site.serverRequestPath; + if (!requestPath) { + return null; + } + return requestPath.split('/').pop() ? requestPath.split('/').pop()!.replace(/\.[^/.]+$/, "") : null; + + } + + + /** + * Ensures a folder exists under a given parent folder. + * @param parentFolderUrl The parent folder's URL. + * @param folderName The name of the folder to ensure. + */ + private async ensureFolder(parentFolderUrl: string, folderName: string): Promise { + try { + const folderUrl = `${parentFolderUrl}/${folderName}`; + const folder = await sp.web.getFolderByServerRelativeUrl(folderUrl).get(); + console.log(`Folder '${folderName}' already exists under '${parentFolderUrl}'`); + } catch (error) { + if (error.message.includes("404")) { + // If the folder does not exist (404 error), create it + await sp.web.getFolderByServerRelativeUrl(parentFolderUrl).folders.add(folderName); + console.log(`Folder '${folderName}' created under '${parentFolderUrl}'`); + } else { + console.error("Error checking or creating folder:", error); + throw error; // Rethrow other unexpected errors + } + } + } + + + /** + * Uploads a file to a specified folder with a unique filename. + * @param filePickerResult The result from the FilePicker. + * @param folderUrl The URL of the folder to upload the file to. + * @returns The absolute URL of the uploaded file. + */ + private async uploadFileToFolder(filePickerResult: any, folderUrl: string): Promise { + try { + if (!filePickerResult.downloadFileContent) { + console.error("No file content to upload."); + return null; + } + + const fileBlob = await filePickerResult.downloadFileContent(); + + // Generate a unique filename with a timestamp + const timestamp = new Date().toISOString().replace(/[-:.]/g, ""); + const uniqueFileName = `${filePickerResult.fileName.replace(/\.[^/.]+$/, "")}_${timestamp}${filePickerResult.fileName.match(/\.[^/.]+$/)[0]}`; + + // Convert the fileBlob to an ArrayBuffer + const arrayBuffer = await fileBlob.arrayBuffer(); + + // Upload the file + const uploadResult = await sp.web.getFolderByServerRelativeUrl(folderUrl).files.add(uniqueFileName, arrayBuffer, true); + + // Return the absolute URL of the uploaded file + return `${this.props.context.pageContext.web.absoluteUrl}${uploadResult.data.ServerRelativeUrl}`; + } catch (error) { + console.error("Error uploading file:", error); + return null; + } + } + + private _clearSelection = () => { + this.props.updateUrlProperty(""); + this.setState({ + isFilePickerOpen: false + }); + } private _onConfigure = () => { if (Environment.type === EnvironmentType.Local) { this.setState({ isFilePickerOpen: false }, () => {