From 3ecff4cf016ab5e2329531a58a98cad8cf717476 Mon Sep 17 00:00:00 2001 From: Eric Nguyen Date: Fri, 23 Sep 2022 15:59:42 +0000 Subject: [PATCH] Merged PR 194: Added option to disable any API call Added option to disable any API call Fix http.js and node-http.js Fix MainContainer not using IAvailableContainer from MainContainer Fix generate_dts.py script More docs Fix MainContainer default style. Extend config will now be taken into account. But Main container can still have its own type. --- public/smartcomponent/svg-layout-designer.ts | 111 +-- src/Components/App/Actions/MenuActions.ts | 7 +- .../Editor/Actions/ContextMenuActions.ts | 32 +- src/Components/Editor/Editor.tsx | 6 +- .../MessagesSidebar/MessagesSidebar.tsx | 5 +- src/dts/generate_dts.py | 3 +- src/dts/svgld.d.ts | 942 +++++++++--------- src/utils/default.ts | 73 +- test-server/http.js | 18 +- test-server/node-http.js | 21 +- 10 files changed, 648 insertions(+), 570 deletions(-) diff --git a/public/smartcomponent/svg-layout-designer.ts b/public/smartcomponent/svg-layout-designer.ts index 7dfa633..d054577 100644 --- a/public/smartcomponent/svg-layout-designer.ts +++ b/public/smartcomponent/svg-layout-designer.ts @@ -7,6 +7,7 @@ export class SVGLayoutDesigner extends Components.ComponentBase { + private _hooks: Record void>; public App: AppController; public Editor: EditorController; @@ -14,6 +15,7 @@ super(componentInfo, params); this.App = new AppController(this, this.$component); this.Editor = new EditorController(this, this.$component); + this._hooks = {}; } /** @@ -32,7 +34,7 @@ * @param callback Callback function to call in the event listener * @param eventType Event type for the listener to listen to */ - public AddEventListener(callback: ((...args: any[]) => void) | undefined, eventType: string) { + public AddEventListener(eventType: string, callback: ((...args: any[]) => void) | undefined) { const root = this.GetRootComponent(); const listener = (e: CustomEvent) => { e.target.removeEventListener(e.type, listener); @@ -41,6 +43,40 @@ root.addEventListener(eventType, listener); } + /// Hooks /// + + private static EDITOR_LISTENER_TYPE = 'editorListener'; + + /** + * Add a hook to the editor state change. + * After every time an action is perform on the editor, the callback will be called + * @param callback Callback to add that listen to the event + */ + public AddEditorListenerHook(hookId: string, callback: (state: IEditorState) => void): void { + const root = this.GetRootComponent(); + const customEvent = (e: CustomEvent) => { + callback(e.detail); + } + + if (this._hooks[hookId] !== undefined) { + console.error(`HookId is already occupied. Please use a different HookId: ${hookId}`); + return; + } + + this._hooks[hookId] = customEvent; + root.addEventListener(SVGLayoutDesigner.EDITOR_LISTENER_TYPE, customEvent); + } + + /** + * Remove a hook to the editor state change. + * @param callback Callback to remove that listen to the event + */ + public RemoveEditorListenerHook(hookId): void { + const root = this.GetRootComponent(); + root.removeEventListener(SVGLayoutDesigner.EDITOR_LISTENER_TYPE, this._hooks[hookId]); + delete this._hooks[hookId]; + } + /// Macros /// @@ -99,7 +135,7 @@ */ public SetEditor(newEditor: IEditorState, callback?: (state: IEditorState) => void) { const eventType = 'setEditor'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const component = this.GetAppComponent(); component.dispatchEvent(new CustomEvent(eventType, { detail: newEditor })) } @@ -112,7 +148,7 @@ */ public SetLoaded(isLoaded: boolean, callback?: (state: IEditorState) => void) { const eventType = 'setLoaded'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const component = this.GetAppComponent(); component.dispatchEvent(new CustomEvent(eventType, { detail: isLoaded })) } @@ -122,12 +158,10 @@ class EditorController { app: SVGLayoutDesigner; $component: JQuery; - private _hooks: Record void>; constructor(app: SVGLayoutDesigner, $component: JQuery) { this.app = app; this.$component = $component; - this._hooks = {}; } /** @@ -154,7 +188,7 @@ */ public GetCurrentHistoryState(callback: (state: IHistoryState) => void) { const eventType = 'getCurrentHistoryState'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const component = this.GetEditorComponent(); component.dispatchEvent(new CustomEvent(eventType)); } @@ -165,7 +199,7 @@ */ public GetEditorState(callback: (state: IEditorState) => void) { const eventType = 'getEditorState'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const component = this.GetEditorComponent(); component.dispatchEvent(new CustomEvent(eventType)); } @@ -177,7 +211,7 @@ */ public SetHistory(history: IHistoryState[], callback?: (state: IEditorState) => void) { const eventType = 'setHistory'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const component = this.GetEditorComponent(); component.dispatchEvent(new CustomEvent(eventType, { detail: history })); } @@ -190,7 +224,7 @@ */ public ReviveEditorState(editorState: IEditorState, callback: (state: IEditorState) => void) { const eventType = 'reviveEditorState'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const component = this.GetEditorComponent(); component.dispatchEvent(new CustomEvent(eventType, { detail: editorState })); } @@ -203,7 +237,7 @@ */ public ReviveHistory(history: IHistoryState[], callback: (state: IHistoryState[]) => void) { const eventType = 'reviveHistory'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const component = this.GetEditorComponent(); component.dispatchEvent(new CustomEvent(eventType, { detail: history })); } @@ -215,7 +249,7 @@ */ public AppendNewHistoryState(historyState: IHistoryState, callback?: (state: IEditorState) => void) { const eventType = 'appendNewState'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail: historyState })); } @@ -228,7 +262,7 @@ */ public AddContainer(index: number, type: string, parentId: string, callback?: (state: IEditorState) => void) { const eventType = 'addContainer'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const detail = { index, type, @@ -245,7 +279,7 @@ */ public AddContainerToSelectedContainer(index: number, type: string, callback?: (state: IEditorState) => void) { const eventType = 'addContainerToSelectedContainer'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const detail = { index, type @@ -261,7 +295,7 @@ */ public AppendContainer(type: string, parentId: string, callback?: (state: IEditorState) => void) { const eventType = 'appendContainer'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const detail = { type, parentId @@ -277,7 +311,7 @@ */ public AppendContainerToSelectedContainer(type: string, callback?: (state: IEditorState) => void) { const eventType = 'appendContainerToSelectedContainer'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const detail = { type } @@ -291,7 +325,7 @@ */ public SelectContainer(containerId: string, callback?: (state: IEditorState) => void) { const eventType = 'selectContainer'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const detail = { containerId } @@ -305,7 +339,7 @@ */ public DeleteContainer(containerId: string, callback?: (state: IEditorState) => void) { const eventType = 'deleteContainer'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const detail = { containerId } @@ -320,7 +354,7 @@ */ public AddSymbol(name: string, callback?: (state: IEditorState) => void) { const eventType = 'addSymbol'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const detail = { name } @@ -334,7 +368,7 @@ */ public SelectSymbol(symbolId: string, callback?: (state: IEditorState) => void) { const eventType = 'selectSymbol'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const detail = { symbolId } @@ -348,49 +382,12 @@ */ public DeleteSymbol(symbolId: string, callback?: (state: IEditorState) => void) { const eventType = 'deleteSymbol'; - this.app.AddEventListener(callback, eventType); + this.app.AddEventListener(eventType, callback); const detail = { symbolId } this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail })); } - - - - /// Hooks /// - - private static EDITOR_LISTENER_TYPE = 'editorListener'; - - /** - * Add a hook to the editor state change. - * After every time an action is perform on the editor, the callback will be called - * @param callback Callback to add that listen to the event - */ - public AddEditorListenerHook(hookId: string, callback: (state: IEditorState) => void): void { - const root = this.app.GetRootComponent(); - const customEvent = (e: CustomEvent) => { - callback(e.detail); - } - - if (this._hooks[hookId] !== undefined) { - console.error(`HookId is already occupied. Please use a different HookId: ${hookId}`); - return; - } - - this._hooks[hookId] = customEvent; - root.addEventListener(EditorController.EDITOR_LISTENER_TYPE, customEvent); - } - - /** - * Remove a hook to the editor state change. - * @param callback Callback to remove that listen to the event - */ - public RemoveEditorListenerHook(hookId): void { - const root = this.app.GetRootComponent(); - root.removeEventListener(EditorController.EDITOR_LISTENER_TYPE, this._hooks[hookId]); - delete this._hooks[hookId]; - } - } ko.components.register('svg-layout-designer', { diff --git a/src/Components/App/Actions/MenuActions.ts b/src/Components/App/Actions/MenuActions.ts index 9d074f6..500269a 100644 --- a/src/Components/App/Actions/MenuActions.ts +++ b/src/Components/App/Actions/MenuActions.ts @@ -3,12 +3,17 @@ import { IConfiguration } from '../../../Interfaces/IConfiguration'; import { FetchConfiguration } from '../../API/api'; import { IEditorState } from '../../../Interfaces/IEditorState'; import { LoadState } from './Load'; -import { GetDefaultEditorState } from '../../../utils/default'; +import { DISABLE_API, GetDefaultEditorState } from '../../../utils/default'; export function NewEditor( setEditorState: Dispatch>, setLoaded: Dispatch> ): void { + if (DISABLE_API) { + setLoaded(true); + return; + } + // Fetch the configuration from the API FetchConfiguration() .then((configuration: IConfiguration) => { diff --git a/src/Components/Editor/Actions/ContextMenuActions.ts b/src/Components/Editor/Actions/ContextMenuActions.ts index ae7ad51..a4c99db 100644 --- a/src/Components/Editor/Actions/ContextMenuActions.ts +++ b/src/Components/Editor/Actions/ContextMenuActions.ts @@ -33,20 +33,7 @@ export function GetAction( } /* eslint-disable @typescript-eslint/naming-convention */ - let prev; - let next; - if (container.parent !== undefined && - container.parent !== null && - container.parent.children.length > 1 - ) { - const index = container.parent.children.indexOf(container); - if (index > 0) { - prev = container.parent.children[index - 1]; - } - if (index < container.parent.children.length - 1) { - next = container.parent.children[index + 1]; - } - } + const { prev, next } = GetPreviousAndNextSiblings(container); const request: ISetContainerListRequest = { Container: container, @@ -72,6 +59,23 @@ export function GetAction( }; } +function GetPreviousAndNextSiblings(container: IContainerModel): { prev: IContainerModel | undefined; next: IContainerModel | undefined; } { + let prev; + let next; + if (container.parent !== undefined && + container.parent !== null && + container.parent.children.length > 1) { + const index = container.parent.children.indexOf(container); + if (index > 0) { + prev = container.parent.children[index - 1]; + } + if (index < container.parent.children.length - 1) { + next = container.parent.children[index + 1]; + } + } + return { prev, next }; +} + function HandleSetContainerList( action: IAction, selectedContainer: IContainerModel, diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index 8be26b0..c262f12 100644 --- a/src/Components/Editor/Editor.tsx +++ b/src/Components/Editor/Editor.tsx @@ -9,7 +9,7 @@ import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save'; import { OnKey } from './Actions/Shortcuts'; import { events as EVENTS } from '../../Events/EditorEvents'; import { IEditorState } from '../../Interfaces/IEditorState'; -import { MAX_HISTORY } from '../../utils/default'; +import { DISABLE_API, MAX_HISTORY } from '../../utils/default'; import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, SelectSymbol } from './Actions/SymbolOperations'; import { FindContainerById } from '../../utils/itertools'; import { IMenuAction, Menu } from '../Menu/Menu'; @@ -62,6 +62,10 @@ function InitActions( ); // API Actions + if (DISABLE_API) { + return; + } + for (const availableContainer of configuration.AvailableContainers) { if (availableContainer.Actions === undefined || availableContainer.Actions === null) { continue; diff --git a/src/Components/MessagesSidebar/MessagesSidebar.tsx b/src/Components/MessagesSidebar/MessagesSidebar.tsx index 3db42ea..e7d59db 100644 --- a/src/Components/MessagesSidebar/MessagesSidebar.tsx +++ b/src/Components/MessagesSidebar/MessagesSidebar.tsx @@ -6,6 +6,7 @@ import { IGetFeedbackRequest } from '../../Interfaces/IGetFeedbackRequest'; import { IGetFeedbackResponse } from '../../Interfaces/IGetFeedbackResponse'; import { IHistoryState } from '../../Interfaces/IHistoryState'; import { IMessage } from '../../Interfaces/IMessage'; +import { DISABLE_API } from '../../utils/default'; import { GetCircularReplacerKeepDataStructure } from '../../utils/saveload'; interface IMessagesSidebarProps { @@ -68,12 +69,12 @@ export function MessagesSidebar(props: IMessagesSidebarProps): JSX.Element { const [messages, setMessages] = React.useState([]); // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (window.Worker) { + if (window.Worker && !DISABLE_API) { UseWorker( props.historyState, setMessages ); - } else { + } else if (!DISABLE_API) { UseAsync( props.historyState, setMessages diff --git a/src/dts/generate_dts.py b/src/dts/generate_dts.py index 8ad0e60..b53214d 100644 --- a/src/dts/generate_dts.py +++ b/src/dts/generate_dts.py @@ -27,7 +27,7 @@ export_pattern = re.compile( r"export ([*] from [\"'\s].*[\"'\s]|{.*}(?: from [\"'\s].*[\"'\s])?);" ) -filter_directories = ["./Enums", "./Interfaces"] +filter_directories = ["./dist\Enums", "./dist\Interfaces"] def main(): ''' @@ -37,6 +37,7 @@ def main(): output_file.write(('declare namespace {} {{\n'.format(namespace))) for root, subdirs, files in os.walk('./'): if root not in filter_directories: + print('SKIP ' + root) continue print('--\nroot = ' + root) diff --git a/src/dts/svgld.d.ts b/src/dts/svgld.d.ts index 6b51b11..0d9ae0c 100644 --- a/src/dts/svgld.d.ts +++ b/src/dts/svgld.d.ts @@ -1,469 +1,473 @@ -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 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 : - * ``` - * ` - * - * - * - * ` - * ``` - */ - CustomSVG?: string; - /** - * (optional) - * Disabled when Pattern is used. - * - * Replace a 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 : - * ``` - * ` - * - * - * - * ` - * ``` - */ - 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 - */ - 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; -} -/** - * 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; - 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 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 : - * ``` - * ` - * - * - * - * ` - * ``` - */ - customSVG?: string; - /** - * (optional) - * Style of the - */ - 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; - /** List of symbols */ - symbols: Map; - /** 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, patterns: Map): ContainerOrPattern | undefined; -export function IsPattern(id: string, configs: Map, patterns: Map): 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; -} - -} +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 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 : + * ``` + * ` + * + * + * + * ` + * ``` + */ + CustomSVG?: string; + /** + * (optional) + * Disabled when Pattern is used. + * + * Replace a 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 : + * ``` + * ` + * + * + * + * ` + * ``` + */ + 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; + /** Hide the children in the treeview */ + HideChildrenInTreeview?: boolean; + /** 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 + */ + 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; +} +/** + * 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; + 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; + /** Hide the children in the treeview */ + hideChildrenInTreeview: boolean; + /** 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 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 : + * ``` + * ` + * + * + * + * ` + * ``` + */ + customSVG?: string; + /** + * (optional) + * Style of the + */ + 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; + /** List of symbols */ + symbols: Map; + /** 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, patterns: Map): ContainerOrPattern | undefined; +export function IsPattern(id: string, configs: Map, patterns: Map): 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; +} + +} diff --git a/src/utils/default.ts b/src/utils/default.ts index bf86825..585e26c 100644 --- a/src/utils/default.ts +++ b/src/utils/default.ts @@ -9,10 +9,22 @@ import { ISymbolModel } from '../Interfaces/ISymbolModel'; /// EDITOR DEFAULTS /// -export const FAST_BOOT = import.meta.env.PROD; +/** Enable fast boot and disable main menu */ +export const FAST_BOOT = false; + +/** Disable any call to the API */ +export const DISABLE_API = false; + +/** Enable keyboard shortcuts */ export const ENABLE_SHORTCUTS = true; + +/** Size of the history */ export const MAX_HISTORY = 200; + +/** Apply beheviors on children */ export const APPLY_BEHAVIORS_ON_CHILDREN = true; + +/** Framerate of the svg controller */ export const MAX_FRAMERATE = 60; /// CONTAINER DEFAULTS /// @@ -53,15 +65,58 @@ export const DEFAULT_SYMBOL_HEIGHT = 32; * Returns the default editor state given the configuration */ export function GetDefaultEditorState(configuration: IConfiguration): IEditorState { + if (configuration.MainContainer.Width === undefined || + configuration.MainContainer.Height === undefined) { + throw new Error('Cannot initialize project! Main container has an undefined size'); + } + + const containerConfig = configuration.AvailableContainers.find(config => config.Type === configuration.MainContainer.Type); + + let mainContainerConfig: IContainerProperties; + if (containerConfig !== undefined) { + const clone = structuredClone(containerConfig); + const extendedContainerConfig = Object.assign(clone, configuration.MainContainer); + + if (containerConfig.Style !== undefined) { + const styleClone = structuredClone(containerConfig.Style); + extendedContainerConfig.Style = Object.assign(styleClone, configuration.MainContainer.Style); + } + + if (extendedContainerConfig.Width === undefined || + extendedContainerConfig.Height === undefined) { + throw new Error('Cannot initialize project! Main container has an undefined size'); + } + + mainContainerConfig = GetDefaultContainerProps( + extendedContainerConfig.Type, + 0, + null, + 0, + 0, + extendedContainerConfig.Width, + extendedContainerConfig.Height, + extendedContainerConfig + ); + } else { + mainContainerConfig = GetDefaultContainerProps( + configuration.MainContainer.Type, + 0, + null, + 0, + 0, + configuration.MainContainer.Width, + configuration.MainContainer.Height, + configuration.MainContainer + ); + } const mainContainer = new ContainerModel( null, - { - ...DEFAULT_MAINCONTAINER_PROPS, - width: Number(configuration.MainContainer.Width), - height: Number(configuration.MainContainer.Height) - } + mainContainerConfig ); + const typeCounters = {}; + (typeCounters as any)[mainContainer.properties.type] = 0; + return { configuration, history: [ @@ -69,7 +124,7 @@ export function GetDefaultEditorState(configuration: IConfiguration): IEditorSta lastAction: '', mainContainer, selectedContainerId: mainContainer.properties.id, - typeCounters: {}, + typeCounters, symbols: new Map(), selectedSymbolId: '' } @@ -152,7 +207,7 @@ export const DEFAULT_MAINCONTAINER_PROPS: IContainerProperties = { */ export function GetDefaultContainerProps(type: string, typeCount: number, - parent: IContainerModel, + parent: IContainerModel | undefined | null, x: number, y: number, width: number, @@ -161,7 +216,7 @@ export function GetDefaultContainerProps(type: string, return ({ id: `${type}-${typeCount}`, type, - parentId: parent.properties.id, + parentId: parent?.properties.id ?? '', linkedSymbolId: '', displayedText: `${containerConfig.DisplayedText ?? type}-${typeCount}`, x, diff --git a/test-server/http.js b/test-server/http.js index f7fce66..49afa10 100644 --- a/test-server/http.js +++ b/test-server/http.js @@ -83,8 +83,6 @@ const GetSVGLayoutConfiguration = () => { { Type: 'Trou', Blacklist: ["Chassis"], - DefaultX: 0, - DefaultY: 0, Margin: { left: 10, bottom: 10, @@ -112,10 +110,14 @@ const GetSVGLayoutConfiguration = () => { , Actions: [ { + Id: "SplitRemplissage", Action: "SplitRemplissage", Label: "Diviser le remplissage", Description: "Diviser le remplissage en insérant un montant", CustomLogo: { + Base64Image: null, + Name: 'Image1', + Svg: null, Url: "" }, AddingBehavior: 2 @@ -186,7 +188,7 @@ const GetSVGLayoutConfiguration = () => { Height: 32, Image: { Base64Image: null, - Name: null, + Name: 'Image1', Svg: null, Url: 'https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg' }, @@ -198,7 +200,7 @@ const GetSVGLayoutConfiguration = () => { Height: 32, Image: { Base64Image: null, - Name: null, + Name: 'Image2', Svg: null, Url: 'https://e7.pngegg.com/pngimages/647/127/png-clipart-svg-working-group-information-world-wide-web-internet-structure.png' }, @@ -216,11 +218,13 @@ const GetSVGLayoutConfiguration = () => { DisplayedText: "Stuff not made here" } ], + Patterns: [], MainContainer: { - Height: 200, - Width: 800 + Type: 'main', + Width: 800, + Height: 200 } - }; + } }; const FillHoleWithChassis = (request) => { diff --git a/test-server/node-http.js b/test-server/node-http.js index 7f0cc34..1ac0d5b 100644 --- a/test-server/node-http.js +++ b/test-server/node-http.js @@ -2,7 +2,7 @@ import http from 'http'; const host = 'localhost'; const port = 5000; -const requestListener = async(request, response) => { +const requestListener = async (request, response) => { response.setHeader('Access-Control-Allow-Origin', '*'); response.setHeader('Access-Control-Allow-Headers', '*'); response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS'); @@ -81,7 +81,6 @@ const GetSVGLayoutConfiguration = () => { stroke: 'red', fill: '#d3c9b7', }, - HideChildrenInTreeview: true, ShowSelfDimensions: true, IsDimensionBorrower: true, Category: "Stuff" @@ -89,8 +88,6 @@ const GetSVGLayoutConfiguration = () => { { Type: 'Trou', Blacklist: ["Chassis"], - DefaultX: 0, - DefaultY: 0, Margin: { left: 10, bottom: 10, @@ -118,10 +115,14 @@ const GetSVGLayoutConfiguration = () => { , Actions: [ { + Id: "SplitRemplissage", Action: "SplitRemplissage", Label: "Diviser le remplissage", Description: "Diviser le remplissage en insérant un montant", CustomLogo: { + Base64Image: null, + Name: 'Image1', + Svg: null, Url: "" }, AddingBehavior: 2 @@ -192,7 +193,7 @@ const GetSVGLayoutConfiguration = () => { Height: 32, Image: { Base64Image: null, - Name: null, + Name: 'Image1', Svg: null, Url: 'https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg' }, @@ -204,7 +205,7 @@ const GetSVGLayoutConfiguration = () => { Height: 32, Image: { Base64Image: null, - Name: null, + Name: 'Image2', Svg: null, Url: 'https://e7.pngegg.com/pngimages/647/127/png-clipart-svg-working-group-information-world-wide-web-internet-structure.png' }, @@ -222,11 +223,13 @@ const GetSVGLayoutConfiguration = () => { DisplayedText: "Stuff not made here" } ], + Patterns: [], MainContainer: { - Height: 200, - Width: 800 + Type: 'main', + Width: 800, + Height: 200 } - }; + } }; const FillHoleWithChassis = (request) => {