Добавлен редкатор дашборда

This commit is contained in:
Pavel Gnedov 2023-11-07 01:09:00 +07:00
parent 6925fde0e9
commit 8f36e6cca2
5 changed files with 223 additions and 0 deletions

View file

@ -8,6 +8,7 @@
"name": "frontend", "name": "frontend",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@monaco-editor/react": "^4.6.0",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
@ -2977,6 +2978,30 @@
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A=="
}, },
"node_modules/@monaco-editor/loader": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.4.0.tgz",
"integrity": "sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==",
"dependencies": {
"state-local": "^1.0.6"
},
"peerDependencies": {
"monaco-editor": ">= 0.21.0 < 1"
}
},
"node_modules/@monaco-editor/react": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.6.0.tgz",
"integrity": "sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==",
"dependencies": {
"@monaco-editor/loader": "^1.4.0"
},
"peerDependencies": {
"monaco-editor": ">= 0.25.0 < 1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
"version": "5.1.1-v1", "version": "5.1.1-v1",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
@ -11945,6 +11970,12 @@
"mobx": "^6.3.0" "mobx": "^6.3.0"
} }
}, },
"node_modules/monaco-editor": {
"version": "0.44.0",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.44.0.tgz",
"integrity": "sha512-5SmjNStN6bSuSE5WPT2ZV+iYn1/yI9sd4Igtk23ChvqB7kDk9lZbB9F5frsuvpB+2njdIeGGFf2G4gbE6rCC9Q==",
"peer": true
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -15205,6 +15236,11 @@
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="
}, },
"node_modules/state-local": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="
},
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@ -19072,6 +19108,22 @@
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A=="
}, },
"@monaco-editor/loader": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.4.0.tgz",
"integrity": "sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==",
"requires": {
"state-local": "^1.0.6"
}
},
"@monaco-editor/react": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.6.0.tgz",
"integrity": "sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==",
"requires": {
"@monaco-editor/loader": "^1.4.0"
}
},
"@nicolo-ribaudo/eslint-scope-5-internals": { "@nicolo-ribaudo/eslint-scope-5-internals": {
"version": "5.1.1-v1", "version": "5.1.1-v1",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
@ -25596,6 +25648,12 @@
"integrity": "sha512-oe82BNgMr408e6DxMDNat8msXQTuyuqzJ97DPupbhchEfjjHyjsmPSwtXHl+nXiW3tybpb/cr5siUClBqKqv+Q==", "integrity": "sha512-oe82BNgMr408e6DxMDNat8msXQTuyuqzJ97DPupbhchEfjjHyjsmPSwtXHl+nXiW3tybpb/cr5siUClBqKqv+Q==",
"requires": {} "requires": {}
}, },
"monaco-editor": {
"version": "0.44.0",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.44.0.tgz",
"integrity": "sha512-5SmjNStN6bSuSE5WPT2ZV+iYn1/yI9sd4Igtk23ChvqB7kDk9lZbB9F5frsuvpB+2njdIeGGFf2G4gbE6rCC9Q==",
"peer": true
},
"ms": { "ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -27772,6 +27830,11 @@
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="
}, },
"state-local": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="
},
"statuses": { "statuses": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",

View file

@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@monaco-editor/react": "^4.6.0",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",

View file

@ -3,6 +3,7 @@ import React from 'react';
import * as DashboardStoreNs from './dashboard-store'; import * as DashboardStoreNs from './dashboard-store';
import * as TopRightMenuNs from '../misc-components/top-right-menu'; import * as TopRightMenuNs from '../misc-components/top-right-menu';
import * as WidgetNs from './widget'; import * as WidgetNs from './widget';
import * as EditorNs from './editor';
export type Props = { store: DashboardStoreNs.IDashboard }; export type Props = { store: DashboardStoreNs.IDashboard };
@ -19,11 +20,20 @@ export const Dashboard = observer((props: Props): JSX.Element => {
return <WidgetNs.Widget key={widget.id} store={widget}></WidgetNs.Widget>; return <WidgetNs.Widget key={widget.id} store={widget}></WidgetNs.Widget>;
}); });
const editorStore = EditorNs.Store.create({ dashboardId: props.store.id });
const onEditClick = (e: React.MouseEvent) => {
if (e.target !== e.currentTarget) return;
e.stopPropagation();
editorStore.show();
};
const res = ( const res = (
<div> <div>
<EditorNs.Editor store={editorStore}></EditorNs.Editor>
<TopRightMenuNs.TopRightMenu store={topRightMenuStore}> <TopRightMenuNs.TopRightMenu store={topRightMenuStore}>
<a href="/dashboards">Назад</a> <a href="/dashboards">Назад</a>
<span>Дашборд - {props.store.data?.title || props.store.id}</span> <span>Дашборд - {props.store.data?.title || props.store.id}</span>
<button onClick={onEditClick}>edit</button>
</TopRightMenuNs.TopRightMenu> </TopRightMenuNs.TopRightMenu>
{widgets} {widgets}
</div> </div>

View file

@ -0,0 +1,27 @@
.reset {
all: initial;
}
.modal {
z-index: 1000;
position: fixed;
display: none;
padding-top: 40px;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.4);
}
.modalContent {
height: 80%;
overflow: auto;
background-color: #fefefe;
margin: auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
}

View file

@ -0,0 +1,122 @@
import React, { useState } from 'react';
import { Editor as MonacoEditor } from '@monaco-editor/react';
import { observer } from 'mobx-react-lite';
import { Instance, onSnapshot, types } from 'mobx-state-tree';
import Css from './editor.module.css';
export const Store = types
.model({
loaded: false,
dashboardId: '',
visible: false,
data: '',
})
.actions((self) => {
return {
setData: (data: string) => {
self.loaded = true;
self.data = data;
},
show: () => {
if (!self.loaded) LoadDashboardToStore(self as any);
self.visible = true;
},
hide: () => {
self.visible = false;
},
toggleVisible: () => {
self.visible = !self.visible;
},
};
})
.views((self) => {
return {
get displayStyle(): React.CSSProperties {
return { display: self.visible ? 'block' : 'none' };
},
};
});
type IStore = Instance<typeof Store>;
export async function LoadDashboard(dashboardId: string): Promise<string> {
const url = `${process.env.REACT_APP_BACKEND}api/dashboard/${dashboardId}`;
const resp = await fetch(url);
if (!resp || !resp.ok) return '';
const data = await resp.json();
const text = JSON.stringify(data, null, ' ');
return text;
}
export async function LoadDashboardToStore(store: IStore): Promise<void> {
const data = await LoadDashboard(store.dashboardId);
if (data) store.setData(data);
}
export async function SaveDashboard(
dashboardId: string,
data: string,
): Promise<void> {
const url = `${process.env.REACT_APP_BACKEND}api/dashboard/${dashboardId}`;
await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: data,
});
}
export async function SaveDashboardFromStore(store: IStore): Promise<void> {
await SaveDashboard(store.dashboardId, store.data);
}
export type Props = {
store: IStore;
};
export const Editor = observer((props: Props): JSX.Element => {
const onCloseClick = (e: React.MouseEvent) => {
if (e.target !== e.currentTarget) return;
e.stopPropagation();
props.store.hide();
};
const onSaveClick = async (e: React.MouseEvent) => {
if (e.target !== e.currentTarget) return;
e.stopPropagation();
props.store.setData(editorValue);
await SaveDashboardFromStore(props.store);
alert('Сохранено');
};
const [editorValue, setEditorValue] = useState(props.store.data);
onSnapshot(props.store, (state) => {
setEditorValue(state.data);
});
return (
<div className={Css.reset}>
<div
className={Css.modal}
style={props.store.displayStyle}
onClick={onCloseClick}
>
<div className={Css.modalContent}>
<h1>
<button onClick={onCloseClick}>close</button>
<button onClick={onSaveClick}>save</button>
Редактор дашборда
</h1>
<MonacoEditor
height="80%"
defaultLanguage="json"
defaultValue={editorValue}
value={editorValue}
onChange={(value) => setEditorValue(value || '')}
></MonacoEditor>
</div>
</div>
Editor
</div>
);
});