Merged PR 190: Update master
This commit is contained in:
commit
3a0c4a5714
24 changed files with 1182 additions and 149 deletions
19
README.md
19
README.md
|
@ -104,3 +104,22 @@ Inside `.vscode/settings.json`, set the following :
|
|||
```
|
||||
|
||||
Change the `url` to the dev server url. Set the `runtimeExecutable` to your favorite chromium browser.
|
||||
|
||||
|
||||
# Generate definition files for SmartModeler
|
||||
|
||||
Pre-requisite: `typescript`, `python3`
|
||||
|
||||
Go to the `src/dts` directory and run the following command
|
||||
|
||||
```
|
||||
tsc --project tsconfig.dts.json
|
||||
```
|
||||
|
||||
Then, run `python3` (or `py` on Windows) on `generate_dts.py`:
|
||||
|
||||
```
|
||||
python3 generate_dts.py SVGLD svgld.d.ts
|
||||
```
|
||||
|
||||
A definition will be generated as `svgld.d.ts` with the namespace `SVGLD`.
|
|
@ -9,6 +9,6 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<script type="module" src="./src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
65
public/Interfaces.d.ts
vendored
65
public/Interfaces.d.ts
vendored
|
@ -1,65 +0,0 @@
|
|||
declare interface IHistoryState {
|
||||
LastAction: string
|
||||
MainContainer: IContainerModel
|
||||
SelectedContainer: IContainerModel | null
|
||||
SelectedContainerId: string
|
||||
TypeCounters: Record<string, number>
|
||||
}
|
||||
|
||||
declare interface IAvailableContainer {
|
||||
Type: string
|
||||
Width: number
|
||||
Height: number
|
||||
XPositionReference?: XPositionReference
|
||||
Style: React.CSSProperties
|
||||
}
|
||||
|
||||
declare interface IEditorState {
|
||||
history: IHistoryState[]
|
||||
historyCurrentStep: number
|
||||
configuration: IConfiguration
|
||||
}
|
||||
|
||||
declare interface IConfiguration {
|
||||
AvailableContainers: IAvailableContainer[]
|
||||
AvailableSymbols: IAvailableSymbol[]
|
||||
MainContainer: IAvailableContainer
|
||||
}
|
||||
|
||||
declare interface IContainerModel {
|
||||
children: IContainerModel[]
|
||||
parent: IContainerModel | null
|
||||
properties: IProperties
|
||||
userData: Record<string, string | number>
|
||||
}
|
||||
|
||||
declare interface IProperties extends React.CSSProperties {
|
||||
id: string
|
||||
parentId: string | null
|
||||
x: number
|
||||
y: number
|
||||
|
||||
XPositionReference?: XPositionReference
|
||||
}
|
||||
|
||||
declare enum XPositionReference {
|
||||
Left,
|
||||
Center,
|
||||
Right
|
||||
}
|
||||
|
||||
declare interface IAvailableSymbol {
|
||||
Name: string
|
||||
XPositionReference: XPositionReference
|
||||
Image: IImage
|
||||
Width: number
|
||||
Height: number
|
||||
}
|
||||
|
||||
declare interface IImage {
|
||||
Name: string
|
||||
Url: string
|
||||
Base64Image: string
|
||||
Svg: string
|
||||
}
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
<div id="root">
|
||||
</div>
|
||||
<iframe src="./Components/svg-layout-designer/index.html" style="border:none; width:100%; height:100%">
|
||||
</iframe>
|
|
@ -1,40 +1,99 @@
|
|||
namespace SmartBusiness.Web.Components {
|
||||
import IHistoryState = SVGLD.IHistoryState;
|
||||
import IEditorState = SVGLD.IEditorState;
|
||||
|
||||
export class SVGLayoutDesigner extends Components.ComponentBase {
|
||||
|
||||
public constructor(componentInfo: KnockoutComponentTypes.ComponentInfo, params: any) {
|
||||
super(componentInfo, params);
|
||||
setTimeout(() => (window as any).SVGLayoutDesigner.Render(this.$component[0]));
|
||||
this.InitEventsListener();
|
||||
}
|
||||
|
||||
public GetEditorComponent() {
|
||||
return this.$component[0].querySelector('.Editor');
|
||||
const component = this.$component[0]
|
||||
.querySelector('iframe')
|
||||
.contentDocument
|
||||
.querySelector('.Editor');
|
||||
|
||||
if (component === undefined) {
|
||||
throw new Error('[SVGLD] Cannot hook the event because the editor is not yet open')
|
||||
}
|
||||
|
||||
public GetCurrentHistoryState() {
|
||||
this.GetEditorComponent().dispatchEvent(new CustomEvent('getCurrentHistoryState'));
|
||||
return component;
|
||||
}
|
||||
|
||||
public GetEditorState() {
|
||||
this.GetEditorComponent().dispatchEvent(new CustomEvent('getEditorState'));
|
||||
public GetRootComponent() {
|
||||
return this.$component[0]
|
||||
.querySelector('iframe')
|
||||
.contentDocument;
|
||||
}
|
||||
|
||||
public SetEditorState(editorState: IEditorState) {
|
||||
this.GetEditorComponent().dispatchEvent(new CustomEvent('SetEditorState', { detail: editorState }));
|
||||
public GetCurrentHistoryState(callback: (state: IHistoryState) => void) {
|
||||
const component = this.GetEditorComponent();
|
||||
const eventType = 'getCurrentHistoryState';
|
||||
component.dispatchEvent(new CustomEvent(eventType));
|
||||
this.AddEventListener(callback, eventType);
|
||||
}
|
||||
|
||||
public AppendNewHistoryState(historyState: IHistoryState) {
|
||||
this.GetEditorComponent().dispatchEvent(new CustomEvent('appendNewState', { detail: historyState }));
|
||||
public GetEditorState(callback: (state: IEditorState) => void) {
|
||||
const component = this.GetEditorComponent();
|
||||
const eventType = 'getEditorState';
|
||||
component.dispatchEvent(new CustomEvent(eventType));
|
||||
this.AddEventListener(callback, eventType);
|
||||
}
|
||||
|
||||
public OHistoryState: KnockoutObservable<any>;
|
||||
public AppendNewHistoryState(historyState: SVGLD.IHistoryState, callback?: (state: IEditorState) => void) {
|
||||
const eventType = 'appendNewState';
|
||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail: historyState }));
|
||||
this.AddEventListener(callback, eventType);
|
||||
}
|
||||
|
||||
private InitEventsListener() {
|
||||
this.$component[0].addEventListener('getCurrentHistoryState', (e: CustomEvent) => {
|
||||
this.OHistoryState(e.detail);
|
||||
console.log(this.OHistoryState());
|
||||
});
|
||||
this.$component[0].addEventListener('getEditorState', (e) => console.log((e as any).detail));
|
||||
public AddContainer(index: number, type: string, parentId: string, callback?: (state: IEditorState) => void) {
|
||||
const detail = {
|
||||
index,
|
||||
type,
|
||||
parentId
|
||||
}
|
||||
const eventType = 'addContainer';
|
||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail }));
|
||||
this.AddEventListener(callback, eventType);
|
||||
}
|
||||
|
||||
public AddContainerToSelectedContainer(index: number, type: string, callback?: (state: IEditorState) => void) {
|
||||
const detail = {
|
||||
index,
|
||||
type
|
||||
}
|
||||
const eventType = 'addContainerToSelectedContainer';
|
||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail }));
|
||||
this.AddEventListener(callback, eventType);
|
||||
}
|
||||
|
||||
public AppendContainer(type: string, parentId: string, callback?: (state: IEditorState) => void) {
|
||||
const detail = {
|
||||
type,
|
||||
parentId
|
||||
}
|
||||
const eventType = 'appendContainer';
|
||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail }));
|
||||
this.AddEventListener(callback, eventType);
|
||||
}
|
||||
|
||||
public AppendContainerToSelectedContainer(type: string, callback?: (state: IEditorState) => void) {
|
||||
const detail = {
|
||||
type
|
||||
}
|
||||
const eventType = 'appendContainerToSelectedContainer';
|
||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail }));
|
||||
this.AddEventListener(callback, eventType);
|
||||
}
|
||||
|
||||
private AddEventListener(callback: (...args: any[]) => void, eventType: string) {
|
||||
const root = this.GetRootComponent();
|
||||
const listener = (e: CustomEvent) => {
|
||||
e.target.removeEventListener(e.type, listener);
|
||||
callback && callback(e.detail);
|
||||
};
|
||||
root.addEventListener(eventType, listener);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
69
public/workers/message_packet_worker.js
Normal file
69
public/workers/message_packet_worker.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* Web worker handling message API.
|
||||
* When sending quickly multiple request,
|
||||
* a single packet of message merging all request' responses is returned
|
||||
*/
|
||||
|
||||
const DELAY_BEFORE_SEND = 200;
|
||||
const queue = [];
|
||||
let messagePacket = [];
|
||||
onmessage = async(e) => {
|
||||
let packetLength
|
||||
|
||||
const url = e.data.url;
|
||||
const state = e.data.state;
|
||||
const request = {
|
||||
ApplicationState: state
|
||||
};
|
||||
const dataParsed = JSON.stringify(request, GetCircularReplacerKeepDataStructure());
|
||||
queue.push(request);
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: new Headers({
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'Content-Type': 'application/json'
|
||||
}),
|
||||
body: dataParsed
|
||||
})
|
||||
.then((response) =>
|
||||
response.json()
|
||||
)
|
||||
.then(async(json) => {
|
||||
messagePacket.push.apply(messagePacket, json.messages);
|
||||
queue.pop();
|
||||
|
||||
// The sleep allow the message packet to be filled by
|
||||
// others requests before being sent as a single batch
|
||||
// Reducing the wait time will reduce latency but increase error rate
|
||||
let doLoop = true;
|
||||
do {
|
||||
packetLength = messagePacket.length;
|
||||
await sleep(DELAY_BEFORE_SEND);
|
||||
const newPacketLength = messagePacket.length;
|
||||
doLoop = newPacketLength !== packetLength;
|
||||
packetLength = newPacketLength;
|
||||
} while (doLoop);
|
||||
|
||||
if (queue.length <= 0 && messagePacket.length > 0) {
|
||||
console.debug(`[GetFeedback] Packet size before sent: ${messagePacket.length}`)
|
||||
postMessage(messagePacket)
|
||||
messagePacket.splice(0);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function GetCircularReplacerKeepDataStructure()
|
||||
{
|
||||
return (key, value) => {
|
||||
if (key === 'parent') {
|
||||
return;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,16 +1,15 @@
|
|||
const DELAY_BEFORE_SEND = 200;
|
||||
const queue = [];
|
||||
let messagePacket = [];
|
||||
onmessage = async(e) => {
|
||||
let packetLength
|
||||
/**
|
||||
* Web worker handling message API.
|
||||
* Return a message of the request.
|
||||
*/
|
||||
|
||||
onmessage = async(e) => {
|
||||
const url = e.data.url;
|
||||
const state = e.data.state;
|
||||
const request = {
|
||||
ApplicationState: state
|
||||
};
|
||||
const dataParsed = JSON.stringify(request, GetCircularReplacerKeepDataStructure());
|
||||
queue.push(request);
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: new Headers({
|
||||
|
@ -23,33 +22,10 @@ onmessage = async(e) => {
|
|||
response.json()
|
||||
)
|
||||
.then(async(json) => {
|
||||
messagePacket.push.apply(messagePacket, json.messages);
|
||||
queue.pop();
|
||||
|
||||
// The sleep allow the message packet to be filled by
|
||||
// others requests before being sent as a single batch
|
||||
// Reducing the wait time will reduce latency but increase error rate
|
||||
let doLoop = true;
|
||||
do {
|
||||
packetLength = messagePacket.length;
|
||||
await sleep(DELAY_BEFORE_SEND);
|
||||
const newPacketLength = messagePacket.length;
|
||||
doLoop = newPacketLength !== packetLength;
|
||||
packetLength = newPacketLength;
|
||||
} while (doLoop);
|
||||
|
||||
if (queue.length <= 0 && messagePacket.length > 0) {
|
||||
console.debug(`[GetFeedback] Packet size before sent: ${messagePacket.length}`)
|
||||
postMessage(messagePacket)
|
||||
messagePacket.splice(0);
|
||||
}
|
||||
postMessage(json.messages)
|
||||
});
|
||||
};
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function GetCircularReplacerKeepDataStructure()
|
||||
{
|
||||
return (key, value) => {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { ICategory } from '../../Interfaces/ICategory';
|
|||
import { TruncateString } from '../../utils/stringtools';
|
||||
|
||||
interface ICategoryProps {
|
||||
children: JSX.Element | JSX.Element[]
|
||||
children: JSX.Element[]
|
||||
category: ICategory
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ export function Category(props: ICategoryProps): JSX.Element {
|
|||
title={categoryDisplayedText}
|
||||
>
|
||||
<div
|
||||
className='flex flex-row group cursor-pointer h-16'
|
||||
className={'flex flex-row group h-16 cursor-pointer'}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<span className={`transition-all flex-1 h-full justify-center sidebar-component-left ${isOpen ? 'rounded-b-none bg-slate-400/80 group-hover:bg-blue-600' : ''}`}>
|
||||
|
|
|
@ -112,7 +112,7 @@ function UseShortcuts(
|
|||
});
|
||||
}
|
||||
|
||||
function UseWindowEvents(
|
||||
function UseCustomEvents(
|
||||
root: Element | Document,
|
||||
history: IHistoryState[],
|
||||
historyCurrentStep: number,
|
||||
|
@ -178,7 +178,7 @@ export function Editor(props: IEditorProps): JSX.Element {
|
|||
|
||||
// Events
|
||||
UseShortcuts(history, historyCurrentStep, setHistoryCurrentStep);
|
||||
UseWindowEvents(
|
||||
UseCustomEvents(
|
||||
props.root,
|
||||
history,
|
||||
historyCurrentStep,
|
||||
|
|
59
src/Components/Loader/Loader.scss
Normal file
59
src/Components/Loader/Loader.scss
Normal file
|
@ -0,0 +1,59 @@
|
|||
$foreground: #3b82f6;
|
||||
|
||||
.loader,
|
||||
.loader:before,
|
||||
.loader:after {
|
||||
background:$foreground;
|
||||
-webkit-animation:load1 1s infinite ease-in-out;
|
||||
animation:load1 1s infinite ease-in-out;
|
||||
width:1em;
|
||||
height:4em;
|
||||
}
|
||||
|
||||
.loader {
|
||||
color:$foreground;
|
||||
text-indent:-9999em;
|
||||
margin:88px auto;
|
||||
position:relative;
|
||||
font-size:11px;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
-webkit-animation-delay:-0.16s;
|
||||
animation-delay:-0.16s;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
position:absolute;
|
||||
top:0;
|
||||
content:'';
|
||||
}
|
||||
|
||||
&:before {
|
||||
left:-1.5em;
|
||||
-webkit-animation-delay:-0.32s;
|
||||
animation-delay:-0.32s;
|
||||
}
|
||||
|
||||
&:after {
|
||||
left:1.5em;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@mixin load1-frames {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
box-shadow:0 0;
|
||||
height:4em;
|
||||
}
|
||||
|
||||
40% {
|
||||
box-shadow:0 -2em;
|
||||
height:5em;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes load1 {@include load1-frames;}
|
||||
@keyframes load1 {@include load1-frames;}
|
14
src/Components/Loader/Loader.tsx
Normal file
14
src/Components/Loader/Loader.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import './Loader.scss';
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
export interface ILoaderProps {
|
||||
}
|
||||
|
||||
export function Loader(props: ILoaderProps): JSX.Element {
|
||||
return (
|
||||
<div className='loader'>
|
||||
Loading...
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import { FAST_BOOT } from '../../utils/default';
|
||||
import { Loader } from '../Loader/Loader';
|
||||
|
||||
interface IMainMenuProps {
|
||||
newEditor: () => void
|
||||
|
@ -8,10 +10,21 @@ interface IMainMenuProps {
|
|||
enum WindowState {
|
||||
Main,
|
||||
Load,
|
||||
Loading,
|
||||
}
|
||||
|
||||
export function MainMenu(props: IMainMenuProps): JSX.Element {
|
||||
const [windowState, setWindowState] = React.useState(WindowState.Main);
|
||||
|
||||
if (FAST_BOOT) {
|
||||
props.newEditor();
|
||||
return (
|
||||
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
switch (windowState) {
|
||||
case WindowState.Load:
|
||||
return (
|
||||
|
@ -46,10 +59,19 @@ export function MainMenu(props: IMainMenuProps): JSX.Element {
|
|||
</div>
|
||||
|
||||
);
|
||||
case WindowState.Loading:
|
||||
return (
|
||||
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div className='absolute bg-blue-50 p-12 rounded-lg drop-shadow-lg grid grid-cols-1 md:grid-cols-2 gap-8 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
|
||||
<button type="button" className='mainmenu-btn' onClick={props.newEditor}>Start from scratch</button>
|
||||
<button type="button" className='mainmenu-btn' onClick={() => {
|
||||
setWindowState(WindowState.Loading);
|
||||
props.newEditor();
|
||||
}}>Start from scratch</button>
|
||||
<button type="button" className='mainmenu-btn' onClick={() => setWindowState(WindowState.Load)}>Load a configuration file</button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -30,7 +30,7 @@ function UseWorker(
|
|||
|
||||
React.useEffect(() => {
|
||||
myWorker.onmessage = (event) => {
|
||||
setMessages(messages.concat(event.data as IMessage[]));
|
||||
setMessages(event.data as IMessage[]);
|
||||
};
|
||||
}, [messages, setMessages]);
|
||||
}
|
||||
|
|
|
@ -7,9 +7,10 @@ describe.concurrent('Sidebar', () => {
|
|||
it('Start default', () => {
|
||||
render(
|
||||
<Sidebar
|
||||
selectedContainer={undefined}
|
||||
componentOptions={[]}
|
||||
isOpen={true}
|
||||
buttonOnClick={() => {}}
|
||||
buttonOnClick={() => { } } categories={[]}
|
||||
/>
|
||||
);
|
||||
const stuff = screen.queryByText(/stuff/i);
|
||||
|
@ -22,8 +23,7 @@ describe.concurrent('Sidebar', () => {
|
|||
render(<Sidebar
|
||||
componentOptions={[]}
|
||||
isOpen={false}
|
||||
buttonOnClick={() => {}}
|
||||
/>);
|
||||
buttonOnClick={() => { } } selectedContainer={undefined} categories={[]} />);
|
||||
|
||||
const stuff = screen.queryByText(/stuff/i);
|
||||
expect(screen.getByText(/Components/i).classList.contains('-left-64')).toBeDefined();
|
||||
|
@ -45,8 +45,7 @@ describe.concurrent('Sidebar', () => {
|
|||
}
|
||||
]}
|
||||
isOpen={true}
|
||||
buttonOnClick={handleButtonClick}
|
||||
/>);
|
||||
buttonOnClick={handleButtonClick} selectedContainer={undefined} categories={[]} />);
|
||||
const stuff = screen.getByText(/stuff/i);
|
||||
|
||||
expect(stuff).toBeDefined();
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import { EyeIcon, EyeOffIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
||||
import { ICategory } from '../../Interfaces/ICategory';
|
||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { TruncateString } from '../../utils/stringtools';
|
||||
import { Category } from '../Category/Category';
|
||||
|
||||
interface ISidebarProps {
|
||||
selectedContainer: IContainerModel | undefined
|
||||
componentOptions: IAvailableContainer[]
|
||||
categories: ICategory[]
|
||||
isOpen: boolean
|
||||
|
@ -21,6 +24,8 @@ interface SidebarCategory {
|
|||
}
|
||||
|
||||
export function Sidebar(props: ISidebarProps): JSX.Element {
|
||||
const [hideDisabled, setHideDisabled] = React.useState<boolean>(false);
|
||||
|
||||
const rootElements: Array<JSX.Element | undefined> = [];
|
||||
const categories = new Map<string, SidebarCategory>(props.categories.map(category => [
|
||||
category.Type,
|
||||
|
@ -41,12 +46,25 @@ export function Sidebar(props: ISidebarProps): JSX.Element {
|
|||
</Category>);
|
||||
});
|
||||
|
||||
const selectedContainer = props.selectedContainer;
|
||||
const config = props.componentOptions.find(option => option.Type === selectedContainer?.properties.type);
|
||||
// build the components
|
||||
props.componentOptions.forEach(componentOption => {
|
||||
if (componentOption.IsHidden === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
let disabled = false;
|
||||
if (config?.Whitelist !== undefined) {
|
||||
disabled = config.Whitelist?.find(type => type === componentOption.Type) === undefined;
|
||||
} else if (config?.Blacklist !== undefined) {
|
||||
disabled = config.Blacklist?.find(type => type === componentOption.Type) !== undefined ?? false;
|
||||
}
|
||||
|
||||
if (disabled && hideDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const componentButton = (<button
|
||||
key={componentOption.Type}
|
||||
type="button"
|
||||
|
@ -56,9 +74,11 @@ export function Sidebar(props: ISidebarProps): JSX.Element {
|
|||
onClick={() => props.buttonOnClick(componentOption.Type)}
|
||||
draggable={true}
|
||||
onDragStart={(event) => HandleDragStart(event)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{TruncateString(componentOption.DisplayedText ?? componentOption.Type, 25)}
|
||||
</button>);
|
||||
|
||||
if (componentOption.Category === null || componentOption.Category === undefined) {
|
||||
rootElements.push(componentButton);
|
||||
return;
|
||||
|
@ -69,6 +89,7 @@ export function Sidebar(props: ISidebarProps): JSX.Element {
|
|||
console.error(`[Category] Category does not exists in configuration.Categories: ${componentOption.Category}`);
|
||||
return;
|
||||
}
|
||||
|
||||
category.children.push(componentButton);
|
||||
});
|
||||
|
||||
|
@ -77,10 +98,22 @@ export function Sidebar(props: ISidebarProps): JSX.Element {
|
|||
<div className={`fixed z-10 bg-slate-200
|
||||
text-gray-700 transition-all h-full w-64
|
||||
overflow-y-auto ${isOpenClasses}`}>
|
||||
<div className='bg-slate-100 sidebar-title'>
|
||||
<div className='bg-slate-100 sidebar-title flex place-content-between'>
|
||||
Components
|
||||
<button
|
||||
onClick={() => { setHideDisabled(!hideDisabled); }}
|
||||
className='h-6'
|
||||
aria-label='Hide disabled component'
|
||||
title='Hide disabled component'
|
||||
>
|
||||
{
|
||||
hideDisabled
|
||||
? <EyeOffIcon className='heroicon'></EyeOffIcon>
|
||||
: <EyeIcon className='heroicon'></EyeIcon>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div className='grid grid-cols-1 md:grid-cols-1 gap-2
|
||||
<div className='transition-all grid grid-cols-1 md:grid-cols-1 gap-2
|
||||
m-2 md:text-xs font-bold'>
|
||||
{rootElements}
|
||||
</div>
|
||||
|
|
|
@ -84,6 +84,7 @@ export function UI(props: IUIProps): JSX.Element {
|
|||
} }/>
|
||||
|
||||
<Sidebar
|
||||
selectedContainer={props.selectedContainer}
|
||||
componentOptions={props.availableContainers}
|
||||
categories={props.categories}
|
||||
isOpen={isSidebarOpen}
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { AddContainer as AddContainerAction, AddContainerToSelectedContainer as AddContainerToSelectedContainerAction } from '../Components/Editor/Actions/AddContainer';
|
||||
import { DeleteContainer as DeleteContainerAction, SelectContainer as SelectContainerAction } from '../Components/Editor/Actions/ContainerOperations';
|
||||
import { AddSymbol as AddSymbolAction, DeleteSymbol as DeleteSymbolAction, SelectSymbol as SelectSymbolAction } from '../Components/Editor/Actions/SymbolOperations';
|
||||
import { GetCurrentHistory } from '../Components/Editor/Editor';
|
||||
import { IConfiguration } from '../Interfaces/IConfiguration';
|
||||
import { IEditorState } from '../Interfaces/IEditorState';
|
||||
import { IHistoryState } from '../Interfaces/IHistoryState';
|
||||
import { FindContainerById } from '../utils/itertools';
|
||||
import { ReviveState } from '../utils/saveload';
|
||||
|
||||
function InitEditor(configuration: IConfiguration): void {
|
||||
// faire comme la callback de fetch
|
||||
}
|
||||
|
||||
function GetEditorState(root: Element | Document,
|
||||
editorState: IEditorState): void {
|
||||
const customEvent = new CustomEvent<IEditorState>('getEditorState', { detail: editorState });
|
||||
const customEvent = new CustomEvent<IEditorState>('getEditorState', { detail: structuredClone(editorState) });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
|
@ -19,7 +17,7 @@ function GetCurrentHistoryState(root: Element | Document,
|
|||
editorState: IEditorState): void {
|
||||
const customEvent = new CustomEvent<IHistoryState>(
|
||||
'getCurrentHistoryState',
|
||||
{ detail: editorState.history[editorState.historyCurrentStep] });
|
||||
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
|
@ -33,6 +31,244 @@ function AppendNewState(root: Element | Document,
|
|||
|
||||
history.push(state);
|
||||
setNewHistory(history);
|
||||
|
||||
const customEvent = new CustomEvent<IHistoryState>(
|
||||
'appendNewState',
|
||||
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
function AddContainer(root: Element | Document,
|
||||
editorState: IEditorState,
|
||||
setNewHistory: (newHistory: IHistoryState[]) => void,
|
||||
eventInitDict?: CustomEventInit): void {
|
||||
const {
|
||||
index,
|
||||
type,
|
||||
parentId
|
||||
} = eventInitDict?.detail;
|
||||
|
||||
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
|
||||
const newHistory = AddContainerAction(
|
||||
index,
|
||||
type,
|
||||
parentId,
|
||||
editorState.configuration,
|
||||
history,
|
||||
editorState.historyCurrentStep
|
||||
);
|
||||
setNewHistory(newHistory);
|
||||
|
||||
const customEvent = new CustomEvent<IHistoryState>(
|
||||
'addContainer',
|
||||
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
function AddContainerToSelectedContainer(root: Element | Document,
|
||||
editorState: IEditorState,
|
||||
setNewHistory: (newHistory: IHistoryState[]) => void,
|
||||
eventInitDict?: CustomEventInit): void {
|
||||
const {
|
||||
index,
|
||||
type
|
||||
} = eventInitDict?.detail;
|
||||
|
||||
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
|
||||
const currentState = history[editorState.historyCurrentStep];
|
||||
|
||||
const newHistory = AddContainerAction(
|
||||
index,
|
||||
type,
|
||||
currentState.selectedContainerId,
|
||||
editorState.configuration,
|
||||
history,
|
||||
editorState.historyCurrentStep
|
||||
);
|
||||
setNewHistory(newHistory);
|
||||
|
||||
const customEvent = new CustomEvent<IHistoryState>(
|
||||
'addContainerToSelectedContainer',
|
||||
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
function AppendContainer(root: Element | Document,
|
||||
editorState: IEditorState,
|
||||
setNewHistory: (newHistory: IHistoryState[]) => void,
|
||||
eventInitDict?: CustomEventInit): void {
|
||||
const {
|
||||
type,
|
||||
parentId
|
||||
} = eventInitDict?.detail;
|
||||
|
||||
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
|
||||
const currentState = history[editorState.historyCurrentStep];
|
||||
|
||||
const parent = FindContainerById(currentState.mainContainer, parentId);
|
||||
|
||||
const newHistory = AddContainerAction(
|
||||
parent?.children.length ?? 0,
|
||||
type,
|
||||
parentId,
|
||||
editorState.configuration,
|
||||
history,
|
||||
editorState.historyCurrentStep
|
||||
);
|
||||
setNewHistory(newHistory);
|
||||
|
||||
const customEvent = new CustomEvent<IHistoryState>(
|
||||
'appendContainerToSelectedContainer',
|
||||
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
function AppendContainerToSelectedContainer(root: Element | Document,
|
||||
editorState: IEditorState,
|
||||
setNewHistory: (newHistory: IHistoryState[]) => void,
|
||||
eventInitDict?: CustomEventInit): void {
|
||||
const {
|
||||
type
|
||||
} = eventInitDict?.detail;
|
||||
|
||||
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
|
||||
const currentState = history[editorState.historyCurrentStep];
|
||||
|
||||
const selected = FindContainerById(currentState.mainContainer, currentState.selectedContainerId);
|
||||
|
||||
const newHistory = AddContainerToSelectedContainerAction(
|
||||
type,
|
||||
selected,
|
||||
editorState.configuration,
|
||||
history,
|
||||
editorState.historyCurrentStep
|
||||
);
|
||||
|
||||
if (newHistory === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setNewHistory(newHistory);
|
||||
|
||||
const customEvent = new CustomEvent<IHistoryState>(
|
||||
'appendContainerToSelectedContainer',
|
||||
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
function SelectContainer(root: Element | Document,
|
||||
editorState: IEditorState,
|
||||
setNewHistory: (newHistory: IHistoryState[]) => void,
|
||||
eventInitDict?: CustomEventInit): void {
|
||||
const {
|
||||
containerId
|
||||
} = eventInitDict?.detail;
|
||||
|
||||
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
|
||||
|
||||
const newHistory = SelectContainerAction(
|
||||
containerId,
|
||||
history,
|
||||
editorState.historyCurrentStep
|
||||
);
|
||||
setNewHistory(newHistory);
|
||||
|
||||
const customEvent = new CustomEvent<IHistoryState>(
|
||||
'selectContainer',
|
||||
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
function DeleteContainer(root: Element | Document,
|
||||
editorState: IEditorState,
|
||||
setNewHistory: (newHistory: IHistoryState[]) => void,
|
||||
eventInitDict?: CustomEventInit): void {
|
||||
const {
|
||||
containerId
|
||||
} = eventInitDict?.detail;
|
||||
|
||||
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
|
||||
|
||||
const newHistory = DeleteContainerAction(
|
||||
containerId,
|
||||
history,
|
||||
editorState.historyCurrentStep
|
||||
);
|
||||
setNewHistory(newHistory);
|
||||
|
||||
const customEvent = new CustomEvent<IHistoryState>(
|
||||
'deleteContainer',
|
||||
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
function AddSymbol(root: Element | Document,
|
||||
editorState: IEditorState,
|
||||
setNewHistory: (newHistory: IHistoryState[]) => void,
|
||||
eventInitDict?: CustomEventInit): void {
|
||||
const {
|
||||
name
|
||||
} = eventInitDict?.detail;
|
||||
|
||||
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
|
||||
|
||||
const newHistory = AddSymbolAction(
|
||||
name,
|
||||
editorState.configuration,
|
||||
history,
|
||||
editorState.historyCurrentStep
|
||||
);
|
||||
setNewHistory(newHistory);
|
||||
|
||||
const customEvent = new CustomEvent<IHistoryState>(
|
||||
'AddSymbol',
|
||||
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
function SelectSymbol(root: Element | Document,
|
||||
editorState: IEditorState,
|
||||
setNewHistory: (newHistory: IHistoryState[]) => void,
|
||||
eventInitDict?: CustomEventInit): void {
|
||||
const {
|
||||
symbolId
|
||||
} = eventInitDict?.detail;
|
||||
|
||||
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
|
||||
|
||||
const newHistory = SelectSymbolAction(
|
||||
symbolId,
|
||||
history,
|
||||
editorState.historyCurrentStep
|
||||
);
|
||||
setNewHistory(newHistory);
|
||||
|
||||
const customEvent = new CustomEvent<IHistoryState>(
|
||||
'SelectSymbol',
|
||||
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
function DeleteSymbol(root: Element | Document,
|
||||
editorState: IEditorState,
|
||||
setNewHistory: (newHistory: IHistoryState[]) => void,
|
||||
eventInitDict?: CustomEventInit): void {
|
||||
const {
|
||||
symbolId
|
||||
} = eventInitDict?.detail;
|
||||
|
||||
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
|
||||
const newHistory = DeleteSymbolAction(
|
||||
symbolId,
|
||||
history,
|
||||
editorState.historyCurrentStep
|
||||
);
|
||||
setNewHistory(newHistory);
|
||||
|
||||
const customEvent = new CustomEvent<IHistoryState>(
|
||||
'DeleteSymbol',
|
||||
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
export interface IEditorEvent {
|
||||
|
@ -48,5 +284,14 @@ export interface IEditorEvent {
|
|||
export const events: IEditorEvent[] = [
|
||||
{ name: 'getEditorState', func: GetEditorState },
|
||||
{ name: 'getCurrentHistoryState', func: GetCurrentHistoryState },
|
||||
{ name: 'appendNewState', func: AppendNewState }
|
||||
{ name: 'appendNewState', func: AppendNewState },
|
||||
{ name: 'addContainer', func: AddContainer },
|
||||
{ name: 'addContainerToSelectedContainer', func: AddContainerToSelectedContainer },
|
||||
{ name: 'appendContainer', func: AppendContainer },
|
||||
{ name: 'appendContainerToSelectedContainer', func: AppendContainerToSelectedContainer },
|
||||
{ name: 'selectContainer', func: SelectContainer },
|
||||
{ name: 'deleteContainer', func: DeleteContainer },
|
||||
{ name: 'addSymbol', func: AddSymbol },
|
||||
{ name: 'selectSymbol', func: SelectSymbol },
|
||||
{ name: 'deleteSymbol', func: DeleteSymbol }
|
||||
];
|
||||
|
|
|
@ -118,6 +118,18 @@ export interface IAvailableContainer {
|
|||
*/
|
||||
IsHidden?: boolean
|
||||
|
||||
/**
|
||||
* Disable a list of available container to be added inside
|
||||
*/
|
||||
Blacklist?: string[]
|
||||
|
||||
/**
|
||||
* Cannot be used with blacklist. Whitelist will be prioritized.
|
||||
* To disable the whitelist, Whitelist must be undefined.
|
||||
* Only allow a set of available container to be added inside
|
||||
*/
|
||||
Whitelist?: string[]
|
||||
|
||||
/**
|
||||
* (optional)
|
||||
* Style of the <rect>
|
||||
|
|
101
src/dts/generate_dts.py
Normal file
101
src/dts/generate_dts.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
'''
|
||||
Generate a definition file with a global namespace
|
||||
from a typescript module definition file
|
||||
'''
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import argparse
|
||||
|
||||
parser = parser = argparse.ArgumentParser(description='Generate a definition file with a global namespace from a typescript module definition file')
|
||||
parser.add_argument('namespace',
|
||||
help='Namespace used in the global script. (example: Three.js use THREE as its namespace)')
|
||||
parser.add_argument('output_filename',
|
||||
help='Output d.ts definition file. (example: three.d.ts)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
output_filename = args.output_filename
|
||||
namespace = args.namespace
|
||||
|
||||
|
||||
import_pattern = re.compile(
|
||||
r"import(?:[\"'\s]*([\w*{}\n\r\t, ]+)from\s*)?[\"'\s].*([@\w_-]+)[\"'\s].*;"
|
||||
)
|
||||
export_pattern = re.compile(
|
||||
r"export ([*] from [\"'\s].*[\"'\s]|{.*}(?: from [\"'\s].*[\"'\s])?);"
|
||||
)
|
||||
|
||||
filter_directories = ["./Enums", "./Interfaces"]
|
||||
|
||||
def main():
|
||||
'''
|
||||
Entry point function
|
||||
'''
|
||||
with open(output_filename, 'w') as output_file:
|
||||
output_file.write(('declare namespace {} {{\n'.format(namespace)))
|
||||
for root, subdirs, files in os.walk('./'):
|
||||
if root not in filter_directories:
|
||||
continue
|
||||
|
||||
print('--\nroot = ' + root)
|
||||
|
||||
for subdir in subdirs:
|
||||
print('\t- subdirectory ' + subdir)
|
||||
|
||||
for filename in files:
|
||||
if filename == 'output.d.ts':
|
||||
continue
|
||||
|
||||
if filename == 'my-directory-list.txt':
|
||||
os.remove(os.path.join(root, filename))
|
||||
continue
|
||||
|
||||
suffixes = pathlib.Path(filename).suffixes
|
||||
if (suffixes != ['.d', '.ts']):
|
||||
continue
|
||||
|
||||
file_path = os.path.join(root, filename)
|
||||
file_path = file_path.replace('\\', '/')
|
||||
|
||||
print('\t- file %s (full path: %s)' % (filename, file_path))
|
||||
with open(file_path, 'r') as cur_file:
|
||||
f_content = cur_file.read()
|
||||
|
||||
# removes imports
|
||||
# see https://gist.github.com/manekinekko/7e58a17bc62a9be47172
|
||||
f_content = import_pattern.sub('', f_content)
|
||||
|
||||
# Replace 'export { my_class as synonym };'
|
||||
# => 'class synonym extend my_class {}'
|
||||
f_content = re.sub(
|
||||
r"export ({ ([A-Z].*) as ([A-Z].*) });",
|
||||
"export class \\g<3> extends \\g<2> {}",
|
||||
f_content,
|
||||
0,
|
||||
re.MULTILINE
|
||||
)
|
||||
|
||||
# Replace 'export declare class' => 'export class'
|
||||
f_content = re.sub(r"export (declare) class", 'export class', f_content, 0, re.MULTILINE)
|
||||
f_content = re.sub(r"export (declare) enum", 'export enum', f_content, 0, re.MULTILINE)
|
||||
f_content = re.sub(r"export (declare) function", 'export function', f_content, 0, re.MULTILINE)
|
||||
f_content = re.sub(r"export (declare) type", 'export type', f_content, 0, re.MULTILINE)
|
||||
|
||||
# Replace 'export { my_func as synonym }' => 'export function synonym = my_func'
|
||||
|
||||
# Replace 'export default class' => 'export class'
|
||||
f_content = re.sub(r"(export) default", "\\1", f_content, 0, re.MULTILINE)
|
||||
|
||||
# Replace other exports : 'export { .* } from '.*';' and 'export [*] from '.*';
|
||||
f_content = export_pattern.sub('', f_content)
|
||||
|
||||
# Specific to your module
|
||||
f_content = re.sub('export as namespace {};'.format(namespace), '', f_content)
|
||||
|
||||
output_file.write(f_content)
|
||||
output_file.write('\n')
|
||||
output_file.write(('}\n'))
|
||||
|
||||
main()
|
469
src/dts/svgld.d.ts
vendored
Normal file
469
src/dts/svgld.d.ts
vendored
Normal file
|
@ -0,0 +1,469 @@
|
|||
declare namespace SVGLD {
|
||||
/**
|
||||
* Add method when creating a container
|
||||
* - Append will append to the last children in list
|
||||
* - Insert will always place it at the begining
|
||||
* - Replace will remove the selected container and insert a new one
|
||||
* (default: Append)
|
||||
*/
|
||||
export enum AddMethod {
|
||||
Append = 0,
|
||||
Insert = 1,
|
||||
Replace = 2
|
||||
}
|
||||
|
||||
export enum MessageType {
|
||||
Normal = 0,
|
||||
Success = 1,
|
||||
Warning = 2,
|
||||
Error = 3
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the type of the property.
|
||||
* Used for the assignation in the OnPropertyChange function
|
||||
* See ContainerOperations.ts's OnPropertyChange
|
||||
*/
|
||||
export enum PropertyType {
|
||||
/**
|
||||
* Simple property: is not inside any object: id, x, width... (default)
|
||||
*/
|
||||
Simple = 0,
|
||||
/**
|
||||
* Style property: is inside the style object: stroke, fillOpacity...
|
||||
*/
|
||||
Style = 1,
|
||||
/**
|
||||
* Margin property: is inside the margin property: left, bottom, top, right...
|
||||
*/
|
||||
Margin = 2
|
||||
}
|
||||
|
||||
export enum XPositionReference {
|
||||
Left = 0,
|
||||
Center = 1,
|
||||
Right = 2
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface IAction {
|
||||
Id: string;
|
||||
CustomLogo: IImage;
|
||||
Label: string;
|
||||
Description: string;
|
||||
Action: string;
|
||||
AddingBehavior: AddMethod;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** Model of available container used in application configuration */
|
||||
export interface IAvailableContainer {
|
||||
/** type */
|
||||
Type: string;
|
||||
/** displayed text */
|
||||
DisplayedText?: string;
|
||||
/** category */
|
||||
Category?: string;
|
||||
/** horizontal offset */
|
||||
X?: number;
|
||||
/** vertical offset */
|
||||
Y?: number;
|
||||
/** width */
|
||||
Width?: number;
|
||||
/** height */
|
||||
Height?: number;
|
||||
/**
|
||||
* Minimum width (min=1)
|
||||
* Allows the container to set isRigidBody to false when it gets squeezed
|
||||
* by an anchor
|
||||
*/
|
||||
MinWidth?: number;
|
||||
/**
|
||||
* Maximum width
|
||||
*/
|
||||
MaxWidth?: number;
|
||||
/** margin */
|
||||
Margin?: IMargin;
|
||||
/** true if anchor, false otherwise */
|
||||
IsAnchor?: boolean;
|
||||
/** true if flex, false otherwise */
|
||||
IsFlex?: boolean;
|
||||
/** Method used on container add */
|
||||
AddMethod?: AddMethod;
|
||||
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
||||
XPositionReference?: XPositionReference;
|
||||
/**
|
||||
* (optional)
|
||||
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
|
||||
* to draw some patterns that can be bind to the properties of the container
|
||||
* Use {prop} to bind a property. Use {{ styleProp }} to use an object.
|
||||
* Example :
|
||||
* ```
|
||||
* `<rect width="{width}" height="{height}" style="{style}"></rect>
|
||||
* <rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
|
||||
* <line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
|
||||
* <line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
|
||||
* `
|
||||
* ```
|
||||
*/
|
||||
CustomSVG?: string;
|
||||
/**
|
||||
* (optional)
|
||||
* Disabled when Pattern is used.
|
||||
*
|
||||
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
|
||||
* to draw some patterns that can be bind to the properties of the container
|
||||
* Use {prop} to bind a property. Use {{ styleProp }} to use an object.
|
||||
* Example :
|
||||
* ```
|
||||
* `<rect width="{width}" height="{height}" style="{style}"></rect>
|
||||
* <rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
|
||||
* <line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
|
||||
* <line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
|
||||
* `
|
||||
* ```
|
||||
*/
|
||||
DefaultChildType?: string;
|
||||
/**
|
||||
* Allow to use a Pattern to create the list of children
|
||||
* Cannot be used with DefaultChildType,
|
||||
* DefaultChildType will be disabled for this container and the children
|
||||
*/
|
||||
Pattern?: string;
|
||||
/** if true, show the dimension of the container */
|
||||
ShowSelfDimensions?: boolean;
|
||||
/** if true show the overall dimensions of its children */
|
||||
ShowChildrenDimensions?: boolean;
|
||||
/**
|
||||
* if true, allows a parent dimension borrower to uses its x coordinate for as a reference point for a dimension
|
||||
*/
|
||||
MarkPositionToDimensionBorrower?: boolean;
|
||||
/**
|
||||
* if true, show a dimension from the edge of the container to end
|
||||
* and insert dimensions marks at lift up children (see liftDimensionToBorrower)
|
||||
*/
|
||||
IsDimensionBorrower?: boolean;
|
||||
/**
|
||||
* if true, hide the entry in the sidebar (default: false)
|
||||
*/
|
||||
IsHidden?: boolean;
|
||||
/**
|
||||
* Disable a list of available container to be added inside
|
||||
*/
|
||||
Blacklist?: string[];
|
||||
/**
|
||||
* Cannot be used with blacklist. Whitelist will be prioritized.
|
||||
* To disable the whitelist, Whitelist must be undefined.
|
||||
* Only allow a set of available container to be added inside
|
||||
*/
|
||||
Whitelist?: string[];
|
||||
/**
|
||||
* (optional)
|
||||
* Style of the <rect>
|
||||
*/
|
||||
Style?: React.CSSProperties;
|
||||
/**
|
||||
* List of possible actions shown on right-click
|
||||
*/
|
||||
Actions?: IAction[];
|
||||
/**
|
||||
* (optional)
|
||||
* User data that can be used for data storage or custom SVG
|
||||
*/
|
||||
UserData?: object;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Model of available symbol to configure the application */
|
||||
export interface IAvailableSymbol {
|
||||
Name: string;
|
||||
Image: IImage;
|
||||
Width?: number;
|
||||
Height?: number;
|
||||
XPositionReference?: XPositionReference;
|
||||
}
|
||||
|
||||
export interface ICategory {
|
||||
Type: string;
|
||||
DisplayedText?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** Model of configuration for the application to configure it */
|
||||
export interface IConfiguration {
|
||||
AvailableContainers: IAvailableContainer[];
|
||||
AvailableSymbols: IAvailableSymbol[];
|
||||
Categories: ICategory[];
|
||||
Patterns: IPattern[];
|
||||
MainContainer: IAvailableContainer;
|
||||
}
|
||||
|
||||
|
||||
export interface IContainerModel {
|
||||
children: IContainerModel[];
|
||||
parent: IContainerModel | null;
|
||||
properties: IContainerProperties;
|
||||
userData: Record<string, string | number>;
|
||||
}
|
||||
/**
|
||||
* Macro for creating the interface
|
||||
* Do not add methods since they will be lost during serialization
|
||||
*/
|
||||
export class ContainerModel implements IContainerModel {
|
||||
children: IContainerModel[];
|
||||
parent: IContainerModel | null;
|
||||
properties: IContainerProperties;
|
||||
userData: Record<string, string | number>;
|
||||
constructor(parent: IContainerModel | null, properties: IContainerProperties, children?: IContainerModel[], userData?: {});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Properties of a container
|
||||
*/
|
||||
export interface IContainerProperties {
|
||||
/** id of the container */
|
||||
id: string;
|
||||
/** type matching the configuration on construction */
|
||||
type: string;
|
||||
/** id of the parent container (null when there is no parent) */
|
||||
parentId: string;
|
||||
/** id of the linked symbol ('' when there is no parent) */
|
||||
linkedSymbolId: string;
|
||||
/** Text displayed in the container */
|
||||
displayedText: string;
|
||||
/** horizontal offset */
|
||||
x: number;
|
||||
/** vertical offset */
|
||||
y: number;
|
||||
/** margin */
|
||||
margin: IMargin;
|
||||
/**
|
||||
* Minimum width (min=1)
|
||||
* Allows the container to set isRigidBody to false when it gets squeezed
|
||||
* by an anchor
|
||||
*/
|
||||
minWidth: number;
|
||||
/**
|
||||
* Maximum width
|
||||
*/
|
||||
maxWidth: number;
|
||||
/** width */
|
||||
width: number;
|
||||
/** height */
|
||||
height: number;
|
||||
/** true if anchor, false otherwise */
|
||||
isAnchor: boolean;
|
||||
/** true if flex, false otherwise */
|
||||
isFlex: boolean;
|
||||
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
||||
xPositionReference: XPositionReference;
|
||||
/** if true, show the dimension of the container */
|
||||
showSelfDimensions: boolean;
|
||||
/** if true show the overall dimensions of its children */
|
||||
showChildrenDimensions: boolean;
|
||||
/**
|
||||
* if true, allows a parent dimension borrower to borrow its x coordinate
|
||||
* as a reference point for a dimension
|
||||
*/
|
||||
markPositionToDimensionBorrower: boolean;
|
||||
/**
|
||||
* if true, show a dimension from the edge of the container to end
|
||||
* and insert dimensions marks at lift up children (see liftDimensionToBorrower)
|
||||
*/
|
||||
isDimensionBorrower: boolean;
|
||||
/**
|
||||
* Warnings of a container
|
||||
*/
|
||||
warning: string;
|
||||
/**
|
||||
* (optional)
|
||||
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
|
||||
* to draw some patterns that can be bind to the properties of the container
|
||||
* Use {prop} to bind a property. Use {{ styleProp }} to use an object.
|
||||
* Example :
|
||||
* ```
|
||||
* `<rect width="{width}" height="{height}" style="{style}"></rect>
|
||||
* <rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
|
||||
* <line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
|
||||
* <line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
|
||||
* `
|
||||
* ```
|
||||
*/
|
||||
customSVG?: string;
|
||||
/**
|
||||
* (optional)
|
||||
* Style of the <rect>
|
||||
*/
|
||||
style?: React.CSSProperties;
|
||||
/**
|
||||
* (optional)
|
||||
* User data that can be used for data storage or custom SVG
|
||||
*/
|
||||
userData?: object;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface IEditorState {
|
||||
history: IHistoryState[];
|
||||
historyCurrentStep: number;
|
||||
configuration: IConfiguration;
|
||||
}
|
||||
|
||||
|
||||
export interface IGetFeedbackRequest {
|
||||
/** Current application state */
|
||||
ApplicationState: IHistoryState;
|
||||
}
|
||||
|
||||
|
||||
export interface IGetFeedbackResponse {
|
||||
messages: IMessage[];
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface IHistoryState {
|
||||
/** Last editor action */
|
||||
lastAction: string;
|
||||
/** Reference to the main container */
|
||||
mainContainer: IContainerModel;
|
||||
/** Id of the selected container */
|
||||
selectedContainerId: string;
|
||||
/** Counter of type of container. Used for ids. */
|
||||
typeCounters: Record<string, number>;
|
||||
/** List of symbols */
|
||||
symbols: Map<string, ISymbolModel>;
|
||||
/** Selected symbols id */
|
||||
selectedSymbolId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model of an image with multiple source
|
||||
* It must at least have one source.
|
||||
*
|
||||
* If Url/Base64Image and Svg are set,
|
||||
* Url/Base64Image will be shown in the menu while SVG will be drawn
|
||||
*/
|
||||
export interface IImage {
|
||||
/** Name of the image */
|
||||
Name: string;
|
||||
/** (optional) Url of the image */
|
||||
Url?: string;
|
||||
/** (optional) base64 data of the image */
|
||||
Base64Image?: string;
|
||||
/** (optional) SVG string */
|
||||
Svg?: string;
|
||||
}
|
||||
|
||||
|
||||
export interface IInputGroup {
|
||||
text: React.ReactNode;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface IMargin {
|
||||
left?: number;
|
||||
bottom?: number;
|
||||
top?: number;
|
||||
right?: number;
|
||||
}
|
||||
|
||||
|
||||
export interface IMessage {
|
||||
text: string;
|
||||
type: MessageType;
|
||||
}
|
||||
|
||||
|
||||
export interface IPattern {
|
||||
/**
|
||||
* Unique id for the pattern
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Text to display in the sidebar
|
||||
*/
|
||||
text: string;
|
||||
/**
|
||||
* IAvailableContainer id used to wrap the children.
|
||||
*/
|
||||
wrapper: string;
|
||||
/**
|
||||
* List of ids of Pattern or IAvailableContainer
|
||||
* If a IAvailableContainer and a Pattern have the same id,
|
||||
* IAvailableContainer will be prioritized
|
||||
*/
|
||||
children: string[];
|
||||
}
|
||||
export type ContainerOrPattern = IAvailableContainer | IPattern;
|
||||
export function GetPattern(id: string, configs: Map<string, IAvailableContainer>, patterns: Map<string, IPattern>): ContainerOrPattern | undefined;
|
||||
export function IsPattern(id: string, configs: Map<string, IAvailableContainer>, patterns: Map<string, IPattern>): boolean;
|
||||
|
||||
export interface IPoint {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface ISetContainerListRequest {
|
||||
/** Name of the action declared in the API */
|
||||
Action: string;
|
||||
/** Selected container */
|
||||
Container: IContainerModel;
|
||||
/** The previous sibling container */
|
||||
PreviousContainer: IContainerModel | undefined;
|
||||
/** The next sibling container */
|
||||
NextContainer: IContainerModel | undefined;
|
||||
/** Current application state */
|
||||
ApplicationState: IHistoryState;
|
||||
}
|
||||
|
||||
|
||||
export interface ISetContainerListResponse {
|
||||
Containers: IAvailableContainer[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A SizePointer is a pointer in a 1 dimensional array of width/space
|
||||
* x being the address where the pointer is pointing
|
||||
* width being the overall (un)allocated space affected to the address
|
||||
*/
|
||||
export interface ISizePointer {
|
||||
x: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
|
||||
export interface ISymbolModel {
|
||||
/** Identifier */
|
||||
id: string;
|
||||
/** Type */
|
||||
type: string;
|
||||
/** Configuration of the symbol */
|
||||
config: IAvailableSymbol;
|
||||
/** Horizontal offset */
|
||||
x: number;
|
||||
/** Width */
|
||||
width: number;
|
||||
/** Height */
|
||||
height: number;
|
||||
/** List of linked container id */
|
||||
linkedContainers: Set<string>;
|
||||
}
|
||||
|
||||
}
|
15
src/dts/tsconfig.dts.json
Normal file
15
src/dts/tsconfig.dts.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
// Change this to match your project
|
||||
"include": ["../Enums/*", "../Interfaces/*"],
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"module": "es2020",
|
||||
"allowJs": false,
|
||||
"declaration": true,
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "dist",
|
||||
"declarationMap": false
|
||||
}
|
||||
}
|
|
@ -8,7 +8,9 @@
|
|||
}
|
||||
|
||||
.sidebar-component {
|
||||
@apply transition-all px-2 py-6 text-sm rounded-lg bg-slate-300/80 hover:bg-blue-500 hover:text-slate-50
|
||||
@apply transition-all px-2 py-6 text-sm rounded-lg
|
||||
bg-slate-300/80 hover:bg-blue-500 hover:text-slate-50
|
||||
disabled:bg-slate-400 disabled:text-slate-500
|
||||
}
|
||||
|
||||
.sidebar-component-left {
|
||||
|
|
|
@ -7,6 +7,14 @@ import { IContainerProperties } from '../Interfaces/IContainerProperties';
|
|||
import { IEditorState } from '../Interfaces/IEditorState';
|
||||
import { ISymbolModel } from '../Interfaces/ISymbolModel';
|
||||
|
||||
/// EDITOR DEFAULTS ///
|
||||
|
||||
export const FAST_BOOT = import.meta.env.PROD;
|
||||
export const ENABLE_SHORTCUTS = true;
|
||||
export const MAX_HISTORY = 200;
|
||||
export const APPLY_BEHAVIORS_ON_CHILDREN = true;
|
||||
export const MAX_FRAMERATE = 60;
|
||||
|
||||
/// CONTAINER DEFAULTS ///
|
||||
|
||||
/** Enable the swap behavior */
|
||||
|
@ -41,13 +49,6 @@ export const NOTCHES_LENGTH = 10;
|
|||
export const DEFAULT_SYMBOL_WIDTH = 32;
|
||||
export const DEFAULT_SYMBOL_HEIGHT = 32;
|
||||
|
||||
/// EDITOR DEFAULTS ///
|
||||
|
||||
export const ENABLE_SHORTCUTS = true;
|
||||
export const MAX_HISTORY = 200;
|
||||
export const APPLY_BEHAVIORS_ON_CHILDREN = true;
|
||||
export const MAX_FRAMERATE = 60;
|
||||
|
||||
/**
|
||||
* Returns the default editor state given the configuration
|
||||
*/
|
||||
|
|
|
@ -65,6 +65,7 @@ const GetSVGLayoutConfiguration = () => {
|
|||
{
|
||||
Type: 'Chassis',
|
||||
DisplayedText: 'Chassis?',
|
||||
Whitelist: ["Trou"],
|
||||
MaxWidth: 500,
|
||||
MinWidth: 200,
|
||||
Width: 200,
|
||||
|
@ -81,6 +82,7 @@ const GetSVGLayoutConfiguration = () => {
|
|||
},
|
||||
{
|
||||
Type: 'Trou',
|
||||
Blacklist: ["Chassis"],
|
||||
DefaultX: 0,
|
||||
DefaultY: 0,
|
||||
Margin: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue