diff --git a/csharp/SVGLDLibs/SVGLDLibs/Models/AvailableSymbolModel.cs b/csharp/SVGLDLibs/SVGLDLibs/Models/AvailableSymbolModel.cs index 5c2c18a..2759f1f 100644 --- a/csharp/SVGLDLibs/SVGLDLibs/Models/AvailableSymbolModel.cs +++ b/csharp/SVGLDLibs/SVGLDLibs/Models/AvailableSymbolModel.cs @@ -7,13 +7,26 @@ namespace SVGLDLibs.Models { [DataMember(EmitDefaultValue = false)] public string Name { get; set; } + [DataMember(EmitDefaultValue = false)] public ImageModel Image { get; set; } + [DataMember(EmitDefaultValue = false)] - public PositionReferenceEnumModel PositionReference { get; set; } + public double X { get; set; } + + [DataMember(EmitDefaultValue = false)] + public double Y { get; set; } + [DataMember(EmitDefaultValue = false)] public double Width { get; set; } + [DataMember(EmitDefaultValue = false)] public double Height { get; set; } + + [DataMember(EmitDefaultValue = false)] + public PositionReferenceEnumModel PositionReference { get; set; } + + [DataMember(EmitDefaultValue = false)] + public AvailableContainerModel AssociatedContainer { get; set; } } } \ No newline at end of file diff --git a/csharp/SVGLDLibs/SVGLDLibs/Models/SymbolModel.cs b/csharp/SVGLDLibs/SVGLDLibs/Models/SymbolModel.cs index 97e7615..5e16bdf 100644 --- a/csharp/SVGLDLibs/SVGLDLibs/Models/SymbolModel.cs +++ b/csharp/SVGLDLibs/SVGLDLibs/Models/SymbolModel.cs @@ -8,16 +8,22 @@ namespace SVGLDLibs.Models { [DataMember(EmitDefaultValue = false)] public string id { get; set; } + [DataMember(EmitDefaultValue = false)] public string type { get; set; } + [DataMember(EmitDefaultValue = false)] - public AvailableSymbolModel config { get; set; } + public AvailableSymbolModel config { get; set; } + [DataMember(EmitDefaultValue = false)] public double x { get; set; } + [DataMember(EmitDefaultValue = false)] public double width { get; set; } + [DataMember(EmitDefaultValue = false)] public double height { get; set; } + [DataMember(EmitDefaultValue = false)] public List linkedContainers { get; set; } } diff --git a/src/Components/Editor/Actions/AddContainer.ts b/src/Components/Editor/Actions/AddContainer.ts index 0281d94..7cf54ce 100644 --- a/src/Components/Editor/Actions/AddContainer.ts +++ b/src/Components/Editor/Actions/AddContainer.ts @@ -12,7 +12,7 @@ import { ISymbolModel } from '../../../Interfaces/ISymbolModel'; import { Orientation } from '../../../Enums/Orientation'; import { GetDefaultContainerProps } from '../../../utils/default'; import { FindContainerById } from '../../../utils/itertools'; -import { ApplyMargin, RestoreX, RestoreY, TransformX } from '../../../utils/svg'; +import { ApplyMargin, RestoreX, RestoreY } from '../../../utils/svg'; import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors'; import { GetCurrentHistory, UpdateCounters } from '../Editor'; import { SortChildren } from './ContainerOperations'; @@ -59,7 +59,10 @@ export function AddContainers( configuration: IConfiguration, fullHistory: IHistoryState[], historyCurrentStep: number -): IHistoryState[] { +): { + history: IHistoryState[] + newContainers: IContainerModel[] + } { const history = GetCurrentHistory(fullHistory, historyCurrentStep); const current = history[history.length - 1]; @@ -77,18 +80,27 @@ export function AddContainers( // Deep clone the counters const newCounters = Object.assign({}, current.typeCounters); - // containerIds is used for logging purpose (see setHistory below) - const containerIds: string[] = []; - // Iterate over the containers + const newContainers: IContainerModel[] = []; availableContainers.forEach((availableContainer, typeIndex) => { // Get the preset properties from the API - AddNewContainerToParent(availableContainer, configuration, containers, parentClone, index, typeIndex, newCounters, current.symbols, containerIds); + const newContainer = AddNewContainerToParent( + availableContainer, + configuration, + containers, + parentClone, + index, + typeIndex, + newCounters, + current.symbols + ); + newContainers.push(newContainer); }); // Update the state + const containersIds = newContainers.map(container => container.properties.id); history.push({ - lastAction: `Add [${containerIds.join(', ')}] in ${parentClone.properties.id}`, + lastAction: `Add [${containersIds.join(', ')}] in ${parentClone.properties.id}`, mainContainer: current.mainContainer, selectedContainerId: parentClone.properties.id, containers, @@ -97,7 +109,10 @@ export function AddContainers( selectedSymbolId: current.selectedSymbolId }); - return history; + return { + history, + newContainers + }; } function AddNewContainerToParent( @@ -109,7 +124,6 @@ function AddNewContainerToParent( typeIndex: number, newCounters: Record, symbols: Map, - containerIds: string[] = [], initChilds: boolean = true ): ContainerModel { const type = availableContainer.Type; @@ -207,9 +221,6 @@ function AddNewContainerToParent( } } - // Add to the list of container id for logging purpose - containerIds.push(newContainer.properties.id); - return newContainer; } @@ -234,7 +245,7 @@ export function AddContainer( historyCurrentStep: number ): IHistoryState[] { // just call AddContainers with an array on a single element - return AddContainers( + const { history } = AddContainers( index, // eslint-disable-next-line @typescript-eslint/naming-convention [{ Type: type }], @@ -243,6 +254,7 @@ export function AddContainer( fullHistory, historyCurrentStep ); + return history; } /** @@ -432,7 +444,6 @@ function AddContainerInLevel( 0, 0, newCounters, symbols, - undefined, false ); diff --git a/src/Components/Editor/Actions/ContainerOperations.ts b/src/Components/Editor/Actions/ContainerOperations.ts index f264f5a..9267325 100644 --- a/src/Components/Editor/Actions/ContainerOperations.ts +++ b/src/Components/Editor/Actions/ContainerOperations.ts @@ -282,11 +282,12 @@ function SetContainer( AssignProperty(container, key, value, type); // link the symbol if it exists + const oldSymbol = symbols.get(oldSymbolId); + const newSymbol = symbols.get(container.properties.linkedSymbolId); LinkSymbol( container.properties.id, - oldSymbolId, - container.properties.linkedSymbolId, - symbols + oldSymbol, + newSymbol ); // Apply special behaviors: rigid, flex, symbol, anchor @@ -357,15 +358,11 @@ function AssignProperty(container: ContainerModel, key: string, value: string | * @param symbols Current list of symbols * @returns */ -function LinkSymbol( +export function LinkSymbol( containerId: string, - oldSymbolId: string, - newSymbolId: string, - symbols: Map + oldSymbol: ISymbolModel | undefined, + newSymbol: ISymbolModel | undefined ): void { - const oldSymbol = symbols.get(oldSymbolId); - const newSymbol = symbols.get(newSymbolId); - if (newSymbol === undefined) { if (oldSymbol !== undefined) { oldSymbol.linkedContainers.delete(containerId); diff --git a/src/Components/Editor/Actions/ContextMenuActions.ts b/src/Components/Editor/Actions/ContextMenuActions.ts index 68425d3..b8209bb 100644 --- a/src/Components/Editor/Actions/ContextMenuActions.ts +++ b/src/Components/Editor/Actions/ContextMenuActions.ts @@ -203,20 +203,21 @@ function HandleSetContainerList( const containers = current.containers; switch (addingBehavior) { case AddMethod.Insert: - case AddMethod.Append: + case AddMethod.Append: { response.Containers.forEach(config => { config.AddMethod = config.AddMethod ?? addingBehavior; }); - setNewHistory( - AddContainers( - selectedContainer.children.length, - response.Containers, - selectedContainer.properties.id, - configuration, - history, - historyCurrentStep - )); + const { history: newHistory } = AddContainers( + selectedContainer.children.length, + response.Containers, + selectedContainer.properties.id, + configuration, + history, + historyCurrentStep); + + setNewHistory(newHistory); break; + } case AddMethod.Replace: setNewHistory( HandleReplace( @@ -275,7 +276,7 @@ function HandleReplace( historyCurrentStep ); - const newHistoryBeforeDelete = AddContainers( + const { history: newHistoryBeforeDelete } = AddContainers( index, response.Containers, selectedContainer.properties.parentId, @@ -318,7 +319,7 @@ function HandleInsert( historyCurrentStep ); - const newHistoryBeforeDelete = AddContainers( + const { history: newHistoryBeforeDelete } = AddContainers( index, response.Containers, selectedContainer.properties.parentId, diff --git a/src/Components/Editor/Actions/SymbolOperations.ts b/src/Components/Editor/Actions/SymbolOperations.ts index 63caafb..7464193 100644 --- a/src/Components/Editor/Actions/SymbolOperations.ts +++ b/src/Components/Editor/Actions/SymbolOperations.ts @@ -6,7 +6,9 @@ import { GetDefaultSymbolModel } from '../../../utils/default'; import { FindContainerById } from '../../../utils/itertools'; import { RestoreX } from '../../../utils/svg'; import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors'; -import { GetCurrentHistory, UpdateCounters } from '../Editor'; +import { GetCurrentHistory, GetCurrentHistoryState, UpdateCounters } from '../Editor'; +import { AddContainers } from './AddContainer'; +import { LinkSymbol } from './ContainerOperations'; export function AddSymbol( name: string, @@ -14,7 +16,7 @@ export function AddSymbol( fullHistory: IHistoryState[], historyCurrentStep: number ): IHistoryState[] { - const history = GetCurrentHistory(fullHistory, historyCurrentStep); + let history = GetCurrentHistory(fullHistory, historyCurrentStep); const current = history[history.length - 1]; const symbolConfig = configuration.AvailableSymbols @@ -24,24 +26,48 @@ export function AddSymbol( throw new Error('[AddSymbol] Symbol could not be found in the config'); } const type = `symbol-${name}`; - const newCounters = structuredClone(current.typeCounters); - UpdateCounters(newCounters, type); + const typeCounters = structuredClone(current.typeCounters); + UpdateCounters(typeCounters, type); const newSymbols = structuredClone(current.symbols); - const newSymbol: ISymbolModel = GetDefaultSymbolModel(name, newCounters, type, symbolConfig); + const newSymbol: ISymbolModel = GetDefaultSymbolModel(name, typeCounters, type, symbolConfig); + const containers = structuredClone(current.containers); newSymbol.x = RestoreX(newSymbol.x, newSymbol.width, newSymbol.config.PositionReference); newSymbols.set(newSymbol.id, newSymbol); history.push({ lastAction: `Add ${name}`, - mainContainer: structuredClone(current.mainContainer), - containers: structuredClone(current.containers), + mainContainer: current.mainContainer, + containers, selectedContainerId: current.selectedContainerId, - typeCounters: newCounters, + typeCounters, symbols: newSymbols, selectedSymbolId: newSymbol.id }); + + if (symbolConfig.AssociatedContainer !== undefined) { + const { + history: newHistory, + newContainers + } = AddContainers( + 0, + [symbolConfig.AssociatedContainer], + current.mainContainer, + configuration, + history, + historyCurrentStep + 1 + ); + + history = newHistory; + const newCurrent = GetCurrentHistoryState(newHistory, historyCurrentStep + 2); + const newerSymbol = newCurrent.symbols.get(newSymbol.id); + + newContainers.forEach((newContainer) => { + LinkContainer(newerSymbol, newContainer, newSymbols); + }); + } + return history; } @@ -55,7 +81,7 @@ export function SelectSymbol( history.push({ lastAction: `Select ${symbolId}`, - mainContainer: structuredClone(current.mainContainer), + mainContainer: current.mainContainer, containers: structuredClone(current.containers), selectedContainerId: current.selectedContainerId, typeCounters: structuredClone(current.typeCounters), @@ -166,3 +192,20 @@ export function OnPropertyChange( }); return history; } + +/** + * Link a container to a symbol. + * If symbol is undefined, unlink the previous symbol of the container + * @param symbol + * @param container + * @param symbols + */ +function LinkContainer( + symbol: ISymbolModel | undefined, + container: IContainerModel, + symbols: Map +): void { + const oldSymbol = symbols.get(container.properties.linkedSymbolId); + LinkSymbol(container.properties.id, oldSymbol, symbol); + container.properties.linkedSymbolId = symbol !== undefined ? symbol.id : ''; +} diff --git a/src/Interfaces/IAvailableSymbol.ts b/src/Interfaces/IAvailableSymbol.ts index a9adf92..6f2b7b2 100644 --- a/src/Interfaces/IAvailableSymbol.ts +++ b/src/Interfaces/IAvailableSymbol.ts @@ -1,13 +1,25 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { PositionReference } from '../Enums/PositionReference'; +import { IAvailableContainer } from './IAvailableContainer'; import { IImage } from './IImage'; /** * Model of available symbol to configure the application */ export interface IAvailableSymbol { Name: string + Image: IImage + + X?: number + + Y?: number + Width?: number + Height?: number + PositionReference?: PositionReference + + /** An existing or new available container */ + AssociatedContainer?: IAvailableContainer } diff --git a/src/Interfaces/ISymbolModel.ts b/src/Interfaces/ISymbolModel.ts index a99966f..c974dd7 100644 --- a/src/Interfaces/ISymbolModel.ts +++ b/src/Interfaces/ISymbolModel.ts @@ -13,6 +13,8 @@ export interface ISymbolModel { /** Horizontal offset */ x: number + // TODO: Implement Y and verticality + /** Width */ width: number diff --git a/test-server/http.js b/test-server/http.js index f49ff14..fd4286f 100644 --- a/test-server/http.js +++ b/test-server/http.js @@ -283,7 +283,10 @@ const GetSVGLayoutConfiguration = () => { Url: 'https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg' }, Name: 'Poteau structure', - PositionReference: 1 + PositionReference: 1, + AssociatedContainer: { + Type: 'Montant' + } }, { Width: 32,