vtseditor
Version:
819 lines (663 loc) • 27.3 kB
text/typescript
import {BrowserJsPlumbInstance} from '@jsplumb/browser-ui';
import {ProjectSave} from '../SchemaProject/SchemaProjectSave.js';
import {ProjectsData, SchemaProjectsResponse} from '../SchemaProject/SchemaProjectsResponse.js';
import {SchemaTypesUtil} from '../SchemaUtil/SchemaTypesUtil.js';
import {BaseTable} from './Base/BaseTable.js';
import {EnumTable} from './Enum/EnumTable.js';
import {JsonDataFS, SchemaJsonDataFS, SchemaJsonDataFSType} from './JsonData.js';
import jsPlumbInstance from './jsPlumbInstance.js';
import {LinkTable} from './Link/LinkTable.js';
import {SchemaTable} from './Schema/SchemaTable.js';
import {SchemaExtends} from './Register/SchemaExtends.js';
import {SchemaTypes} from './Register/SchemaTypes.js';
import {Treeview} from './Treeview/Treeview.js';
/**
* SchemaEditor move event detail
*/
type SchemaEditorMoveEventDetail = {
sourceType: string;
destinationType: string;
sourceId: string;
detionationId: string;
};
/**
* SchemaEditor update name event detail
*/
type SchemaEditorUpdatenameEventDetail = {
sourceId: string;
sourceType: string;
};
/**
* SchemaEditor wiggle event detail
*/
type SchemaEditorWiggleEventDetail = {
tableId: string;
};
/**
* Schema Editor
*/
export class SchemaEditor {
/**
* Container for diagram
* @protected
*/
protected _container: HTMLElement | null = null;
/**
* Controls
* @protected
*/
protected _controls: HTMLElement | null = null;
/**
* Button for add Schema
* @protected
*/
protected _btnAddSchema: HTMLElement | null = null;
/**
* Button for add enum
* @protected
*/
protected _btnAddEnum: HTMLElement | null = null;
/**
* Treeview
* @protected
*/
protected _treeview: Treeview | null = null;
/**
* Js Plumb instance
* @protected
*/
protected _jsPlumbInstance: BrowserJsPlumbInstance | null = null;
/**
* Add a new Schema
* @protected
*/
protected _addSchema(): void {
if (Treeview.getActiveEntry() === null) {
return;
}
const table = new SchemaTable(crypto.randomUUID(), 'NewSchema');
Treeview.getActiveEntry()!.addSchemaTable(table);
this._container!.appendChild(table.getElement());
this._jsPlumbInstance!.revalidate(table.getElement());
SchemaExtends.getInstance().setExtend(table.getUnid(), table.getName());
table.openEditDialog();
}
/**
* Add a new Enum
* @protected
*/
protected _addEnum(): void {
if (Treeview.getActiveEntry() === null) {
return;
}
const table = new EnumTable(crypto.randomUUID(), 'NewEnum');
Treeview.getActiveEntry()!.addEnumTable(table);
this._container!.appendChild(table.getElement());
this._jsPlumbInstance!.revalidate(table.getElement());
table.openEditDialog();
}
/**
* Add a new link
* @param {string} sourceUnid id from schema or enum
* @protected
*/
protected _addLink(sourceUnid: string): void {
if (Treeview.getActiveEntry() === null) {
return;
}
const rootEntry = this._treeview?.getRoot();
if (rootEntry) {
const entry = rootEntry.findParentEntry(sourceUnid);
if (entry) {
let table: BaseTable|null = entry.getTableById(sourceUnid);
if (table === null) {
table = entry.getEnumById(sourceUnid);
}
if (table !== null) {
const link = new LinkTable(crypto.randomUUID(), table.getUnid(), table);
Treeview.getActiveEntry()!.addLinkTable(link);
const linkElement = link.getElement();
if (linkElement) {
this._container!.appendChild(linkElement);
this._jsPlumbInstance!.revalidate(linkElement);
this._updateView();
}
}
}
}
}
/**
* Init
*/
public init() {
this._jsPlumbInstance = jsPlumbInstance;
this._container = jsPlumbInstance.getContainer();
// add schema button -------------------------------------------------------------------------------------------
this._btnAddSchema = document.getElementById('addSchemaBtn');
this._btnAddSchema!.style.display = 'none';
this._btnAddSchema!.addEventListener('click', () => {
if (Treeview.getActiveEntry() !== null) {
this._addSchema();
} else {
alert('Please select first a File for your Schema!');
}
})
// add enum button ---------------------------------------------------------------------------------------------
this._btnAddEnum = document.getElementById('addEnumBtn');
this._btnAddEnum!.style.display = 'none';
this._btnAddEnum!.addEventListener('click', () => {
if (Treeview.getActiveEntry() !== null) {
this._addEnum();
} else {
alert('Please select first a File for your Schema!');
}
});
// treeview ----------------------------------------------------------------------------------------------------
this._treeview = new Treeview();
// update events -----------------------------------------------------------------------------------------------
window.addEventListener('schemaeditor:updatedata', (event: Event) => {
const customEvent = event as CustomEvent<{
updateView?: boolean,
updateTreeView?: boolean;
}>;
const rootEntry = this._treeview?.getRoot();
if (rootEntry) {
rootEntry.sortingEntrys();
}
this.saveData().then(() => {
if (customEvent.detail) {
if (customEvent.detail.updateTreeView === true) {
this._updateTreeview();
}
if (customEvent.detail.updateView === true) {
this._updateView();
}
}
});
});
window.addEventListener('schemaeditor:updateview', () => {
this._updateView();
});
window.addEventListener('schemaeditor:deleteschematable', (event: Event) => {
const customEvent = event as CustomEvent<{ id: string }>;
const rootEntry = this._treeview?.getRoot();
if (rootEntry) {
if (rootEntry.isSchemaTableUse(customEvent.detail.id)) {
alert('Your Schema is used and can not delete!');
return;
}
if (confirm('Do you really want to delete Schema?')) {
if (rootEntry.removeSchemaTable(customEvent.detail.id)) {
rootEntry.removeEntry(customEvent.detail.id);
window.dispatchEvent(new CustomEvent('schemaeditor:updatedata', {
detail: {
updateView: true,
updateTreeView: true
}
}));
}
}
}
});
window.addEventListener('schemaeditor:deleteenumtable', (event: Event) => {
const customEvent = event as CustomEvent<{ id: string }>;
const rootEntry = this._treeview?.getRoot();
if (rootEntry) {
if (rootEntry.isSchemaTableUse(customEvent.detail.id)) {
alert('Your Enum is used and can not delete!');
return;
}
if (confirm('Do you really want to delete enum?')) {
if (rootEntry.removeEnumTable(customEvent.detail.id)) {
rootEntry.removeEntry(customEvent.detail.id);
window.dispatchEvent(new CustomEvent('schemaeditor:updatedata', {
detail: {
updateView: true,
updateTreeView: true
}
}));
}
}
}
});
window.addEventListener('schemaeditor:deletelinktable', (event: Event) => {
const customEvent = event as CustomEvent<{ id: string }>;
const activEntry = Treeview.getActiveEntry();
if (activEntry) {
if (activEntry.hasLinkObject(customEvent.detail.id)) {
if (confirm('Do you really want to delete link?')) {
activEntry.removeLinkTable(customEvent.detail.id);
window.dispatchEvent(new CustomEvent('schemaeditor:updatedata', {
detail: {
updateView: true,
updateTreeView: true
}
}));
}
}
}
});
window.addEventListener('schemaeditor:deletefolderfile', (event: Event) => {
const customEvent = event as CustomEvent<{ id: string }>;
const rootEntry = this._treeview?.getRoot();
if (rootEntry) {
const entry = rootEntry.getEntryById(customEvent.detail.id);
if (entry) {
if (entry.getType() === SchemaJsonDataFSType.file || entry.getType() === SchemaJsonDataFSType.folder) {
if (entry.isEmpty()) {
const parentEntry = rootEntry.findParentEntry(entry.getUnid());
if (parentEntry) {
parentEntry.removeEntry(entry.getUnid());
window.dispatchEvent(new CustomEvent('schemaeditor:updatedata', {
detail: {
updateView: true,
updateTreeView: true
}
}));
}
} else {
alert('Folder/File is not empty, please remove all entries first!');
}
}
}
}
});
window.addEventListener('schemaeditor:sortingentrys', () => {
const rootEntry = this._treeview?.getRoot();
if (rootEntry) {
const activeEntryTableId = Treeview.getActivEntryTable()?.getUnid();
const activeEntryId = Treeview.getActiveEntry()?.getUnid();
rootEntry.sortingEntrys();
this._updateTreeview();
if (activeEntryTableId) {
const tentry = rootEntry.getEntryById(activeEntryTableId);
if (tentry) {
Treeview.setActivEntryTable(tentry);
}
} else {
if (activeEntryId) {
const tentry = rootEntry.getEntryById(activeEntryId);
if (tentry) {
Treeview.setActivEntry(tentry);
}
}
}
this._updateView();
}
});
window.addEventListener('schemaeditor:moveto', (event: Event) => {
const customEvent = event as CustomEvent<SchemaEditorMoveEventDetail>;
const treeview = this._treeview;
if (treeview) {
switch (customEvent.detail.sourceType) {
case SchemaJsonDataFSType.folder:
case SchemaJsonDataFSType.file:
treeview.moveToEntry(customEvent.detail.sourceId, customEvent.detail.detionationId);
break;
case SchemaJsonDataFSType.schema:
treeview.moveTableToEntry(customEvent.detail.sourceId, customEvent.detail.detionationId);
break;
case SchemaJsonDataFSType.enum:
treeview.moveEnumToEntry(customEvent.detail.sourceId, customEvent.detail.detionationId);
break;
}
window.dispatchEvent(new CustomEvent('schemaeditor:updatedata', {}));
}
});
window.addEventListener('schemaeditor:updatename', (event: Event) => {
const customEvent = event as CustomEvent<SchemaEditorUpdatenameEventDetail>;
const treeview = this._treeview;
if (treeview) {
switch (customEvent.detail.sourceType) {
case SchemaJsonDataFSType.schema:
treeview.updateEntryNameTable(customEvent.detail.sourceId);
break;
case SchemaJsonDataFSType.enum:
treeview.updateEntryNameEnum(customEvent.detail.sourceId);
break;
}
this._updateView();
}
});
window.addEventListener('schemaeditor:showtable', (event: Event) => {
const customEvent = event as CustomEvent<SchemaEditorWiggleEventDetail>;
const treeview = this._treeview;
if (treeview) {
const entry = treeview.getRoot().findEntry(customEvent.detail.tableId);
const activeEntry = Treeview.getActiveEntry();
if (activeEntry) {
const activeEntryId = activeEntry.getUnid();
if (entry && activeEntryId) {
if (entry.getUnid() === activeEntryId) {
const table = entry.getTableById(customEvent.detail.tableId);
if (table !== null) {
table.runWiggle();
return;
}
const aenum = entry.getEnumById(customEvent.detail.tableId);
if (aenum !== null) {
aenum.runWiggle();
return;
}
} else if(activeEntry.hasLinkObject(customEvent.detail.tableId)) {
const link = activeEntry.getLinkTableByObjectUnid(customEvent.detail.tableId);
if (link) {
link.getLinkObject()?.runWiggle();
}
} else {
if (confirm('Do you want to add a link object to see the source schema/enum?')) {
this._addLink(customEvent.detail.tableId);
}
}
}
}
}
});
// resizer -----------------------------------------------------------------------------------------------------
const resizer = document.getElementById('resizer')!;
const resizerTop = document.getElementById('resizer-topbar')!;
const controls = document.getElementById('controls')!;
const topbarheader = document.getElementById('topbarheader')!;
this._controls = controls;
let isResizing = false;
resizerTop.addEventListener('mousedown', () => {
isResizing = true;
document.body.style.cursor = 'col-resize';
});
resizer.addEventListener('mousedown', () => {
isResizing = true;
document.body.style.cursor = 'col-resize';
});
document.addEventListener('mousemove', (e) => {
if (!isResizing) {
return;
}
const newWidth = e.clientX;
if (newWidth > 150 && newWidth < window.innerWidth - 100) {
controls.style.width = `${newWidth}px`;
topbarheader.style.width = `${newWidth}px`;
}
});
document.addEventListener('mouseup', () => {
isResizing = false;
document.body.style.cursor = 'default';
});
// load data ---------------------------------------------------------------------------------------------------
this.loadData().then();
}
/**
* Update view
* @protected
*/
protected _updateView(): void {
if (this._jsPlumbInstance && this._container) {
this._jsPlumbInstance.deleteEveryConnection();
this._container.innerHTML = '';
}
this._treeview?.removeAllActiveName();
const rootEntry = this._treeview?.getRoot();
const entryTable = Treeview.getActivEntryTable();
let entry = Treeview.getActiveEntry();
if (entry !== null) {
const entryId = entry.getUnid();
if (rootEntry) {
const pentry = rootEntry.findEntry(entryId);
if (pentry) {
const tentry = pentry.getEntryById(entryId);
if (tentry) {
entry = tentry;
Treeview.setActivEntry(entry);
}
}
}
}
if (entryTable !== null) {
if (rootEntry) {
const tentry = rootEntry.findEntry(entryTable.getUnid());
if (tentry) {
entry = tentry;
Treeview.setActivEntry(entry);
}
}
}
// -------------------------------------------------------------------------------------------------------------
if (entry) {
this._btnAddSchema!.style.display = '';
this._btnAddEnum!.style.display = '';
// Schemas -------------------------------------------------------------------------------------------------
const sTables = entry.getSchemaTables();
for (const table of sTables) {
this._container!.appendChild(table.getElement());
requestAnimationFrame(() => {
this._jsPlumbInstance!.revalidate(table.getElement());
});
}
// Enums ---------------------------------------------------------------------------------------------------
const sEnums = entry.getEnumTables();
for (const tenum of sEnums) {
this._container!.appendChild(tenum.getElement());
requestAnimationFrame(() => {
this._jsPlumbInstance!.revalidate(tenum.getElement());
});
}
// links ---------------------------------------------------------------------------------------------------
const links = entry.getLinkTables();
for (const link of links) {
if (link.getLinkObject() === null) {
const parentEntry = rootEntry?.findParentEntry(link.getLinkUnid());
if (parentEntry) {
let aTable: BaseTable|null = parentEntry.getTableById(link.getLinkUnid());
if (aTable === null) {
aTable = parentEntry.getEnumById(link.getLinkUnid());
}
if (aTable) {
link.setLinkObject(aTable);
}
}
}
// readd to container
const linkElement = link.getElement();
if (linkElement) {
this._container!.appendChild(linkElement);
requestAnimationFrame(() => {
this._jsPlumbInstance!.revalidate(linkElement);
});
}
}
this._jsPlumbInstance!.repaintEverything();
// updates view --------------------------------------------------------------------------------------------
for (const tenum of sEnums) {
tenum.updateView();
if (entryTable) {
if (tenum.getUnid() === entryTable.getUnid()) {
tenum.setActivView(true);
} else {
tenum.setActivView(false);
}
}
}
for (const link of links) {
link.updateView();
}
for (const table of sTables) {
table.showDropArea(false);
table.updateView();
if (entryTable) {
if (table.getUnid() === entryTable.getUnid()) {
table.setActivView(true);
} else {
table.setActivView(false);
}
}
}
entry.setActiveName();
}
if (entryTable) {
entryTable.setActiveName();
}
}
/**
* Update tree view
*/
public _updateTreeview(): void {
const data = this.getData();
this._treeview?.getRoot().removeEntrys();
this.setData(data);
}
/**
* Return the data
* @return {JsonData}
*/
public getData(): ProjectsData {
const data: ProjectsData = {
projects: [],
extern: [],
editor: {
controls_width: parseInt(this._controls!.style.width , 10) || 300
}
};
if (this._treeview) {
const treeviewData = this._treeview.getData();
if (treeviewData.type === SchemaJsonDataFSType.root) {
const entrys = treeviewData.entrys;
for (const aEntry of entrys) {
if (SchemaJsonDataFS.validate(aEntry, [])) {
if (aEntry.type === SchemaJsonDataFSType.project) {
data.projects.push({
unid: aEntry.unid,
name: aEntry.name,
fs: {
unid: 'root',
name: 'root',
istoggle: true,
icon: 'root',
type: SchemaJsonDataFSType.root,
entrys: aEntry.entrys,
schemas: [],
enums: []
}
});
} else if (aEntry.type === SchemaJsonDataFSType.extern) {
data.extern.push({
unid: aEntry.unid,
name: aEntry.name,
fs: {
unid: 'root',
name: 'root',
istoggle: true,
icon: 'root',
type: SchemaJsonDataFSType.root,
entrys: aEntry.entrys,
schemas: [],
enums: []
}
});
}
}
}
}
}
return data;
}
/**
* Update registers
* @param {JsonDataFS} data
* @protected
*/
protected _updateRegisters(data: JsonDataFS): void {
for (const aenum of data.enums) {
SchemaTypes.getInstance().setEnumType(aenum.unid, aenum.name);
}
for (const schema of data.schemas) {
if (SchemaTypesUtil.isVtsType(schema.extend.type, true)) {
SchemaExtends.getInstance().unsetExtend(schema.unid);
} else {
SchemaExtends.getInstance().setExtend(schema.unid, schema.name);
}
SchemaTypes.getInstance().setType(schema.unid, schema.name);
}
for (const entry of data.entrys) {
if (SchemaJsonDataFS.validate(entry, [])) {
this._updateRegisters(entry);
}
}
}
/**
* Set data
* @param {ProjectsResponse} data
*/
public setData(data: ProjectsData): void {
const rootEntry: JsonDataFS = {
unid: 'root',
name: 'Root',
istoggle: true,
icon: 'root',
type: SchemaJsonDataFSType.root,
entrys: [],
schemas: [],
enums: []
};
for (const project of data.projects) {
const projectEntry: JsonDataFS = {
unid: project.unid,
name: project.name,
istoggle: true,
icon: 'project',
type: SchemaJsonDataFSType.project,
entrys: project.fs.entrys,
schemas: [],
enums: []
};
rootEntry.entrys.push(projectEntry);
}
for (const extern of data.extern) {
const externEntry: JsonDataFS = {
unid: extern.unid,
name: extern.name,
istoggle: true,
icon: 'extern',
type: SchemaJsonDataFSType.extern,
entrys: extern.fs.entrys,
schemas: [],
enums: []
};
rootEntry.entrys.push(externEntry);
}
this._updateRegisters(rootEntry);
this._treeview?.setData(rootEntry);
if (data.editor) {
this._controls!.style.width = `${data.editor.controls_width}px`;
const topbarheader = document.getElementById('topbarheader')!;
topbarheader!.style.width = `${data.editor.controls_width}px`;
}
this._treeview?.getRoot().updateView(true);
}
/**
* Save data to vite server
*/
public async saveData(): Promise<void> {
const save: ProjectSave = {
data: this.getData()
}
await fetch('/api/save-schema', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(save)
});
}
/**
* Load data by vite server
*/
public async loadData(): Promise<void> {
const response = await fetch('/api/load-schema');
if (!response.ok) {
throw new Error(`Can not load: ${response.statusText}`);
}
const projectResponse = await response.json();
if (SchemaProjectsResponse.validate(projectResponse, [])) {
this.setData(projectResponse.data);
} else {
alert('Schema response format is broken!');
}
}
}