Merged PR 189: Simplify usage of SmartComponent

- Added new Events :
  - AddContainer, AddContainerToSelectedContainer, AppendContainer, AppendContainerToSelectedContainer, SelectContainer, DeleteContainer
  - AddSymbol, SelectSymbol, DeleteSymbol
- Changed the component to an iframe (you only need to copy the whole dist now)
- Added callbacks to every methods in the component
- Create event listener on demand: no need to initialize the event listener!
- Update d.ts
- Added Fastboot and enable it by default on production build
This commit is contained in:
Eric Nguyen 2022-09-21 09:24:14 +00:00
parent 07dbac1b12
commit 23ed3ed1ad
14 changed files with 1047 additions and 108 deletions

View file

@ -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. 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`.

View file

@ -9,6 +9,6 @@
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="./src/main.tsx"></script>
</body> </body>
</html> </html>

View file

@ -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
}

View file

@ -1,2 +1,2 @@
<div id="root"> <iframe src="./Components/svg-layout-designer/index.html" style="border:none; width:100%; height:100%">
</div> </iframe>

View file

@ -1,40 +1,99 @@
namespace SmartBusiness.Web.Components { namespace SmartBusiness.Web.Components {
import IHistoryState = SVGLD.IHistoryState;
import IEditorState = SVGLD.IEditorState;
export class SVGLayoutDesigner extends Components.ComponentBase { export class SVGLayoutDesigner extends Components.ComponentBase {
public constructor(componentInfo: KnockoutComponentTypes.ComponentInfo, params: any) { public constructor(componentInfo: KnockoutComponentTypes.ComponentInfo, params: any) {
super(componentInfo, params); super(componentInfo, params);
setTimeout(() => (window as any).SVGLayoutDesigner.Render(this.$component[0]));
this.InitEventsListener();
} }
public GetEditorComponent() { 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')
}
return component;
} }
public GetCurrentHistoryState() { public GetRootComponent() {
this.GetEditorComponent().dispatchEvent(new CustomEvent('getCurrentHistoryState')); return this.$component[0]
.querySelector('iframe')
.contentDocument;
} }
public GetEditorState() { public GetCurrentHistoryState(callback: (state: IHistoryState) => void) {
this.GetEditorComponent().dispatchEvent(new CustomEvent('getEditorState')); const component = this.GetEditorComponent();
const eventType = 'getCurrentHistoryState';
component.dispatchEvent(new CustomEvent(eventType));
this.AddEventListener(callback, eventType);
} }
public SetEditorState(editorState: IEditorState) { public GetEditorState(callback: (state: IEditorState) => void) {
this.GetEditorComponent().dispatchEvent(new CustomEvent('SetEditorState', { detail: editorState })); const component = this.GetEditorComponent();
const eventType = 'getEditorState';
component.dispatchEvent(new CustomEvent(eventType));
this.AddEventListener(callback, eventType);
} }
public AppendNewHistoryState(historyState: IHistoryState) { public AppendNewHistoryState(historyState: SVGLD.IHistoryState, callback?: (state: IEditorState) => void) {
this.GetEditorComponent().dispatchEvent(new CustomEvent('appendNewState', { detail: historyState })); const eventType = 'appendNewState';
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail: historyState }));
this.AddEventListener(callback, eventType);
} }
public OHistoryState: KnockoutObservable<any>; 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);
}
private InitEventsListener() { public AddContainerToSelectedContainer(index: number, type: string, callback?: (state: IEditorState) => void) {
this.$component[0].addEventListener('getCurrentHistoryState', (e: CustomEvent) => { const detail = {
this.OHistoryState(e.detail); index,
console.log(this.OHistoryState()); type
}); }
this.$component[0].addEventListener('getEditorState', (e) => console.log((e as any).detail)); 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);
} }
} }

View file

@ -112,7 +112,7 @@ function UseShortcuts(
}); });
} }
function UseWindowEvents( function UseCustomEvents(
root: Element | Document, root: Element | Document,
history: IHistoryState[], history: IHistoryState[],
historyCurrentStep: number, historyCurrentStep: number,
@ -178,7 +178,7 @@ export function Editor(props: IEditorProps): JSX.Element {
// Events // Events
UseShortcuts(history, historyCurrentStep, setHistoryCurrentStep); UseShortcuts(history, historyCurrentStep, setHistoryCurrentStep);
UseWindowEvents( UseCustomEvents(
props.root, props.root,
history, history,
historyCurrentStep, historyCurrentStep,

View 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;}

View 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>
);
}

View file

@ -1,4 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { FAST_BOOT } from '../../utils/default';
import { Loader } from '../Loader/Loader';
interface IMainMenuProps { interface IMainMenuProps {
newEditor: () => void newEditor: () => void
@ -8,10 +10,21 @@ interface IMainMenuProps {
enum WindowState { enum WindowState {
Main, Main,
Load, Load,
Loading,
} }
export function MainMenu(props: IMainMenuProps): JSX.Element { export function MainMenu(props: IMainMenuProps): JSX.Element {
const [windowState, setWindowState] = React.useState(WindowState.Main); 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) { switch (windowState) {
case WindowState.Load: case WindowState.Load:
return ( return (
@ -46,10 +59,19 @@ export function MainMenu(props: IMainMenuProps): JSX.Element {
</div> </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: default:
return ( 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'> <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> <button type="button" className='mainmenu-btn' onClick={() => setWindowState(WindowState.Load)}>Load a configuration file</button>
</div> </div>
); );

View file

@ -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 { GetCurrentHistory } from '../Components/Editor/Editor';
import { IConfiguration } from '../Interfaces/IConfiguration';
import { IEditorState } from '../Interfaces/IEditorState'; import { IEditorState } from '../Interfaces/IEditorState';
import { IHistoryState } from '../Interfaces/IHistoryState'; import { IHistoryState } from '../Interfaces/IHistoryState';
import { FindContainerById } from '../utils/itertools';
import { ReviveState } from '../utils/saveload'; import { ReviveState } from '../utils/saveload';
function InitEditor(configuration: IConfiguration): void {
// faire comme la callback de fetch
}
function GetEditorState(root: Element | Document, function GetEditorState(root: Element | Document,
editorState: IEditorState): void { editorState: IEditorState): void {
const customEvent = new CustomEvent<IEditorState>('getEditorState', { detail: editorState }); const customEvent = new CustomEvent<IEditorState>('getEditorState', { detail: structuredClone(editorState) });
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
@ -19,7 +17,7 @@ function GetCurrentHistoryState(root: Element | Document,
editorState: IEditorState): void { editorState: IEditorState): void {
const customEvent = new CustomEvent<IHistoryState>( const customEvent = new CustomEvent<IHistoryState>(
'getCurrentHistoryState', 'getCurrentHistoryState',
{ detail: editorState.history[editorState.historyCurrentStep] }); { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) });
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
@ -33,6 +31,244 @@ function AppendNewState(root: Element | Document,
history.push(state); history.push(state);
setNewHistory(history); 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 { export interface IEditorEvent {
@ -48,5 +284,14 @@ export interface IEditorEvent {
export const events: IEditorEvent[] = [ export const events: IEditorEvent[] = [
{ name: 'getEditorState', func: GetEditorState }, { name: 'getEditorState', func: GetEditorState },
{ name: 'getCurrentHistoryState', func: GetCurrentHistoryState }, { 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 }
]; ];

101
src/dts/generate_dts.py Normal file
View 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
View 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
View 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
}
}

View file

@ -7,6 +7,14 @@ import { IContainerProperties } from '../Interfaces/IContainerProperties';
import { IEditorState } from '../Interfaces/IEditorState'; import { IEditorState } from '../Interfaces/IEditorState';
import { ISymbolModel } from '../Interfaces/ISymbolModel'; 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 /// /// CONTAINER DEFAULTS ///
/** Enable the swap behavior */ /** Enable the swap behavior */
@ -41,13 +49,6 @@ export const NOTCHES_LENGTH = 10;
export const DEFAULT_SYMBOL_WIDTH = 32; export const DEFAULT_SYMBOL_WIDTH = 32;
export const DEFAULT_SYMBOL_HEIGHT = 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 * Returns the default editor state given the configuration
*/ */