visualifyjs 2.5.3 → 3.0.0
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.
- package/.claude/mem/TIMELINE.md +36 -0
- package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-external-script-solution.md +24 -0
- package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-session-summary.md +43 -0
- package/.claude/mem/notes/2026-02-11-cli-fix-editor-command-alias.md +26 -0
- package/.claude/mem/notes/2026-02-11-phase-3-developer-experience-completed.md +51 -0
- package/.claude/mem/notes/2026-02-11-phase-4-web-workers-implementation-complete.md +59 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-2-3d-visualization-complete.md +50 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-2-committed-ready-for-phase-3.md +33 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-3-complete-developer-experience.md +52 -0
- package/.claude/mem/notes/2026-02-11-visualify-repository-cleanup-complete.md +28 -0
- package/.claude/mem/notes/2026-02-18-codebase-cleanup-docsify-plugin-documentation.md +37 -0
- package/.claude/mem/notes/2026-02-19-css-grid-layout-fix-displaycontents-on-vcontroller.md +18 -0
- package/.claude/mem/notes/2026-02-19-docsify-plugin-fixes-latex-and-visualify-code-bloc.md +26 -0
- package/.claude/mem/notes/2026-02-19-page-mode-docs-update-decisions.md +23 -0
- package/.claude/mem/notes/2026-02-19-react-context-infinite-re-render-loop-fix-pattern.md +31 -0
- package/.claude/mem/notes/2026-02-19-version-300-bump-and-build-fixes.md +32 -0
- package/.claude/mem/notes/2026-02-19-visualify-build-deployment-architecture-bug-fixes.md +25 -0
- package/.claude/mem/notes/2026-02-19-visualify-dist-iife-self-contained-build-config.md +30 -0
- package/.claude/mem/notes/2026-02-19-visualify-infinite-loop-i18n-fixes.md +31 -0
- package/.claude/mem/notes/2026-02-19-visualify-v3-bundle-splitting-docs-restructuring.md +32 -0
- package/.claude/mem/notes/2026-02-20-bundle-externalization-final-architecture.md +29 -0
- package/.claude/mem/notes/2026-02-20-chromium-page-fix-unstable-keys-and-double-event-b.md +27 -0
- package/.claude/mem/notes/2026-02-20-console-cleanup-bundle-optimization-commit.md +20 -0
- package/.claude/mem/notes/2026-02-20-dotbio-dot-plot-fix-useeffect-dependency.md +21 -0
- package/.claude/mem/notes/2026-02-20-public-folder-cleanup-and-readme-rewrite.md +25 -0
- package/.claude/mem/notes/2026-02-20-v300-release-and-beta-channel-strategy.md +29 -0
- package/.claude/mem/notes/2026-02-20-visium-background-image-unknown-legend-fix.md +19 -0
- package/.claude/mem/notes/2026-02-20-visualify-cdn-loader-bundle-externalization.md +34 -0
- package/.claude/mem/sessions/session-2026-02-20-031524.md +54 -0
- package/.claude/settings.local.json +21 -0
- package/.github/workflows/static.yml.bak +51 -51
- package/.sisyphus/boulder.json +65 -0
- package/.sisyphus/plans/phase-4-advanced-optimizations.md +217 -0
- package/LICENSE +674 -674
- package/README.md +94 -59
- package/config-overrides.js +31 -31
- package/dist/stats.html +4949 -0
- package/dist/visualify-3d.esm.js +1 -0
- package/dist/visualify-3d.js +1 -0
- package/dist/visualify-core.esm.js +1 -0
- package/dist/visualify-core.js +1 -0
- package/dist/visualify-docs.esm.js +1 -0
- package/dist/visualify-docs.js +1 -0
- package/dist/visualify-loader.js +1 -0
- package/dist/visualify-pages.esm.js +1 -0
- package/dist/visualify-pages.js +1 -0
- package/dist/visualify-portal.esm.js +1 -0
- package/dist/visualify-portal.js +1 -0
- package/dist/visualify-shared.js +26571 -0
- package/dist/visualify.js +1 -188
- package/docs/CHANGELOG.md +148 -0
- package/docs/cli/commands.md +513 -0
- package/docs/configuration/visualify-json.md +474 -0
- package/docs/docs/3d-visualization.md +374 -0
- package/docs/docs/CLI.md +303 -34
- package/docs/docs/README.md +65 -65
- package/docs/docs/Rechart/bar.md +190 -190
- package/docs/docs/Rechart/funnel.md +241 -193
- package/docs/docs/Rechart/line.md +355 -355
- package/docs/docs/Rechart/pie.md +225 -225
- package/docs/docs/Rechart/radar.md +253 -253
- package/docs/docs/Rechart/scatter.md +298 -0
- package/docs/docs/_404.md +51 -51
- package/docs/docs/_coverpage.md +11 -11
- package/docs/docs/_sidebar.md +54 -43
- package/docs/docs/components/dotBio.md +87 -34
- package/docs/docs/components/echart.md +171 -82
- package/docs/docs/components/html.md +61 -34
- package/docs/docs/components/macaron.md +156 -145
- package/docs/docs/components/markdown.md +42 -0
- package/docs/docs/components/more.md +183 -142
- package/docs/docs/components/plotly.md +132 -62
- package/docs/docs/components/scatterL.md +171 -70
- package/docs/docs/components/visium.md +112 -57
- package/docs/docs/configuration.md +121 -123
- package/docs/docs/deploy.md +31 -31
- package/docs/docs/docsify-plugin.md +655 -0
- package/docs/docs/hmr.md +165 -0
- package/docs/docs/i18n.md +332 -0
- package/docs/docs/log.md +30 -1
- package/docs/docs/more-pages.md +23 -23
- package/docs/docs/quickstart.md +148 -119
- package/docs/docs/rechart-attributes.md +74 -74
- package/docs/docs/rechart-basic-usage.md +160 -162
- package/docs/docs/theme.md +5 -5
- package/docs/docs/typescript.md +306 -0
- package/docs/docs/visual-editor.md +359 -0
- package/docs/index.html +85 -71
- package/docs/manifest.json +23 -23
- package/docs/migration/v3-migration.md +392 -0
- package/docs/static/css/fluff-stuff.css +169 -169
- package/docs/static/css/font-awesome.min.css +4 -4
- package/docs/static/css/visualify.css +6 -25
- package/docs/static/js/3d-viz-examples.js +181 -0
- package/docs/static/js/configuration.js +630 -448
- package/docs/static/js/visualify.js +1 -188
- package/package.json +106 -84
- package/rollup.config.mjs +766 -76
- package/src/_css/404.css +115 -115
- package/src/_css/App.css +37 -37
- package/src/_css/autoSuggestion.css +26 -26
- package/src/_css/circular-progress.css +32 -32
- package/src/_css/index.css +36 -36
- package/src/_css/modern.css +350 -25
- package/src/_media/corner.svg +8 -8
- package/src/_media/download.svg +3 -3
- package/src/_media/logo.svg +14 -14
- package/src/_test/App.test.js +15 -15
- package/src/_utils/reportWebVitals.js +13 -13
- package/src/a11y/README.md +177 -0
- package/src/a11y/aria-labels.js +339 -0
- package/src/a11y/color-contrast.js +535 -0
- package/src/a11y/index.js +197 -0
- package/src/a11y/keyboard-nav.js +523 -0
- package/src/a11y/styles.css +165 -0
- package/src/cli/commands/dev.js +214 -0
- package/src/cli/commands/docs.js +521 -0
- package/src/cli/commands/edit.js +379 -0
- package/src/cli/commands/init.js +213 -0
- package/src/cli/commands/portal.js +236 -0
- package/src/cli/dev-server.js +530 -0
- package/src/cli/hmr.js +456 -0
- package/src/cli/index.js +180 -0
- package/src/cli/utils/config.js +207 -0
- package/src/cli/utils/logger.js +241 -0
- package/src/config/defaults.ts +122 -0
- package/src/config/index.ts +72 -0
- package/src/config/loader.ts +478 -0
- package/src/config/schema.ts +227 -0
- package/src/config/validator.ts +337 -0
- package/src/core/appContext.js +34 -27
- package/src/core/components/Bar.js +383 -0
- package/src/core/components/Bar3D.js +473 -0
- package/src/core/components/LargeDatasetChart.js +296 -0
- package/src/core/components/Line3D.js +310 -0
- package/src/core/components/Scatter.js +392 -188
- package/src/core/components/Scatter3D.js +455 -0
- package/src/core/components/ScatterBio.js +601 -572
- package/src/core/components/Surface3D.js +326 -0
- package/src/core/components/ThreeCustom.js +648 -0
- package/src/core/components/ThreeScene.js +459 -0
- package/src/core/components/VisiumPlot.js +191 -165
- package/src/core/components/browser.js +42 -42
- package/src/core/components/dotplot.js +413 -413
- package/src/core/components/html.js +29 -29
- package/src/core/components/list.js +178 -178
- package/src/core/components/macaron.js +206 -201
- package/src/core/components/markdown.js +56 -56
- package/src/core/components/parser.scatterBio.js +582 -579
- package/src/core/components/ratio.js +82 -80
- package/src/core/components/scatterL.js +206 -173
- package/src/core/components/searchbar.js +156 -131
- package/src/core/components/selection.js +310 -193
- package/src/core/components/timeline.js +236 -281
- package/src/core/components/visium.js +114 -97
- package/src/core/data-processor.js +413 -0
- package/src/core/fetch/condfetch.js +82 -82
- package/src/core/fetch/fetch.js +92 -92
- package/src/core/fetch/json.js +29 -29
- package/src/core/fetch/vfetch.js +42 -42
- package/src/core/hmr-client.js +724 -0
- package/src/core/liveEditor.js +44 -44
- package/src/core/modules/codeEditorWithPreview.js +104 -104
- package/src/core/modules/echarts/common.js +20 -20
- package/src/core/modules/echarts/gl.js +228 -0
- package/src/core/modules/echarts/presetHandler.js +41 -41
- package/src/core/modules/echarts/presets/esodev.chromium.js +172 -172
- package/src/core/modules/echarts/presets/esodev.codex.js +130 -130
- package/src/core/modules/echarts/presets/esodev.visium.js +123 -123
- package/src/core/modules/echarts/presets/mmtrbc.js +186 -186
- package/src/core/modules/echarts.js +70 -71
- package/src/core/modules/echartsUtils.js +43 -43
- package/src/core/modules/echartswitcher.js +227 -152
- package/src/core/modules/replotly/presetHandler.js +24 -24
- package/src/core/modules/replotly/presets/minimum.js +18 -18
- package/src/core/modules/replotly/presets/mmtrbc.dot.js +114 -114
- package/src/core/modules/replotly/presets/mmtrbc.violin.js +100 -100
- package/src/core/modules/replotly.js +74 -71
- package/src/core/modules/threejs/Camera.js +373 -0
- package/src/core/modules/threejs/Lighting.js +459 -0
- package/src/core/modules/threejs/Renderer.js +364 -0
- package/src/core/modules/threejs/Scene.js +266 -0
- package/src/core/modules/threejs/index.js +155 -0
- package/src/core/pages/404.js +50 -50
- package/src/core/pages/error.js +27 -27
- package/src/core/pages/jsonPage.js +62 -62
- package/src/core/pages/loading.js +44 -44
- package/src/core/parser/echart.data.js +204 -183
- package/src/core/parser/echart.features.js +125 -125
- package/src/core/parser/echart.general.js +147 -143
- package/src/core/parser/echart.hilbert.js +57 -57
- package/src/core/parser/echart.parser.js +210 -210
- package/src/core/parser/echart.series.js +67 -67
- package/src/core/parser/echart.types.js +76 -76
- package/src/core/parser/plotly.config.js +10 -10
- package/src/core/parser/plotly.data.js +132 -132
- package/src/core/parser/plotly.layout.js +9 -9
- package/src/core/parser/plotly.violin.js +18 -18
- package/src/core/recharts.js +361 -62
- package/src/core/router/alias.js +49 -49
- package/src/core/router/jsonRouter.js +31 -31
- package/src/core/themes/modern.js +32 -32
- package/src/core/themes/themeSelector.js +33 -33
- package/src/core/visualify.js +213 -47
- package/src/core/widgets/circularProgress.js +23 -23
- package/src/core/widgets/controller.js +116 -83
- package/src/core/widgets/errorBoundary.js +36 -36
- package/src/core/widgets/footer.js +185 -177
- package/src/core/widgets/header.js +238 -234
- package/src/core/widgets/layout/Grid.js +31 -31
- package/src/core/widgets/layout.js +36 -36
- package/src/core/widgets/mapping.js +56 -42
- package/src/core/workers/data-worker.js +349 -0
- package/src/core/workers/worker-pool.js +396 -0
- package/src/docsify/bundle.js +215 -0
- package/src/docsify/markdown.js +271 -0
- package/src/docsify/plugin.js +268 -0
- package/src/editor/README.md +172 -0
- package/src/editor/components/ChartBuilder.jsx +341 -0
- package/src/editor/components/ChartTypeSidebar.jsx +91 -0
- package/src/editor/components/Editor.jsx +367 -0
- package/src/editor/components/Preview.jsx +446 -0
- package/src/editor/components/PropertyPanel.jsx +468 -0
- package/src/editor/components/StatusBar.jsx +85 -0
- package/src/editor/context/EditorContext.js +248 -0
- package/src/editor/hooks/useDebounce.js +32 -0
- package/src/editor/index.js +315 -0
- package/src/editor/styles/editor.css +637 -0
- package/src/editor/utils/chartValidator.js +263 -0
- package/src/entries/charts3d.js +70 -0
- package/src/entries/core.js +78 -0
- package/src/entries/docs.js +154 -0
- package/src/entries/pages.js +93 -0
- package/src/entries/portal.js +204 -0
- package/src/entries/shared.js +50 -0
- package/src/i18n/formatters.js +455 -0
- package/src/i18n/index.js +169 -0
- package/src/i18n/locales/ar.json +137 -0
- package/src/i18n/locales/de.json +137 -0
- package/src/i18n/locales/en.json +137 -0
- package/src/i18n/locales/es.json +137 -0
- package/src/i18n/locales/he.json +137 -0
- package/src/i18n/locales/zh.json +137 -0
- package/src/i18n/rtl.css +183 -0
- package/src/index.js +82 -62
- package/src/loader.js +103 -0
- package/src/setupTests.js +5 -5
- package/tsconfig.json +51 -0
- package/types/charts.d.ts +569 -0
- package/types/components.d.ts +441 -0
- package/types/config.d.ts +199 -0
- package/types/index.d.ts +353 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Editor Context - State management for the visual editor
|
|
3
|
+
* @module editor/context/EditorContext
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { createContext, useContext, useReducer, useCallback } from 'react';
|
|
7
|
+
|
|
8
|
+
// Action types
|
|
9
|
+
const ACTIONS = {
|
|
10
|
+
SET_CONFIG: 'SET_CONFIG',
|
|
11
|
+
UPDATE_CHART: 'UPDATE_CHART',
|
|
12
|
+
ADD_CHART: 'ADD_CHART',
|
|
13
|
+
REMOVE_CHART: 'REMOVE_CHART',
|
|
14
|
+
REORDER_CHARTS: 'REORDER_CHARTS',
|
|
15
|
+
SET_SELECTED_CHART: 'SET_SELECTED_CHART',
|
|
16
|
+
UPDATE_LAYOUT: 'UPDATE_LAYOUT',
|
|
17
|
+
SET_HISTORY: 'SET_HISTORY',
|
|
18
|
+
UNDO: 'UNDO',
|
|
19
|
+
REDO: 'REDO',
|
|
20
|
+
SET_LOADING: 'SET_LOADING',
|
|
21
|
+
SET_ERROR: 'SET_ERROR',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Initial state
|
|
25
|
+
const initialState = {
|
|
26
|
+
config: {
|
|
27
|
+
version: '3.0.0',
|
|
28
|
+
charts: [],
|
|
29
|
+
layout: {
|
|
30
|
+
type: 'grid',
|
|
31
|
+
rows: 1,
|
|
32
|
+
cols: 1,
|
|
33
|
+
gap: '10px',
|
|
34
|
+
},
|
|
35
|
+
theme: 'modern',
|
|
36
|
+
},
|
|
37
|
+
selectedChartId: null,
|
|
38
|
+
history: {
|
|
39
|
+
past: [],
|
|
40
|
+
present: null,
|
|
41
|
+
future: [],
|
|
42
|
+
},
|
|
43
|
+
loading: false,
|
|
44
|
+
error: null,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Reducer
|
|
48
|
+
function editorReducer(state, action) {
|
|
49
|
+
switch (action.type) {
|
|
50
|
+
case ACTIONS.SET_CONFIG:
|
|
51
|
+
return {
|
|
52
|
+
...state,
|
|
53
|
+
config: action.payload,
|
|
54
|
+
history: {
|
|
55
|
+
past: [...state.history.past, state.config],
|
|
56
|
+
present: action.payload,
|
|
57
|
+
future: [],
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
case ACTIONS.UPDATE_CHART:
|
|
62
|
+
return {
|
|
63
|
+
...state,
|
|
64
|
+
config: {
|
|
65
|
+
...state.config,
|
|
66
|
+
charts: state.config.charts.map((chart) =>
|
|
67
|
+
chart.id === action.payload.id
|
|
68
|
+
? { ...chart, ...action.payload.updates }
|
|
69
|
+
: chart,
|
|
70
|
+
),
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
case ACTIONS.ADD_CHART:
|
|
75
|
+
return {
|
|
76
|
+
...state,
|
|
77
|
+
config: {
|
|
78
|
+
...state.config,
|
|
79
|
+
charts: [...state.config.charts, action.payload],
|
|
80
|
+
},
|
|
81
|
+
selectedChartId: action.payload.id,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
case ACTIONS.REMOVE_CHART:
|
|
85
|
+
return {
|
|
86
|
+
...state,
|
|
87
|
+
config: {
|
|
88
|
+
...state.config,
|
|
89
|
+
charts: state.config.charts.filter(
|
|
90
|
+
(c) => c.id !== action.payload,
|
|
91
|
+
),
|
|
92
|
+
},
|
|
93
|
+
selectedChartId:
|
|
94
|
+
state.selectedChartId === action.payload
|
|
95
|
+
? null
|
|
96
|
+
: state.selectedChartId,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
case ACTIONS.REORDER_CHARTS:
|
|
100
|
+
return {
|
|
101
|
+
...state,
|
|
102
|
+
config: {
|
|
103
|
+
...state.config,
|
|
104
|
+
charts: action.payload,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
case ACTIONS.SET_SELECTED_CHART:
|
|
109
|
+
return {
|
|
110
|
+
...state,
|
|
111
|
+
selectedChartId: action.payload,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
case ACTIONS.UPDATE_LAYOUT:
|
|
115
|
+
return {
|
|
116
|
+
...state,
|
|
117
|
+
config: {
|
|
118
|
+
...state.config,
|
|
119
|
+
layout: { ...state.config.layout, ...action.payload },
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
case ACTIONS.UNDO:
|
|
124
|
+
if (state.history.past.length === 0) return state;
|
|
125
|
+
const previous = state.history.past[state.history.past.length - 1];
|
|
126
|
+
const newPast = state.history.past.slice(0, -1);
|
|
127
|
+
return {
|
|
128
|
+
...state,
|
|
129
|
+
config: previous,
|
|
130
|
+
history: {
|
|
131
|
+
past: newPast,
|
|
132
|
+
present: previous,
|
|
133
|
+
future: [state.config, ...state.history.future],
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
case ACTIONS.REDO:
|
|
138
|
+
if (state.history.future.length === 0) return state;
|
|
139
|
+
const next = state.history.future[0];
|
|
140
|
+
const newFuture = state.history.future.slice(1);
|
|
141
|
+
return {
|
|
142
|
+
...state,
|
|
143
|
+
config: next,
|
|
144
|
+
history: {
|
|
145
|
+
past: [...state.history.past, state.config],
|
|
146
|
+
present: next,
|
|
147
|
+
future: newFuture,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
case ACTIONS.SET_LOADING:
|
|
152
|
+
return { ...state, loading: action.payload };
|
|
153
|
+
|
|
154
|
+
case ACTIONS.SET_ERROR:
|
|
155
|
+
return { ...state, error: action.payload };
|
|
156
|
+
|
|
157
|
+
default:
|
|
158
|
+
return state;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Context
|
|
163
|
+
const EditorContext = createContext(null);
|
|
164
|
+
|
|
165
|
+
// Provider component
|
|
166
|
+
export function EditorProvider({ children, initialState: customInitialState }) {
|
|
167
|
+
const [state, dispatch] = useReducer(
|
|
168
|
+
editorReducer,
|
|
169
|
+
customInitialState || initialState,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Actions
|
|
173
|
+
const setConfig = useCallback((config) => {
|
|
174
|
+
dispatch({ type: ACTIONS.SET_CONFIG, payload: config });
|
|
175
|
+
}, []);
|
|
176
|
+
|
|
177
|
+
const updateChart = useCallback((id, updates) => {
|
|
178
|
+
dispatch({ type: ACTIONS.UPDATE_CHART, payload: { id, updates } });
|
|
179
|
+
}, []);
|
|
180
|
+
|
|
181
|
+
const addChart = useCallback((chart) => {
|
|
182
|
+
dispatch({ type: ACTIONS.ADD_CHART, payload: chart });
|
|
183
|
+
}, []);
|
|
184
|
+
|
|
185
|
+
const removeChart = useCallback((id) => {
|
|
186
|
+
dispatch({ type: ACTIONS.REMOVE_CHART, payload: id });
|
|
187
|
+
}, []);
|
|
188
|
+
|
|
189
|
+
const reorderCharts = useCallback((charts) => {
|
|
190
|
+
dispatch({ type: ACTIONS.REORDER_CHARTS, payload: charts });
|
|
191
|
+
}, []);
|
|
192
|
+
|
|
193
|
+
const setSelectedChart = useCallback((id) => {
|
|
194
|
+
dispatch({ type: ACTIONS.SET_SELECTED_CHART, payload: id });
|
|
195
|
+
}, []);
|
|
196
|
+
|
|
197
|
+
const updateLayout = useCallback((layout) => {
|
|
198
|
+
dispatch({ type: ACTIONS.UPDATE_LAYOUT, payload: layout });
|
|
199
|
+
}, []);
|
|
200
|
+
|
|
201
|
+
const undo = useCallback(() => {
|
|
202
|
+
dispatch({ type: ACTIONS.UNDO });
|
|
203
|
+
}, []);
|
|
204
|
+
|
|
205
|
+
const redo = useCallback(() => {
|
|
206
|
+
dispatch({ type: ACTIONS.REDO });
|
|
207
|
+
}, []);
|
|
208
|
+
|
|
209
|
+
const canUndo = state.history.past.length > 0;
|
|
210
|
+
const canRedo = state.history.future.length > 0;
|
|
211
|
+
|
|
212
|
+
const value = {
|
|
213
|
+
state,
|
|
214
|
+
dispatch,
|
|
215
|
+
// Actions
|
|
216
|
+
setConfig,
|
|
217
|
+
updateChart,
|
|
218
|
+
addChart,
|
|
219
|
+
removeChart,
|
|
220
|
+
reorderCharts,
|
|
221
|
+
setSelectedChart,
|
|
222
|
+
updateLayout,
|
|
223
|
+
undo,
|
|
224
|
+
redo,
|
|
225
|
+
// Computed
|
|
226
|
+
canUndo,
|
|
227
|
+
canRedo,
|
|
228
|
+
selectedChart: state.config.charts.find(
|
|
229
|
+
(c) => c.id === state.selectedChartId,
|
|
230
|
+
),
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<EditorContext.Provider value={value}>{children}</EditorContext.Provider>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Hook
|
|
239
|
+
export function useEditor() {
|
|
240
|
+
const context = useContext(EditorContext);
|
|
241
|
+
if (!context) {
|
|
242
|
+
throw new Error('useEditor must be used within an EditorProvider');
|
|
243
|
+
}
|
|
244
|
+
return context;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export { ACTIONS };
|
|
248
|
+
export default EditorContext;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Debounce Hook
|
|
3
|
+
* @module editor/hooks/useDebounce
|
|
4
|
+
*
|
|
5
|
+
* Hook for delaying value updates to improve performance.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useEffect } from 'react';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Debounce hook
|
|
12
|
+
* @param {any} value - Value to debounce
|
|
13
|
+
* @param {number} delay - Delay in milliseconds
|
|
14
|
+
* @returns {any} Debounced value
|
|
15
|
+
*/
|
|
16
|
+
export function useDebounce(value, delay) {
|
|
17
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const timer = setTimeout(() => {
|
|
21
|
+
setDebouncedValue(value);
|
|
22
|
+
}, delay);
|
|
23
|
+
|
|
24
|
+
return () => {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
};
|
|
27
|
+
}, [value, delay]);
|
|
28
|
+
|
|
29
|
+
return debouncedValue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default useDebounce;
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Visualify Visual Editor - Main Entry Point
|
|
3
|
+
* @module editor/index
|
|
4
|
+
*
|
|
5
|
+
* Visual configuration editor for creating and editing Visualify configurations
|
|
6
|
+
* without writing JSON. Provides a drag-and-drop interface for building charts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
|
10
|
+
import { createRoot } from 'react-dom/client';
|
|
11
|
+
import { Container, Row, Col, Toast, ToastContainer } from 'react-bootstrap';
|
|
12
|
+
import Editor from './components/Editor';
|
|
13
|
+
import { EditorProvider } from './context/EditorContext';
|
|
14
|
+
import './styles/editor.css';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Default empty configuration
|
|
18
|
+
* @readonly
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_CONFIG = {
|
|
21
|
+
version: '3.0.0',
|
|
22
|
+
charts: [],
|
|
23
|
+
layout: {
|
|
24
|
+
type: 'grid',
|
|
25
|
+
rows: 1,
|
|
26
|
+
cols: 1,
|
|
27
|
+
gap: '10px',
|
|
28
|
+
},
|
|
29
|
+
theme: 'modern',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Load configuration from file or localStorage
|
|
34
|
+
* @param {string|null} filePath - Path to config file
|
|
35
|
+
* @returns {Object} Loaded configuration
|
|
36
|
+
*/
|
|
37
|
+
function loadConfiguration(filePath) {
|
|
38
|
+
// Try to load from localStorage first (auto-save)
|
|
39
|
+
const savedConfig = localStorage.getItem('visualify_editor_autosave');
|
|
40
|
+
if (savedConfig) {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(savedConfig);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.warn('Failed to parse auto-saved config:', e);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Return default config
|
|
49
|
+
return { ...DEFAULT_CONFIG };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Main Editor Application Component
|
|
54
|
+
*/
|
|
55
|
+
function EditorApp({ initialConfig = null, filePath = null }) {
|
|
56
|
+
const [config, setConfig] = useState(() =>
|
|
57
|
+
initialConfig || loadConfiguration(filePath),
|
|
58
|
+
);
|
|
59
|
+
const [toasts, setToasts] = useState([]);
|
|
60
|
+
const [isReady, setIsReady] = useState(false);
|
|
61
|
+
const autoSaveTimeoutRef = useRef(null);
|
|
62
|
+
|
|
63
|
+
// Show toast notification
|
|
64
|
+
const showToast = useCallback((message, type = 'info') => {
|
|
65
|
+
const id = Date.now();
|
|
66
|
+
setToasts((prev) => [...prev, { id, message, type }]);
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
69
|
+
}, 3000);
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
// Auto-save to localStorage with debounce
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (autoSaveTimeoutRef.current) {
|
|
75
|
+
clearTimeout(autoSaveTimeoutRef.current);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
autoSaveTimeoutRef.current = setTimeout(() => {
|
|
79
|
+
try {
|
|
80
|
+
localStorage.setItem(
|
|
81
|
+
'visualify_editor_autosave',
|
|
82
|
+
JSON.stringify(config),
|
|
83
|
+
);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
console.warn('Auto-save failed:', e);
|
|
86
|
+
}
|
|
87
|
+
}, 1000);
|
|
88
|
+
|
|
89
|
+
return () => {
|
|
90
|
+
if (autoSaveTimeoutRef.current) {
|
|
91
|
+
clearTimeout(autoSaveTimeoutRef.current);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}, [config]);
|
|
95
|
+
|
|
96
|
+
// Handle configuration updates
|
|
97
|
+
const updateConfig = useCallback((updates) => {
|
|
98
|
+
setConfig((prev) => ({
|
|
99
|
+
...prev,
|
|
100
|
+
...updates,
|
|
101
|
+
}));
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
// Handle chart updates
|
|
105
|
+
const updateChart = useCallback((chartId, updates) => {
|
|
106
|
+
setConfig((prev) => ({
|
|
107
|
+
...prev,
|
|
108
|
+
charts: prev.charts.map((chart) =>
|
|
109
|
+
chart.id === chartId ? { ...chart, ...updates } : chart,
|
|
110
|
+
),
|
|
111
|
+
}));
|
|
112
|
+
}, []);
|
|
113
|
+
|
|
114
|
+
// Add new chart
|
|
115
|
+
const addChart = useCallback((chartType) => {
|
|
116
|
+
const newChart = {
|
|
117
|
+
id: `chart_${Date.now()}`,
|
|
118
|
+
type: chartType,
|
|
119
|
+
title: `${chartType} Chart`,
|
|
120
|
+
data: [],
|
|
121
|
+
options: {},
|
|
122
|
+
position: { x: 0, y: 0, w: 1, h: 1 },
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
setConfig((prev) => ({
|
|
126
|
+
...prev,
|
|
127
|
+
charts: [...prev.charts, newChart],
|
|
128
|
+
}));
|
|
129
|
+
|
|
130
|
+
showToast(`Added ${chartType} chart`, 'success');
|
|
131
|
+
return newChart.id;
|
|
132
|
+
}, [showToast]);
|
|
133
|
+
|
|
134
|
+
// Remove chart
|
|
135
|
+
const removeChart = useCallback((chartId) => {
|
|
136
|
+
setConfig((prev) => ({
|
|
137
|
+
...prev,
|
|
138
|
+
charts: prev.charts.filter((c) => c.id !== chartId),
|
|
139
|
+
}));
|
|
140
|
+
showToast('Chart removed', 'info');
|
|
141
|
+
}, [showToast]);
|
|
142
|
+
|
|
143
|
+
// Export configuration
|
|
144
|
+
const exportConfig = useCallback(() => {
|
|
145
|
+
try {
|
|
146
|
+
// Validate configuration
|
|
147
|
+
if (config.charts.length === 0) {
|
|
148
|
+
showToast('No charts to export', 'warning');
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Clean up config for export
|
|
153
|
+
const exportData = {
|
|
154
|
+
...config,
|
|
155
|
+
charts: config.charts.map((chart) => ({
|
|
156
|
+
...chart,
|
|
157
|
+
// Remove internal properties
|
|
158
|
+
id: undefined,
|
|
159
|
+
})),
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const json = JSON.stringify(exportData, null, 2);
|
|
163
|
+
const blob = new Blob([json], { type: 'application/json' });
|
|
164
|
+
const url = URL.createObjectURL(blob);
|
|
165
|
+
|
|
166
|
+
const link = document.createElement('a');
|
|
167
|
+
link.href = url;
|
|
168
|
+
link.download = 'visualify.json';
|
|
169
|
+
document.body.appendChild(link);
|
|
170
|
+
link.click();
|
|
171
|
+
document.body.removeChild(link);
|
|
172
|
+
URL.revokeObjectURL(url);
|
|
173
|
+
|
|
174
|
+
showToast('Configuration exported successfully', 'success');
|
|
175
|
+
return json;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
showToast(`Export failed: ${error.message}`, 'danger');
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}, [config, showToast]);
|
|
181
|
+
|
|
182
|
+
// Import configuration
|
|
183
|
+
const importConfig = useCallback((jsonString) => {
|
|
184
|
+
try {
|
|
185
|
+
const imported = JSON.parse(jsonString);
|
|
186
|
+
|
|
187
|
+
// Validate required fields
|
|
188
|
+
if (!imported.charts || !Array.isArray(imported.charts)) {
|
|
189
|
+
throw new Error('Invalid configuration: missing charts array');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Add IDs to charts
|
|
193
|
+
const chartsWithIds = imported.charts.map((chart, index) => ({
|
|
194
|
+
...chart,
|
|
195
|
+
id: chart.id || `chart_${Date.now()}_${index}`,
|
|
196
|
+
}));
|
|
197
|
+
|
|
198
|
+
setConfig({
|
|
199
|
+
...DEFAULT_CONFIG,
|
|
200
|
+
...imported,
|
|
201
|
+
charts: chartsWithIds,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
showToast('Configuration imported successfully', 'success');
|
|
205
|
+
return true;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
showToast(`Import failed: ${error.message}`, 'danger');
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}, [showToast]);
|
|
211
|
+
|
|
212
|
+
// Clear all charts
|
|
213
|
+
const clearAll = useCallback(() => {
|
|
214
|
+
if (window.confirm('Are you sure you want to clear all charts?')) {
|
|
215
|
+
setConfig((prev) => ({
|
|
216
|
+
...prev,
|
|
217
|
+
charts: [],
|
|
218
|
+
}));
|
|
219
|
+
showToast('All charts cleared', 'info');
|
|
220
|
+
}
|
|
221
|
+
}, [showToast]);
|
|
222
|
+
|
|
223
|
+
// Initialize
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
setIsReady(true);
|
|
226
|
+
}, []);
|
|
227
|
+
|
|
228
|
+
if (!isReady) {
|
|
229
|
+
return (
|
|
230
|
+
<Container className='editor-loading'>
|
|
231
|
+
<div className='text-center py-5'>
|
|
232
|
+
<div className='spinner-border text-primary' role='status'>
|
|
233
|
+
<span className='visually-hidden'>Loading...</span>
|
|
234
|
+
</div>
|
|
235
|
+
<p className='mt-3'>Loading Visualify Editor...</p>
|
|
236
|
+
</div>
|
|
237
|
+
</Container>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<EditorProvider initialState={{ config }}>
|
|
243
|
+
<div className='visualify-editor'>
|
|
244
|
+
<Editor />
|
|
245
|
+
|
|
246
|
+
{/* Toast Notifications */}
|
|
247
|
+
<ToastContainer
|
|
248
|
+
position='bottom-end'
|
|
249
|
+
className='p-3'>
|
|
250
|
+
{toasts.map((toast) => (
|
|
251
|
+
<Toast
|
|
252
|
+
key={toast.id}
|
|
253
|
+
bg={toast.type}
|
|
254
|
+
onClose={() =>
|
|
255
|
+
setToasts((prev) =>
|
|
256
|
+
prev.filter((t) => t.id !== toast.id),
|
|
257
|
+
)
|
|
258
|
+
}>
|
|
259
|
+
<Toast.Body className='text-white'>
|
|
260
|
+
{toast.message}
|
|
261
|
+
</Toast.Body>
|
|
262
|
+
</Toast>
|
|
263
|
+
))}
|
|
264
|
+
</ToastContainer>
|
|
265
|
+
</div>
|
|
266
|
+
</EditorProvider>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Mount the editor application
|
|
272
|
+
* @param {HTMLElement|string} element - DOM element or selector
|
|
273
|
+
* @param {Object} options - Editor options
|
|
274
|
+
* @param {Object} options.config - Initial configuration
|
|
275
|
+
* @param {string} options.filePath - Path to config file
|
|
276
|
+
*/
|
|
277
|
+
function mountEditor(element, options = {}) {
|
|
278
|
+
const el =
|
|
279
|
+
typeof element === 'string' ? document.querySelector(element) : element;
|
|
280
|
+
|
|
281
|
+
if (!el) {
|
|
282
|
+
throw new Error(`Element not found: ${element}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const root = createRoot(el);
|
|
286
|
+
root.render(
|
|
287
|
+
<EditorApp
|
|
288
|
+
initialConfig={options.config}
|
|
289
|
+
filePath={options.filePath}
|
|
290
|
+
/>,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
return root;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Export for module usage
|
|
297
|
+
export { EditorApp, mountEditor, DEFAULT_CONFIG };
|
|
298
|
+
export default mountEditor;
|
|
299
|
+
|
|
300
|
+
// Auto-mount if DOM is ready and editor container exists
|
|
301
|
+
if (typeof window !== 'undefined') {
|
|
302
|
+
if (document.readyState === 'loading') {
|
|
303
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
304
|
+
const container = document.getElementById('visualify-editor');
|
|
305
|
+
if (container) {
|
|
306
|
+
mountEditor(container);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
} else {
|
|
310
|
+
const container = document.getElementById('visualify-editor');
|
|
311
|
+
if (container) {
|
|
312
|
+
mountEditor(container);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|