Merge branch 'dev'
This commit is contained in:
commit
bf1c4e543b
34 changed files with 955 additions and 633 deletions
|
@ -1 +1,2 @@
|
||||||
VITE_API_URL=http://localhost:5000
|
VITE_API_FETCH_URL=http://localhost:5000
|
||||||
|
VITE_API_POST_URL=http://localhost:5000/ApplicationState
|
|
@ -1,2 +1,3 @@
|
||||||
|
|
||||||
VITE_API_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/GetSVGLayoutConfiguration
|
VITE_API_FETCH_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/GetSVGLayoutConfiguration
|
||||||
|
VITE_API_POST_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/ApplicationState
|
|
@ -1 +1,2 @@
|
||||||
VITE_API_URL=http://localhost:5000
|
VITE_API_FETCH_URL=http://localhost:5000
|
||||||
|
VITE_API_POST_URL=http://localhost:5000
|
|
@ -4,7 +4,6 @@ module.exports = {
|
||||||
es2021: true
|
es2021: true
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'only-warn',
|
|
||||||
'plugin:react/recommended',
|
'plugin:react/recommended',
|
||||||
'standard-with-typescript'
|
'standard-with-typescript'
|
||||||
],
|
],
|
||||||
|
@ -18,6 +17,7 @@ module.exports = {
|
||||||
project: './tsconfig.json'
|
project: './tsconfig.json'
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
'only-warn',
|
||||||
'react',
|
'react',
|
||||||
'react-hooks',
|
'react-hooks',
|
||||||
'@typescript-eslint'
|
'@typescript-eslint'
|
||||||
|
|
|
@ -9,7 +9,7 @@ const getCircularReplacer = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'Symbols') {
|
if (key === 'symbols') {
|
||||||
return Array.from(value.entries());
|
return Array.from(value.entries());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { FetchConfiguration } from './api';
|
||||||
|
|
||||||
describe.concurrent('API test', () => {
|
describe.concurrent('API test', () => {
|
||||||
it('Load environment', () => {
|
it('Load environment', () => {
|
||||||
const url = import.meta.env.VITE_API_URL;
|
const url = import.meta.env.VITE_API_FETCH_URL;
|
||||||
expect(url).toBe('http://localhost:5000');
|
expect(url).toBe('http://localhost:5000');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import { IConfiguration } from '../../Interfaces/IConfiguration';
|
import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||||
|
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||||
|
import { ISetContainerListRequest } from '../../Interfaces/ISetContainerListRequest';
|
||||||
|
import { ISetContainerListResponse } from '../../Interfaces/ISetContainerListResponse';
|
||||||
|
import { GetCircularReplacer } from '../../utils/saveload';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the configuration from the API
|
* Fetch the configuration from the API
|
||||||
* @returns {Configation} The model of the configuration for the application
|
* @returns {Configation} The model of the configuration for the application
|
||||||
*/
|
*/
|
||||||
export async function FetchConfiguration(): Promise<IConfiguration> {
|
export async function FetchConfiguration(): Promise<IConfiguration> {
|
||||||
const url = `${import.meta.env.VITE_API_URL}`;
|
const url = import.meta.env.VITE_API_FETCH_URL;
|
||||||
// The test library cannot use the Fetch API
|
// The test library cannot use the Fetch API
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||||
|
@ -28,3 +32,30 @@ export async function FetchConfiguration(): Promise<IConfiguration> {
|
||||||
xhr.send();
|
xhr.send();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function SetContainerList(request: ISetContainerListRequest): Promise<ISetContainerListResponse> {
|
||||||
|
const url = import.meta.env.VITE_API_POST_URL;
|
||||||
|
const dataParsed = JSON.stringify(request, GetCircularReplacer());
|
||||||
|
// The test library cannot use the Fetch API
|
||||||
|
// @ts-expect-error
|
||||||
|
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||||
|
if (window.fetch) {
|
||||||
|
return await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: dataParsed
|
||||||
|
})
|
||||||
|
.then(async(response) =>
|
||||||
|
await response.json()
|
||||||
|
) as ISetContainerListResponse;
|
||||||
|
}
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.onreadystatechange = function() { // Call a function when the state changes.
|
||||||
|
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
|
||||||
|
resolve(JSON.parse(this.responseText));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send(dataParsed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -35,7 +35,11 @@ describe.concurrent('Properties', () => {
|
||||||
margin: {},
|
margin: {},
|
||||||
xPositionReference: XPositionReference.Left,
|
xPositionReference: XPositionReference.Left,
|
||||||
isFlex: false,
|
isFlex: false,
|
||||||
isAnchor: false
|
isAnchor: false,
|
||||||
|
showChildrenDimensions: true, // TODO: put the dimension at the top (see pdf)
|
||||||
|
showSelfDimensions: true, // TODO: put the dimension at the bottom (see pdf)
|
||||||
|
isDimensionBorrower: true, // second dimensions from the bottom
|
||||||
|
markPositionToDimensionBorrower: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = vi.fn((key, value) => {
|
const handleChange = vi.fn((key, value) => {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { Dispatch, SetStateAction } from 'react';
|
|
||||||
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||||
import { IConfiguration } from '../../../Interfaces/IConfiguration';
|
import { IConfiguration } from '../../../Interfaces/IConfiguration';
|
||||||
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
|
@ -16,14 +15,13 @@ import { PropertyType } from '../../../Enums/PropertyType';
|
||||||
/**
|
/**
|
||||||
* Select a container
|
* Select a container
|
||||||
* @param container Selected container
|
* @param container Selected container
|
||||||
|
* @returns New history
|
||||||
*/
|
*/
|
||||||
export function SelectContainer(
|
export function SelectContainer(
|
||||||
containerId: string,
|
containerId: string,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
): IHistoryState[] {
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
|
||||||
): void {
|
|
||||||
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
|
@ -35,8 +33,7 @@ export function SelectContainer(
|
||||||
symbols: structuredClone(current.symbols),
|
symbols: structuredClone(current.symbols),
|
||||||
selectedSymbolId: current.selectedSymbolId
|
selectedSymbolId: current.selectedSymbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
return history;
|
||||||
setHistoryCurrentStep(history.length - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,16 +41,13 @@ export function SelectContainer(
|
||||||
* @param containerId containerId of the container to delete
|
* @param containerId containerId of the container to delete
|
||||||
* @param fullHistory History of the editor
|
* @param fullHistory History of the editor
|
||||||
* @param historyCurrentStep Current step
|
* @param historyCurrentStep Current step
|
||||||
* @param setHistory State setter for History
|
* @returns New history
|
||||||
* @param setHistoryCurrentStep State setter for current step
|
|
||||||
*/
|
*/
|
||||||
export function DeleteContainer(
|
export function DeleteContainer(
|
||||||
containerId: string,
|
containerId: string,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
): IHistoryState[] {
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
|
||||||
): void {
|
|
||||||
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
|
@ -80,7 +74,7 @@ export function DeleteContainer(
|
||||||
}
|
}
|
||||||
|
|
||||||
const newSymbols = structuredClone(current.symbols);
|
const newSymbols = structuredClone(current.symbols);
|
||||||
UnlinkSymbol(newSymbols, container);
|
UnlinkContainerFromSymbols(newSymbols, container);
|
||||||
|
|
||||||
const index = container.parent.children.indexOf(container);
|
const index = container.parent.children.indexOf(container);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
|
@ -108,10 +102,21 @@ export function DeleteContainer(
|
||||||
symbols: newSymbols,
|
symbols: newSymbols,
|
||||||
selectedSymbolId: current.selectedSymbolId
|
selectedSymbolId: current.selectedSymbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
return history;
|
||||||
setHistoryCurrentStep(history.length - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next container that will be selected
|
||||||
|
* after the selectedContainer is removed.
|
||||||
|
* If the selected container is removed, select the sibling before,
|
||||||
|
* If there is no sibling, select the parent,
|
||||||
|
*
|
||||||
|
* @param mainContainerClone Main container
|
||||||
|
* @param selectedContainerId Current selected container
|
||||||
|
* @param parent Parent of the selected/deleted container
|
||||||
|
* @param index Index of the selected/deleted container
|
||||||
|
* @returns {IContainerModel} Next selected container
|
||||||
|
*/
|
||||||
function GetSelectedContainerOnDelete(
|
function GetSelectedContainerOnDelete(
|
||||||
mainContainerClone: IContainerModel,
|
mainContainerClone: IContainerModel,
|
||||||
selectedContainerId: string,
|
selectedContainerId: string,
|
||||||
|
@ -125,7 +130,13 @@ function GetSelectedContainerOnDelete(
|
||||||
return newSelectedContainerId;
|
return newSelectedContainerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function UnlinkSymbol(symbols: Map<string, ISymbolModel>, container: IContainerModel): void {
|
/**
|
||||||
|
* Unlink a container and its children to symbols
|
||||||
|
* (used when deleting a container)
|
||||||
|
* @param symbols Symbols to update
|
||||||
|
* @param container Container to unlink
|
||||||
|
*/
|
||||||
|
function UnlinkContainerFromSymbols(symbols: Map<string, ISymbolModel>, container: IContainerModel): void {
|
||||||
const it = MakeIterator(container);
|
const it = MakeIterator(container);
|
||||||
for (const child of it) {
|
for (const child of it) {
|
||||||
const symbol = symbols.get(child.properties.linkedSymbolId);
|
const symbol = symbols.get(child.properties.linkedSymbolId);
|
||||||
|
@ -142,8 +153,6 @@ function UnlinkSymbol(symbols: Map<string, ISymbolModel>, container: IContainerM
|
||||||
* @param configuration Configuration of the App
|
* @param configuration Configuration of the App
|
||||||
* @param fullHistory History of the editor
|
* @param fullHistory History of the editor
|
||||||
* @param historyCurrentStep Current step
|
* @param historyCurrentStep Current step
|
||||||
* @param setHistory State setter for History
|
|
||||||
* @param setHistoryCurrentStep State setter for current step
|
|
||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
export function AddContainerToSelectedContainer(
|
export function AddContainerToSelectedContainer(
|
||||||
|
@ -151,28 +160,153 @@ export function AddContainerToSelectedContainer(
|
||||||
selected: IContainerModel | undefined,
|
selected: IContainerModel | undefined,
|
||||||
configuration: IConfiguration,
|
configuration: IConfiguration,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
): IHistoryState[] | null {
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
|
||||||
): void {
|
|
||||||
if (selected === null ||
|
if (selected === null ||
|
||||||
selected === undefined) {
|
selected === undefined) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parent = selected;
|
const parent = selected;
|
||||||
AddContainer(
|
return AddContainer(
|
||||||
parent.children.length,
|
parent.children.length,
|
||||||
type,
|
type,
|
||||||
parent.properties.id,
|
parent.properties.id,
|
||||||
configuration,
|
configuration,
|
||||||
fullHistory,
|
fullHistory,
|
||||||
historyCurrentStep,
|
historyCurrentStep
|
||||||
setHistory,
|
|
||||||
setHistoryCurrentStep
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and add a new container at `index` in children of parent of `parentId`
|
||||||
|
* @param index Index where to insert to the new container
|
||||||
|
* @param type Type of container
|
||||||
|
* @param parentId Parent in which to insert the new container
|
||||||
|
* @param configuration Configuration of the app
|
||||||
|
* @param fullHistory History of the editor
|
||||||
|
* @param historyCurrentStep Current step
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function AddContainers(
|
||||||
|
index: number,
|
||||||
|
types: string[],
|
||||||
|
parentId: string,
|
||||||
|
configuration: IConfiguration,
|
||||||
|
fullHistory: IHistoryState[],
|
||||||
|
historyCurrentStep: number
|
||||||
|
): IHistoryState[] {
|
||||||
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
|
// Deep clone the main container for the history
|
||||||
|
const clone: IContainerModel = structuredClone(current.mainContainer);
|
||||||
|
|
||||||
|
// Find the parent in the clone
|
||||||
|
const parentClone: IContainerModel | undefined = FindContainerById(
|
||||||
|
clone, parentId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (parentClone === null || parentClone === undefined) {
|
||||||
|
throw new Error('[AddContainer] Container model was not found among children of the main container!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
types.forEach((type, typeIndex) => {
|
||||||
|
// Get the preset properties from the API
|
||||||
|
const containerConfig = configuration.AvailableContainers
|
||||||
|
.find(option => option.Type === type);
|
||||||
|
|
||||||
|
if (containerConfig === undefined) {
|
||||||
|
throw new Error(`[AddContainer] Object type not found. Found: ${type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default margin
|
||||||
|
const left: number = containerConfig.Margin?.left ?? 0;
|
||||||
|
const bottom: number = containerConfig.Margin?.bottom ?? 0;
|
||||||
|
const top: number = containerConfig.Margin?.top ?? 0;
|
||||||
|
const right: number = containerConfig.Margin?.right ?? 0;
|
||||||
|
|
||||||
|
// Default coordinates
|
||||||
|
let x = containerConfig.DefaultX ?? 0;
|
||||||
|
let y = containerConfig.DefaultY ?? 0;
|
||||||
|
let width = containerConfig.Width ?? containerConfig.MaxWidth ?? containerConfig.MinWidth ?? parentClone.properties.width;
|
||||||
|
let height = containerConfig.Height ?? parentClone.properties.height;
|
||||||
|
|
||||||
|
({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right));
|
||||||
|
|
||||||
|
// Apply an add method (append or insert/replace)
|
||||||
|
x = ApplyAddMethod(index + typeIndex, containerConfig, parentClone, x);
|
||||||
|
|
||||||
|
// Set the counter of the object type in order to assign an unique id
|
||||||
|
UpdateCounters(newCounters, type);
|
||||||
|
const count = newCounters[type];
|
||||||
|
|
||||||
|
const defaultProperties = GetDefaultContainerProps(
|
||||||
|
type,
|
||||||
|
count,
|
||||||
|
parentClone,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
containerConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the container
|
||||||
|
const newContainer = new ContainerModel(
|
||||||
|
parentClone,
|
||||||
|
defaultProperties,
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
type
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add it to the parent
|
||||||
|
if (index === parentClone.children.length) {
|
||||||
|
parentClone.children.push(newContainer);
|
||||||
|
} else {
|
||||||
|
parentClone.children.splice(index, 0, newContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle behaviors here ///
|
||||||
|
|
||||||
|
// Initialize default children of the container
|
||||||
|
InitializeDefaultChild(configuration, containerConfig, newContainer, newCounters);
|
||||||
|
|
||||||
|
// Apply the behaviors (flex, rigid, anchor)
|
||||||
|
ApplyBehaviors(newContainer, current.symbols);
|
||||||
|
|
||||||
|
// Then, apply the behaviors on its siblings (mostly for flex)
|
||||||
|
ApplyBehaviorsOnSiblings(newContainer, current.symbols);
|
||||||
|
|
||||||
|
// Sort the parent children by x
|
||||||
|
UpdateParentChildrenList(parentClone);
|
||||||
|
|
||||||
|
// Add to the list of container id for logging purpose
|
||||||
|
containerIds.push(newContainer.properties.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the state
|
||||||
|
history.push({
|
||||||
|
lastAction: `Add [${containerIds.join(', ')}] in ${parentClone.properties.id}`,
|
||||||
|
mainContainer: clone,
|
||||||
|
selectedContainerId: parentClone.properties.id,
|
||||||
|
typeCounters: newCounters,
|
||||||
|
symbols: structuredClone(current.symbols),
|
||||||
|
selectedSymbolId: current.selectedSymbolId
|
||||||
|
});
|
||||||
|
|
||||||
|
return history;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create and add a new container at `index` in children of parent of `parentId`
|
* Create and add a new container at `index` in children of parent of `parentId`
|
||||||
* @param index Index where to insert to the new container
|
* @param index Index where to insert to the new container
|
||||||
|
@ -183,7 +317,7 @@ export function AddContainerToSelectedContainer(
|
||||||
* @param historyCurrentStep Current step
|
* @param historyCurrentStep Current step
|
||||||
* @param setHistory State setter of History
|
* @param setHistory State setter of History
|
||||||
* @param setHistoryCurrentStep State setter of the current step
|
* @param setHistoryCurrentStep State setter of the current step
|
||||||
* @returns void
|
* @returns new history
|
||||||
*/
|
*/
|
||||||
export function AddContainer(
|
export function AddContainer(
|
||||||
index: number,
|
index: number,
|
||||||
|
@ -191,104 +325,55 @@ export function AddContainer(
|
||||||
parentId: string,
|
parentId: string,
|
||||||
configuration: IConfiguration,
|
configuration: IConfiguration,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
): IHistoryState[] {
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
// just call AddContainers with an array on a single element
|
||||||
): void {
|
return AddContainers(
|
||||||
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
index,
|
||||||
const current = history[history.length - 1];
|
[type],
|
||||||
|
parentId,
|
||||||
// Get the preset properties from the API
|
configuration,
|
||||||
const containerConfig = configuration.AvailableContainers
|
fullHistory,
|
||||||
.find(option => option.Type === type);
|
historyCurrentStep
|
||||||
|
|
||||||
if (containerConfig === undefined) {
|
|
||||||
throw new Error(`[AddContainer] Object type not found. Found: ${type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the counter of the object type in order to assign an unique id
|
|
||||||
const newCounters = Object.assign({}, current.typeCounters);
|
|
||||||
UpdateCounters(newCounters, type);
|
|
||||||
const count = newCounters[type];
|
|
||||||
|
|
||||||
// Create maincontainer model
|
|
||||||
const clone: IContainerModel = structuredClone(current.mainContainer);
|
|
||||||
|
|
||||||
// Find the parent
|
|
||||||
const parentClone: IContainerModel | undefined = FindContainerById(
|
|
||||||
clone, parentId
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (parentClone === null || parentClone === undefined) {
|
|
||||||
throw new Error('[AddContainer] Container model was not found among children of the main container!');
|
|
||||||
}
|
|
||||||
const left: number = containerConfig.Margin?.left ?? 0;
|
|
||||||
const bottom: number = containerConfig.Margin?.bottom ?? 0;
|
|
||||||
const top: number = containerConfig.Margin?.top ?? 0;
|
|
||||||
const right: number = containerConfig.Margin?.right ?? 0;
|
|
||||||
|
|
||||||
let x = containerConfig.DefaultX ?? 0;
|
|
||||||
let y = containerConfig.DefaultY ?? 0;
|
|
||||||
let width = containerConfig.Width ?? containerConfig.MaxWidth ?? containerConfig.MinWidth ?? parentClone.properties.width;
|
|
||||||
let height = containerConfig.Height ?? parentClone.properties.height;
|
|
||||||
|
|
||||||
({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right));
|
|
||||||
|
|
||||||
x = ApplyAddMethod(index, containerConfig, parentClone, x);
|
|
||||||
|
|
||||||
const defaultProperties = GetDefaultContainerProps(
|
|
||||||
type,
|
|
||||||
count,
|
|
||||||
parentClone,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
containerConfig
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create the container
|
|
||||||
const newContainer = new ContainerModel(
|
|
||||||
parentClone,
|
|
||||||
defaultProperties,
|
|
||||||
[],
|
|
||||||
{
|
|
||||||
type
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
parentClone.children.push(newContainer);
|
|
||||||
UpdateParentChildrenList(parentClone);
|
|
||||||
|
|
||||||
InitializeDefaultChild(configuration, containerConfig, newContainer, newCounters);
|
|
||||||
|
|
||||||
ApplyBehaviors(newContainer, current.symbols);
|
|
||||||
|
|
||||||
ApplyBehaviorsOnSiblings(newContainer, current.symbols);
|
|
||||||
|
|
||||||
// Update the state
|
|
||||||
history.push({
|
|
||||||
lastAction: `Add ${newContainer.properties.id} in ${parentClone.properties.id}`,
|
|
||||||
mainContainer: clone,
|
|
||||||
selectedContainerId: parentClone.properties.id,
|
|
||||||
typeCounters: newCounters,
|
|
||||||
symbols: structuredClone(current.symbols),
|
|
||||||
selectedSymbolId: current.selectedSymbolId
|
|
||||||
});
|
|
||||||
setHistory(history);
|
|
||||||
setHistoryCurrentStep(history.length - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the parent children by x
|
||||||
|
* @param parentClone The clone used for the sort
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
function UpdateParentChildrenList(parentClone: IContainerModel | null | undefined): void {
|
function UpdateParentChildrenList(parentClone: IContainerModel | null | undefined): void {
|
||||||
if (parentClone === null || parentClone === undefined) {
|
if (parentClone === null || parentClone === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const children = parentClone.children;
|
||||||
parentClone.children.sort(
|
parentClone.children.sort(
|
||||||
(a, b) => TransformX(a.properties.x, a.properties.width, a.properties.xPositionReference) -
|
(a, b) => {
|
||||||
TransformX(b.properties.x, b.properties.width, b.properties.xPositionReference)
|
const xA = TransformX(a.properties.x, a.properties.width, a.properties.xPositionReference);
|
||||||
|
const xB = TransformX(b.properties.x, b.properties.width, b.properties.xPositionReference);
|
||||||
|
if (xA < xB) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (xB < xA) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// xA = xB
|
||||||
|
const indexA = children.indexOf(a);
|
||||||
|
const indexB = children.indexOf(b);
|
||||||
|
return indexA - indexB;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the children of the container
|
||||||
|
* @param configuration Current configuration of the app
|
||||||
|
* @param containerConfig The new container config used to read the default child type
|
||||||
|
* @param newContainer The new container to initialize its default children
|
||||||
|
* @param newCounters Type counter used for unique ids
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function InitializeDefaultChild(
|
function InitializeDefaultChild(
|
||||||
configuration: IConfiguration,
|
configuration: IConfiguration,
|
||||||
containerConfig: IAvailableContainer,
|
containerConfig: IAvailableContainer,
|
||||||
|
@ -368,12 +453,18 @@ function InitializeDefaultChild(
|
||||||
* @param x Additionnal offset
|
* @param x Additionnal offset
|
||||||
* @returns New offset
|
* @returns New offset
|
||||||
*/
|
*/
|
||||||
function ApplyAddMethod(index: number, containerConfig: IAvailableContainer, parent: IContainerModel, x: number): number {
|
function ApplyAddMethod(
|
||||||
|
index: number,
|
||||||
|
containerConfig: IAvailableContainer,
|
||||||
|
parent: IContainerModel,
|
||||||
|
x: number
|
||||||
|
): number {
|
||||||
if (index > 0 && (
|
if (index > 0 && (
|
||||||
containerConfig.AddMethod === undefined ||
|
containerConfig.AddMethod === undefined ||
|
||||||
containerConfig.AddMethod === AddMethod.Append)) {
|
containerConfig.AddMethod === AddMethod.Append)) {
|
||||||
// Append method (default)
|
// Append method (default)
|
||||||
const lastChild: IContainerModel | undefined = parent.children.at(index - 1);
|
const lastChild: IContainerModel | undefined = parent.children
|
||||||
|
.at(index - 1);
|
||||||
|
|
||||||
if (lastChild !== undefined) {
|
if (lastChild !== undefined) {
|
||||||
x += (lastChild.properties.x + lastChild.properties.width);
|
x += (lastChild.properties.x + lastChild.properties.width);
|
||||||
|
@ -394,10 +485,8 @@ export function OnPropertyChange(
|
||||||
type: PropertyType = PropertyType.Simple,
|
type: PropertyType = PropertyType.Simple,
|
||||||
selected: IContainerModel | undefined,
|
selected: IContainerModel | undefined,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
): IHistoryState[] {
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
|
||||||
): void {
|
|
||||||
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
|
@ -423,8 +512,7 @@ export function OnPropertyChange(
|
||||||
symbols: structuredClone(current.symbols),
|
symbols: structuredClone(current.symbols),
|
||||||
selectedSymbolId: current.selectedSymbolId
|
selectedSymbolId: current.selectedSymbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
return history;
|
||||||
setHistoryCurrentStep(history.length - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -465,6 +553,13 @@ function SetContainer(
|
||||||
UpdateParentChildrenList(container.parent);
|
UpdateParentChildrenList(container.parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign the property to a container depending on the type
|
||||||
|
* @param container Container in which the property will be applied to
|
||||||
|
* @param key Key/Id of the property
|
||||||
|
* @param value Value of the property
|
||||||
|
* @param type Type of the property
|
||||||
|
*/
|
||||||
function AssignProperty(container: ContainerModel, key: string, value: string | number | boolean, type: PropertyType): void {
|
function AssignProperty(container: ContainerModel, key: string, value: string | number | boolean, type: PropertyType): void {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PropertyType.Style:
|
case PropertyType.Style:
|
||||||
|
@ -477,7 +572,12 @@ function AssignProperty(container: ContainerModel, key: string, value: string |
|
||||||
(container.properties as any)[key] = value;
|
(container.properties as any)[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the margin property
|
||||||
|
*/
|
||||||
function SetMargin(): void {
|
function SetMargin(): void {
|
||||||
|
// We need to detect change in order to apply transformation to the width and height
|
||||||
|
// Knowing the current margin is not enough as we dont keep the original width and height
|
||||||
const oldMarginValue: number = (container.properties.margin as any)[key];
|
const oldMarginValue: number = (container.properties.margin as any)[key];
|
||||||
const diff = Number(value) - oldMarginValue;
|
const diff = Number(value) - oldMarginValue;
|
||||||
switch (key) {
|
switch (key) {
|
||||||
|
@ -500,6 +600,14 @@ function AssignProperty(container: ContainerModel, key: string, value: string |
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link a symbol to a container
|
||||||
|
* @param containerId Container id
|
||||||
|
* @param oldSymbolId Old Symbol id
|
||||||
|
* @param newSymbolId New Symbol id
|
||||||
|
* @param symbols Current list of symbols
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function LinkSymbol(
|
function LinkSymbol(
|
||||||
containerId: string,
|
containerId: string,
|
||||||
oldSymbolId: string,
|
oldSymbolId: string,
|
||||||
|
@ -519,10 +627,18 @@ function LinkSymbol(
|
||||||
newSymbol.linkedContainers.add(containerId);
|
newSymbol.linkedContainers.add(containerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate over the siblings of newContainer and apply the behaviors
|
||||||
|
* @param newContainer
|
||||||
|
* @param symbols
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function ApplyBehaviorsOnSiblings(newContainer: ContainerModel, symbols: Map<string, ISymbolModel>): void {
|
function ApplyBehaviorsOnSiblings(newContainer: ContainerModel, symbols: Map<string, ISymbolModel>): void {
|
||||||
if (newContainer.parent === null || newContainer.parent === undefined) {
|
if (newContainer.parent === null || newContainer.parent === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
newContainer.parent.children.filter(container => newContainer !== container).forEach(container => ApplyBehaviors(container, symbols));
|
newContainer.parent.children
|
||||||
|
.filter(container => newContainer !== container)
|
||||||
|
.forEach(container => ApplyBehaviors(container, symbols));
|
||||||
}
|
}
|
||||||
|
|
131
src/Components/Editor/Actions/ContextMenuActions.ts
Normal file
131
src/Components/Editor/Actions/ContextMenuActions.ts
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
import Swal from 'sweetalert2';
|
||||||
|
import { AddMethod } from '../../../Enums/AddMethod';
|
||||||
|
import { IAction } from '../../../Interfaces/IAction';
|
||||||
|
import { IConfiguration } from '../../../Interfaces/IConfiguration';
|
||||||
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
|
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||||
|
import { ISetContainerListRequest } from '../../../Interfaces/ISetContainerListRequest';
|
||||||
|
import { ISetContainerListResponse } from '../../../Interfaces/ISetContainerListResponse';
|
||||||
|
import { FindContainerById } from '../../../utils/itertools';
|
||||||
|
import { SetContainerList } from '../../API/api';
|
||||||
|
import { AddContainers, DeleteContainer } from './ContainerOperations';
|
||||||
|
|
||||||
|
export function GetAction(
|
||||||
|
action: IAction,
|
||||||
|
currentState: IHistoryState,
|
||||||
|
configuration: IConfiguration,
|
||||||
|
history: IHistoryState[],
|
||||||
|
historyCurrentStep: number,
|
||||||
|
setNewHistory: (newHistory: IHistoryState[]) => void
|
||||||
|
): (target: HTMLElement) => void {
|
||||||
|
return (target: HTMLElement) => {
|
||||||
|
const id = target.id;
|
||||||
|
const container = FindContainerById(currentState.mainContainer, id);
|
||||||
|
|
||||||
|
if (container === undefined) {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Error',
|
||||||
|
text: 'No container was selected on right click',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
throw new Error(`[API:${action.Action}] No container was selected`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
const request: ISetContainerListRequest = {
|
||||||
|
Container: container,
|
||||||
|
Action: action.Action,
|
||||||
|
ApplicationState: currentState
|
||||||
|
};
|
||||||
|
/* eslint-enable */
|
||||||
|
|
||||||
|
SetContainerList(request)
|
||||||
|
.then((response: ISetContainerListResponse) => {
|
||||||
|
HandleSetContainerList(
|
||||||
|
action,
|
||||||
|
container,
|
||||||
|
response,
|
||||||
|
configuration,
|
||||||
|
history,
|
||||||
|
historyCurrentStep,
|
||||||
|
setNewHistory
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function HandleSetContainerList(
|
||||||
|
action: IAction,
|
||||||
|
selectedContainer: IContainerModel,
|
||||||
|
response: ISetContainerListResponse,
|
||||||
|
configuration: IConfiguration,
|
||||||
|
history: IHistoryState[],
|
||||||
|
historyCurrentStep: number,
|
||||||
|
setNewHistory: (newHistory: IHistoryState[]) => void
|
||||||
|
): void {
|
||||||
|
switch (action.AddingBehavior) {
|
||||||
|
case AddMethod.Append:
|
||||||
|
setNewHistory(
|
||||||
|
AddContainers(
|
||||||
|
selectedContainer.children.length,
|
||||||
|
response.Containers.map(container => container.properties.type),
|
||||||
|
selectedContainer.properties.id,
|
||||||
|
configuration,
|
||||||
|
history,
|
||||||
|
historyCurrentStep
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
case AddMethod.Insert:
|
||||||
|
break;
|
||||||
|
case AddMethod.Replace:
|
||||||
|
setNewHistory(
|
||||||
|
HandleReplace(
|
||||||
|
selectedContainer,
|
||||||
|
response,
|
||||||
|
configuration,
|
||||||
|
history,
|
||||||
|
historyCurrentStep
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function HandleReplace(
|
||||||
|
selectedContainer: IContainerModel,
|
||||||
|
response: ISetContainerListResponse,
|
||||||
|
configuration: IConfiguration,
|
||||||
|
history: IHistoryState[],
|
||||||
|
historyCurrentStep: number
|
||||||
|
): IHistoryState[] {
|
||||||
|
if (selectedContainer.parent === undefined || selectedContainer.parent === null) {
|
||||||
|
throw new Error('[ReplaceContainer] Cannot replace a container that does not exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = selectedContainer.parent.children.indexOf(selectedContainer);
|
||||||
|
|
||||||
|
const types = response.Containers.map(container => container.properties.type);
|
||||||
|
const newHistoryBeforeDelete = AddContainers(
|
||||||
|
index + 1,
|
||||||
|
types,
|
||||||
|
selectedContainer.properties.parentId,
|
||||||
|
configuration,
|
||||||
|
history,
|
||||||
|
historyCurrentStep
|
||||||
|
);
|
||||||
|
|
||||||
|
const newHistoryAfterDelete = DeleteContainer(
|
||||||
|
selectedContainer.properties.id,
|
||||||
|
newHistoryBeforeDelete,
|
||||||
|
newHistoryBeforeDelete.length - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remove AddContainers from history
|
||||||
|
newHistoryAfterDelete.splice(newHistoryAfterDelete.length - 2, 1);
|
||||||
|
|
||||||
|
// Rename the last action by Replace
|
||||||
|
newHistoryAfterDelete[newHistoryAfterDelete.length - 1].lastAction =
|
||||||
|
`Replace ${selectedContainer.properties.id} by [${types.join(', ')}]`;
|
||||||
|
|
||||||
|
return newHistoryAfterDelete;
|
||||||
|
}
|
|
@ -13,10 +13,8 @@ export function AddSymbol(
|
||||||
name: string,
|
name: string,
|
||||||
configuration: IConfiguration,
|
configuration: IConfiguration,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
): IHistoryState[] {
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
|
||||||
): void {
|
|
||||||
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
|
@ -44,17 +42,14 @@ export function AddSymbol(
|
||||||
symbols: newSymbols,
|
symbols: newSymbols,
|
||||||
selectedSymbolId: newSymbol.id
|
selectedSymbolId: newSymbol.id
|
||||||
});
|
});
|
||||||
setHistory(history);
|
return history;
|
||||||
setHistoryCurrentStep(history.length - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SelectSymbol(
|
export function SelectSymbol(
|
||||||
symbolId: string,
|
symbolId: string,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
): IHistoryState[] {
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
|
||||||
): void {
|
|
||||||
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
|
@ -66,17 +61,14 @@ export function SelectSymbol(
|
||||||
symbols: structuredClone(current.symbols),
|
symbols: structuredClone(current.symbols),
|
||||||
selectedSymbolId: symbolId
|
selectedSymbolId: symbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
return history;
|
||||||
setHistoryCurrentStep(history.length - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DeleteSymbol(
|
export function DeleteSymbol(
|
||||||
symbolId: string,
|
symbolId: string,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
): IHistoryState[] {
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
|
||||||
): void {
|
|
||||||
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
|
@ -89,7 +81,7 @@ export function DeleteSymbol(
|
||||||
|
|
||||||
const newMainContainer = structuredClone(current.mainContainer);
|
const newMainContainer = structuredClone(current.mainContainer);
|
||||||
|
|
||||||
UnlinkContainers(symbol, newMainContainer);
|
UnlinkSymbolFromContainers(symbol, newMainContainer);
|
||||||
|
|
||||||
newSymbols.delete(symbolId);
|
newSymbols.delete(symbolId);
|
||||||
|
|
||||||
|
@ -101,13 +93,17 @@ export function DeleteSymbol(
|
||||||
symbols: newSymbols,
|
symbols: newSymbols,
|
||||||
selectedSymbolId: symbolId
|
selectedSymbolId: symbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
return history;
|
||||||
setHistoryCurrentStep(history.length - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function UnlinkContainers(symbol: ISymbolModel, newMainContainer: IContainerModel): void {
|
/**
|
||||||
|
* Unlink a symbol to a container and its children
|
||||||
|
* @param symbol Symbol to remove
|
||||||
|
* @param root Container and its children to remove a symbol from
|
||||||
|
*/
|
||||||
|
function UnlinkSymbolFromContainers(symbol: ISymbolModel, root: IContainerModel): void {
|
||||||
symbol.linkedContainers.forEach((containerId) => {
|
symbol.linkedContainers.forEach((containerId) => {
|
||||||
const container = FindContainerById(newMainContainer, containerId);
|
const container = FindContainerById(root, containerId);
|
||||||
|
|
||||||
if (container === undefined) {
|
if (container === undefined) {
|
||||||
return;
|
return;
|
||||||
|
@ -127,10 +123,8 @@ export function OnPropertyChange(
|
||||||
key: string,
|
key: string,
|
||||||
value: string | number | boolean,
|
value: string | number | boolean,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
): IHistoryState[] {
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
|
||||||
): void {
|
|
||||||
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
|
@ -166,6 +160,5 @@ export function OnPropertyChange(
|
||||||
symbols: newSymbols,
|
symbols: newSymbols,
|
||||||
selectedSymbolId: symbol.id
|
selectedSymbolId: symbol.id
|
||||||
});
|
});
|
||||||
setHistory(history);
|
return history;
|
||||||
setHistoryCurrentStep(history.length - 1);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,16 @@ import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||||
import { SVG } from '../SVG/SVG';
|
import { SVG } from '../SVG/SVG';
|
||||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||||
import { UI } from '../UI/UI';
|
import { UI } from '../UI/UI';
|
||||||
import { SelectContainer, DeleteContainer, AddContainerToSelectedContainer, OnPropertyChange } from './Actions/ContainerOperations';
|
import { SelectContainer, DeleteContainer, AddContainerToSelectedContainer, OnPropertyChange, AddContainers } from './Actions/ContainerOperations';
|
||||||
import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save';
|
import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save';
|
||||||
import { OnKey } from './Actions/Shortcuts';
|
import { OnKey } from './Actions/Shortcuts';
|
||||||
import EditorEvents from '../../Events/EditorEvents';
|
import { events as EVENTS } from '../../Events/EditorEvents';
|
||||||
import { IEditorState } from '../../Interfaces/IEditorState';
|
import { IEditorState } from '../../Interfaces/IEditorState';
|
||||||
import { MAX_HISTORY } from '../../utils/default';
|
import { MAX_HISTORY } from '../../utils/default';
|
||||||
import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, SelectSymbol } from './Actions/SymbolOperations';
|
import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, SelectSymbol } from './Actions/SymbolOperations';
|
||||||
import { FindContainerById } from '../../utils/itertools';
|
import { FindContainerById } from '../../utils/itertools';
|
||||||
|
import { IMenuAction, Menu } from '../Menu/Menu';
|
||||||
|
import { GetAction } from './Actions/ContextMenuActions';
|
||||||
|
|
||||||
interface IEditorProps {
|
interface IEditorProps {
|
||||||
root: Element | Document
|
root: Element | Document
|
||||||
|
@ -20,24 +22,71 @@ interface IEditorProps {
|
||||||
historyCurrentStep: number
|
historyCurrentStep: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UpdateCounters(counters: Record<string, number>, type: string): void {
|
function InitActions(
|
||||||
if (counters[type] === null ||
|
menuActions: Map<string, IMenuAction[]>,
|
||||||
counters[type] === undefined) {
|
configuration: IConfiguration,
|
||||||
counters[type] = 0;
|
history: IHistoryState[],
|
||||||
} else {
|
historyCurrentStep: number,
|
||||||
counters[type]++;
|
setNewHistory: (newHistory: IHistoryState[]) => void
|
||||||
}
|
): void {
|
||||||
}
|
menuActions.set(
|
||||||
|
'elements-sidebar-row',
|
||||||
export function GetCurrentHistory(history: IHistoryState[], historyCurrentStep: number): IHistoryState[] {
|
[{
|
||||||
return history.slice(
|
text: 'Delete',
|
||||||
Math.max(0, history.length - MAX_HISTORY),
|
action: (target: HTMLElement) => {
|
||||||
historyCurrentStep + 1
|
const id = target.id;
|
||||||
|
const newHistory = DeleteContainer(
|
||||||
|
id,
|
||||||
|
history,
|
||||||
|
historyCurrentStep
|
||||||
|
);
|
||||||
|
setNewHistory(newHistory);
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
menuActions.set(
|
||||||
|
'symbols-sidebar-row',
|
||||||
|
[{
|
||||||
|
text: 'Delete',
|
||||||
|
action: (target: HTMLElement) => {
|
||||||
|
const id = target.id;
|
||||||
|
const newHistory = DeleteSymbol(
|
||||||
|
id,
|
||||||
|
history,
|
||||||
|
historyCurrentStep
|
||||||
|
);
|
||||||
|
setNewHistory(newHistory);
|
||||||
|
}
|
||||||
|
}]
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
export function GetCurrentHistoryState(history: IHistoryState[], historyCurrentStep: number): IHistoryState {
|
// API Actions
|
||||||
return history[historyCurrentStep];
|
for (const availableContainer of configuration.AvailableContainers) {
|
||||||
|
if (availableContainer.Actions === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const action of availableContainer.Actions) {
|
||||||
|
if (menuActions.get(availableContainer.Type) === undefined) {
|
||||||
|
menuActions.set(availableContainer.Type, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentState = GetCurrentHistoryState(history, historyCurrentStep);
|
||||||
|
const newAction: IMenuAction = {
|
||||||
|
text: action.Label,
|
||||||
|
action: GetAction(
|
||||||
|
action,
|
||||||
|
currentState,
|
||||||
|
configuration,
|
||||||
|
history,
|
||||||
|
historyCurrentStep,
|
||||||
|
setNewHistory
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
menuActions.get(availableContainer.Type)?.push(newAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function UseShortcuts(
|
function UseShortcuts(
|
||||||
|
@ -68,11 +117,9 @@ function UseWindowEvents(
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number,
|
||||||
configuration: IConfiguration,
|
configuration: IConfiguration,
|
||||||
editorRef: React.RefObject<HTMLDivElement>,
|
editorRef: React.RefObject<HTMLDivElement>,
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setNewHistory: (newHistory: IHistoryState[]) => void
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
|
||||||
): void {
|
): void {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const events = EditorEvents;
|
|
||||||
const editorState: IEditorState = {
|
const editorState: IEditorState = {
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
|
@ -80,13 +127,12 @@ function UseWindowEvents(
|
||||||
};
|
};
|
||||||
|
|
||||||
const funcs = new Map<string, () => void>();
|
const funcs = new Map<string, () => void>();
|
||||||
for (const event of events) {
|
for (const event of EVENTS) {
|
||||||
function Func(eventInitDict?: CustomEventInit): void {
|
function Func(eventInitDict?: CustomEventInit): void {
|
||||||
return event.func(
|
return event.func(
|
||||||
root,
|
root,
|
||||||
editorState,
|
editorState,
|
||||||
setHistory,
|
setNewHistory,
|
||||||
setHistoryCurrentStep,
|
|
||||||
eventInitDict
|
eventInitDict
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -94,7 +140,7 @@ function UseWindowEvents(
|
||||||
funcs.set(event.name, Func);
|
funcs.set(event.name, Func);
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
for (const event of events) {
|
for (const event of EVENTS) {
|
||||||
const func = funcs.get(event.name);
|
const func = funcs.get(event.name);
|
||||||
if (func === undefined) {
|
if (func === undefined) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -105,11 +151,31 @@ function UseWindowEvents(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a macro function to use both setHistory
|
||||||
|
* and setHistoryCurrentStep at the same time
|
||||||
|
* @param setHistory
|
||||||
|
* @param setHistoryCurrentStep
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function UseNewHistoryState(
|
||||||
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
|
): (newHistory: IHistoryState[]) => void {
|
||||||
|
return (newHistory) => {
|
||||||
|
setHistory(newHistory);
|
||||||
|
setHistoryCurrentStep(newHistory.length - 1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function Editor(props: IEditorProps): JSX.Element {
|
export function Editor(props: IEditorProps): JSX.Element {
|
||||||
|
// States
|
||||||
const [history, setHistory] = React.useState<IHistoryState[]>(structuredClone(props.history));
|
const [history, setHistory] = React.useState<IHistoryState[]>(structuredClone(props.history));
|
||||||
const [historyCurrentStep, setHistoryCurrentStep] = React.useState<number>(props.historyCurrentStep);
|
const [historyCurrentStep, setHistoryCurrentStep] = React.useState<number>(props.historyCurrentStep);
|
||||||
const editorRef = useRef<HTMLDivElement>(null);
|
const editorRef = useRef<HTMLDivElement>(null);
|
||||||
|
const setNewHistory = UseNewHistoryState(setHistory, setHistoryCurrentStep);
|
||||||
|
|
||||||
|
// Events
|
||||||
UseShortcuts(history, historyCurrentStep, setHistoryCurrentStep);
|
UseShortcuts(history, historyCurrentStep, setHistoryCurrentStep);
|
||||||
UseWindowEvents(
|
UseWindowEvents(
|
||||||
props.root,
|
props.root,
|
||||||
|
@ -117,10 +183,20 @@ export function Editor(props: IEditorProps): JSX.Element {
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
props.configuration,
|
props.configuration,
|
||||||
editorRef,
|
editorRef,
|
||||||
setHistory,
|
setNewHistory
|
||||||
setHistoryCurrentStep
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Context Menu
|
||||||
|
const menuActions = new Map();
|
||||||
|
InitActions(
|
||||||
|
menuActions,
|
||||||
|
props.configuration,
|
||||||
|
history,
|
||||||
|
historyCurrentStep,
|
||||||
|
setNewHistory
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render
|
||||||
const configuration = props.configuration;
|
const configuration = props.configuration;
|
||||||
const current = GetCurrentHistoryState(history, historyCurrentStep);
|
const current = GetCurrentHistoryState(history, historyCurrentStep);
|
||||||
const selected = FindContainerById(current.mainContainer, current.selectedContainerId);
|
const selected = FindContainerById(current.mainContainer, current.selectedContainerId);
|
||||||
|
@ -133,66 +209,62 @@ export function Editor(props: IEditorProps): JSX.Element {
|
||||||
historyCurrentStep={historyCurrentStep}
|
historyCurrentStep={historyCurrentStep}
|
||||||
availableContainers={configuration.AvailableContainers}
|
availableContainers={configuration.AvailableContainers}
|
||||||
availableSymbols={configuration.AvailableSymbols}
|
availableSymbols={configuration.AvailableSymbols}
|
||||||
selectContainer={(container) => SelectContainer(
|
selectContainer={(container) => setNewHistory(
|
||||||
container,
|
SelectContainer(
|
||||||
history,
|
container,
|
||||||
historyCurrentStep,
|
history,
|
||||||
setHistory,
|
historyCurrentStep
|
||||||
setHistoryCurrentStep
|
))}
|
||||||
)}
|
deleteContainer={(containerId: string) => setNewHistory(
|
||||||
deleteContainer={(containerId: string) => DeleteContainer(
|
DeleteContainer(
|
||||||
containerId,
|
containerId,
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep
|
||||||
setHistory,
|
))}
|
||||||
setHistoryCurrentStep
|
onPropertyChange={(key, value, type) => setNewHistory(
|
||||||
)}
|
OnPropertyChange(
|
||||||
onPropertyChange={(key, value, type) => OnPropertyChange(
|
key, value, type,
|
||||||
key, value, type,
|
selected,
|
||||||
selected,
|
history,
|
||||||
history,
|
historyCurrentStep
|
||||||
historyCurrentStep,
|
))}
|
||||||
setHistory,
|
addContainer={(type) => {
|
||||||
setHistoryCurrentStep
|
const newHistory = AddContainerToSelectedContainer(
|
||||||
)}
|
type,
|
||||||
addContainer={(type) => AddContainerToSelectedContainer(
|
selected,
|
||||||
type,
|
configuration,
|
||||||
selected,
|
history,
|
||||||
configuration,
|
historyCurrentStep
|
||||||
history,
|
);
|
||||||
historyCurrentStep,
|
if (newHistory !== null) {
|
||||||
setHistory,
|
setNewHistory(newHistory);
|
||||||
setHistoryCurrentStep
|
}
|
||||||
)}
|
}}
|
||||||
addSymbol={(type) => AddSymbol(
|
addSymbol={(type) => setNewHistory(
|
||||||
type,
|
AddSymbol(
|
||||||
configuration,
|
type,
|
||||||
history,
|
configuration,
|
||||||
historyCurrentStep,
|
history,
|
||||||
setHistory,
|
historyCurrentStep
|
||||||
setHistoryCurrentStep
|
))}
|
||||||
)}
|
onSymbolPropertyChange={(key, value) => setNewHistory(
|
||||||
onSymbolPropertyChange={(key, value) => OnSymbolPropertyChange(
|
OnSymbolPropertyChange(
|
||||||
key, value,
|
key, value,
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep
|
||||||
setHistory,
|
))}
|
||||||
setHistoryCurrentStep
|
selectSymbol={(symbolId) => setNewHistory(
|
||||||
)}
|
SelectSymbol(
|
||||||
selectSymbol={(symbolId) => SelectSymbol(
|
symbolId,
|
||||||
symbolId,
|
history,
|
||||||
history,
|
historyCurrentStep
|
||||||
historyCurrentStep,
|
))}
|
||||||
setHistory,
|
deleteSymbol={(symbolId) => setNewHistory(
|
||||||
setHistoryCurrentStep
|
DeleteSymbol(
|
||||||
)}
|
symbolId,
|
||||||
deleteSymbol={(symbolId) => DeleteSymbol(
|
history,
|
||||||
symbolId,
|
historyCurrentStep
|
||||||
history,
|
))}
|
||||||
historyCurrentStep,
|
|
||||||
setHistory,
|
|
||||||
setHistoryCurrentStep
|
|
||||||
)}
|
|
||||||
saveEditorAsJSON={() => SaveEditorAsJSON(
|
saveEditorAsJSON={() => SaveEditorAsJSON(
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
|
@ -208,6 +280,31 @@ export function Editor(props: IEditorProps): JSX.Element {
|
||||||
>
|
>
|
||||||
{current.mainContainer}
|
{current.mainContainer}
|
||||||
</SVG>
|
</SVG>
|
||||||
|
<Menu
|
||||||
|
getListener={() => editorRef.current}
|
||||||
|
actions={menuActions}
|
||||||
|
className="z-30 transition-opacity rounded bg-slate-200 py-1 drop-shadow-xl"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function UpdateCounters(counters: Record<string, number>, type: string): void {
|
||||||
|
if (counters[type] === null ||
|
||||||
|
counters[type] === undefined) {
|
||||||
|
counters[type] = 0;
|
||||||
|
} else {
|
||||||
|
counters[type]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetCurrentHistory(history: IHistoryState[], historyCurrentStep: number): IHistoryState[] {
|
||||||
|
return history.slice(
|
||||||
|
Math.max(0, history.length - MAX_HISTORY),
|
||||||
|
historyCurrentStep + 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetCurrentHistoryState(history: IHistoryState[], historyCurrentStep: number): IHistoryState {
|
||||||
|
return history[historyCurrentStep];
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { ElementsSidebar } from './ElementsSidebar';
|
||||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||||
import { FindContainerById } from '../../utils/itertools';
|
import { FindContainerById } from '../../utils/itertools';
|
||||||
|
import { DEFAULT_MAINCONTAINER_PROPS } from '../../utils/default';
|
||||||
|
|
||||||
describe.concurrent('Elements sidebar', () => {
|
describe.concurrent('Elements sidebar', () => {
|
||||||
it('With a MainContainer', () => {
|
it('With a MainContainer', () => {
|
||||||
|
@ -13,23 +14,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
mainContainer={{
|
mainContainer={{
|
||||||
children: [],
|
children: [],
|
||||||
parent: null,
|
parent: null,
|
||||||
properties: {
|
properties: DEFAULT_MAINCONTAINER_PROPS,
|
||||||
id: 'main',
|
|
||||||
parentId: '',
|
|
||||||
linkedSymbolId: '',
|
|
||||||
displayedText: 'main',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 2000,
|
|
||||||
height: 100,
|
|
||||||
margin: {},
|
|
||||||
minWidth: 1,
|
|
||||||
type: 'type',
|
|
||||||
maxWidth: Infinity,
|
|
||||||
isFlex: false,
|
|
||||||
xPositionReference: XPositionReference.Left,
|
|
||||||
isAnchor: false
|
|
||||||
},
|
|
||||||
userData: {}
|
userData: {}
|
||||||
}}
|
}}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
|
@ -37,7 +22,6 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
selectedContainer={undefined}
|
selectedContainer={undefined}
|
||||||
onPropertyChange={() => {}}
|
onPropertyChange={() => {}}
|
||||||
selectContainer={() => {}}
|
selectContainer={() => {}}
|
||||||
deleteContainer={() => {}}
|
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect(screen.getByText(/Elements/i));
|
expect(screen.getByText(/Elements/i));
|
||||||
|
@ -49,23 +33,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
const mainContainer: IContainerModel = {
|
const mainContainer: IContainerModel = {
|
||||||
children: [],
|
children: [],
|
||||||
parent: null,
|
parent: null,
|
||||||
properties: {
|
properties: DEFAULT_MAINCONTAINER_PROPS,
|
||||||
id: 'main',
|
|
||||||
parentId: '',
|
|
||||||
linkedSymbolId: '',
|
|
||||||
displayedText: 'main',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 2000,
|
|
||||||
height: 100,
|
|
||||||
margin: {},
|
|
||||||
minWidth: 1,
|
|
||||||
isFlex: false,
|
|
||||||
maxWidth: Infinity,
|
|
||||||
type: 'type',
|
|
||||||
isAnchor: false,
|
|
||||||
xPositionReference: XPositionReference.Left
|
|
||||||
},
|
|
||||||
userData: {}
|
userData: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -77,7 +45,6 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
selectedContainer={mainContainer}
|
selectedContainer={mainContainer}
|
||||||
onPropertyChange={() => {}}
|
onPropertyChange={() => {}}
|
||||||
selectContainer={() => {}}
|
selectContainer={() => {}}
|
||||||
deleteContainer={() => {}}
|
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect(screen.getByText(/Elements/i));
|
expect(screen.getByText(/Elements/i));
|
||||||
|
@ -112,23 +79,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
const mainContainer: IContainerModel = {
|
const mainContainer: IContainerModel = {
|
||||||
children,
|
children,
|
||||||
parent: null,
|
parent: null,
|
||||||
properties: {
|
properties: DEFAULT_MAINCONTAINER_PROPS,
|
||||||
id: 'main',
|
|
||||||
parentId: '',
|
|
||||||
linkedSymbolId: '',
|
|
||||||
displayedText: 'main',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
minWidth: 1,
|
|
||||||
width: 2000,
|
|
||||||
height: 100,
|
|
||||||
xPositionReference: XPositionReference.Left,
|
|
||||||
margin: {},
|
|
||||||
isFlex: false,
|
|
||||||
maxWidth: Infinity,
|
|
||||||
type: 'type',
|
|
||||||
isAnchor: false
|
|
||||||
},
|
|
||||||
userData: {}
|
userData: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -151,6 +102,10 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
maxWidth: Infinity,
|
maxWidth: Infinity,
|
||||||
type: 'type',
|
type: 'type',
|
||||||
isAnchor: false,
|
isAnchor: false,
|
||||||
|
showChildrenDimensions: true,
|
||||||
|
showSelfDimensions: true,
|
||||||
|
isDimensionBorrower: true,
|
||||||
|
markPositionToDimensionBorrower: false,
|
||||||
xPositionReference: XPositionReference.Left
|
xPositionReference: XPositionReference.Left
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
|
@ -176,6 +131,10 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
isFlex: false,
|
isFlex: false,
|
||||||
maxWidth: Infinity,
|
maxWidth: Infinity,
|
||||||
type: 'type',
|
type: 'type',
|
||||||
|
showChildrenDimensions: true,
|
||||||
|
showSelfDimensions: true,
|
||||||
|
isDimensionBorrower: true,
|
||||||
|
markPositionToDimensionBorrower: false,
|
||||||
isAnchor: false
|
isAnchor: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
|
@ -190,7 +149,6 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
selectedContainer={mainContainer}
|
selectedContainer={mainContainer}
|
||||||
onPropertyChange={() => {}}
|
onPropertyChange={() => {}}
|
||||||
selectContainer={() => {}}
|
selectContainer={() => {}}
|
||||||
deleteContainer={() => {}}
|
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect(screen.getByText(/Elements/i));
|
expect(screen.getByText(/Elements/i));
|
||||||
|
@ -205,23 +163,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
const mainContainer: IContainerModel = {
|
const mainContainer: IContainerModel = {
|
||||||
children,
|
children,
|
||||||
parent: null,
|
parent: null,
|
||||||
properties: {
|
properties: DEFAULT_MAINCONTAINER_PROPS,
|
||||||
id: 'main',
|
|
||||||
parentId: '',
|
|
||||||
linkedSymbolId: '',
|
|
||||||
displayedText: 'main',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
minWidth: 1,
|
|
||||||
width: 2000,
|
|
||||||
height: 100,
|
|
||||||
xPositionReference: XPositionReference.Left,
|
|
||||||
margin: {},
|
|
||||||
isFlex: false,
|
|
||||||
maxWidth: Infinity,
|
|
||||||
type: 'type',
|
|
||||||
isAnchor: false
|
|
||||||
},
|
|
||||||
userData: {}
|
userData: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -243,6 +185,10 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
isFlex: false,
|
isFlex: false,
|
||||||
maxWidth: Infinity,
|
maxWidth: Infinity,
|
||||||
type: 'type',
|
type: 'type',
|
||||||
|
showChildrenDimensions: true,
|
||||||
|
showSelfDimensions: true,
|
||||||
|
isDimensionBorrower: true,
|
||||||
|
markPositionToDimensionBorrower: false,
|
||||||
isAnchor: false
|
isAnchor: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
|
@ -262,7 +208,6 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
selectedContainer={selectedContainer}
|
selectedContainer={selectedContainer}
|
||||||
onPropertyChange={() => {}}
|
onPropertyChange={() => {}}
|
||||||
selectContainer={selectContainer}
|
selectContainer={selectContainer}
|
||||||
deleteContainer={() => {}}
|
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect(screen.getByText(/Elements/i));
|
expect(screen.getByText(/Elements/i));
|
||||||
|
@ -285,7 +230,6 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
selectedContainer={selectedContainer}
|
selectedContainer={selectedContainer}
|
||||||
onPropertyChange={() => {}}
|
onPropertyChange={() => {}}
|
||||||
selectContainer={selectContainer}
|
selectContainer={selectContainer}
|
||||||
deleteContainer={() => {}}
|
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect((propertyId as HTMLInputElement).value === 'main').toBeFalsy();
|
expect((propertyId as HTMLInputElement).value === 'main').toBeFalsy();
|
||||||
|
|
|
@ -3,10 +3,6 @@ import { FixedSizeList as List } from 'react-window';
|
||||||
import { Properties } from '../ContainerProperties/ContainerProperties';
|
import { Properties } from '../ContainerProperties/ContainerProperties';
|
||||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
import { GetDepth, MakeIterator } from '../../utils/itertools';
|
import { GetDepth, MakeIterator } from '../../utils/itertools';
|
||||||
import { Menu } from '../Menu/Menu';
|
|
||||||
import { MenuItem } from '../Menu/MenuItem';
|
|
||||||
import { UseMouseEvents } from './MouseEventHandlers';
|
|
||||||
import { IPoint } from '../../Interfaces/IPoint';
|
|
||||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import { PropertyType } from '../../Enums/PropertyType';
|
import { PropertyType } from '../../Enums/PropertyType';
|
||||||
|
|
||||||
|
@ -22,29 +18,9 @@ interface IElementsSidebarProps {
|
||||||
type?: PropertyType
|
type?: PropertyType
|
||||||
) => void
|
) => void
|
||||||
selectContainer: (containerId: string) => void
|
selectContainer: (containerId: string) => void
|
||||||
deleteContainer: (containerid: string) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
|
export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
|
||||||
// States
|
|
||||||
const [isContextMenuOpen, setIsContextMenuOpen] = React.useState<boolean>(false);
|
|
||||||
const [onClickContainerId, setOnClickContainerId] = React.useState<string>('');
|
|
||||||
const [contextMenuPosition, setContextMenuPosition] = React.useState<IPoint>({
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
const elementRef = React.useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
// Event listeners
|
|
||||||
UseMouseEvents(
|
|
||||||
isContextMenuOpen,
|
|
||||||
elementRef,
|
|
||||||
setIsContextMenuOpen,
|
|
||||||
setOnClickContainerId,
|
|
||||||
setContextMenuPosition
|
|
||||||
);
|
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
let isOpenClasses = '-right-64';
|
let isOpenClasses = '-right-64';
|
||||||
if (props.isOpen) {
|
if (props.isOpen) {
|
||||||
|
@ -74,7 +50,7 @@ export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<button type="button"
|
<button type="button"
|
||||||
className={`w-full border-blue-500 elements-sidebar-row whitespace-pre
|
className={`w-full border-blue-500 elements-sidebar-row whitespace-pre
|
||||||
text-left text-sm font-medium transition-all ${selectedClass}`}
|
text-left text-sm font-medium transition-all ${container.properties.type} ${selectedClass}`}
|
||||||
id={key}
|
id={key}
|
||||||
key={key}
|
key={key}
|
||||||
style={style}
|
style={style}
|
||||||
|
@ -90,7 +66,7 @@ export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
|
||||||
className={`fixed flex flex-col bg-slate-100 text-gray-800 transition-all h-full w-64 overflow-y-auto z-20 ${isOpenClasses}`}
|
className={`fixed flex flex-col bg-slate-100 text-gray-800 transition-all h-full w-64 overflow-y-auto z-20 ${isOpenClasses}`}
|
||||||
>
|
>
|
||||||
<div className="bg-slate-100 font-bold sidebar-title">Elements</div>
|
<div className="bg-slate-100 font-bold sidebar-title">Elements</div>
|
||||||
<div ref={elementRef} className="h-96 text-gray-800">
|
<div className="h-96 text-gray-800">
|
||||||
<List
|
<List
|
||||||
className="List divide-y divide-black"
|
className="List divide-y divide-black"
|
||||||
itemCount={containers.length}
|
itemCount={containers.length}
|
||||||
|
@ -101,20 +77,6 @@ export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
|
||||||
{Row}
|
{Row}
|
||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
<Menu
|
|
||||||
className="transition-opacity rounded bg-slate-200 py-1 drop-shadow-xl"
|
|
||||||
x={contextMenuPosition.x}
|
|
||||||
y={contextMenuPosition.y}
|
|
||||||
isOpen={isContextMenuOpen}
|
|
||||||
>
|
|
||||||
<MenuItem
|
|
||||||
className="contextmenu-item"
|
|
||||||
text="Delete"
|
|
||||||
onClick={() => {
|
|
||||||
setIsContextMenuOpen(false);
|
|
||||||
props.deleteContainer(onClickContainerId);
|
|
||||||
} } />
|
|
||||||
</Menu>
|
|
||||||
<Properties
|
<Properties
|
||||||
properties={props.selectedContainer?.properties}
|
properties={props.selectedContainer?.properties}
|
||||||
symbols={props.symbols}
|
symbols={props.symbols}
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
import React, { RefObject, Dispatch, SetStateAction, useEffect } from 'react';
|
|
||||||
import { IPoint } from '../../Interfaces/IPoint';
|
|
||||||
|
|
||||||
export function UseMouseEvents(
|
|
||||||
isContextMenuOpen: boolean,
|
|
||||||
elementRef: RefObject<HTMLDivElement>,
|
|
||||||
setIsContextMenuOpen: Dispatch<SetStateAction<boolean>>,
|
|
||||||
setOnClickContainerId: Dispatch<SetStateAction<string>>,
|
|
||||||
setContextMenuPosition: Dispatch<SetStateAction<IPoint>>
|
|
||||||
): void {
|
|
||||||
useEffect(() => {
|
|
||||||
function OnContextMenu(event: MouseEvent): void {
|
|
||||||
return HandleRightClick(
|
|
||||||
event,
|
|
||||||
setIsContextMenuOpen,
|
|
||||||
setOnClickContainerId,
|
|
||||||
setContextMenuPosition
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function OnLeftClick(): void {
|
|
||||||
return HandleLeftClick(
|
|
||||||
isContextMenuOpen,
|
|
||||||
setIsContextMenuOpen,
|
|
||||||
setOnClickContainerId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
elementRef.current?.addEventListener(
|
|
||||||
'contextmenu',
|
|
||||||
OnContextMenu
|
|
||||||
);
|
|
||||||
|
|
||||||
window.addEventListener(
|
|
||||||
'click',
|
|
||||||
OnLeftClick
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
elementRef.current?.removeEventListener(
|
|
||||||
'contextmenu',
|
|
||||||
OnContextMenu
|
|
||||||
);
|
|
||||||
|
|
||||||
window.removeEventListener(
|
|
||||||
'click',
|
|
||||||
OnLeftClick
|
|
||||||
);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function HandleRightClick(
|
|
||||||
event: MouseEvent,
|
|
||||||
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
|
||||||
setOnClickContainerId: React.Dispatch<React.SetStateAction<string>>,
|
|
||||||
setContextMenuPosition: React.Dispatch<React.SetStateAction<IPoint>>
|
|
||||||
): void {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (!(event.target instanceof HTMLButtonElement)) {
|
|
||||||
setIsContextMenuOpen(false);
|
|
||||||
setOnClickContainerId('');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const contextMenuPosition: IPoint = { x: event.pageX, y: event.pageY };
|
|
||||||
setIsContextMenuOpen(true);
|
|
||||||
setOnClickContainerId(event.target.id);
|
|
||||||
setContextMenuPosition(contextMenuPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function HandleLeftClick(
|
|
||||||
isContextMenuOpen: boolean,
|
|
||||||
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
|
||||||
setOnClickContainerId: React.Dispatch<React.SetStateAction<string>>
|
|
||||||
): void {
|
|
||||||
if (!isContextMenuOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsContextMenuOpen(false);
|
|
||||||
setOnClickContainerId('');
|
|
||||||
}
|
|
|
@ -1,23 +1,111 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { IPoint } from '../../Interfaces/IPoint';
|
||||||
|
import { MenuItem } from './MenuItem';
|
||||||
|
|
||||||
interface IMenuProps {
|
interface IMenuProps {
|
||||||
|
getListener: () => HTMLElement | null
|
||||||
|
actions: Map<string, IMenuAction[]>
|
||||||
className?: string
|
className?: string
|
||||||
x: number
|
}
|
||||||
y: number
|
|
||||||
isOpen: boolean
|
export interface IMenuAction {
|
||||||
children: React.ReactNode[] | React.ReactNode
|
/** displayed */
|
||||||
|
text: string
|
||||||
|
|
||||||
|
/** function to be called on button click */
|
||||||
|
action: (target: HTMLElement) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function UseMouseEvents(
|
||||||
|
getListener: () => HTMLElement | null,
|
||||||
|
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
||||||
|
setContextMenuPosition: React.Dispatch<React.SetStateAction<IPoint>>,
|
||||||
|
setTarget: React.Dispatch<React.SetStateAction<HTMLElement | undefined>>
|
||||||
|
): void {
|
||||||
|
React.useEffect(() => {
|
||||||
|
function OnContextMenu(event: MouseEvent): void {
|
||||||
|
event.preventDefault();
|
||||||
|
const contextMenuPosition: IPoint = { x: event.pageX, y: event.pageY };
|
||||||
|
|
||||||
|
setIsOpen(true);
|
||||||
|
setContextMenuPosition(contextMenuPosition);
|
||||||
|
setTarget(event.target as HTMLElement); // this is wrong since target can be null and not undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function OnLeftClick(): void {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
getListener()?.addEventListener(
|
||||||
|
'contextmenu',
|
||||||
|
OnContextMenu
|
||||||
|
);
|
||||||
|
|
||||||
|
window.addEventListener(
|
||||||
|
'click',
|
||||||
|
OnLeftClick
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
getListener()?.removeEventListener(
|
||||||
|
'contextmenu',
|
||||||
|
OnContextMenu
|
||||||
|
);
|
||||||
|
|
||||||
|
window.removeEventListener(
|
||||||
|
'click',
|
||||||
|
OnLeftClick
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Menu(props: IMenuProps): JSX.Element {
|
export function Menu(props: IMenuProps): JSX.Element {
|
||||||
const visible = props.isOpen ? 'visible opacity-1' : 'invisible opacity-0';
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
const [contextMenuPosition, setContextMenuPosition] = React.useState<IPoint>({
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
});
|
||||||
|
const [target, setTarget] = React.useState<HTMLElement>();
|
||||||
|
|
||||||
|
UseMouseEvents(
|
||||||
|
props.getListener,
|
||||||
|
setIsOpen,
|
||||||
|
setContextMenuPosition,
|
||||||
|
setTarget
|
||||||
|
);
|
||||||
|
|
||||||
|
const children: JSX.Element[] = [];
|
||||||
|
|
||||||
|
if (target !== undefined) {
|
||||||
|
let count = 0;
|
||||||
|
for (const className of target.classList) {
|
||||||
|
count++;
|
||||||
|
const actions = props.actions.get(className);
|
||||||
|
if (actions === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.forEach((action, index) => {
|
||||||
|
children.push(<MenuItem
|
||||||
|
key={`contextmenu-item-${count}`}
|
||||||
|
className="contextmenu-item"
|
||||||
|
text={action.text}
|
||||||
|
onClick={() => action.action(target)} />);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Fix css
|
||||||
|
const visible = isOpen ? 'visible opacity-1' : 'invisible opacity-0';
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`fixed ${props.className ?? ''} ${visible}`}
|
className={`fixed ${props.className ?? ''} ${visible}`}
|
||||||
style={{
|
style={{
|
||||||
left: props.x,
|
left: contextMenuPosition.x,
|
||||||
top: props.y
|
top: contextMenuPosition.y
|
||||||
}}>
|
}}>
|
||||||
{props.children}
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Interweave, Node } from 'interweave';
|
import { Interweave, Node } from 'interweave';
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { DIMENSION_MARGIN, SHOW_CHILDREN_DIMENSIONS, SHOW_PARENT_DIMENSION, SHOW_TEXT } from '../../../utils/default';
|
import { DIMENSION_MARGIN, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS, SHOW_TEXT } from '../../../utils/default';
|
||||||
import { GetDepth } from '../../../utils/itertools';
|
import { CancelParentTransform, GetDepth, MakeIterator, Pairwise } from '../../../utils/itertools';
|
||||||
import { Dimension } from './Dimension';
|
import { Dimension } from './Dimension';
|
||||||
import { IContainerProperties } from '../../../Interfaces/IContainerProperties';
|
import { IContainerProperties } from '../../../Interfaces/IContainerProperties';
|
||||||
import { TransformX } from '../../../utils/svg';
|
import { RestoreX, TransformX } from '../../../utils/svg';
|
||||||
import { Camelize } from '../../../utils/stringtools';
|
import { Camelize } from '../../../utils/stringtools';
|
||||||
|
|
||||||
interface IContainerProps {
|
interface IContainerProps {
|
||||||
|
@ -59,14 +59,14 @@ export function Container(props: IContainerProps): JSX.Element {
|
||||||
const xEnd = width;
|
const xEnd = width;
|
||||||
const yDim = -dimensionMargin;
|
const yDim = -dimensionMargin;
|
||||||
const strokeWidth = 1;
|
const strokeWidth = 1;
|
||||||
const text = (width ?? 0).toString();
|
const text = (width.toFixed(2) ?? 0).toString();
|
||||||
|
|
||||||
let dimensionChildren: JSX.Element | null = null;
|
let childrenDimensions: JSX.Element | null = null;
|
||||||
if (props.model.children.length > 1 && SHOW_CHILDREN_DIMENSIONS) {
|
if (props.model.properties.showChildrenDimensions && props.model.children.length > 1 && SHOW_CHILDREN_DIMENSIONS) {
|
||||||
const {
|
const {
|
||||||
childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren
|
childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren
|
||||||
} = GetChildrenDimensionProps(props, dimensionMargin);
|
} = GetChildrenDimensionProps(props, dimensionMargin);
|
||||||
dimensionChildren = <Dimension
|
childrenDimensions = <Dimension
|
||||||
id={childrenId}
|
id={childrenId}
|
||||||
xStart={xChildrenStart}
|
xStart={xChildrenStart}
|
||||||
xEnd={xChildrenEnd}
|
xEnd={xChildrenEnd}
|
||||||
|
@ -76,13 +76,51 @@ export function Container(props: IContainerProps): JSX.Element {
|
||||||
text={textChildren} />;
|
text={textChildren} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const borrowerDimensions: JSX.Element[] = [];
|
||||||
|
if (props.model.properties.isDimensionBorrower && SHOW_BORROWER_DIMENSIONS) {
|
||||||
|
const it = MakeIterator(props.model);
|
||||||
|
const marks = [];
|
||||||
|
for (const container of it) {
|
||||||
|
if (!container.properties.markPositionToDimensionBorrower) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = TransformX(
|
||||||
|
container.properties.x,
|
||||||
|
container.properties.width,
|
||||||
|
container.properties.xPositionReference
|
||||||
|
);
|
||||||
|
const [restoredX] = CancelParentTransform(container.parent, x, 0, props.model);
|
||||||
|
marks.push(
|
||||||
|
restoredX
|
||||||
|
);
|
||||||
|
}
|
||||||
|
marks.push(0);
|
||||||
|
marks.push(props.model.properties.width);
|
||||||
|
marks.sort((a, b) => a - b);
|
||||||
|
let count = 0;
|
||||||
|
for (const { cur, next } of Pairwise(marks)) {
|
||||||
|
const id = `dim-borrow-${props.model.properties.id}-{${count}}`;
|
||||||
|
borrowerDimensions.push(<Dimension
|
||||||
|
key={id}
|
||||||
|
id={id}
|
||||||
|
xStart={cur}
|
||||||
|
xEnd={next}
|
||||||
|
yStart={props.model.properties.height + yDim * -1}
|
||||||
|
yEnd={props.model.properties.height + yDim * -1}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
text={(next - cur).toFixed(2).toString()} />);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g
|
<g
|
||||||
style={defaultStyle}
|
style={defaultStyle}
|
||||||
transform={transform}
|
transform={transform}
|
||||||
key={`container-${props.model.properties.id}`}
|
key={`container-${props.model.properties.id}`}
|
||||||
>
|
>
|
||||||
{SHOW_PARENT_DIMENSION
|
{(props.model.properties.showSelfDimensions && SHOW_SELF_DIMENSIONS)
|
||||||
? <Dimension
|
? <Dimension
|
||||||
id={id}
|
id={id}
|
||||||
xStart={xStart}
|
xStart={xStart}
|
||||||
|
@ -92,7 +130,8 @@ export function Container(props: IContainerProps): JSX.Element {
|
||||||
strokeWidth={strokeWidth}
|
strokeWidth={strokeWidth}
|
||||||
text={text} />
|
text={text} />
|
||||||
: null}
|
: null}
|
||||||
{dimensionChildren}
|
{childrenDimensions}
|
||||||
|
{borrowerDimensions}
|
||||||
{svg}
|
{svg}
|
||||||
{SHOW_TEXT
|
{SHOW_TEXT
|
||||||
? <text
|
? <text
|
||||||
|
@ -128,7 +167,9 @@ function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: numb
|
||||||
}
|
}
|
||||||
|
|
||||||
const yChildren = props.model.properties.height + dimensionMargin;
|
const yChildren = props.model.properties.height + dimensionMargin;
|
||||||
const textChildren = (xChildrenEnd - xChildrenStart).toString();
|
const textChildren = (xChildrenEnd - xChildrenStart)
|
||||||
|
.toFixed(2)
|
||||||
|
.toString();
|
||||||
return { childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren };
|
return { childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,9 @@ function AddNewDimension(currentDepth: number, min: number, max: number, lastY:
|
||||||
const y = lastY + (DIMENSION_MARGIN * (currentDepth + 1));
|
const y = lastY + (DIMENSION_MARGIN * (currentDepth + 1));
|
||||||
const strokeWidth = 1;
|
const strokeWidth = 1;
|
||||||
const width = xEnd - xStart;
|
const width = xEnd - xStart;
|
||||||
const text = width.toString();
|
const text = width
|
||||||
|
.toFixed(2)
|
||||||
|
.toString();
|
||||||
|
|
||||||
if (width === 0) {
|
if (width === 0) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -18,7 +18,9 @@ function GetDimensionsNodes(root: ContainerModel): React.ReactNode[] {
|
||||||
const xEnd = xStart + width;
|
const xEnd = xStart + width;
|
||||||
const y = (container.properties.y + container.properties.height) + (DIMENSION_MARGIN * (depth + 1));
|
const y = (container.properties.y + container.properties.height) + (DIMENSION_MARGIN * (depth + 1));
|
||||||
const strokeWidth = 1;
|
const strokeWidth = 1;
|
||||||
const text = width.toString();
|
const text = width
|
||||||
|
.toFixed(2)
|
||||||
|
.toString();
|
||||||
dimensions.push(
|
dimensions.push(
|
||||||
<Dimension
|
<Dimension
|
||||||
key={id}
|
key={id}
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
import { RefObject, Dispatch, SetStateAction, useEffect } from 'react';
|
|
||||||
import { IPoint } from '../../Interfaces/IPoint';
|
|
||||||
|
|
||||||
export function UseMouseEvents(
|
|
||||||
isContextMenuOpen: boolean,
|
|
||||||
elementRef: RefObject<HTMLDivElement>,
|
|
||||||
setIsContextMenuOpen: Dispatch<SetStateAction<boolean>>,
|
|
||||||
setOnClickSymbolId: Dispatch<SetStateAction<string>>,
|
|
||||||
setContextMenuPosition: Dispatch<SetStateAction<IPoint>>
|
|
||||||
): void {
|
|
||||||
useEffect(() => {
|
|
||||||
function OnContextMenu(event: MouseEvent): void {
|
|
||||||
return HandleRightClick(
|
|
||||||
event,
|
|
||||||
setIsContextMenuOpen,
|
|
||||||
setOnClickSymbolId,
|
|
||||||
setContextMenuPosition
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function OnLeftClick(): void {
|
|
||||||
return HandleLeftClick(
|
|
||||||
isContextMenuOpen,
|
|
||||||
setIsContextMenuOpen,
|
|
||||||
setOnClickSymbolId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
elementRef.current?.addEventListener(
|
|
||||||
'contextmenu',
|
|
||||||
OnContextMenu
|
|
||||||
);
|
|
||||||
|
|
||||||
window.addEventListener(
|
|
||||||
'click',
|
|
||||||
OnLeftClick
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
elementRef.current?.removeEventListener(
|
|
||||||
'contextmenu',
|
|
||||||
OnContextMenu
|
|
||||||
);
|
|
||||||
|
|
||||||
window.removeEventListener(
|
|
||||||
'click',
|
|
||||||
OnLeftClick
|
|
||||||
);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function HandleRightClick(
|
|
||||||
event: MouseEvent,
|
|
||||||
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
|
||||||
setOnClickSymbolId: React.Dispatch<React.SetStateAction<string>>,
|
|
||||||
setContextMenuPosition: React.Dispatch<React.SetStateAction<IPoint>>
|
|
||||||
): void {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (!(event.target instanceof HTMLButtonElement)) {
|
|
||||||
setIsContextMenuOpen(false);
|
|
||||||
setOnClickSymbolId('');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const contextMenuPosition: IPoint = { x: event.pageX, y: event.pageY };
|
|
||||||
setIsContextMenuOpen(true);
|
|
||||||
setOnClickSymbolId(event.target.id);
|
|
||||||
setContextMenuPosition(contextMenuPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function HandleLeftClick(
|
|
||||||
isContextMenuOpen: boolean,
|
|
||||||
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
|
||||||
setOnClickContainerId: React.Dispatch<React.SetStateAction<string>>
|
|
||||||
): void {
|
|
||||||
if (!isContextMenuOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsContextMenuOpen(false);
|
|
||||||
setOnClickContainerId('');
|
|
||||||
}
|
|
|
@ -1,9 +1,5 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { FixedSizeList as List } from 'react-window';
|
import { FixedSizeList as List } from 'react-window';
|
||||||
import { Menu } from '../Menu/Menu';
|
|
||||||
import { MenuItem } from '../Menu/MenuItem';
|
|
||||||
import { UseMouseEvents } from './MouseEventHandlers';
|
|
||||||
import { IPoint } from '../../Interfaces/IPoint';
|
|
||||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import { SymbolProperties } from '../SymbolProperties/SymbolProperties';
|
import { SymbolProperties } from '../SymbolProperties/SymbolProperties';
|
||||||
|
|
||||||
|
@ -14,29 +10,9 @@ interface ISymbolsSidebarProps {
|
||||||
isHistoryOpen: boolean
|
isHistoryOpen: boolean
|
||||||
onPropertyChange: (key: string, value: string | number | boolean) => void
|
onPropertyChange: (key: string, value: string | number | boolean) => void
|
||||||
selectSymbol: (symbolId: string) => void
|
selectSymbol: (symbolId: string) => void
|
||||||
deleteSymbol: (containerid: string) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SymbolsSidebar(props: ISymbolsSidebarProps): JSX.Element {
|
export function SymbolsSidebar(props: ISymbolsSidebarProps): JSX.Element {
|
||||||
// States
|
|
||||||
const [isContextMenuOpen, setIsContextMenuOpen] = React.useState<boolean>(false);
|
|
||||||
const [onClickSymbolId, setOnClickSymbolId] = React.useState<string>('');
|
|
||||||
const [contextMenuPosition, setContextMenuPosition] = React.useState<IPoint>({
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
const elementRef = React.useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
// Event listeners
|
|
||||||
UseMouseEvents(
|
|
||||||
isContextMenuOpen,
|
|
||||||
elementRef,
|
|
||||||
setIsContextMenuOpen,
|
|
||||||
setOnClickSymbolId,
|
|
||||||
setContextMenuPosition
|
|
||||||
);
|
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
let isOpenClasses = '-right-64';
|
let isOpenClasses = '-right-64';
|
||||||
if (props.isOpen) {
|
if (props.isOpen) {
|
||||||
|
@ -57,7 +33,7 @@ export function SymbolsSidebar(props: ISymbolsSidebarProps): JSX.Element {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button type="button"
|
<button type="button"
|
||||||
className={`w-full border-blue-500 elements-sidebar-row whitespace-pre
|
className={`w-full border-blue-500 symbols-sidebar-row whitespace-pre
|
||||||
text-left text-sm font-medium transition-all ${selectedClass}`}
|
text-left text-sm font-medium transition-all ${selectedClass}`}
|
||||||
id={key}
|
id={key}
|
||||||
key={key}
|
key={key}
|
||||||
|
@ -74,7 +50,7 @@ export function SymbolsSidebar(props: ISymbolsSidebarProps): JSX.Element {
|
||||||
<div className='bg-slate-100 font-bold sidebar-title'>
|
<div className='bg-slate-100 font-bold sidebar-title'>
|
||||||
Elements
|
Elements
|
||||||
</div>
|
</div>
|
||||||
<div ref={elementRef} className='h-96 text-gray-800'>
|
<div className='h-96 text-gray-800'>
|
||||||
<List
|
<List
|
||||||
className='List divide-y divide-black'
|
className='List divide-y divide-black'
|
||||||
itemCount={containers.length}
|
itemCount={containers.length}
|
||||||
|
@ -85,17 +61,6 @@ export function SymbolsSidebar(props: ISymbolsSidebarProps): JSX.Element {
|
||||||
{Row}
|
{Row}
|
||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
<Menu
|
|
||||||
className='transition-opacity rounded bg-slate-200 py-1 drop-shadow-xl'
|
|
||||||
x={contextMenuPosition.x}
|
|
||||||
y={contextMenuPosition.y}
|
|
||||||
isOpen={isContextMenuOpen}
|
|
||||||
>
|
|
||||||
<MenuItem className='contextmenu-item' text='Delete' onClick={() => {
|
|
||||||
setIsContextMenuOpen(false);
|
|
||||||
props.deleteSymbol(onClickSymbolId);
|
|
||||||
} } />
|
|
||||||
</Menu>
|
|
||||||
<SymbolProperties
|
<SymbolProperties
|
||||||
symbol={props.symbols.get(props.selectedSymbolId)}
|
symbol={props.symbols.get(props.selectedSymbolId)}
|
||||||
symbols={props.symbols}
|
symbols={props.symbols}
|
||||||
|
|
|
@ -87,7 +87,7 @@ export function UI(props: IUIProps): JSX.Element {
|
||||||
isHistoryOpen={isHistoryOpen}
|
isHistoryOpen={isHistoryOpen}
|
||||||
onPropertyChange={props.onPropertyChange}
|
onPropertyChange={props.onPropertyChange}
|
||||||
selectContainer={props.selectContainer}
|
selectContainer={props.selectContainer}
|
||||||
deleteContainer={props.deleteContainer} />
|
/>
|
||||||
<SymbolsSidebar
|
<SymbolsSidebar
|
||||||
selectedSymbolId={props.current.selectedSymbolId}
|
selectedSymbolId={props.current.selectedSymbolId}
|
||||||
symbols={props.current.symbols}
|
symbols={props.current.symbols}
|
||||||
|
@ -95,7 +95,7 @@ export function UI(props: IUIProps): JSX.Element {
|
||||||
isHistoryOpen={isHistoryOpen}
|
isHistoryOpen={isHistoryOpen}
|
||||||
onPropertyChange={props.onSymbolPropertyChange}
|
onPropertyChange={props.onSymbolPropertyChange}
|
||||||
selectSymbol={props.selectSymbol}
|
selectSymbol={props.selectSymbol}
|
||||||
deleteSymbol={props.deleteSymbol} />
|
/>
|
||||||
<History
|
<History
|
||||||
history={props.history}
|
history={props.history}
|
||||||
historyCurrentStep={props.historyCurrentStep}
|
historyCurrentStep={props.historyCurrentStep}
|
||||||
|
|
|
@ -5,61 +5,48 @@ import { IEditorState } from '../Interfaces/IEditorState';
|
||||||
import { IHistoryState } from '../Interfaces/IHistoryState';
|
import { IHistoryState } from '../Interfaces/IHistoryState';
|
||||||
import { ReviveState } from '../utils/saveload';
|
import { ReviveState } from '../utils/saveload';
|
||||||
|
|
||||||
|
function InitEditor(configuration: IConfiguration): void {
|
||||||
const initEditor = (configuration: IConfiguration): void => {
|
|
||||||
// faire comme la callback de fetch
|
// faire comme la callback de fetch
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getEditorState = (
|
function GetEditorState(root: Element | Document,
|
||||||
root: Element | Document,
|
editorState: IEditorState): void {
|
||||||
editorState: IEditorState
|
|
||||||
): void => {
|
|
||||||
const customEvent = new CustomEvent<IEditorState>('getEditorState', { detail: editorState });
|
const customEvent = new CustomEvent<IEditorState>('getEditorState', { detail: editorState });
|
||||||
root.dispatchEvent(customEvent);
|
root.dispatchEvent(customEvent);
|
||||||
};
|
}
|
||||||
|
|
||||||
const getCurrentHistoryState = (
|
function GetCurrentHistoryState(root: Element | Document,
|
||||||
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: editorState.history[editorState.historyCurrentStep] });
|
||||||
root.dispatchEvent(customEvent);
|
root.dispatchEvent(customEvent);
|
||||||
};
|
}
|
||||||
|
|
||||||
const appendNewState = (
|
function AppendNewState(root: Element | Document,
|
||||||
root: Element | Document,
|
|
||||||
editorState: IEditorState,
|
editorState: IEditorState,
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setNewHistory: (newHistory: IHistoryState[]) => void,
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>,
|
eventInitDict?: CustomEventInit): void {
|
||||||
eventInitDict?: CustomEventInit
|
|
||||||
): void => {
|
|
||||||
const state: IHistoryState = JSON.parse(eventInitDict?.detail.state);
|
const state: IHistoryState = JSON.parse(eventInitDict?.detail.state);
|
||||||
ReviveState(state);
|
ReviveState(state);
|
||||||
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
|
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
|
||||||
|
|
||||||
history.push(state);
|
history.push(state);
|
||||||
setHistory(history);
|
setNewHistory(history);
|
||||||
setHistoryCurrentStep(history.length - 1);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export interface IEditorEvent {
|
export interface IEditorEvent {
|
||||||
name: string
|
name: string
|
||||||
func: (
|
func: (
|
||||||
root: Element | Document,
|
root: Element | Document,
|
||||||
editorState: IEditorState,
|
editorState: IEditorState,
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setNewHistory: (newHistory: IHistoryState[]) => void,
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>,
|
|
||||||
eventInitDict?: CustomEventInit
|
eventInitDict?: CustomEventInit
|
||||||
) => void
|
) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
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 }
|
||||||
];
|
];
|
||||||
|
|
||||||
export default events;
|
|
||||||
|
|
12
src/Interfaces/IAction.ts
Normal file
12
src/Interfaces/IAction.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
import { AddMethod } from '../Enums/AddMethod';
|
||||||
|
import { IImage } from './IImage';
|
||||||
|
|
||||||
|
export interface IAction {
|
||||||
|
Id: string
|
||||||
|
CustomLogo: IImage
|
||||||
|
Label: string
|
||||||
|
Description: string
|
||||||
|
Action: string
|
||||||
|
AddingBehavior: AddMethod
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AddMethod } from '../Enums/AddMethod';
|
import { AddMethod } from '../Enums/AddMethod';
|
||||||
import { XPositionReference } from '../Enums/XPositionReference';
|
import { XPositionReference } from '../Enums/XPositionReference';
|
||||||
|
import { IAction } from './IAction';
|
||||||
import { IMargin } from './IMargin';
|
import { IMargin } from './IMargin';
|
||||||
|
|
||||||
/** Model of available container used in application configuration */
|
/** Model of available container used in application configuration */
|
||||||
|
@ -14,11 +15,29 @@ export interface IAvailableContainer {
|
||||||
MinWidth?: number
|
MinWidth?: number
|
||||||
MaxWidth?: number
|
MaxWidth?: number
|
||||||
Margin?: IMargin
|
Margin?: IMargin
|
||||||
|
IsAnchor?: boolean
|
||||||
IsFlex?: boolean
|
IsFlex?: boolean
|
||||||
AddMethod?: AddMethod
|
AddMethod?: AddMethod
|
||||||
XPositionReference?: XPositionReference
|
XPositionReference?: XPositionReference
|
||||||
CustomSVG?: string
|
CustomSVG?: string
|
||||||
DefaultChildType?: string
|
DefaultChildType?: 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
|
||||||
Style?: React.CSSProperties
|
Style?: React.CSSProperties
|
||||||
|
Actions?: IAction[]
|
||||||
UserData?: object
|
UserData?: object
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,24 @@ export interface IContainerProperties {
|
||||||
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
||||||
xPositionReference: XPositionReference
|
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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (optional)
|
* (optional)
|
||||||
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
|
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
|
||||||
|
|
14
src/Interfaces/ISetContainerListRequest.ts
Normal file
14
src/Interfaces/ISetContainerListRequest.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { IContainerModel } from './IContainerModel';
|
||||||
|
import { IHistoryState } from './IHistoryState';
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
export interface ISetContainerListRequest {
|
||||||
|
/** Name of the action declared in the API */
|
||||||
|
Action: string
|
||||||
|
|
||||||
|
/** Selected container */
|
||||||
|
Container: IContainerModel
|
||||||
|
|
||||||
|
/** Current application state */
|
||||||
|
ApplicationState: IHistoryState
|
||||||
|
}
|
6
src/Interfaces/ISetContainerListResponse.ts
Normal file
6
src/Interfaces/ISetContainerListResponse.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
import { IContainerModel } from './IContainerModel';
|
||||||
|
|
||||||
|
export interface ISetContainerListResponse {
|
||||||
|
Containers: IContainerModel[]
|
||||||
|
}
|
|
@ -19,6 +19,10 @@
|
||||||
@apply pl-6 pr-6 pt-2 pb-2 w-full
|
@apply pl-6 pr-6 pt-2 pb-2 w-full
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.symbols-sidebar-row {
|
||||||
|
@apply elements-sidebar-row
|
||||||
|
}
|
||||||
|
|
||||||
.close-button {
|
.close-button {
|
||||||
@apply transition-all w-full h-auto p-4 flex
|
@apply transition-all w-full h-auto p-4 flex
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,10 @@ export const DEFAULTCHILDTYPE_MAX_DEPTH = 10;
|
||||||
|
|
||||||
/// DIMENSIONS DEFAULTS ///
|
/// DIMENSIONS DEFAULTS ///
|
||||||
|
|
||||||
export const SHOW_PARENT_DIMENSION = true;
|
export const SHOW_SELF_DIMENSIONS = true;
|
||||||
export const SHOW_CHILDREN_DIMENSIONS = false;
|
export const SHOW_CHILDREN_DIMENSIONS = false;
|
||||||
export const SHOW_DIMENSIONS_PER_DEPTH = true;
|
export const SHOW_BORROWER_DIMENSIONS = true;
|
||||||
|
export const SHOW_DIMENSIONS_PER_DEPTH = false;
|
||||||
export const DIMENSION_MARGIN = 50;
|
export const DIMENSION_MARGIN = 50;
|
||||||
export const NOTCHES_LENGTH = 4;
|
export const NOTCHES_LENGTH = 4;
|
||||||
|
|
||||||
|
@ -51,7 +52,7 @@ export function GetDefaultEditorState(configuration: IConfiguration): IEditorSta
|
||||||
history: [
|
history: [
|
||||||
{
|
{
|
||||||
lastAction: '',
|
lastAction: '',
|
||||||
mainContainer: mainContainer,
|
mainContainer,
|
||||||
selectedContainerId: mainContainer.properties.id,
|
selectedContainerId: mainContainer.properties.id,
|
||||||
typeCounters: {},
|
typeCounters: {},
|
||||||
symbols: new Map(),
|
symbols: new Map(),
|
||||||
|
@ -110,6 +111,10 @@ export const DEFAULT_MAINCONTAINER_PROPS: IContainerProperties = {
|
||||||
isAnchor: false,
|
isAnchor: false,
|
||||||
isFlex: false,
|
isFlex: false,
|
||||||
xPositionReference: XPositionReference.Left,
|
xPositionReference: XPositionReference.Left,
|
||||||
|
showChildrenDimensions: true, // TODO: put the dimension at the top (see pdf)
|
||||||
|
showSelfDimensions: true, // TODO: put the dimension at the bottom (see pdf)
|
||||||
|
isDimensionBorrower: true, // second dimensions from the bottom
|
||||||
|
markPositionToDimensionBorrower: false,
|
||||||
style: {
|
style: {
|
||||||
stroke: 'black',
|
stroke: 'black',
|
||||||
fillOpacity: 0
|
fillOpacity: 0
|
||||||
|
@ -145,11 +150,15 @@ export function GetDefaultContainerProps(type: string,
|
||||||
margin: containerConfig.Margin ?? {},
|
margin: containerConfig.Margin ?? {},
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
isAnchor: false,
|
isAnchor: containerConfig.IsAnchor ?? false,
|
||||||
isFlex: containerConfig.IsFlex ?? containerConfig.Width === undefined,
|
isFlex: containerConfig.IsFlex ?? containerConfig.Width === undefined,
|
||||||
xPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
|
xPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
|
||||||
minWidth: containerConfig.MinWidth ?? 1,
|
minWidth: containerConfig.MinWidth ?? 1,
|
||||||
maxWidth: containerConfig.MaxWidth ?? Number.MAX_SAFE_INTEGER,
|
maxWidth: containerConfig.MaxWidth ?? Number.MAX_SAFE_INTEGER,
|
||||||
|
showChildrenDimensions: containerConfig.ShowChildrenDimensions ?? false,
|
||||||
|
showSelfDimensions: containerConfig.ShowSelfDimensions ?? false,
|
||||||
|
markPositionToDimensionBorrower: containerConfig.MarkPositionToDimensionBorrower ?? false,
|
||||||
|
isDimensionBorrower: containerConfig.IsDimensionBorrower ?? false,
|
||||||
customSVG: containerConfig.CustomSVG,
|
customSVG: containerConfig.CustomSVG,
|
||||||
style: structuredClone(containerConfig.Style),
|
style: structuredClone(containerConfig.Style),
|
||||||
userData: structuredClone(containerConfig.UserData)
|
userData: structuredClone(containerConfig.UserData)
|
||||||
|
|
|
@ -83,9 +83,14 @@ export function GetAbsolutePosition(container: IContainerModel): [number, number
|
||||||
* @param y value to be restored
|
* @param y value to be restored
|
||||||
* @returns x and y such that the transformations of the parent are cancelled
|
* @returns x and y such that the transformations of the parent are cancelled
|
||||||
*/
|
*/
|
||||||
export function CancelParentTransform(parent: IContainerModel | null, x: number, y: number): [number, number] {
|
export function CancelParentTransform(
|
||||||
|
parent: IContainerModel | null,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
stop?: IContainerModel
|
||||||
|
): [number, number] {
|
||||||
let current = parent;
|
let current = parent;
|
||||||
while (current != null) {
|
while (current !== stop && current != null) {
|
||||||
x += current.properties.x;
|
x += current.properties.x;
|
||||||
y += current.properties.y;
|
y += current.properties.y;
|
||||||
current = current.parent;
|
current = current.parent;
|
||||||
|
|
|
@ -50,7 +50,7 @@ export function GetCircularReplacer(): (key: any, value: object | Map<string, an
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'Symbols') {
|
if (key === 'symbols') {
|
||||||
return Array.from((value as Map<string, any>).entries());
|
return Array.from((value as Map<string, any>).entries());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
3
src/vite-env.d.ts
vendored
3
src/vite-env.d.ts
vendored
|
@ -1,7 +1,8 @@
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_API_URL: string
|
readonly VITE_API_FETCH_URL: string
|
||||||
|
readonly VITE_API_POST_URL: string
|
||||||
// more env variables...
|
// more env variables...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,9 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
stroke: 'red',
|
stroke: 'red',
|
||||||
fill: '#d3c9b7',
|
fill: '#d3c9b7',
|
||||||
}
|
},
|
||||||
|
ShowSelfDimensions: true,
|
||||||
|
IsDimensionBorrower: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: 'Trou',
|
Type: 'Trou',
|
||||||
|
@ -90,6 +92,17 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
<line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
|
<line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
|
||||||
`
|
`
|
||||||
,
|
,
|
||||||
|
Actions: [
|
||||||
|
{
|
||||||
|
Action: "SplitRemplissage",
|
||||||
|
Label: "Diviser le remplissage",
|
||||||
|
Description: "Diviser le remplissage en insérant un montant",
|
||||||
|
CustomLogo: {
|
||||||
|
Url: ""
|
||||||
|
},
|
||||||
|
AddingBehavior: 2
|
||||||
|
}
|
||||||
|
],
|
||||||
Style: {
|
Style: {
|
||||||
fillOpacity: 1,
|
fillOpacity: 1,
|
||||||
strokeWidth: 1,
|
strokeWidth: 1,
|
||||||
|
@ -106,6 +119,19 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
Type: 'Montant',
|
Type: 'Montant',
|
||||||
Width: 10,
|
Width: 10,
|
||||||
XPositionReference: 1,
|
XPositionReference: 1,
|
||||||
|
MarkPositionToDimensionBorrower: true,
|
||||||
|
Style: {
|
||||||
|
fillOpacity: 0,
|
||||||
|
strokeWidth: 2,
|
||||||
|
stroke: '#713f12',
|
||||||
|
fill: '#713f12',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: 'Dilatation',
|
||||||
|
Width: 4,
|
||||||
|
XPositionReference: 1,
|
||||||
|
MarkPositionToDimensionBorrower: true,
|
||||||
Style: {
|
Style: {
|
||||||
fillOpacity: 0,
|
fillOpacity: 0,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
|
@ -186,20 +212,30 @@ const FillHoleWithChassis = (request) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
ApplicationState: {
|
Containers: lstModels
|
||||||
Containers: lstModels
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const SplitRemplissage = (request) => {
|
const SplitRemplissage = (request) => {
|
||||||
const lstModels = [
|
const lstModels = [
|
||||||
{ Type: 'Remplissage' },
|
{
|
||||||
{ Type: 'Montant' },
|
properties: {
|
||||||
{ Type: 'Remplissage' }
|
type: 'Remplissage'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
type: 'Montant'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
type: 'Remplissage'
|
||||||
|
}
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ApplicationState: { Containers: lstModels }
|
Containers: lstModels
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue