ソースを参照

copy screenshot workaround for browsers without ClipboardItem support

David Sehnal 4 年 前
コミット
397f001352

+ 14 - 1
src/mol-plugin-ui/controls/screenshot.tsx

@@ -54,9 +54,20 @@ export function ScreenshotPreview({ plugin, suspend, frameColor = 'rgba(255, 87,
         subscribe(helper.behaviors.values, () => updateQueue.next());
         subscribe(helper.behaviors.cropParams, () => updateQueue.next());
 
+        let resizeObserver: any = void 0;
+        if (typeof ResizeObserver !== 'undefined') {
+            resizeObserver = new ResizeObserver(() => updateQueue.next());
+        }
+
+        const canvas = canvasRef.current;
+        resizeObserver?.observe(canvas);
+
         preview();
 
-        return () => subs.forEach(s => s.unsubscribe());
+        return () => {
+            subs.forEach(s => s.unsubscribe());
+            resizeObserver?.unobserve(canvas);
+        };
     }, [helper]);
 
     return <>
@@ -67,6 +78,8 @@ export function ScreenshotPreview({ plugin, suspend, frameColor = 'rgba(255, 87,
     </>;
 }
 
+declare const ResizeObserver: any;
+
 function drawPreview(helper: ViewportScreenshotHelper, target: HTMLCanvasElement) {
     const { canvas, width, height } = helper.getPreview()!;
     const ctx = target.getContext('2d');

+ 17 - 0
src/mol-plugin-ui/skin/base/components/controls.scss

@@ -401,4 +401,21 @@
         font-size: 80%;
         line-height: 15px;
     }
+}
+
+.msp-copy-image-wrapper {
+    position: relative;
+
+    div {
+        font-weight: bold;
+        padding: 3px;
+        margin: 1px 0;
+        width: 100%;
+        background: $msp-form-control-background;
+        text-align: center;
+    }
+
+    img {
+        margin-top: 1px;
+    }
 }

+ 21 - 4
src/mol-plugin-ui/viewport/screenshot.tsx

@@ -17,8 +17,9 @@ import { useBehavior } from '../hooks/use-behavior';
 import { LocalStateSnapshotParams, StateExportImportControls } from '../state/snapshots';
 
 interface ImageControlsState {
-    showPreview: boolean
-    isDisabled: boolean
+    showPreview: boolean,
+    isDisabled: boolean,
+    imageData?: string
 }
 
 export class DownloadScreenshotControls extends PluginUIComponent<{ close: () => void }, ImageControlsState> {
@@ -41,18 +42,29 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
         });
     }
 
+    private copyImg = async () => {
+        const src = await this.plugin.helpers.viewportScreenshot?.getImageDataUri();
+        this.setState({ imageData: src });
+    }
+
     componentDidMount() {
         this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
             this.setState({ isDisabled: v });
         });
     }
 
+    componentWillUnmount() {
+        this.setState({ imageData: void 0 });
+    }
+
     open = (e: React.ChangeEvent<HTMLInputElement>) => {
         if (!e.target.files || !e.target.files![0]) return;
         PluginCommands.State.Snapshots.OpenFile(this.plugin, { file: e.target.files![0] });
     }
 
     render() {
+        const hasClipboardApi = !!(navigator.clipboard as any).write;
+
         return <div>
             {this.state.showPreview && <div className='msp-image-preview'>
                 <div style={{ height: '180px', width: '100%', position: 'relative' }}>
@@ -61,10 +73,15 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
                 <CropControls plugin={this.plugin} />
             </div>}
             <div className='msp-flex-row'>
-                {/* TODO: figure out how to do copy/paste in Firefox */}
-                {!!(navigator.clipboard as any).write && <Button icon={CopySvg} onClick={this.copy} disabled={this.state.isDisabled}>Copy</Button>}
+                {hasClipboardApi && <Button icon={CopySvg} onClick={this.copy} disabled={this.state.isDisabled}>Copy</Button>}
+                {!hasClipboardApi && !this.state.imageData && <Button icon={CopySvg} onClick={this.copyImg} disabled={this.state.isDisabled}>Copy</Button>}
+                {this.state.imageData && <Button onClick={() => this.setState({ imageData: void 0 })} disabled={this.state.isDisabled}>Clear</Button>}
                 <Button icon={GetAppSvg} onClick={this.download} disabled={this.state.isDisabled}>Download</Button>
             </div>
+            {this.state.imageData && <div className='msp-row msp-copy-image-wrapper'>
+                <div>Right click below + Copy Image</div>
+                <img src={this.state.imageData} style={{ width: '100%', height: 32, display: 'block' }} />
+            </div>}
             <ScreenshotParams plugin={this.plugin} isDisabled={this.state.isDisabled} />
             <ExpandGroup header='State'>
                 <StateExportImportControls onAction={this.props.close} />

+ 9 - 1
src/mol-plugin/util/viewport-screenshot.ts

@@ -309,7 +309,7 @@ class ViewportScreenshotHelper extends PluginComponent {
 
         return Task.create('Copy Image', async ctx => {
             await this.draw(ctx);
-            await ctx.update('Downloading image...');
+            await ctx.update('Converting image...');
             const blob = await canvasToBlob(this.canvas, 'png');
             const item = new ClipboardItem({ 'image/png': blob });
             cb.write([item]);
@@ -317,6 +317,14 @@ class ViewportScreenshotHelper extends PluginComponent {
         });
     }
 
+    getImageDataUri() {
+        return this.plugin.runTask(Task.create('Generate Image', async ctx => {
+            await this.draw(ctx);
+            await ctx.update('Converting image...');
+            return this.canvas.toDataURL('png');
+        }));
+    }
+
     copyToClipboard() {
         const task = this.copyToClipboardTask();
         if (!task) return;