@jbrowse/plugin-circular-view 4.1.3 → 4.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
3
  import { Dialog, ErrorMessage } from '@jbrowse/core/ui';
4
4
  import { getSession, useLocalStorage } from '@jbrowse/core/util';
5
- import { Button, Checkbox, CircularProgress, DialogActions, DialogContent, FormControlLabel, MenuItem, TextField, Typography, } from '@mui/material';
6
- function LoadingMessage() {
7
- return (_jsxs("div", { children: [_jsx(CircularProgress, { size: 20, style: { marginRight: 20 } }), _jsx(Typography, { display: "inline", children: "Creating SVG" })] }));
5
+ import { Button, Checkbox, CircularProgress, DialogActions, DialogContent, FormControlLabel, MenuItem, TextField, ToggleButton, ToggleButtonGroup, Typography, } from '@mui/material';
6
+ function LoadingMessage({ format }) {
7
+ return (_jsxs("div", { children: [_jsx(CircularProgress, { size: 20, style: { marginRight: 20 } }), _jsxs(Typography, { display: "inline", children: ["Creating ", format.toUpperCase()] })] }));
8
8
  }
9
9
  function TextField2({ children, ...rest }) {
10
10
  return (_jsx("div", { children: _jsx(TextField, { ...rest, children: children }) }));
@@ -18,11 +18,22 @@ export default function ExportSvgDialog({ model, handleClose, }) {
18
18
  const [rasterizeLayers, setRasterizeLayers] = useState(offscreenCanvas);
19
19
  const [loading, setLoading] = useState(false);
20
20
  const [error, setError] = useState();
21
+ const [format, setFormat] = useSvgLocal('format', 'svg');
21
22
  const [filename, setFilename] = useSvgLocal('file', 'jbrowse.svg');
22
23
  const [themeName, setThemeName] = useSvgLocal('theme', session.themeName || 'default');
23
- return (_jsxs(Dialog, { open: true, onClose: handleClose, title: "Export SVG", children: [_jsxs(DialogContent, { children: [error ? (_jsx(ErrorMessage, { error: error })) : loading ? (_jsx(LoadingMessage, {})) : null, _jsx(TextField2, { helperText: "filename", value: filename, onChange: event => {
24
- setFilename(event.target.value);
25
- } }), session.allThemes ? (_jsx(TextField2, { select: true, label: "Theme", value: themeName, onChange: event => {
24
+ return (_jsxs(Dialog, { open: true, onClose: handleClose, title: "Export image", children: [_jsxs(DialogContent, { children: [error ? (_jsx(ErrorMessage, { error: error })) : loading ? (_jsx(LoadingMessage, { format: format })) : null, _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx(TextField, { helperText: "filename", value: filename, onChange: event => {
25
+ setFilename(event.target.value);
26
+ } }), _jsxs(ToggleButtonGroup, { value: format, exclusive: true, onChange: (_event, value) => {
27
+ if (value) {
28
+ setFormat(value);
29
+ if (filename.endsWith('.svg') && value === 'png') {
30
+ setFilename(filename.replace(/\.svg$/, '.png'));
31
+ }
32
+ else if (filename.endsWith('.png') && value === 'svg') {
33
+ setFilename(filename.replace(/\.png$/, '.svg'));
34
+ }
35
+ }
36
+ }, size: "small", children: [_jsx(ToggleButton, { value: "svg", children: "SVG" }), _jsx(ToggleButton, { value: "png", children: "PNG" })] })] }), session.allThemes ? (_jsx(TextField2, { select: true, label: "Theme", value: themeName, onChange: event => {
26
37
  setThemeName(event.target.value);
27
38
  }, children: Object.entries(session.allThemes()).map(([key, val]) => (_jsx(MenuItem, { value: key, children: val.name || '(Unknown name)' }, key))) })) : null, offscreenCanvas ? (_jsx(FormControlLabel, { control: _jsx(Checkbox, { checked: rasterizeLayers, onChange: () => {
28
39
  setRasterizeLayers(val => !val);
@@ -34,6 +45,7 @@ export default function ExportSvgDialog({ model, handleClose, }) {
34
45
  try {
35
46
  await model.exportSvg({
36
47
  rasterizeLayers,
48
+ format,
37
49
  filename,
38
50
  themeName,
39
51
  });
@@ -11,6 +11,7 @@ export interface CircularViewInit {
11
11
  }
12
12
  export interface ExportSvgOptions {
13
13
  rasterizeLayers?: boolean;
14
+ format?: 'svg' | 'png';
14
15
  filename?: string;
15
16
  Wrapper?: React.FC<{
16
17
  children: React.ReactNode;
@@ -337,7 +337,38 @@ function stateModelFactory(pluginManager) {
337
337
  const { renderToSvg } = await import("./svgcomponents/SVGCircularView.js");
338
338
  const html = await renderToSvg(self, opts);
339
339
  const { saveAs } = await import('file-saver-es');
340
- saveAs(new Blob([html], { type: 'image/svg+xml' }), opts.filename || 'image.svg');
340
+ if (opts.format === 'png') {
341
+ const img = new Image();
342
+ const svgBlob = new Blob([html], { type: 'image/svg+xml' });
343
+ const url = URL.createObjectURL(svgBlob);
344
+ await new Promise((resolve, reject) => {
345
+ img.onload = () => {
346
+ const canvas = document.createElement('canvas');
347
+ canvas.width = img.width;
348
+ canvas.height = img.height;
349
+ const ctx = canvas.getContext('2d');
350
+ ctx.drawImage(img, 0, 0);
351
+ URL.revokeObjectURL(url);
352
+ canvas.toBlob(blob => {
353
+ if (blob) {
354
+ saveAs(blob, opts.filename || 'image.png');
355
+ resolve();
356
+ }
357
+ else {
358
+ reject(new Error(`Failed to create PNG. The image may be too large (${img.width}x${img.height}). Try reducing the view size or use SVG format.`));
359
+ }
360
+ }, 'image/png');
361
+ };
362
+ img.onerror = () => {
363
+ URL.revokeObjectURL(url);
364
+ reject(new Error('Failed to load SVG for PNG conversion'));
365
+ };
366
+ img.src = url;
367
+ });
368
+ }
369
+ else {
370
+ saveAs(new Blob([html], { type: 'image/svg+xml' }), opts.filename || 'image.svg');
371
+ }
341
372
  },
342
373
  }))
343
374
  .actions(self => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-circular-view",
3
- "version": "4.1.3",
3
+ "version": "4.1.4",
4
4
  "type": "module",
5
5
  "description": "JBrowse 2 circular view",
6
6
  "keywords": [
@@ -22,13 +22,13 @@
22
22
  ],
23
23
  "dependencies": {
24
24
  "@jbrowse/mobx-state-tree": "^5.5.0",
25
- "@mui/icons-material": "^7.3.7",
26
- "@mui/material": "^7.3.7",
25
+ "@mui/icons-material": "^7.3.8",
26
+ "@mui/material": "^7.3.8",
27
27
  "@types/file-saver-es": "^2.0.3",
28
28
  "file-saver-es": "^2.0.5",
29
29
  "mobx": "^6.15.0",
30
30
  "mobx-react": "^9.2.1",
31
- "@jbrowse/core": "^4.1.3"
31
+ "@jbrowse/core": "^4.1.4"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "react": ">=18.0.0",