Merged PR 179: Fix bugs about flex and context menu (see desc) + disable hard rigid behavior + add missing properties to form + Clean up css
- Clean up some css class - Fix wrong order when applying flex - Fix Replace behavior not working because previous container was still existing - Disable hard rigid behavior which disallow two container to overlap - Add ENABLE_FLEX, ENABLE_HARD_RIGID ENABLE_SWAP - Add missing form properties with dimensions - Update readme
This commit is contained in:
parent
353f461f4b
commit
443a15e150
16 changed files with 158 additions and 45 deletions
37
README.md
37
README.md
|
@ -13,8 +13,9 @@ An svg layout designer.
|
|||
Requierements :
|
||||
- NodeJS
|
||||
- npm
|
||||
- pnpm (optional but recommanded unless you prefer having a huge `node_modules` directory)
|
||||
- Chrome > 98
|
||||
- pnpm (optional but recommanded unless you prefer having a huge `node_modules` directory)
|
||||
- [`git-lfs`](https://git-lfs.github.com/) (in order to clone the documentation)
|
||||
|
||||
# Developping
|
||||
|
||||
|
@ -22,9 +23,6 @@ Run `npm ci`
|
|||
|
||||
Run `npm run dev`
|
||||
|
||||
|
||||
|
||||
|
||||
# Deploy
|
||||
|
||||
Run `npm ci`
|
||||
|
@ -73,3 +71,34 @@ bun run http.js
|
|||
The web server will be running at `http://localhost:5000`
|
||||
|
||||
Configure the file `.env.development` with the url
|
||||
|
||||
# Recommanded tools
|
||||
|
||||
- [VSCode](https://code.visualstudio.com/)
|
||||
- [React DevTools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi)
|
||||
- [vscode-tailwindcss](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)
|
||||
- [vscode-eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
|
||||
|
||||
# Setup debuggin with chrome
|
||||
|
||||
Inside `.vscode/settings.json`, set the following :
|
||||
|
||||
```json
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:5173",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Change the `url` to the dev server url. Set the `runtimeExecutable` to you favorite chromium browser.
|
|
@ -1,5 +1,4 @@
|
|||
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';
|
||||
|
@ -43,6 +42,7 @@ export async function SetContainerList(request: ISetContainerListRequest): Promi
|
|||
return await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: new Headers({
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'Content-Type': 'application/json'
|
||||
}),
|
||||
body: dataParsed
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import './App.scss';
|
||||
import { MainMenu } from '../MainMenu/MainMenu';
|
||||
import { ContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { Editor } from '../Editor/Editor';
|
||||
|
@ -78,7 +77,7 @@ export function App(props: IAppProps): JSX.Element {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className='bg-blue-100 h-full w-full'>
|
||||
<div className='mainmenu-bg'>
|
||||
<MainMenu
|
||||
newEditor={() => NewEditor(
|
||||
setEditorState, setLoaded
|
||||
|
|
|
@ -16,7 +16,7 @@ export const BAR_WIDTH = 64; // 4rem
|
|||
|
||||
export function Bar(props: IBarProps): JSX.Element {
|
||||
return (
|
||||
<div className='fixed z-20 flex flex-col top-0 left-0 h-full w-16 bg-slate-100'>
|
||||
<div className='bar'>
|
||||
<BarIcon
|
||||
isActive={props.isSidebarOpen}
|
||||
title='Components'
|
||||
|
|
|
@ -4,6 +4,7 @@ import { PropertyType } from '../../Enums/PropertyType';
|
|||
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
|
||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
import { SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS } from '../../utils/default';
|
||||
import { ApplyWidthMargin, ApplyXMargin, RemoveWidthMargin, RemoveXMargin, RestoreX, TransformX } from '../../utils/svg';
|
||||
import { InputGroup } from '../InputGroup/InputGroup';
|
||||
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
|
||||
|
@ -34,7 +35,7 @@ function GetCSSInputs(properties: IContainerProperties,
|
|||
|
||||
export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
||||
return (
|
||||
<div className='grid grid-cols-2 gap-y-4'>
|
||||
<div className='grid grid-cols-2 gap-y-4 items-center'>
|
||||
<InputGroup
|
||||
labelText='Name'
|
||||
inputKey='id'
|
||||
|
@ -228,6 +229,49 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
value={props.properties.linkedSymbolId ?? ''}
|
||||
onChange={(event) => props.onChange('linkedSymbolId', event.target.value)} />
|
||||
{GetCSSInputs(props.properties, props.onChange)}
|
||||
{
|
||||
SHOW_SELF_DIMENSIONS &&
|
||||
<InputGroup
|
||||
labelText='Show dimension'
|
||||
inputKey='showSelfDimensions'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='checkbox'
|
||||
checked={props.properties.showSelfDimensions}
|
||||
onChange={(event) => props.onChange('showSelfDimensions', event.target.checked)} />
|
||||
}
|
||||
{
|
||||
SHOW_CHILDREN_DIMENSIONS &&
|
||||
<InputGroup
|
||||
labelText='Show overall dimension of its children'
|
||||
inputKey='showChildrenDimensions'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='checkbox'
|
||||
checked={props.properties.showChildrenDimensions}
|
||||
onChange={(event) => props.onChange('showChildrenDimensions', event.target.checked)} />
|
||||
}
|
||||
{
|
||||
SHOW_BORROWER_DIMENSIONS &&
|
||||
<>
|
||||
<InputGroup
|
||||
labelText='Mark the position'
|
||||
inputKey='markPositionToDimensionBorrower'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='checkbox'
|
||||
checked={props.properties.markPositionToDimensionBorrower}
|
||||
onChange={(event) => props.onChange('markPositionToDimensionBorrower', event.target.checked)} />
|
||||
<InputGroup
|
||||
labelText='Show dimension with marked children'
|
||||
inputKey='isDimensionBorrower'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='checkbox'
|
||||
checked={props.properties.isDimensionBorrower}
|
||||
onChange={(event) => props.onChange('isDimensionBorrower', event.target.checked)} />
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -276,6 +276,9 @@ export function AddContainers(
|
|||
parentClone.children.splice(index, 0, newContainer);
|
||||
}
|
||||
|
||||
// Sort the parent children by x
|
||||
UpdateParentChildrenList(parentClone);
|
||||
|
||||
/// Handle behaviors here ///
|
||||
|
||||
// Initialize default children of the container
|
||||
|
@ -287,9 +290,6 @@ export function AddContainers(
|
|||
// Then, apply the behaviors on its siblings (mostly for flex)
|
||||
ApplyBehaviorsOnSiblingsChildren(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);
|
||||
});
|
||||
|
|
|
@ -105,27 +105,30 @@ function HandleReplace(
|
|||
const index = selectedContainer.parent.children.indexOf(selectedContainer);
|
||||
|
||||
const types = response.Containers.map(container => container.Type);
|
||||
const newHistoryBeforeDelete = AddContainers(
|
||||
index + 1,
|
||||
types,
|
||||
selectedContainer.properties.parentId,
|
||||
configuration,
|
||||
|
||||
const newHistoryAfterDelete = DeleteContainer(
|
||||
selectedContainer.properties.id,
|
||||
history,
|
||||
historyCurrentStep
|
||||
);
|
||||
|
||||
const newHistoryAfterDelete = DeleteContainer(
|
||||
selectedContainer.properties.id,
|
||||
newHistoryBeforeDelete,
|
||||
newHistoryBeforeDelete.length - 1
|
||||
const newHistoryBeforeDelete = AddContainers(
|
||||
index,
|
||||
types,
|
||||
selectedContainer.properties.parentId,
|
||||
configuration,
|
||||
newHistoryAfterDelete,
|
||||
newHistoryAfterDelete.length - 1
|
||||
);
|
||||
|
||||
// Remove AddContainers from history
|
||||
newHistoryAfterDelete.splice(newHistoryAfterDelete.length - 2, 1);
|
||||
if (import.meta.env.PROD) {
|
||||
newHistoryBeforeDelete.splice(newHistoryBeforeDelete.length - 2, 1);
|
||||
}
|
||||
|
||||
// Rename the last action by Replace
|
||||
newHistoryAfterDelete[newHistoryAfterDelete.length - 1].lastAction =
|
||||
newHistoryBeforeDelete[newHistoryBeforeDelete.length - 1].lastAction =
|
||||
`Replace ${selectedContainer.properties.id} by [${types.join(', ')}]`;
|
||||
|
||||
return newHistoryAfterDelete;
|
||||
return newHistoryBeforeDelete;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||
import { APPLY_BEHAVIORS_ON_CHILDREN } from '../../../utils/default';
|
||||
import { APPLY_BEHAVIORS_ON_CHILDREN, ENABLE_RIGID, ENABLE_SWAP } from '../../../utils/default';
|
||||
import { ApplyAnchor } from './AnchorBehaviors';
|
||||
import { Flex } from './FlexBehaviors';
|
||||
import { ApplyRigidBody } from './RigidBodyBehaviors';
|
||||
|
@ -23,11 +23,15 @@ export function ApplyBehaviors(container: IContainerModel, symbols: Map<string,
|
|||
ApplyAnchor(container);
|
||||
}
|
||||
|
||||
if (ENABLE_SWAP) {
|
||||
ApplySwap(container);
|
||||
}
|
||||
|
||||
Flex(container);
|
||||
|
||||
if (ENABLE_RIGID) {
|
||||
ApplyRigidBody(container);
|
||||
}
|
||||
|
||||
if (APPLY_BEHAVIORS_ON_CHILDREN) {
|
||||
// Apply DFS by recursion
|
||||
|
|
|
@ -38,6 +38,10 @@ function FlexGroup(flexibleGroup: IFlexibleGroup): void {
|
|||
nonFlexibleContainers
|
||||
} = SeparateFlexibleContainers(children);
|
||||
|
||||
if (flexibleContainers.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const minWidths = flexibleContainers
|
||||
.map(sibling => sibling.properties.minWidth);
|
||||
|
||||
|
@ -50,12 +54,8 @@ function FlexGroup(flexibleGroup: IFlexibleGroup): void {
|
|||
|
||||
const checkSumMinWidthsIsFitting = minimumPossibleWidth > requiredMaxWidth;
|
||||
if (checkSumMinWidthsIsFitting) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Cannot fit!',
|
||||
text: 'Cannot fit at all even when squeezing all flex containers to the minimum.'
|
||||
});
|
||||
throw new Error('[FlexBehavior] Cannot fit at all even when squeezing all flex containers to the minimum.');
|
||||
console.warn('[FlexBehavior] Cannot fit at all even when squeezing all flex containers to the minimum.');
|
||||
return;
|
||||
}
|
||||
|
||||
const maxMinWidths = Math.max(...minWidths);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import Swal from 'sweetalert2';
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { ISizePointer } from '../../../Interfaces/ISizePointer';
|
||||
import { ENABLE_HARD_RIGID } from '../../../utils/default';
|
||||
|
||||
/**
|
||||
* "Transform the container into a rigid body"
|
||||
|
@ -23,7 +24,11 @@ export function ApplyRigidBody(
|
|||
container: IContainerModel
|
||||
): IContainerModel {
|
||||
container = ConstraintBodyInsideParent(container);
|
||||
|
||||
if (ENABLE_HARD_RIGID) {
|
||||
container = ConstraintBodyInsideUnallocatedWidth(container);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,12 +17,7 @@ interface IInputGroupProps {
|
|||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
}
|
||||
|
||||
const className = `
|
||||
w-full
|
||||
text-xs font-medium transition-all text-gray-800 mt-1 px-3 py-2
|
||||
bg-white border-2 border-white rounded-lg placeholder-gray-800
|
||||
focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500
|
||||
disabled:bg-slate-300 disabled:text-gray-500 disabled:border-slate-300 disabled:shadow-none`;
|
||||
const className = 'input-group';
|
||||
|
||||
export function InputGroup(props: IInputGroupProps): JSX.Element {
|
||||
return <>
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
.elements-sidebar-row {
|
||||
@apply pl-6 pr-6 pt-2 pb-2 w-full
|
||||
}
|
||||
|
||||
.symbols-sidebar-row {
|
||||
@apply elements-sidebar-row
|
||||
}
|
||||
|
@ -27,6 +26,10 @@
|
|||
@apply transition-all w-full h-auto p-4 flex
|
||||
}
|
||||
|
||||
.mainmenu-bg {
|
||||
@apply bg-blue-100 h-full w-full
|
||||
}
|
||||
|
||||
.mainmenu-btn {
|
||||
@apply transition-all bg-blue-100 hover:bg-blue-200 text-blue-700 text-lg font-semibold p-8 rounded-lg
|
||||
}
|
||||
|
@ -42,7 +45,13 @@
|
|||
}
|
||||
|
||||
.floating-btn {
|
||||
@apply h-full w-full text-white align-middle items-center justify-center
|
||||
@apply h-full w-full text-white align-middle
|
||||
items-center justify-center
|
||||
}
|
||||
|
||||
.bar {
|
||||
@apply fixed z-20 flex flex-col top-0 left-0
|
||||
h-full w-16 bg-slate-100
|
||||
}
|
||||
|
||||
.bar-btn {
|
||||
|
@ -64,10 +73,18 @@
|
|||
text-gray-800 bg-slate-100
|
||||
dark:text-white dark:bg-gray-800
|
||||
text-xs font-bold
|
||||
transition-all duration-100 scale-0 origin-left;
|
||||
transition-all duration-100 scale-0 origin-left
|
||||
}
|
||||
|
||||
.contextmenu-item {
|
||||
@apply px-2 py-1 hover:bg-slate-300 text-left
|
||||
}
|
||||
|
||||
.input-group {
|
||||
@apply w-full
|
||||
text-xs font-medium transition-all text-gray-800 mt-1 px-3 py-2
|
||||
bg-white border-2 border-white rounded-lg placeholder-gray-800
|
||||
focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500
|
||||
disabled:bg-slate-300 disabled:text-gray-500 disabled:border-slate-300 disabled:shadow-none;
|
||||
}
|
||||
}
|
|
@ -11,7 +11,10 @@ function RenderRoot(root: Element | Document): void {
|
|||
);
|
||||
}
|
||||
|
||||
// Specific for Modeler apps
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace SVGLayoutDesigner {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const Render = RenderRoot;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,19 @@ import { ISymbolModel } from '../Interfaces/ISymbolModel';
|
|||
|
||||
/// CONTAINER DEFAULTS ///
|
||||
|
||||
/** Enable the swap behavior */
|
||||
export const ENABLE_SWAP = false;
|
||||
|
||||
/** Enable the rigid behavior */
|
||||
export const ENABLE_RIGID = true;
|
||||
|
||||
/**
|
||||
* Enable the hard rigid behavior
|
||||
* disallowing the container to overlap (ENABLE_RIGID must be true)
|
||||
*/
|
||||
export const ENABLE_HARD_RIGID = false;
|
||||
|
||||
/** Enalbe the text in the containers */
|
||||
export const SHOW_TEXT = false;
|
||||
export const SHOW_SELECTOR_TEXT = true;
|
||||
export const DEFAULTCHILDTYPE_ALLOW_CYCLIC = false;
|
||||
|
|
|
@ -55,6 +55,7 @@ const GetSVGLayoutConfiguration = () => {
|
|||
Type: 'Chassis',
|
||||
MaxWidth: 500,
|
||||
MinWidth: 200,
|
||||
Width: 200,
|
||||
DefaultChildType: 'Trou',
|
||||
Style: {
|
||||
fillOpacity: 1,
|
||||
|
@ -63,7 +64,7 @@ const GetSVGLayoutConfiguration = () => {
|
|||
fill: '#d3c9b7',
|
||||
},
|
||||
ShowSelfDimensions: true,
|
||||
IsDimensionBorrower: true
|
||||
IsDimensionBorrower: true,
|
||||
},
|
||||
{
|
||||
Type: 'Trou',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue