Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 57 additions & 4 deletions js/panes/Pane.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@
* LICENSE file in the root directory of this source tree.
*
*/

//Pane.js
import React, { forwardRef, useRef, useState } from 'react';

import PropertyItem from './PropertyItem';
var classNames = require('classnames');

var Pane = forwardRef((props, ref) => {
const { id, title, content, children, widgets, enablePropertyList } = props;
var { barwidgets } = props;
var { barwidgets = [] } = props;

// state varibles
// --------------
const [propertyListShown, setPropertyListShown] = useState(false);
const [exportMenuOpen, setExportMenuOpen] = useState(false);
const barRef = useRef();

// public events
Expand All @@ -29,6 +30,8 @@ var Pane = forwardRef((props, ref) => {
const handleZoom = props.handleZoom || (() => {});
const handleMouseMove = props.handleMouseMove || (() => {});
const handleClose = props.handleClose || (() => props.onClose(id));
const handleShowExportHistory =
props.onShowExportHistory || (() => {});

// rendering
// ---------
Expand Down Expand Up @@ -111,10 +114,60 @@ var Pane = forwardRef((props, ref) => {
{' '}
X{' '}
</button>
<button title="save" onClick={handleDownload}>

<button
title="save"
onClick={(e) => {
e.stopPropagation();
setExportMenuOpen((v) => !v);
}}
>
{' '}
&#8681;{' '}
</button>

{exportMenuOpen && (
<div
className="export-menu"
onClick={(e) => e.stopPropagation()}
style={{
position: 'absolute',
zIndex: 20,
marginTop: '4px',
padding: '6px',
background: '#fff',
border: '1px solid #ccc',
borderRadius: '4px',
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
}}
>
<button
onClick={() => {
handleDownload('svg');
setExportMenuOpen(false);
}}
>
Export SVG
</button>
<button
onClick={() => {
handleDownload('png');
setExportMenuOpen(false);
}}
>
Export PNG
</button>
<button
onClick={() => {
handleShowExportHistory();
setExportMenuOpen(false);
}}
>
Export history
</button>
</div>
)}

<button title="reset" onClick={handleReset} hidden={!props.handleReset}>
{' '}
&#10226;{' '}
Expand Down Expand Up @@ -208,4 +261,4 @@ function PropertyList(props) {
else return propitems;
}

export default Pane;
export default Pane;
133 changes: 123 additions & 10 deletions js/panes/PlotPane.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*
*/

//PlotPane.js
import React, { useEffect, useRef, useState } from 'react';
const { usePrevious } = require('../util');
import Pane from './Pane';
Expand All @@ -22,6 +22,16 @@ var PlotPane = (props) => {
const maxsmoothvalue = 100;
const [smoothWidgetActive, setSmoothWidgetActive] = useState(false);
const [smoothvalue, setSmoothValue] = useState(1);
const [showExportHistory, setShowExportHistory] = useState(false);
const [exportHistory, setExportHistory] = useState([]);

const loadExportHistory = () => {
try {
return JSON.parse(localStorage.getItem('visdom_export_log') || '[]');
} catch {
return [];
}
};

// private events
// -------------
Expand All @@ -31,11 +41,40 @@ var PlotPane = (props) => {
const updateSmoothSlider = (value) => {
setSmoothValue(value);
};
const handleDownload = () => {

const saveExportLog = (entry) => {
try {
const existing = JSON.parse(
localStorage.getItem('visdom_export_log') || '[]'
);
existing.push(entry);
localStorage.setItem('visdom_export_log', JSON.stringify(existing));
} catch (err) {
console.error('Failed to save export log', err);
}
};

const handleDownload = (format = 'svg') => {
console.log('handleDownload clicked', contentID, format);
const filename = `${contentID}_${new Date()
.toISOString()
.replace(/[:.]/g, '-')}`;

Plotly.downloadImage(plotlyRef.current, {
format: 'svg',
filename: contentID,
format,
filename,
});

saveExportLog({
contentID,
filename,
format,
timestamp: new Date().toISOString(),
title: content?.layout?.title || '',
status: 'success',
});

setExportHistory(loadExportHistory().slice().reverse());
};

// events
Expand Down Expand Up @@ -72,8 +111,27 @@ var PlotPane = (props) => {
}

newPlot();

// 🔥 ADD THIS BELOW newPlot()
setTimeout(() => {
if (plotlyRef.current) {
Plotly.Plots.resize(plotlyRef.current);
}
}, 0);
});

useEffect(() => {
if (showExportHistory) {
setExportHistory(loadExportHistory().slice().reverse());
}
// 🔥 ADD THIS
if (plotlyRef.current) {
setTimeout(() => {
Plotly.Plots.resize(plotlyRef.current);
}, 0);
}
}, [showExportHistory]);

// rendering
// ---------

Expand Down Expand Up @@ -139,6 +197,7 @@ var PlotPane = (props) => {
Plotly.react(contentID, data.concat(smooth_data), content.layout, {
showLink: true,
linkText: 'Edit',
modeBarButtonsToRemove: ['toImage'],
});
};

Expand All @@ -147,8 +206,8 @@ var PlotPane = (props) => {
return data['type'] == 'scatter' && data['mode'] == 'lines';
});

var smooth_widget_button = '';
var smooth_widget = '';
var smooth_widget_button = null;
var smooth_widget = null;
if (contains_line_plots) {
smooth_widget_button = (
<button
Expand Down Expand Up @@ -183,17 +242,71 @@ var PlotPane = (props) => {
<Pane
{...props}
handleDownload={handleDownload}
barwidgets={[smooth_widget_button]}
widgets={[smooth_widget]}
onShowExportHistory={() => setShowExportHistory((v) => !v)}
barwidgets={[smooth_widget_button].filter(Boolean)}
widgets={[
smooth_widget,
showExportHistory && (
<div
key="export_history"
className="widget"
style={{
maxHeight: '200px',
overflowY: 'auto',
overflowX: 'hidden',
padding: '6px'
}}
>
<h4>Export history</h4>
{exportHistory.length === 0 ? (
<div>No export history yet.</div>
) : (
exportHistory.map((item, idx) => (
<div key={idx}
style={{
marginBottom: '10px',
borderBottom: '1px solid #eee',
paddingBottom: '6px'
}}
>
{/* FORMAT */}
<div style={{ fontWeight: 'bold' }}>
{item.format.toUpperCase()}
</div>

{/* TIME */}
<div style={{ fontSize: '12px', color: '#555' }}>
{item.timestamp
? new Date(item.timestamp).toLocaleString()
: ''}
</div>

{/* STATUS */}
<div style={{ fontSize: '12px', color: 'green' }}>
{item.status}
</div>
</div>
))
)}
</div>
),
].filter(Boolean)}
enablePropertyList
>


<div
id={contentID}
style={{ height: '100%', width: '100%' }}
style={{
height: showExportHistory ? '70%' : '100%',
width: '100%'
}}
className="plotly-graph-div"
ref={plotlyRef}
/>

</Pane>

);
};

Expand All @@ -206,4 +319,4 @@ PlotPane = React.memo(PlotPane, (props, nextProps) => {
return true;
});

export default PlotPane;
export default PlotPane;