Compare commits
No commits in common. "master" and "dev.tests" have entirely different histories.
266 changed files with 5915 additions and 24840 deletions
17
.drone.yml
17
.drone.yml
|
@ -6,9 +6,11 @@ steps:
|
||||||
- name: test
|
- name: test
|
||||||
image: node:16
|
image: node:16
|
||||||
commands:
|
commands:
|
||||||
- node ./test-server/http.js &
|
- curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7
|
||||||
- npm i
|
- node ./test-server/node-http.js &
|
||||||
- npm run build
|
- pnpm install
|
||||||
|
- pnpm run test:nowatch
|
||||||
|
- pnpm run build
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
|
@ -18,7 +20,8 @@ steps:
|
||||||
- name: test
|
- name: test
|
||||||
image: node
|
image: node
|
||||||
commands:
|
commands:
|
||||||
- node ./test-server/http.js &
|
- curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7
|
||||||
- npm i
|
- node ./test-server/node-http.js &
|
||||||
- npm run test:nowatch
|
- pnpm install
|
||||||
- npm run build
|
- pnpm run test:nowatch
|
||||||
|
- pnpm run build
|
|
@ -1,3 +1 @@
|
||||||
VITE_API_FETCH_URL=http://localhost:5000
|
VITE_API_URL=http://localhost:5000
|
||||||
VITE_API_SET_CONTAINER_LIST_URL=http://localhost:5000/SetContainerList
|
|
||||||
VITE_API_GET_FEEDBACK_URL=http://localhost:5000/GetFeedback
|
|
|
@ -1,3 +1,2 @@
|
||||||
VITE_API_FETCH_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/GetSVGLayoutConfiguration
|
|
||||||
VITE_API_SET_CONTAINER_LIST_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/SetContainerList
|
VITE_API_URL=https://localhost/SmartMenuiserieTemplate
|
||||||
VITE_API_GET_FEEDBACK_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/GetFeedback
|
|
|
@ -1,3 +1 @@
|
||||||
VITE_API_FETCH_URL=http://localhost:5000
|
VITE_API_URL=http://localhost:5000
|
||||||
VITE_API_SET_CONTAINER_LIST_URL=http://localhost:5000/SetContainerList
|
|
||||||
VITE_API_GET_FEEDBACK_URL=http://localhost:5000/GetFeedback
|
|
|
@ -1,7 +0,0 @@
|
||||||
**/*.md
|
|
||||||
|
|
||||||
/csharp
|
|
||||||
/dist
|
|
||||||
/docs
|
|
||||||
/public
|
|
||||||
/src/dts
|
|
|
@ -1,9 +1,4 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: "detect"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
es2021: true
|
es2021: true
|
||||||
|
@ -19,47 +14,19 @@ module.exports = {
|
||||||
},
|
},
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
project: './tsconfig.json',
|
project: './tsconfig.json'
|
||||||
tsconfigRootDir: __dirname
|
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
'only-warn',
|
|
||||||
'react',
|
'react',
|
||||||
'react-hooks',
|
|
||||||
'@typescript-eslint'
|
'@typescript-eslint'
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'prefer-arrow-callback': 'error',
|
|
||||||
'func-style': ['error', 'declaration'],
|
|
||||||
'space-before-function-paren': ['error', 'never'],
|
'space-before-function-paren': ['error', 'never'],
|
||||||
'max-len': ['error', { 'code': 120 }],
|
|
||||||
'max-depth': ['error', 4],
|
|
||||||
'function-paren-newline': ['error', 'multiline-arguments'],
|
|
||||||
'multiline-ternary': ['error', 'always'],
|
|
||||||
'prefer-template': "error",
|
|
||||||
|
|
||||||
// Import/export
|
|
||||||
'import/no-default-export': 'error',
|
|
||||||
'import/no-duplicates': 'error',
|
|
||||||
'import/newline-after-import': 'error',
|
|
||||||
'import/order': 'error',
|
|
||||||
|
|
||||||
// Typescript overload
|
|
||||||
indent: 'off',
|
|
||||||
semi: 'off',
|
|
||||||
"camelcase": "off",
|
|
||||||
'no-unused-vars': 'off',
|
|
||||||
|
|
||||||
// Typescript
|
|
||||||
'@typescript-eslint/space-before-function-paren': ['error', 'never'],
|
'@typescript-eslint/space-before-function-paren': ['error', 'never'],
|
||||||
'@typescript-eslint/indent': ['warn', 2, {SwitchCase: 1}],
|
indent: ['warn', 2, { SwitchCase: 1 }],
|
||||||
|
semi: 'off',
|
||||||
'@typescript-eslint/semi': ['warn', 'always'],
|
'@typescript-eslint/semi': ['warn', 'always'],
|
||||||
|
'no-unused-vars': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': 'error',
|
'@typescript-eslint/no-unused-vars': 'error',
|
||||||
'@typescript-eslint/ban-types': ['error'],
|
|
||||||
'@typescript-eslint/no-floating-promises': 'off', // disabled cuz troublesome for SweetAlert since they never reject
|
|
||||||
|
|
||||||
// React
|
|
||||||
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
|
|
||||||
'react-hooks/exhaustive-deps': 'warn' // Checks effect dependencies
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
5
.gitattributes
vendored
5
.gitattributes
vendored
|
@ -1,5 +0,0 @@
|
||||||
*.drawio filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.pdf filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.dwg filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.pptx filter=lfs diff=lfs merge=lfs -text
|
|
407
.gitignore
vendored
407
.gitignore
vendored
|
@ -20,410 +20,5 @@ dist-ssr
|
||||||
*.suo
|
*.suo
|
||||||
*.ntvs*
|
*.ntvs*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
# Created by https://www.toptal.com/developers/gitignore/api/visualstudio
|
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio
|
|
||||||
|
|
||||||
### VisualStudio ###
|
|
||||||
## Ignore Visual Studio temporary files, build results, and
|
|
||||||
## files generated by popular Visual Studio add-ons.
|
|
||||||
##
|
|
||||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.rsuser
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.userosscache
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
||||||
*.userprefs
|
|
||||||
|
|
||||||
# Mono auto generated files
|
|
||||||
mono_crash.*
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
[Ww][Ii][Nn]32/
|
|
||||||
[Aa][Rr][Mm]/
|
|
||||||
[Aa][Rr][Mm]64/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
[Ll]og/
|
|
||||||
[Ll]ogs/
|
|
||||||
|
|
||||||
# Visual Studio 2015/2017 cache/options directory
|
|
||||||
.vs/
|
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# Visual Studio 2017 auto generated files
|
|
||||||
Generated\ Files/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUnit
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
nunit-*.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# Benchmark Results
|
|
||||||
BenchmarkDotNet.Artifacts/
|
|
||||||
|
|
||||||
# .NET Core
|
|
||||||
project.lock.json
|
|
||||||
project.fragment.lock.json
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
# ASP.NET Scaffolding
|
|
||||||
ScaffoldingReadMe.txt
|
|
||||||
|
|
||||||
# StyleCop
|
|
||||||
StyleCopReport.xml
|
|
||||||
|
|
||||||
# Files built by Visual Studio
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_h.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.iobj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.ipdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*_wpftmp.csproj
|
|
||||||
*.log
|
|
||||||
*.tlog
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
*.VC.db
|
|
||||||
*.VC.VC.opendb
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# Visual Studio Trace Files
|
|
||||||
*.e2e
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# AxoCover is a Code Coverage Tool
|
|
||||||
.axoCover/*
|
|
||||||
!.axoCover/settings.json
|
|
||||||
|
|
||||||
# Coverlet is a free, cross platform Code Coverage Tool
|
|
||||||
coverage*.json
|
|
||||||
coverage*.xml
|
|
||||||
coverage*.info
|
|
||||||
|
|
||||||
# Visual Studio code coverage results
|
|
||||||
*.coverage
|
|
||||||
*.coveragexml
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
||||||
# in these scripts will be unencrypted
|
|
||||||
PublishScripts/
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# NuGet Symbol Packages
|
|
||||||
*.snupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/[Pp]ackages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/[Pp]ackages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/[Pp]ackages/repositories.config
|
|
||||||
# NuGet v3's project.json files produces more ignorable files
|
|
||||||
*.nuget.props
|
|
||||||
*.nuget.targets
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
Package.StoreAssociation.xml
|
|
||||||
_pkginfo.txt
|
|
||||||
*.appx
|
|
||||||
*.appxbundle
|
|
||||||
*.appxupload
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!?*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.jfm
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# Including strong name files can present a security risk
|
|
||||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
|
||||||
#*.snk
|
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
||||||
#bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
ServiceFabricBackup/
|
|
||||||
*.rptproj.bak
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
*.ndf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
*.rptproj.rsuser
|
|
||||||
*- [Bb]ackup.rdl
|
|
||||||
*- [Bb]ackup ([0-9]).rdl
|
|
||||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
|
||||||
*.vbw
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
|
||||||
*.vbp
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
|
||||||
*.dsw
|
|
||||||
*.dsp
|
|
||||||
|
|
||||||
# Visual Studio 6 technical files
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
paket-files/
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|
||||||
# CodeRush personal settings
|
|
||||||
.cr/personal
|
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Cake - Uncomment if you are using it
|
|
||||||
# tools/**
|
|
||||||
# !tools/packages.config
|
|
||||||
|
|
||||||
# Tabs Studio
|
|
||||||
*.tss
|
|
||||||
|
|
||||||
# Telerik's JustMock configuration file
|
|
||||||
*.jmconfig
|
|
||||||
|
|
||||||
# BizTalk build output
|
|
||||||
*.btp.cs
|
|
||||||
*.btm.cs
|
|
||||||
*.odx.cs
|
|
||||||
*.xsd.cs
|
|
||||||
|
|
||||||
# OpenCover UI analysis results
|
|
||||||
OpenCover/
|
|
||||||
|
|
||||||
# Azure Stream Analytics local run output
|
|
||||||
ASALocalRun/
|
|
||||||
|
|
||||||
# MSBuild Binary and Structured Log
|
|
||||||
*.binlog
|
|
||||||
|
|
||||||
# NVidia Nsight GPU debugger configuration file
|
|
||||||
*.nvuser
|
|
||||||
|
|
||||||
# MFractors (Xamarin productivity tool) working folder
|
|
||||||
.mfractor/
|
|
||||||
|
|
||||||
# Local History for Visual Studio
|
|
||||||
.localhistory/
|
|
||||||
|
|
||||||
# Visual Studio History (VSHistory) files
|
|
||||||
.vshistory/
|
|
||||||
|
|
||||||
# BeatPulse healthcheck temp database
|
|
||||||
healthchecksdb
|
|
||||||
|
|
||||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
|
||||||
MigrationBackup/
|
|
||||||
|
|
||||||
# Ionide (cross platform F# VS Code tools) working folder
|
|
||||||
.ionide/
|
|
||||||
|
|
||||||
# Fody - auto-generated XML schema
|
|
||||||
FodyWeavers.xsd
|
|
||||||
|
|
||||||
# VS Code files for those working on multiple tools
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
*.code-workspace
|
|
||||||
|
|
||||||
# Local History for Visual Studio Code
|
|
||||||
.history/
|
|
||||||
|
|
||||||
# Windows Installer files from build outputs
|
|
||||||
*.cab
|
|
||||||
*.msi
|
|
||||||
*.msix
|
|
||||||
*.msm
|
|
||||||
*.msp
|
|
||||||
|
|
||||||
# JetBrains Rider
|
|
||||||
*.sln.iml
|
|
||||||
|
|
||||||
### VisualStudio Patch ###
|
|
||||||
# Additional files built by Visual Studio
|
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/visualstudio
|
|
38
.vscode/launch.json
vendored
38
.vscode/launch.json
vendored
|
@ -1,38 +0,0 @@
|
||||||
{
|
|
||||||
// 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}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "msedge",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Launch Edge against localhost",
|
|
||||||
"url": "http://localhost:5173",
|
|
||||||
"webRoot": "${workspaceFolder}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "chrome",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Launch Brave against localhost",
|
|
||||||
"url": "http://localhost:5173",
|
|
||||||
"webRoot": "${workspaceFolder}",
|
|
||||||
"runtimeExecutable": "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "chrome",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Launch Thorium against localhost",
|
|
||||||
"url": "http://localhost:5173",
|
|
||||||
"webRoot": "${workspaceFolder}",
|
|
||||||
"runtimeExecutable": "/bin/thorium-browser"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"eslint.rules.customizations": [
|
|
||||||
{ "rule": "*", "severity": "warn" }
|
|
||||||
]
|
|
||||||
}
|
|
41
.vscode/tasks.json
vendored
41
.vscode/tasks.json
vendored
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "build",
|
|
||||||
"command": "dotnet",
|
|
||||||
"type": "process",
|
|
||||||
"args": [
|
|
||||||
"build",
|
|
||||||
"${workspaceFolder}/csharp/SVGLDLibs/SVGLDWebAPI/SVGLDWebAPI.csproj",
|
|
||||||
"/property:GenerateFullPaths=true",
|
|
||||||
"/consoleloggerparameters:NoSummary"
|
|
||||||
],
|
|
||||||
"problemMatcher": "$msCompile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "publish",
|
|
||||||
"command": "dotnet",
|
|
||||||
"type": "process",
|
|
||||||
"args": [
|
|
||||||
"publish",
|
|
||||||
"${workspaceFolder}/csharp/SVGLDLibs/SVGLDWebAPI/SVGLDWebAPI.csproj",
|
|
||||||
"/property:GenerateFullPaths=true",
|
|
||||||
"/consoleloggerparameters:NoSummary"
|
|
||||||
],
|
|
||||||
"problemMatcher": "$msCompile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "watch",
|
|
||||||
"command": "dotnet",
|
|
||||||
"type": "process",
|
|
||||||
"args": [
|
|
||||||
"watch",
|
|
||||||
"run",
|
|
||||||
"--project",
|
|
||||||
"${workspaceFolder}/csharp/SVGLDLibs/SVGLDWebAPI/SVGLDWebAPI.csproj"
|
|
||||||
],
|
|
||||||
"problemMatcher": "$msCompile"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
216
CONTRIBUTING.md
216
CONTRIBUTING.md
|
@ -1,216 +0,0 @@
|
||||||
# Contributing to SVGLayoutDesigner
|
|
||||||
|
|
||||||
First off, thank you for contributing to the project.
|
|
||||||
You will be able to navigate through this document with the table of contents.
|
|
||||||
|
|
||||||
# Table of contents
|
|
||||||
|
|
||||||
- [Contributing to SVGLayoutDesigner](#contributing-to-svglayoutdesigner)
|
|
||||||
- [Table of contents](#table-of-contents)
|
|
||||||
- [I want to contribute](#i-want-to-contribute)
|
|
||||||
- [I want to contribute to the .NETFramework API](#i-want-to-contribute-to-the-netframework-api)
|
|
||||||
- [Getting Started](#getting-started)
|
|
||||||
- [Before developing](#before-developing)
|
|
||||||
- [Testing](#testing)
|
|
||||||
- [Releasing](#releasing)
|
|
||||||
- [I want to contribute to the React component](#i-want-to-contribute-to-the-react-component)
|
|
||||||
- [Getting Started](#getting-started-1)
|
|
||||||
- [Before developing](#before-developing-1)
|
|
||||||
- [CORS](#cors)
|
|
||||||
- [Develop with Vite and pnpm](#develop-with-vite-and-pnpm)
|
|
||||||
- [Develop with mprocs](#develop-with-mprocs)
|
|
||||||
- [Testing the external API without .NETFramework or Windows](#testing-the-external-api-without-netframework-or-windows)
|
|
||||||
- [Setup debugging with chrome](#setup-debugging-with-chrome)
|
|
||||||
- [Testing](#testing-1)
|
|
||||||
- [Releasing](#releasing-1)
|
|
||||||
- [I want to report a bug](#i-want-to-report-a-bug)
|
|
||||||
- [Before submitting a bug report](#before-submitting-a-bug-report)
|
|
||||||
- [How do i submit a good bug report?](#how-do-i-submit-a-good-bug-report)
|
|
||||||
- [I want to suggest enhancements](#i-want-to-suggest-enhancements)
|
|
||||||
- [Before submitting an enhancement](#before-submitting-an-enhancement)
|
|
||||||
- [How do i submit a good enhancement suggestion?](#how-do-i-submit-a-good-enhancement-suggestion)
|
|
||||||
|
|
||||||
# I want to contribute
|
|
||||||
|
|
||||||
- Assign a `work item` in Azure DevOps to yourself and set it to `Work-in-progress`
|
|
||||||
- There are two main projects: the back-end API SVGLDLibs in .NETFramework and, the front-end SVGLD in Vite.
|
|
||||||
|
|
||||||
## I want to contribute to the .NETFramework API
|
|
||||||
|
|
||||||
### Getting Started
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
- `Windows` >= 10.x
|
|
||||||
- `MSBuildTools` >= 2017.x or `Visual Studio` >= 2017.x
|
|
||||||
- `dotnet` >= 6.x
|
|
||||||
|
|
||||||
Anything below is not officially supported.
|
|
||||||
|
|
||||||
### Before developing
|
|
||||||
|
|
||||||
- Make sure you have an existing work item
|
|
||||||
- Create a new branch for the new API
|
|
||||||
- Push the branch
|
|
||||||
- Create a pull request
|
|
||||||
- Link the work item to the pull request
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
SVGLDLibs is a set of models and does not run anything.
|
|
||||||
Any API must be build from scratch by the end-user.
|
|
||||||
|
|
||||||
In order to test the models, the project SVGLDWebAPI was created.
|
|
||||||
|
|
||||||
- Make sure the dll is compiling with `pnpm build:dotnet`
|
|
||||||
- Update SVGLDWebAPI with new or updated models
|
|
||||||
- After that run `pnpm test:full`
|
|
||||||
|
|
||||||
### Releasing
|
|
||||||
|
|
||||||
When the tests are valids and the feature is ready to deploy, please update the version by following the versioning method in [RELEASING.md](RELEASING.md).
|
|
||||||
|
|
||||||
Updating the API is usually a breaking change, so most of the time, the major version will change. So please, create a release candidate branch (or tag if merged on master).
|
|
||||||
|
|
||||||
|
|
||||||
## I want to contribute to the React component
|
|
||||||
|
|
||||||
### Getting Started
|
|
||||||
|
|
||||||
If you are new to React and Node.js, please install these tools:
|
|
||||||
|
|
||||||
- A chromium-based browser (`chromium`, `chrome`, `edge`, `brave`, `vivaldi`).
|
|
||||||
- [VSCode](https://code.visualstudio.com/)
|
|
||||||
- [pnpm](https://pnpm.io/)
|
|
||||||
- [React DevTools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi)
|
|
||||||
- [Typescript React code snippets](https://marketplace.visualstudio.com/items?itemName=infeng.vscode-react-typescript)
|
|
||||||
- [vscode-tailwindcss](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)
|
|
||||||
- [vscode-eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
|
|
||||||
- [vscode-drawio](https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio)
|
|
||||||
|
|
||||||
### Before developing
|
|
||||||
|
|
||||||
- Make sure you have an existing work item
|
|
||||||
- Create a new branch for the new feature
|
|
||||||
- Push the branch
|
|
||||||
- Create a pull request
|
|
||||||
- Link the work item to the pull request
|
|
||||||
- Copy the `.env.development` to `.env.development.local`
|
|
||||||
- Update `.env.development.local` urls to the test server `http://localhost:5000` or `https://localhost/SmartMenuiserieTemplate`
|
|
||||||
|
|
||||||
### CORS
|
|
||||||
|
|
||||||
If you are getting CORS or HTTPS related error, please access the test server with your browser to allow the access to the unsecure website.
|
|
||||||
|
|
||||||
### Develop with Vite and pnpm
|
|
||||||
|
|
||||||
Install the dependencies :
|
|
||||||
|
|
||||||
```
|
|
||||||
pnpm i
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run the following command to run the projet in a dev environment:
|
|
||||||
|
|
||||||
```
|
|
||||||
pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Develop with mprocs
|
|
||||||
|
|
||||||
[Mprocs](https://github.com/pvolok/mprocs) runs multiple commands in parallel and shows output of each command separately.
|
|
||||||
|
|
||||||
It is useful to run `vite` and the test server at the same time with `mprocs`.
|
|
||||||
|
|
||||||
Run `pnpm d` or `pnpm mprocs` to run mprocs.
|
|
||||||
|
|
||||||
### Testing the external API without .NETFramework or Windows
|
|
||||||
|
|
||||||
Use the Node.js server in `/test-server` to simulate the api.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node run ./test-server/http.js
|
|
||||||
```
|
|
||||||
|
|
||||||
The web server will be running at `http://localhost:5000`
|
|
||||||
|
|
||||||
### Setup debugging 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. Additionally, set the `runtimeExecutable` to your favorite chromium browser.
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
Run `pnpm test` to test.
|
|
||||||
|
|
||||||
### Releasing
|
|
||||||
|
|
||||||
When the tests are valids and the feature is ready to deploy, please update the version by following the versioning method in [RELEASING.md](RELEASING.md).
|
|
||||||
|
|
||||||
|
|
||||||
# I want to report a bug
|
|
||||||
|
|
||||||
## Before submitting a bug report
|
|
||||||
|
|
||||||
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
|
|
||||||
|
|
||||||
- Make sure that you are using the last version for both the project `svg-layout-designer` and the API `SVGLDLibs`
|
|
||||||
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
|
|
||||||
- Determine if your bug is really a bug and not an error on your side e.g. e.g. using incompatible environment components/versions.
|
|
||||||
- Make sure you have read the [documentation](docs/%23Project/Home.md).
|
|
||||||
- Make sure the issue is not already duplicated or solved in [the issue list](https://dev.azure.com/techformsa/SmartConfigurator/_backlogs/backlog/SmartConfigurator%20Team/Epics/?workitem=7092)
|
|
||||||
- Make sure to search the internet
|
|
||||||
|
|
||||||
Collect the information about the bug:
|
|
||||||
- Stack trace
|
|
||||||
- OS, Platform and Version (`Windows`, `Linux`, `macOS`, `x86`, `arm`)
|
|
||||||
- Browser and version (`Chrome`, `Chromium`, `Firefox` and any chromium and gecko based browsers)
|
|
||||||
|
|
||||||
## How do i submit a good bug report?
|
|
||||||
|
|
||||||
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to <enguyen@techform.fr>.
|
|
||||||
|
|
||||||
- Open an [Issue](https://dev.azure.com/techformsa/SmartConfigurator/_backlogs/backlog/SmartConfigurator%20Team/Epics/?workitem=7763)
|
|
||||||
- Explain the behavior you would expect and the actual behavior
|
|
||||||
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
|
|
||||||
- Provide the information you collected in the previous section.
|
|
||||||
|
|
||||||
Once it's filed:
|
|
||||||
|
|
||||||
- The project team will assign the bug accordingly
|
|
||||||
- If the team is not able to reproduce the issue, more information will be asked
|
|
||||||
- If the team is able to reproduce the issue, it will be marked `TODO`
|
|
||||||
|
|
||||||
# I want to suggest enhancements
|
|
||||||
|
|
||||||
## Before submitting an enhancement
|
|
||||||
|
|
||||||
- Make sure you are using the latest version
|
|
||||||
- Read the [documentation](docs/%23Project/Home.md) carefully and find out if the functionality is already covered, maybe by an individual configuration.
|
|
||||||
|
|
||||||
## How do i submit a good enhancement suggestion?
|
|
||||||
|
|
||||||
- Use a **clear and descriptive title** for the issue to identify the suggestion.
|
|
||||||
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
|
|
||||||
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
|
|
||||||
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to
|
|
||||||
- **Explain why this enhancement would be useful** to most users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
|
|
||||||
- Use Azure DevOps to add the new feature request under the [epic](https://dev.azure.com/techformsa/SmartConfigurator/_backlogs/backlog/SmartConfigurator%20Team/Epics/?workitem=7092).
|
|
71
README.md
71
README.md
|
@ -1,27 +1,72 @@
|
||||||
# SVG Layout Designer React
|
# SVG Layout Designer React
|
||||||
|
|
||||||
[](https://dev.azure.com/techformsa/SmartConfigurator/_build/latest?definitionId=9&branchName=master)
|
[](https://drone.siklos-chaneru.duckdns.org/Siklos/svg-layout-designer-react)
|
||||||
|
|
||||||
|
[](https://drone.siklos-chaneru.duckdns.org/Siklos/svg-layout-designer-react)
|
||||||
|
|
||||||
An svg layout designer.
|
An svg layout designer.
|
||||||
|
|
||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
||||||
Install these dependancies :
|
Requierements :
|
||||||
|
- NodeJS
|
||||||
|
- npm
|
||||||
|
- pnpm (optional but recommanded unless you prefer having a huge `node_modules` directory)
|
||||||
|
|
||||||
- [`node`](https://nodejs.org/en/) >= 18.x
|
# Developping
|
||||||
- [`dotnet`](https://dotnet.microsoft.com/en-us/download/visual-studio-sdks) >= 6.x (optional) used for api test
|
|
||||||
- [`git-lfs`](https://git-lfs.github.com/) (in order to clone the documentation)
|
|
||||||
|
|
||||||
# Test the project
|
Run `npm ci`
|
||||||
|
|
||||||
Test the project with these commands :
|
Run `npm run dev`
|
||||||
|
|
||||||
```
|
|
||||||
npm i
|
|
||||||
npm run dev
|
|
||||||
|
# Deploy
|
||||||
|
|
||||||
|
Run `npm ci`
|
||||||
|
|
||||||
|
Run `npm run build`
|
||||||
|
|
||||||
|
|
||||||
|
# Run the tests
|
||||||
|
|
||||||
|
Run `npm ci`
|
||||||
|
|
||||||
|
Run `npm test`
|
||||||
|
|
||||||
|
|
||||||
|
# API
|
||||||
|
|
||||||
|
You can preload a state by setting the `state` URL parameter
|
||||||
|
with a url address to a `state.json` file.
|
||||||
|
|
||||||
|
Example: `http://localhost:4000/?state=http://localhost:5000/state.json`
|
||||||
|
|
||||||
|
# Testing the external API
|
||||||
|
|
||||||
|
This program fetch the data structure from others applications, allowing it to assemble them later.
|
||||||
|
|
||||||
|
## With NodeJS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node run ./test-server/node-http.js
|
||||||
```
|
```
|
||||||
|
|
||||||
# Contributing
|
The web server will be running at `http://localhost:5000`
|
||||||
|
|
||||||
If you want to contribute to the project, please read [CONTRIBUTING.md](CONTRIBUTING.md)
|
Configure the file `.env.development` with the url
|
||||||
|
|
||||||
|
## With bun.sh
|
||||||
|
|
||||||
|
Install `bun`
|
||||||
|
|
||||||
|
Inside `test-server` folder, run :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run http.js
|
||||||
|
```
|
||||||
|
|
||||||
|
The web server will be running at `http://localhost:5000`
|
||||||
|
|
||||||
|
Configure the file `.env.development` with the url
|
64
RELEASING.md
64
RELEASING.md
|
@ -1,64 +0,0 @@
|
||||||
# Versioning
|
|
||||||
|
|
||||||
Le projet utilise [la méthode sémantique de version](https://semver.org/lang/fr/).
|
|
||||||
|
|
||||||
> Étant donné un numéro de version MAJEUR.MINEUR.CORRECTIF, il faut incrémenter :
|
|
||||||
>
|
|
||||||
> 1. le numéro de version MAJEUR quand il y a des changements non rétrocompatibles,
|
|
||||||
> 1. le numéro de version MINEUR quand il y a des ajouts de fonctionnalités rétrocompatibles,
|
|
||||||
> 1. le numéro de version de CORRECTIF quand il y a des corrections d’anomalies rétrocompatibles.
|
|
||||||
>
|
|
||||||
> Des libellés supplémentaires peuvent être ajoutés pour les versions de pré-livraison et pour des méta-données de construction sous forme d’extension du format MAJEURE.MINEURE.CORRECTIF.
|
|
||||||
|
|
||||||
Ainsi nous utilerons le format `MAJOR.MINOR.PATCH` pour nos versions.
|
|
||||||
Le libellé supplémentaire sera le suffixe `-rcX` pour les version en pré-release (release candidate).
|
|
||||||
|
|
||||||
Examples de noms de versions valides: `1.0.0`, `1.0.1`, `1.1.0`, `2.0.0`, `2.0-rc1`, `2.0-rc2` .
|
|
||||||
|
|
||||||
Un example réel: https://kernel.org/
|
|
||||||
|
|
||||||
|
|
||||||
# `package.json`
|
|
||||||
|
|
||||||
La version courante complète est indiquée dans le `package.json`.
|
|
||||||
|
|
||||||
En utilisant `npm`, on peut facilement mettre à jour ce nombre :
|
|
||||||
|
|
||||||
```
|
|
||||||
npm version major
|
|
||||||
npm version minor
|
|
||||||
npm version patch
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
# Git
|
|
||||||
|
|
||||||
Chaque version est tag par git sur la branche master.
|
|
||||||
|
|
||||||
Pour tag :
|
|
||||||
|
|
||||||
```
|
|
||||||
git tag -a v1.0.0 -m "Release v1.0.0"
|
|
||||||
```
|
|
||||||
|
|
||||||
Pour pousser les modifications puis les tags :
|
|
||||||
|
|
||||||
```
|
|
||||||
git push origin master
|
|
||||||
git push origin master --tags
|
|
||||||
```
|
|
||||||
|
|
||||||
Ou pour faire les deux en même temps
|
|
||||||
|
|
||||||
```
|
|
||||||
git push origin master --follow-tags
|
|
||||||
```
|
|
||||||
|
|
||||||
Si vous êtes sur une branche autre que `master`, veuillez **NE PAS TAG**. Si vous voulez versionner la branche, créez une branche avec la version dans le nom : `v1.1-rc1`.
|
|
||||||
|
|
||||||
|
|
||||||
# Azure Pipeline
|
|
||||||
|
|
||||||
Les tests d'Azure Pipeline sont lancés à condition que le nom de la branche soit `master` ou soit préfixée par la lettre `v`
|
|
||||||
|
|
||||||
Exemples: `master`, `v1.0.2`, `v1.0-rc1`, `v1.0.1-mafeature` etc.
|
|
|
@ -5,17 +5,14 @@
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
- master
|
- master
|
||||||
- v*
|
- dev
|
||||||
|
- dev*
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ubuntu-latest
|
vmImage: ubuntu-latest
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
pnpm_config_cache: $(Pipeline.Workspace)/.pnpm-store
|
pnpm_config_cache: $(Pipeline.Workspace)/.pnpm-store
|
||||||
CSWebProjectLocation: '$(System.DefaultWorkingDirectory)/csharp/SVGLDLibs/SVGLDWebAPI/SVGLDWebAPI.csproj'
|
|
||||||
CSLibsProjectLocation: '$(System.DefaultWorkingDirectory)/csharp/SVGLDLibs/SVGLDLibs/SVGLDLibs.csproj'
|
|
||||||
CSLibsProjectModelsLocation: '$(System.DefaultWorkingDirectory)/csharp/SVGLDLibs/SVGLDLibs/Models'
|
|
||||||
buildConfiguration: 'Release'
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: Cache@2
|
- task: Cache@2
|
||||||
|
@ -24,59 +21,37 @@ steps:
|
||||||
path: $(pnpm_config_cache)
|
path: $(pnpm_config_cache)
|
||||||
displayName: Cache pnpm
|
displayName: Cache pnpm
|
||||||
|
|
||||||
- bash: |
|
- script: |
|
||||||
curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7
|
curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7
|
||||||
pnpm config set store-dir $(pnpm_config_cache)
|
pnpm config set store-dir $(pnpm_config_cache)
|
||||||
displayName: "Setup pnpm"
|
displayName: "Setup pnpm"
|
||||||
|
|
||||||
- task: NodeTool@0
|
- task: NodeTool@0
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: '18.x'
|
versionSpec: '16.x'
|
||||||
displayName: 'Install Node.js 18.x LTS'
|
displayName: 'Install Node.js 16.x LTS'
|
||||||
|
|
||||||
- bash: |
|
- script: |
|
||||||
set -euo pipefail
|
|
||||||
node --version
|
node --version
|
||||||
node ./test-server/http.js &
|
node ./test-server/node-http.js &
|
||||||
dotnet run --project=$(CSWebProjectLocation) &
|
|
||||||
jobs
|
jobs
|
||||||
sleep 10
|
|
||||||
pnpm i
|
pnpm i
|
||||||
pnpm run test:nowatch
|
pnpm run test:nowatch
|
||||||
pnpm run build
|
pnpm run build
|
||||||
kill -2 %1 %2 2>/dev/null
|
kill -2 %1 2>/dev/null
|
||||||
displayName: 'Test on Node.js 18.x LTS'
|
displayName: 'Test on Node.js 16.x LTS'
|
||||||
|
|
||||||
- task: NodeTool@0
|
- task: NodeTool@0
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: '>=19.1.0'
|
versionSpec: '>=18.7.0'
|
||||||
displayName: 'Install Node.js Latest'
|
displayName: 'Install Node.js Latest'
|
||||||
|
|
||||||
- bash: |
|
- script: |
|
||||||
set -euo pipefail
|
|
||||||
node --version
|
node --version
|
||||||
node ./test-server/http.js &
|
node ./test-server/node-http.js &
|
||||||
dotnet run --project=$(CSWebProjectLocation) &
|
|
||||||
jobs
|
jobs
|
||||||
sleep 10
|
|
||||||
pnpm i
|
pnpm i
|
||||||
pnpm run test:nowatch
|
pnpm run test:nowatch
|
||||||
pnpm run build
|
pnpm run build
|
||||||
kill -2 %1 %2 2>/dev/null
|
kill -2 %1 2>/dev/null
|
||||||
displayName: 'Test on Node.js Latest'
|
displayName: 'Test on Node.js 18.x Latest'
|
||||||
|
|
||||||
- bash: |
|
|
||||||
pnpm run linter
|
|
||||||
displayName: 'Run eslint on src'
|
|
||||||
|
|
||||||
- publish: $(System.DefaultWorkingDirectory)/dist
|
|
||||||
artifact: svg-layout-designer
|
|
||||||
|
|
||||||
- script: dotnet build $(CSLibsProjectLocation) --configuration $(buildConfiguration) --output $(build.artifactstagingdirectory)
|
|
||||||
displayName: 'dotnet build $(buildConfiguration)'
|
|
||||||
|
|
||||||
- publish: $(Build.ArtifactStagingDirectory)
|
|
||||||
artifact: svg-layout-designer-net
|
|
||||||
|
|
||||||
- publish: $(CSLibsProjectModelsLocation)
|
|
||||||
artifact: svg-layout-designer-net-source
|
|
|
@ -1,9 +0,0 @@
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
end_of_line = crlf
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
insert_final_newline = false
|
|
|
@ -1,31 +0,0 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 17
|
|
||||||
VisualStudioVersion = 17.0.31903.59
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SVGLDLibs", "SVGLDLibs\SVGLDLibs.csproj", "{462AAD8D-9C0F-44E0-BF9B-B7533741C1FD}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SVGLDWebAPI", "SVGLDWebAPI\SVGLDWebAPI.csproj", "{22E1C9F8-9019-481A-9658-0DFE5F363B34}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{462AAD8D-9C0F-44E0-BF9B-B7533741C1FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{462AAD8D-9C0F-44E0-BF9B-B7533741C1FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{462AAD8D-9C0F-44E0-BF9B-B7533741C1FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{462AAD8D-9C0F-44E0-BF9B-B7533741C1FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{22E1C9F8-9019-481A-9658-0DFE5F363B34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{22E1C9F8-9019-481A-9658-0DFE5F363B34}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{22E1C9F8-9019-481A-9658-0DFE5F363B34}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{22E1C9F8-9019-481A-9658-0DFE5F363B34}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {AB7354D7-38C1-482B-A973-01AD4E554A3F}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
|
@ -1,19 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
public class APIConfiguration
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string apiFetchUrl { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string apiSetContainerListUrl { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string apiGetFeedbackUrl { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class ActionContainerModel
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string Id { get; set; }
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public ImageModel CustomLogo { get; set; }
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string Label { get; set; }
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string Description { get; set; }
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string Action { get; set; }
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public AddingBehaviorEnumModel AddingBehavior { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public enum AddingBehaviorEnumModel
|
|
||||||
{
|
|
||||||
[EnumMember]
|
|
||||||
Append,
|
|
||||||
|
|
||||||
[EnumMember]
|
|
||||||
InsertInto,
|
|
||||||
|
|
||||||
[EnumMember]
|
|
||||||
Replace,
|
|
||||||
|
|
||||||
[EnumMember]
|
|
||||||
ReplaceParent,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,177 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class AvailableContainerModel
|
|
||||||
{
|
|
||||||
/** type */
|
|
||||||
[DataMember(EmitDefaultValue = false, IsRequired = true)]
|
|
||||||
public string Type { get; set; }
|
|
||||||
|
|
||||||
/** displayed text */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string DisplayedText { get; set; }
|
|
||||||
|
|
||||||
/** category */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string Category { get; set; }
|
|
||||||
|
|
||||||
/** orientation */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Orientation Orientation { get; set; }
|
|
||||||
|
|
||||||
/** horizontal offset */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? X { get; set; }
|
|
||||||
|
|
||||||
/** vertical offset */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? Y { get; set; }
|
|
||||||
|
|
||||||
/** width */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? Width { get; set; }
|
|
||||||
|
|
||||||
/** height */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? Height { get; set; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum width (min=1)
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? MinWidth { get; set; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum width
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? MaxWidth { get; set; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum height (min=1)
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? MinHeight { get; set; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum height
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? MaxHeight { get; set; }
|
|
||||||
|
|
||||||
/** margin */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Margin Margin { get; set; }
|
|
||||||
|
|
||||||
/** true if anchor, false otherwise */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public bool? IsAnchor { get; set; }
|
|
||||||
|
|
||||||
/** true if flex, false otherwise */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public bool? IsFlex { get; set; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allow to use a Pattern to create the list of children
|
|
||||||
* Cannot be used with DefaultChildType,
|
|
||||||
* DefaultChildType will be disabled for this container and the children
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string Pattern { get; set; }
|
|
||||||
|
|
||||||
/** Method used on container add */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public AddingBehaviorEnumModel? AddMethod { get; set; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (optional)
|
|
||||||
* Disabled when Pattern is used.
|
|
||||||
*
|
|
||||||
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
|
|
||||||
* to draw some patterns that can be bind to the properties of the container
|
|
||||||
* Use {prop} to bind a property. Use {{ styleProp }} to use an object.
|
|
||||||
* Example :
|
|
||||||
* ```
|
|
||||||
* `<rect width="{width}" height="{height}" style="{style}"></rect>
|
|
||||||
* <rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
|
|
||||||
* <line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
|
|
||||||
* <line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
|
|
||||||
* `
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string DefaultChildType { get; set; }
|
|
||||||
|
|
||||||
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public PositionReferenceEnumModel? PositionReference { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public bool? HideChildrenInTreeview { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Dimensions DimensionOptions { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* if true, hide the entry in the sidebar (default: false)
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public bool? IsHidden { get; set; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* if true, hide the entry in the sidebar (default: false)
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string[] Blacklist { get; set; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cannot be used with blacklist. Whitelist will be prioritized.
|
|
||||||
* To disable the whitelist, Whitelist must be undefined.
|
|
||||||
* Only allow a set of available container to be added inside
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string[] Whitelist { get; set; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of possible actions shown on right-click
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public List<ActionContainerModel> Actions { get; set; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (optional)
|
|
||||||
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
|
|
||||||
* to draw some patterns that can be bind to the properties of the container
|
|
||||||
* Use {prop} to bind a property. Use {{ styleProp }} to use an object.
|
|
||||||
* Example :
|
|
||||||
* ```
|
|
||||||
* `<rect width="{width}" height="{height}" style="{style}"></rect>
|
|
||||||
* <rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
|
|
||||||
* <line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
|
|
||||||
* <line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
|
|
||||||
* `
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string CustomSVG { get; set; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (optional)
|
|
||||||
* Style of the <rect>
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public CSSStyle Style { get; set; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (optional)
|
|
||||||
* User data that can be used for data storage or custom SVG
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Dictionary<string, string> UserData { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class AvailableSymbolModel
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public ImageModel Image { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double offset { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public bool isVertical { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string DisplayedText { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double Width { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double Height { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public PositionReferenceEnumModel PositionReference { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public AvailableContainerModel AssociatedContainer { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class CSSStyle
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string stroke;
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? strokeOpacity;
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? strokeWidth;
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string fill;
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? fillOpacity;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class Category
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string Type { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string DisplayedText { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
public class Configuration
|
|
||||||
{
|
|
||||||
|
|
||||||
public Configuration(AvailableContainerModel mainContainer)
|
|
||||||
{
|
|
||||||
MainContainer = mainContainer;
|
|
||||||
AvailableContainers = new List<AvailableContainerModel>();
|
|
||||||
AvailableSymbols = new List<AvailableSymbolModel>();
|
|
||||||
Categories = new List<Category>();
|
|
||||||
Patterns = new List<Pattern>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public List<AvailableContainerModel> AvailableContainers { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public List<AvailableSymbolModel> AvailableSymbols { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public List<Category> Categories { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public List<Pattern> Patterns { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false, IsRequired = true)]
|
|
||||||
public AvailableContainerModel MainContainer { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public APIConfiguration APIConfiguration { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class ContainerModel
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public List<string> children;
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public ContainerProperties properties;
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Dictionary<string, string> userData;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class ContainerProperties
|
|
||||||
{
|
|
||||||
/** id of the container */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string id;
|
|
||||||
|
|
||||||
/** type matching the configuration on construction */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string type;
|
|
||||||
|
|
||||||
/** id of the parent container (null when there is no parent) */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string parentId;
|
|
||||||
|
|
||||||
/** id of the linked symbol ('' when there is no parent) */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string linkedSymbolId;
|
|
||||||
|
|
||||||
/** Text displayed in the container */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string displayedText;
|
|
||||||
|
|
||||||
/** orientation */
|
|
||||||
[DataMember(EmitDefaultValue =false)]
|
|
||||||
public Orientation orientation;
|
|
||||||
|
|
||||||
/** horizontal offset */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double x;
|
|
||||||
|
|
||||||
/** vertical offset */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double y;
|
|
||||||
|
|
||||||
/** margin */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Margin margin;
|
|
||||||
|
|
||||||
/** width */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double width;
|
|
||||||
|
|
||||||
/** height */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double height;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum width (min=1)
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double minWidth;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum width
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double maxWidth;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum height (min=1)
|
|
||||||
* Allows the container to set isRigidBody to false when it gets squeezed
|
|
||||||
* by an anchor
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double minHeight;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum height
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double maxHeight;
|
|
||||||
|
|
||||||
|
|
||||||
/** true if anchor, false otherwise */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public bool isAnchor;
|
|
||||||
|
|
||||||
/** true if flex, false otherwise */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public bool isFlex;
|
|
||||||
|
|
||||||
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public PositionReferenceEnumModel positionReference;
|
|
||||||
|
|
||||||
/** Hide the children in the treeview */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public bool hideChildrenInTreeview;
|
|
||||||
|
|
||||||
/** Dimension options */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Dimensions dimensionOptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Warnings of a container
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string warning;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (optional)
|
|
||||||
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
|
|
||||||
* to draw some patterns that can be bind to the properties of the container
|
|
||||||
* Use {prop} to bind a property. Use {{ styleProp }} to use an object.
|
|
||||||
* Example :
|
|
||||||
* ```
|
|
||||||
* `<rect width="{width}" height="{height}" style="{style}"></rect>
|
|
||||||
* <rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
|
|
||||||
* <line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
|
|
||||||
* <line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
|
|
||||||
* `
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string customSVG;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (optional)
|
|
||||||
* Style of the <rect>
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public CSSStyle style;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (optional)
|
|
||||||
* User data that can be used for data storage or custom SVG
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Dictionary<string, string> userData;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
public class DimensionOptions
|
|
||||||
{
|
|
||||||
|
|
||||||
/** positions of the dimension */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Position[] positions;
|
|
||||||
|
|
||||||
/** color */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string color;
|
|
||||||
|
|
||||||
/** width */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double width;
|
|
||||||
|
|
||||||
/** color */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string dashArray;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class Dimensions
|
|
||||||
{
|
|
||||||
|
|
||||||
/** if true, show the dimension of the container */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public DimensionOptions selfDimensions;
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public DimensionOptions selfMarginsDimensions;
|
|
||||||
|
|
||||||
/** if true show the overall dimensions of its children */
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public DimensionOptions childrenDimensions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* if true, allows a parent dimension borrower to borrow its x coordinate
|
|
||||||
* as a reference point for a dimension
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Orientation[] markPosition;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* if true, show a dimension from the edge of the container to end
|
|
||||||
* and insert dimensions marks at lift up children (see liftDimensionToBorrower)
|
|
||||||
*/
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public DimensionOptions dimensionWithMarks;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class EditorState
|
|
||||||
{
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public List<HistoryState> history { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public int historyCurrentStep { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Configuration configuration { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class GetFeedbackRequest
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public HistoryState ApplicationState { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class GetFeedbackResponse
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public List<Message> messages { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class HistoryState
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string lastAction;
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string mainContainer;
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Dictionary<string, ContainerModel> containers;
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Dictionary<string, string> typeCounters;
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Dictionary<string, SymbolModel> symbols;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class ImageModel
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string Name { get; set; }
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string Url { get; set; }
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string Base64Image { get; set; }
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string Svg { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class Margin
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? left { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? bottom { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? top { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double? right { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class Message
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string text { get; set; }
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public MessageType type { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public enum MessageType
|
|
||||||
{
|
|
||||||
[EnumMember]
|
|
||||||
Normal,
|
|
||||||
[EnumMember]
|
|
||||||
Success,
|
|
||||||
[EnumMember]
|
|
||||||
Warning,
|
|
||||||
[EnumMember]
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public enum Orientation
|
|
||||||
{
|
|
||||||
[EnumMember]
|
|
||||||
Horizontal,
|
|
||||||
[EnumMember]
|
|
||||||
Vertical
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class Pattern
|
|
||||||
{
|
|
||||||
|
|
||||||
public Pattern()
|
|
||||||
{
|
|
||||||
children = new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string id { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string text { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string wrapper { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = true, IsRequired = true)]
|
|
||||||
public List<string> children { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class PointModel
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double x { get; set; }
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double y { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public enum Position
|
|
||||||
{
|
|
||||||
[EnumMember]
|
|
||||||
Left,
|
|
||||||
[EnumMember]
|
|
||||||
Down,
|
|
||||||
[EnumMember]
|
|
||||||
Up,
|
|
||||||
[EnumMember]
|
|
||||||
Right
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public enum PositionReferenceEnumModel
|
|
||||||
{
|
|
||||||
[EnumMember]
|
|
||||||
TopLeft,
|
|
||||||
[EnumMember]
|
|
||||||
TopCenter,
|
|
||||||
[EnumMember]
|
|
||||||
TopRight,
|
|
||||||
[EnumMember]
|
|
||||||
CenterLeft,
|
|
||||||
[EnumMember]
|
|
||||||
CenterCenter,
|
|
||||||
[EnumMember]
|
|
||||||
CenterRight,
|
|
||||||
[EnumMember]
|
|
||||||
BottomLeft,
|
|
||||||
[EnumMember]
|
|
||||||
BottomCenter,
|
|
||||||
[EnumMember]
|
|
||||||
BottomRight
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class SetContainerListRequest
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public ActionContainerModel Action { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public ContainerModel Container { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public ContainerModel PreviousContainer { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public ContainerModel NextContainer { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public HistoryState ApplicationState { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class SetContainerListResponse
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public List<AvailableContainerModel> Containers { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public AddingBehaviorEnumModel AddingBehavior { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace SVGLDLibs.Models
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class SymbolModel
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string id { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string type { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string displayedText { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public AvailableSymbolModel config { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double offset { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public bool isVertical { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double width { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public double height { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public List<string> linkedContainers { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public bool showDimension { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net472</TargetFramework>
|
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
|
||||||
<Nullable>disable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
|
@ -1,155 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using SVGLDLibs.Models;
|
|
||||||
|
|
||||||
namespace SVGLDWebAPI.Controllers;
|
|
||||||
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("[controller]/[Action]")]
|
|
||||||
public class SVGLDController : ControllerBase
|
|
||||||
{
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.ActionContainerModel))]
|
|
||||||
public bool ActionContainerModel(ActionContainerModel model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.AddingBehaviorEnumModel))]
|
|
||||||
public bool AddingBehaviorEnumModel(AddingBehaviorEnumModel model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.HistoryState))]
|
|
||||||
public bool ApplicationStateModel(HistoryState model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.AvailableContainerModel))]
|
|
||||||
public bool AvailableContainerModel(AvailableContainerModel model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.AvailableSymbolModel))]
|
|
||||||
public bool AvailableSymbolModel(AvailableSymbolModel model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.Category))]
|
|
||||||
public bool Category(Category model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.Configuration))]
|
|
||||||
public bool Configuration(Configuration model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.ContainerModel))]
|
|
||||||
public bool ContainerModel(ContainerModel model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.ContainerProperties))]
|
|
||||||
public bool ContainerProperties(ContainerProperties model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.CSSStyle))]
|
|
||||||
public bool CSSStyle(CSSStyle model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.GetFeedbackRequest))]
|
|
||||||
public bool GetFeedbackRequest(GetFeedbackRequest model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.GetFeedbackResponse))]
|
|
||||||
public bool GetFeedbackResponse(GetFeedbackResponse model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.ImageModel))]
|
|
||||||
public bool ImageModel(ImageModel model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.Margin))]
|
|
||||||
public bool Margin(Margin model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.Message))]
|
|
||||||
public bool Message(Message model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.MessageType))]
|
|
||||||
public bool MessageType(MessageType model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.Orientation))]
|
|
||||||
public bool Orientation(Orientation model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.Pattern))]
|
|
||||||
public bool Pattern(Pattern model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.PointModel))]
|
|
||||||
public bool PointModel(PointModel model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.Position))]
|
|
||||||
public bool Position(Position model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.PositionReferenceEnumModel))]
|
|
||||||
public bool PositionReferenceEnumModel(PositionReferenceEnumModel model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.SetContainerListRequest))]
|
|
||||||
public bool SetContainerListRequest(SetContainerListRequest model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.SetContainerListResponse))]
|
|
||||||
public bool SetContainerListResponse(SetContainerListResponse model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(Name = nameof(SVGLDLibs.Models.SymbolModel))]
|
|
||||||
public bool SymbolModel(SymbolModel model)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
|
||||||
|
|
||||||
// Add services to the container.
|
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
|
||||||
|
|
||||||
const string allowOrigin = "allowOrigin";
|
|
||||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
|
||||||
builder.Services.AddCors(options =>
|
|
||||||
{
|
|
||||||
options.AddPolicy(name: allowOrigin,
|
|
||||||
policy =>
|
|
||||||
{
|
|
||||||
policy.WithOrigins("http://localhost:3000");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
|
||||||
builder.Services.AddSwaggerGen();
|
|
||||||
|
|
||||||
var app = builder.Build();
|
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
|
||||||
if (app.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
app.UseSwagger();
|
|
||||||
app.UseSwaggerUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UseCors(allowOrigin);
|
|
||||||
|
|
||||||
// app.UseHttpsRedirection();
|
|
||||||
|
|
||||||
app.UseAuthorization();
|
|
||||||
|
|
||||||
app.MapControllers();
|
|
||||||
|
|
||||||
app.Run();
|
|
|
@ -1,31 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
|
||||||
"iisSettings": {
|
|
||||||
"windowsAuthentication": false,
|
|
||||||
"anonymousAuthentication": true,
|
|
||||||
"iisExpress": {
|
|
||||||
"applicationUrl": "http://localhost:2891",
|
|
||||||
"sslPort": 44305
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"profiles": {
|
|
||||||
"SVGLDWebAPI": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": true,
|
|
||||||
"launchUrl": "swagger",
|
|
||||||
"applicationUrl": "https://localhost:7280;http://localhost:5209",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"IIS Express": {
|
|
||||||
"commandName": "IISExpress",
|
|
||||||
"launchBrowser": true,
|
|
||||||
"launchUrl": "swagger",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
# SVGLDWebAPI
|
|
||||||
|
|
||||||
This project tests the serialization of C# models of SVGLayoutDesigner
|
|
||||||
by creating an entry point for each model.
|
|
||||||
|
|
||||||
This project must be used in pair with the api.test.tsx from SVGLayoutDesigner nodejs project.
|
|
||||||
|
|
||||||
api.test.tsx will serialize each of its models accordingly to its types
|
|
||||||
and POST to each corresponding entry point.
|
|
||||||
|
|
||||||
When a test is succeeding `true` will be return.
|
|
||||||
|
|
||||||
When a test is failing `400 Bad Request` will be returned.
|
|
||||||
|
|
||||||
This project does not test any logic in the models.
|
|
|
@ -1,17 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\SVGLDLibs\SVGLDLibs.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*"
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
|
|
||||||
Bienvenue à la documentation du projet de SVGLayoutDesigner.
|
|
||||||
|
|
||||||
Ici se trouve les documents qui explique en détail l'implémentation
|
|
||||||
des fonctionnalités du projet.
|
|
||||||
|
|
||||||
Sélectionnez un lien ou un fichier pour lire la documentation
|
|
||||||
|
|
||||||
Liens :
|
|
||||||
|
|
||||||
- [Structure du projet](Pages/Project_Structure.md)
|
|
||||||
- [Structure des composants](Pages/ComponentStructure.drawio) (nécessite diagrams.net)
|
|
||||||
- [Dépendences du projet](Pages/Dependencies.md)
|
|
||||||
- [Structure de données des conteneurs](Pages/DataStructure.md)
|
|
||||||
- [Système de comportement](Pages/Behaviors.md)
|
|
||||||
- [Cycle de vie de l'application](Pages/Application.md)
|
|
||||||
- [Implémentation du menu contextuel](Pages/ContextMenu.md)
|
|
||||||
- [Implémentation du système de cote](Pages/SVGLD_Cotes.pdf)
|
|
||||||
- [Web workers](Pages/WebWorkers.md) (pdf)
|
|
||||||
- [Traductions](Pages/Translations.drawio) (nécessite diagrams.net)
|
|
||||||
- [Méthode de release](../../RELEASING.md)
|
|
||||||
- [Système de CI/CD](Pages/Behaviors.md)
|
|
||||||
- [Mise en place du SmartComponent sur Modeler](Pages/SmartComponent.md)
|
|
|
@ -1,179 +0,0 @@
|
||||||
# Cycle de vie de l'application
|
|
||||||
|
|
||||||
# Menu principal
|
|
||||||
|
|
||||||
Lorsque l'on lance l'application pour la première fois,
|
|
||||||
le premier composant qui s'affiche est `App.tsx`.
|
|
||||||
|
|
||||||
En fonction de la valeur de `FAST_BOOT`,
|
|
||||||
il peut soit afficher un menu principal (composant `MainMenu`) si `FAST_BOOT=false`
|
|
||||||
ou soit afficher l'editeur directement (composant `Editor`).
|
|
||||||
|
|
||||||
Lorsque le menu principal est affiché il y a 3 états : *Main*, *Load* ou *Loading*.
|
|
||||||
|
|
||||||
Lorsqu'on est dans Main, nous avons les deux boutons principaux affiché : *Start from scratch* ou *Load*
|
|
||||||
|
|
||||||
## Start from scratch
|
|
||||||
|
|
||||||
Lorsque l'on clique sur *Start from scratch*,
|
|
||||||
l'action qui se fait est de charger une configuration depuis l'API
|
|
||||||
(ou d'utiliser une configuration préchargée via un event custom).
|
|
||||||
On passe à l'état *Loading*.
|
|
||||||
|
|
||||||
Après chargement, `isLoaded` est `true` et le composant `Editor` est affiché.
|
|
||||||
|
|
||||||
## Load
|
|
||||||
|
|
||||||
Quand on veut charger un json, on clique sur *Load* et l'état passe à *Load*.
|
|
||||||
|
|
||||||
Ici, le composant `MainMenu` affichera un bouton pour charger un fichier.
|
|
||||||
Charger le fichier change l'état de la configuration dans `App`
|
|
||||||
et active `isLoaded` qui enfin affiche `Editor`.
|
|
||||||
|
|
||||||
|
|
||||||
# Editor
|
|
||||||
|
|
||||||
Lorsque `Editor` est affiché,
|
|
||||||
il reçoit en props la configuration que l'on a chargé au menu principal.
|
|
||||||
|
|
||||||
Il reçoit également un historique par défaut pour avoir au moins quelque chose d'affiché.
|
|
||||||
|
|
||||||
Il reçoit `root` un element HTML où l'on insert SVGLayoutDesigner par `main.tsx`.
|
|
||||||
`root` est utilisé pour lui donner des events qui seront utilisé pour communiquer avec le SmartComponent.
|
|
||||||
|
|
||||||
Plusieurs sous-composant sont affichés ensuite.
|
|
||||||
Vous pouvez en savoir plus sur [la structure de composants](./ComponentStructure.drawio)
|
|
||||||
avec dragrams.net.
|
|
||||||
|
|
||||||
|
|
||||||
# Save and load
|
|
||||||
|
|
||||||
Pour enregistrer le travail fait, il existe plusieurs méthode de le faire.
|
|
||||||
|
|
||||||
## SmartComponent
|
|
||||||
|
|
||||||
La première, en passant par le SmartComponent `svg-layout-designer.ts`, est de stringifier l'état de l'éditeur
|
|
||||||
et de le sauvegarder quelque part.
|
|
||||||
|
|
||||||
On utilise `GetEditorAsString` pour obtenir une version stringifié du projet
|
|
||||||
que l'on peut ensuite sauvegarder dans un fichier, bdd ou autre.
|
|
||||||
On peut également utiliser GetEditorState et le sauvegarder tel quel dans le JS
|
|
||||||
mais on ne peut pas le stringifier à un object json ne peut pas avoir deux références
|
|
||||||
(sauf si on a le code source de saveload.ts,
|
|
||||||
utilisant un *replacer* éliminant les références circulaires,
|
|
||||||
voir [JSON par interaction](#json-par-interaction).
|
|
||||||
|
|
||||||
Pour charger l'état de l'éditeur, il faut en premier le parser avec `JSON.parse` (pas de soucis à ce niveau là);
|
|
||||||
Enfin, on peut ensuite utiliser `LoadEditor` du SmartComponent pour charger l'état.
|
|
||||||
|
|
||||||
`LoadEditor` est une macro de trois autres appels :
|
|
||||||
- `ReviveEditorState` qui fait revivre tous les doublons de références
|
|
||||||
- `SetEditor` qui charge la configuration de l'application
|
|
||||||
- `SetHistory` qui charge l'historique de l'editeur
|
|
||||||
|
|
||||||
La raison que l'on utilise `SetHistory` en plus de `SetEditor` est parce que,
|
|
||||||
`SetEditor` ne fait que charger une configuration par défaut de `App.tsx`
|
|
||||||
(exemple: lorsque l'on crée un conteneur, l'éditeur va lire cette configuration).
|
|
||||||
Si l'application est déjà chargée, c'est-à-dire que *isLoaded* de `App` est `true`,
|
|
||||||
alors l'application ne va pas relire `history` et `historyCurrentStep` et l'application n'aura pas *chargé*.
|
|
||||||
C'est pourquoi on a besoin de `SetHistory` pour charger l'état courant dans `Editor.tsx` (à l'opposé de `App.tsx`).
|
|
||||||
|
|
||||||
Note: Pour rappel, *App* n'est qu'un menu principal.
|
|
||||||
Comme dans un menu principal de jeu vidéo,
|
|
||||||
écraser une sauvegarde ne fait pas charger la sauvegarde.
|
|
||||||
C'est en chargeant la sauvegarde en cours de jeu ou dans le menu principal,
|
|
||||||
que la partie change.
|
|
||||||
|
|
||||||
|
|
||||||
## JSON par interaction
|
|
||||||
|
|
||||||
On peut charger un fichier JSON manuellement.
|
|
||||||
|
|
||||||
Pour cela il faut que `FAST_BOOT` de `default.ts` soit désactivé.
|
|
||||||
|
|
||||||
Dans l'éditeur, on peut exporter une configuration par fichier JSON grâce au composant `Settings.tsx`.
|
|
||||||
Cette sauvegarde, comme pour sauvegarder avec le SmartComponent, utilise `JSON.stringify`.
|
|
||||||
Et donc utilise un *replacer* pour supprimer les dépendances circulaires.
|
|
||||||
|
|
||||||
Ce *replacer* se trouve dans `worker.js` mais
|
|
||||||
un fallback est également donné dans `saveload.ts`
|
|
||||||
dans le cas où la fonctionnalité de Web Worker n'est pas supportée par le navigateur web.
|
|
||||||
Pour corriger des bugs sur la sauvegarde, il faudra donc modifier ces deux fichiers.
|
|
||||||
|
|
||||||
Décrivons rapidement ce qu'elle fait :
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
export function GetCircularReplacer(): (key: any, value: object | Map<string, any> | null) => object | null | undefined {
|
|
||||||
return (key: any, value: object | null) => {
|
|
||||||
if (key === 'parent') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'containers') {
|
|
||||||
return Array.from((value as Map<string, any>).entries());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'symbols') {
|
|
||||||
return Array.from((value as Map<string, any>).entries());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'linkedContainers') {
|
|
||||||
return Array.from(value as Set<string>);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Nous faisons les actions suivantes pour supprimer les types non supportés par le stringify :
|
|
||||||
- nous transformons les *Map* `containers` et `symbols` en tableau de vecteur clés-valeurs
|
|
||||||
- nous transformons les *Set* `linkedContainers` en tableau de clés.
|
|
||||||
|
|
||||||
Nous supprimons toutes les références de `parent` qui sont déjà référencés dans `containers`.
|
|
||||||
|
|
||||||
Enfin, nous retournons `value` si tout est bon.
|
|
||||||
|
|
||||||
Normalement, le JSON retourné ressemble à l'objet qu'était EditorState
|
|
||||||
mais sans les références de parent et avec des tableaux partout.
|
|
||||||
|
|
||||||
|
|
||||||
Pour le charger, il faut revenir au menu principal et
|
|
||||||
cliquer sur Load pour charger avec l'input.
|
|
||||||
|
|
||||||
Charger le fichier JSON, utilisera ce qu'on appelle un *reviver*
|
|
||||||
qui s'occupe de recréer les types non supportés par JSON et de remettre les références dupliquées.
|
|
||||||
|
|
||||||
Ce reviver se trouve dans `saveload.ts` avec la fonction `Revive()`.
|
|
||||||
|
|
||||||
`Revive` change la valeur de `historyCurrentStep` avec de faire revivre l'historique
|
|
||||||
|
|
||||||
`ReviveHistory` itère sur chaque état de `history` pour les revivre.
|
|
||||||
|
|
||||||
`ReviveState` fait revivre en premier les *Map* et les *Set*.
|
|
||||||
Ensuite, il va itérer sur tous les conteneurs existants,
|
|
||||||
pour remettre leur parent.
|
|
||||||
|
|
||||||
Comme vous pouvez vous en douter cette algorithme coûte O(n) juste pour revivre les parents.
|
|
||||||
avec *n* le nombre total de conteneurs sur toute la durée de vie de l'application.
|
|
||||||
Surtout qu'il n'est plus très utile depuis que tous les conteneurs sont dans une hash *Map*
|
|
||||||
(et que donc parent est accessible avec un coût O(1)).
|
|
||||||
|
|
||||||
On pourrait juste utiliser `GetContainerById` au lieu de la référence.
|
|
||||||
|
|
||||||
|
|
||||||
## JSON par requête GET HTTP
|
|
||||||
|
|
||||||
Pour charger un JSON via HTTP GET,
|
|
||||||
il faut que le serveur web distant autorise notre domaine
|
|
||||||
dans le méchanisme *cross-origin resource sharing (CORS)*
|
|
||||||
et qu'il nous autorise à faire des requêtes *GET*.
|
|
||||||
|
|
||||||
Après cela pour charger le JSON,
|
|
||||||
il faut que l'url de SVGLayoutDesigner soit paramétrée avec l'url de la resource :
|
|
||||||
|
|
||||||
```
|
|
||||||
http://localhost:5173/?state=http://other-server.com/state.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Après cela, l'éditeur chargera directement le fichier.
|
|
|
@ -1,449 +0,0 @@
|
||||||
> TL;DR
|
|
||||||
> Behaviors.ts définis les comportements qui sont utilisés.
|
|
||||||
|
|
||||||
> Actuellement sont activés par défaut :
|
|
||||||
> - Corps rigide (simple) : Est restraint dans le parent
|
|
||||||
> - Ancrage : Impose la priorité de position et de taille
|
|
||||||
> - Flex : se redimensionne automatiquement
|
|
||||||
|
|
||||||
> Désactivés:
|
|
||||||
> - Corps rigide (complet) : Est restraint par les parents et par ses voisins (fonctionne mais pas intuitif)
|
|
||||||
> - Poussé : quand un conteneur est ajouté au bout de la bande filante, pousse tous les conteneurs à sa gauche (fonctionne mais pas intuitif)
|
|
||||||
> - Swap : échange de place avec un autre conteneur lorsqu'ils sont superposés (buggué ne pas utiliser)
|
|
||||||
|
|
||||||
# Comportements des conteneurs
|
|
||||||
|
|
||||||
Ce document traite des comportements spéciaux et uniques qu'un conteneur peut avoir.
|
|
||||||
|
|
||||||
Chaque comportement est documenté dans une section avec 3 sous-sections :
|
|
||||||
- Règles
|
|
||||||
- Applications
|
|
||||||
- Références de code et algorithmes
|
|
||||||
|
|
||||||
|
|
||||||
# Comportement par défaut
|
|
||||||
|
|
||||||
Le comportement par défaut est le panneau flottant. Il peut se déplacer et se redimensionner mais pas ses frères et soeurs.
|
|
||||||
|
|
||||||
|
|
||||||
## Règles
|
|
||||||
|
|
||||||
Le comportement par défaut n'a pas de règles particulières qui s'appliquent à lui-même.
|
|
||||||
|
|
||||||
Cependant, il a une règle commune pour tout comportement qui s'applique à ses enfants.
|
|
||||||
|
|
||||||
Il s'agit d'appliquer les comportements spéciaux de ses enfants (rigide ou ancré).
|
|
||||||
|
|
||||||
|
|
||||||
## Applications
|
|
||||||
|
|
||||||
The default behavior is important to have a good user experience when adding object.
|
|
||||||
|
|
||||||
The golden rule is to never oppose the user which is why we don't want to applies rigid body by default
|
|
||||||
as it can block the addition of container.
|
|
||||||
Allowing freedom of movement can help for better precision if not the same as the rigid property.
|
|
||||||
|
|
||||||
An example would be trying to overlap an element in order to use it as a layer.
|
|
||||||
|
|
||||||
|
|
||||||
## Références de code et algorithmes
|
|
||||||
|
|
||||||
Dans le module `ContainerOperations.ts` dans les fonctions suivantes :
|
|
||||||
- `OnPropertyChange()`
|
|
||||||
|
|
||||||
et dans le module `AddContainer.ts` dans `AddContainer()`,
|
|
||||||
|
|
||||||
il utilise la fonction `ApplyBehaviors` du module `Behaviors.ts` pour appliquer les comportements spéciaux de ses enfants.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Comportement du corps rigide
|
|
||||||
|
|
||||||
Le comportement de corps rigide est un comportement spécial
|
|
||||||
qui permet de restreindre un conteneur dans un espace donné.
|
|
||||||
|
|
||||||
|
|
||||||
## Règles
|
|
||||||
|
|
||||||
Les principales règles sont :
|
|
||||||
- Le conteneur rigide doit être maintenu à l'intérieur de son conteneur parent.
|
|
||||||
- Le conteneur rigide doit se trouver à l'intérieur d'un espace non alloué de son parent. C'est-à-dire qu'il ne peut pas se superposer à un autre frère ou sœur.
|
|
||||||
|
|
||||||
|
|
||||||
## Applications
|
|
||||||
|
|
||||||
Ce comportement a de nombreuses applications. Principalement sur les recalculs.
|
|
||||||
|
|
||||||
Vous pouvez vouloir redimensionner/déplacer rapidement et être certain qu'il ne déborde pas de son parent.
|
|
||||||
|
|
||||||
Vous pouvez vouloir redimensionner son parent et faire en sorte qu'il redimensionne ses enfants.
|
|
||||||
|
|
||||||
Vous pouvez vouloir que les frères et sœurs interagissent les uns avec les autres.
|
|
||||||
|
|
||||||
|
|
||||||
## Références de code et algorithmes
|
|
||||||
|
|
||||||
Son algorithme peut être un peu compliqué en raison des nombreux cas d'utilisation.
|
|
||||||
|
|
||||||
|
|
||||||
### Première règle
|
|
||||||
|
|
||||||
Commençons par la première règle : *Le conteneur rigide doit être maintenu à l'intérieur de son conteneur parent*.
|
|
||||||
|
|
||||||
Dans le fichier `RigidBodyBehaviors.ts`, voyez `constraintBodyInsideParent()` et `constraintBodyInsideSpace()`.
|
|
||||||
|
|
||||||
Comme vous pouvez le voir, `constraintBodyInsideParent()` n'est qu'une enveloppe pour `constraintBodyInsideSpace()`, donc étudions juste cette dernière fonction.
|
|
||||||
|
|
||||||
C'est un problème simple de deux rectangles.
|
|
||||||
|
|
||||||
Afin de restreindre l'enfant à son parent,
|
|
||||||
nous devons d'abord savoir si l'enfant n'est pas plus grand que son parent.
|
|
||||||
|
|
||||||
Si c'est le cas, il suffit de placer l'enfant au début et de lui faire prendre la taille complète de son parent.
|
|
||||||
|
|
||||||
Si ce n'est pas le cas, nous devons vérifier si l'enfant est hors limites (en dehors de son parent). Et si c'est le cas, nous devons le ramener à l'intérieur.
|
|
||||||
|
|
||||||
Pour vérifier s'il est plus grand que son parent, il suffit de comparer leurs tailles : `childWidth > parentWidth` et verticalement `childHeight > parentHeight`.
|
|
||||||
|
|
||||||
Si c'est faux, nous devons vérifier la sortie de l'objet, vérifier pour x (et y) : `child.x < parent.x` pour le côté gauche ou `child.x + child.width > parent.x + parent.width` pour le côté droit. Nous ne voulons pas non plus de chevauchement, c'est pourquoi nous utilisons `child.width`.
|
|
||||||
|
|
||||||
La condition est également équivalente à `child.x > parent.x + parent.width - child.width` qui pourrait être plus logique puisque l'espace requis doit être plus petit à cause de la taille de l'enfant.
|
|
||||||
|
|
||||||
|
|
||||||
Dans mon algorithme, j'ai décidé de les placer près du bord où ils sont sortis de la limite :
|
|
||||||
|
|
||||||
```
|
|
||||||
left oob: child.x = parent.x
|
|
||||||
right oob: child.x = parent.x + parent.width - child.width
|
|
||||||
```
|
|
||||||
|
|
||||||
Pseudo-code :
|
|
||||||
|
|
||||||
```c
|
|
||||||
constraintBodyInsideSpace(child, parent) {
|
|
||||||
if (child is bigger than parent) {
|
|
||||||
if (child is larger) {
|
|
||||||
set child x and width;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (child is taller) {
|
|
||||||
set child y and height;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (child is to the left of parent) {
|
|
||||||
set child x at the left side of parent
|
|
||||||
}
|
|
||||||
|
|
||||||
if (child is to the right of parent) {
|
|
||||||
set child x at the right side of parent
|
|
||||||
}
|
|
||||||
|
|
||||||
if (child is higher than parent) {
|
|
||||||
set child y at the top of parent
|
|
||||||
}
|
|
||||||
|
|
||||||
if (child is lower than parent) {
|
|
||||||
set child y at the bottom of parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Deuxième règle
|
|
||||||
|
|
||||||
La deuxième règle est la plus importante et la plus compliquée car elle doit interagir avec ses frères et sœurs.
|
|
||||||
|
|
||||||
*Le conteneur rigide doit se trouver dans un espace non alloué de son parent. Ce qui signifie qu'il ne peut pas chevaucher un autre frère ou une autre soeur.
|
|
||||||
|
|
||||||
Définissons d'abord ce qu'est un *espace* : un *espace* est la largeur d'un conteneur. Ce qui signifie donc que la règle ne s'applique que sur la vue horizontale. Pour simplifier la chose, cela signifie également que nous ne devons travailler que sur une seule dimension.
|
|
||||||
|
|
||||||
Pour résoudre ce problème, comme pour le parent, nous pourrions utiliser la détection de collision entre ses frères et sœurs. Cependant, cela pourrait être très lent car le pire scénario est un produit cartésien : O(n2). En effet, pour chaque conteneur, nous devons rechercher les autres conteneurs qui entrent en collision avec lui. Lorsqu'il entre en collision, nous devons le déplacer et recommencer la recherche.
|
|
||||||
|
|
||||||
Rappelez-vous, cette règle est appliquée chaque fois que vous changez une propriété du conteneur, c'est le *lag*. Nous ne pouvons pas nous permettre des boucles inefficaces.
|
|
||||||
|
|
||||||
Utilisons un "système d'espace" qui a des "conteneurs" qui ne peuvent pas "se chevaucher".
|
|
||||||
|
|
||||||
La mémoire.
|
|
||||||
|
|
||||||
La mémoire, la RAM, l'espace du disque dur, gèrent leur espace par un système d'adresses et de morceaux d'espaces (mots, octets...). Dans notre cas, nous n'avons pas de morceaux d'espaces, mais des nombres flottants (qui peuvent être un casse-tête à cause des cas limites).
|
|
||||||
|
|
||||||
Ce système est particulièrement utile car il se souvient de l'espace utilisé après chaque itération d'allocation, ce qui signifie que nous pouvons savoir exactement quand il n'y a plus d'espace à l'intérieur du parent et quand un conteneur doit se redimensionner afin de tenir à l'intérieur.
|
|
||||||
|
|
||||||
Bien, commençons l'algorithme. Voir `constraintBodyInsideUnallocatedWidth()`, `getAvailableWidths()` et `getAvailableWidthsTwoLines()` dans `RigidBodyBehaviors.ts` pour les références de l'implémentation.
|
|
||||||
|
|
||||||
Nous avons initialement tout l'espace disponible : laissez `space` être cet espace disponible dans le parent.
|
|
||||||
|
|
||||||
`space` est un pointeur, donc au début il a `0` à son adresse de pointeur et `parent.width` comme espace.
|
|
||||||
|
|
||||||
Pour simplifier l'algorithme lors de l'ajout d'un conteneur, comparons cela à manger une bûche de Noël.
|
|
||||||
|
|
||||||
Comme pour le gâteau, il faut le couper et en prendre une part.
|
|
||||||
|
|
||||||
Il y a 5 façons possibles de le couper :
|
|
||||||
- Ne pas manger le gâteau (on préfère peut-être manger un autre gâteau/une autre part)
|
|
||||||
- Manger le gâteau entier
|
|
||||||
- Couper le gâteau à gauche
|
|
||||||
- Couper le gâteau sur le côté droit
|
|
||||||
- Couper le gâteau au milieu
|
|
||||||
|
|
||||||
Ne pas couper le gâteau signifie rendre le gâteau entier tel quel.
|
|
||||||
|
|
||||||
Manger le gâteau entier, c'est ne rien rendre.
|
|
||||||
|
|
||||||
Couper à gauche ou à droite signifie laisser une partie.
|
|
||||||
|
|
||||||
Couper au milieu signifie laisser deux parts.
|
|
||||||
|
|
||||||
Après avoir coupé le gâteau, *pendant* qu'il en reste encore, on peut continuer l'opération. (c'est une boucle *pour* dans le code cependant pour des raisons de syntaxe)
|
|
||||||
|
|
||||||
Cependant, après avoir servi les frères et sœurs, nous pouvons remarquer qu'il n'en reste plus pour nous. Nous nous mettons en colère, nous *jetons* une crise de colère.
|
|
||||||
|
|
||||||
```c
|
|
||||||
// si vous n'avez pas compris la blague
|
|
||||||
if (there no more cake) {
|
|
||||||
throw tantrum
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Attends, il y a vraiment du gâteau !
|
|
||||||
|
|
||||||
Mais il est laissé en plusieurs morceaux, nous allons juste prendre le plus proche qui correspond à notre faim.
|
|
||||||
|
|
||||||
S'il y en a un qui correspond à notre faim, prenons-le !
|
|
||||||
|
|
||||||
Pourtant ! Il y a des gâteaux mais aucun ne correspond à notre faim. Mais nous avons une acceptation *minimale*, soyons humbles, nous prendrons quand même la petite part. D'ailleurs, prendre plusieurs parts serait mauvais pour nous. Néanmoins, si mon acceptation *minimale* devait être supérieure à ce qui reste, je lancerais un *avertissement* pour la prochaine fois.
|
|
||||||
|
|
||||||
|
|
||||||
Traduisons cela en pseudo-code.
|
|
||||||
|
|
||||||
Commençons par obtenir les espaces disponibles :
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
getAvailableSpaces(parent, me) {
|
|
||||||
spaces = [{ x: 0, size: parent.width }]
|
|
||||||
|
|
||||||
let i = 0
|
|
||||||
while (spaces.length > 0 and i < parent.length) {
|
|
||||||
let sibling = parent.children[i];
|
|
||||||
|
|
||||||
if (sibling is me or is neither rigid nor is anchor) {
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let spacesLeft be an array
|
|
||||||
|
|
||||||
foreach(space in spaces) {
|
|
||||||
spacesLeftOfSpace = allocate(sibling, space);
|
|
||||||
spacesLeft.concat(spacesLeftOfSpace)
|
|
||||||
}
|
|
||||||
|
|
||||||
spaces = spacesLeft
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
return spaces;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Pour allouer :
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
allocate(sibling, space) {
|
|
||||||
if (sibling is not overlapping the space) {
|
|
||||||
return [space]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sibling overlap the space entirely) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sibling overlap at the left side) {
|
|
||||||
return [{
|
|
||||||
x: right side of sibling
|
|
||||||
size: right side of space - right side of sibling // "cut the left part"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sibling overlap at the right side) {
|
|
||||||
return [{
|
|
||||||
x: left side of space
|
|
||||||
size: leftSide of sibling - leftSide of space
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (sibling overlap in the middle)
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
x: left side of space
|
|
||||||
size: left side of sibling - left side of space
|
|
||||||
},
|
|
||||||
{
|
|
||||||
x: right side of sibling
|
|
||||||
size: right side of space - right side of sibling
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Enfin pour l'appelant :
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
constraintBodyInsideUnallocatedWidth(parent, container) {
|
|
||||||
spaces = getAvailableSpaces(parent, container)
|
|
||||||
if (there is no more spaces) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
spaces = sort spaces by closest from the middle of container
|
|
||||||
|
|
||||||
spaceFound = spaces.find(space that fit container.space)
|
|
||||||
|
|
||||||
if (no spaceFound) {
|
|
||||||
|
|
||||||
spaceFound = spaces.find(space that fit container.minimumSpace)
|
|
||||||
|
|
||||||
if (no spaceFound) {
|
|
||||||
show warning
|
|
||||||
return container
|
|
||||||
}
|
|
||||||
|
|
||||||
set container x and width to make it fit
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
constraintBodyInsideSpace(container, spaceFound)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Cet algorithme est génial, mais certains problèmes subsistent :
|
|
||||||
- Trouver le plus proche prend O(nlogn), n étant le nombre d'espaces. Ce n'est généralement pas mauvais puisque l'objectif du corps rigide est de **remplir** l'espace. Mais il y a toujours un très mauvais cas de figure.
|
|
||||||
- Il y a 2 recherches pour l'espace, même problème mais le tri précédent aide à le rendre plus rapide pour les meilleurs cas.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Comportement d'ancrage
|
|
||||||
|
|
||||||
Le comportement d'ancrage permet à un conteneur d'être prioritaire sur ses frères et sœurs.
|
|
||||||
|
|
||||||
|
|
||||||
## Règles
|
|
||||||
|
|
||||||
Il a les règles suivantes :
|
|
||||||
- Le conteneur ne peut pas être déplacé par un autre conteneur frère ou sœur rigide.
|
|
||||||
- Le conteneur ne peut pas être redimensionné par un autre conteneur de la même famille.
|
|
||||||
- Le conteneur ne peut pas chevaucher un autre conteneur rigide de la même famille :
|
|
||||||
- les conteneurs qui se chevauchent sont déplacés vers l'espace/largeur disponible le plus proche
|
|
||||||
- ou redimensionnés lorsqu'il n'y a plus d'autre espace disponible que le leur
|
|
||||||
- ou perdent leurs propriétés de corps rigide lorsqu'il n'y a absolument plus d'espace disponible (même pas le leur).
|
|
||||||
|
|
||||||
|
|
||||||
## Applications
|
|
||||||
|
|
||||||
Le gain de priorité permet de s'assurer qu'un objet rigide ne bougera pas, quoi qu'il arrive, et qu'il bougera absolument, peu importe ce qui se trouve sous lui.
|
|
||||||
|
|
||||||
|
|
||||||
## Références de code et algorithmes
|
|
||||||
|
|
||||||
Bien qu'il y ait plusieurs règles appliquées à ce comportement, la plupart d'entre elles ne sont que des conditions.
|
|
||||||
|
|
||||||
Ces trois règles :
|
|
||||||
- Le conteneur ne peut pas être déplacé par d'autres conteneurs frères et sœurs rigides.
|
|
||||||
- Le conteneur ne peut pas être redimensionné par un autre conteneur de la même famille.
|
|
||||||
- Il ne peut pas chevaucher un autre conteneur rigide de la même famille.
|
|
||||||
|
|
||||||
Il peut être traduit en un seul : "Le conteneur est un espace alloué, donc tout conteneur en contact se déplacera ou sera redimensionné".
|
|
||||||
|
|
||||||
Ce qui signifie que l'application des propriétés du corps rigide du frère ou de la sœur appliquera également cette règle. La différence entre le comportement par défaut et le comportement d'ancrage est que le conteneur d'ancrage sera pris en compte lors du calcul de l'espace disponible.
|
|
||||||
|
|
||||||
Vous pouvez considérer le conteneur par défaut comme un panneau flottant et le conteneur d'ancrage comme un mur. Vous pouvez passer sous le panneau flottant mais pas par-dessus le mur.
|
|
||||||
|
|
||||||
Pour optimiser l'algorithme, il suffit de trouver les frères et sœurs qui se chevauchent puisque l'ancre n'est pas appliquée à ceux qui ne sont pas en collision.
|
|
||||||
|
|
||||||
Pseudo-code :
|
|
||||||
|
|
||||||
```c#
|
|
||||||
ImposePosition(container) {
|
|
||||||
let rigidBodies be the rigid siblings that are not anchor
|
|
||||||
let overlappingRigidBodies be the overlapping rigid siblings of rigidBodies
|
|
||||||
|
|
||||||
foreach(overlappingRigidBody of overlappingRigidBodies) {
|
|
||||||
constraintBodyInsideUnallocatedWidth(overlappingRigidBody)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
De plus, nous devons modifier `getAvailableSpaces()` pour qu'il prenne en compte les conteneurs d'ancrage.
|
|
||||||
|
|
||||||
|
|
||||||
# Le comportement Flex
|
|
||||||
|
|
||||||
Le comportement flex est un comportement qui modifie à la fois la position et la taille d'un conteneur tout en interagissant avec ses frères et sœurs.
|
|
||||||
|
|
||||||
|
|
||||||
## Application
|
|
||||||
|
|
||||||
Le comportement flex est utile pour le redimensionnement automatique d'un conteneur en fonction de son parent et de ses frères et sœurs.
|
|
||||||
|
|
||||||
|
|
||||||
## Références de code et algorithmes
|
|
||||||
|
|
||||||
Tout d'abord, nous devons déterminer ce qu'est un espace flexible. Un espace flexible est la zone située entre deux objets d'ancrage, un objet d'ancrage pouvant être un conteneur ancré ou les bords du conteneur parent.
|
|
||||||
|
|
||||||
L'algorithme pour trouver un espace flexible est d'itérer sur les conteneurs et de créer un groupe à chaque fois qu'une ancre est trouvée (voir `GetFlexibleGroups`).
|
|
||||||
|
|
||||||
Ensuite, pour appliquer le flex dans ces groupes, il y a trois scénarios principaux :
|
|
||||||
- Il n'y a pas assez d'espace même si l'on comprime tous les conteneurs à leur largeur minimale.
|
|
||||||
- Il y a suffisamment d'espace pour que tous les conteneurs aient la même taille.
|
|
||||||
- Il n'y a pas assez d'espace pour que tous les conteneurs aient la même taille, mais nous pouvons les comprimer.
|
|
||||||
|
|
||||||
Dans le premier scénario, il suffit de renvoyer une erreur.
|
|
||||||
|
|
||||||
Dans le second scénario, nous devons changer la taille de chaque conteneur pour qu'ils aient la même largeur. Ainsi, `wantedWidth = sum(space_width) / n`. La position sera alors `containerIndex * wantedWidth`.
|
|
||||||
|
|
||||||
Enfin, dans le troisième scénario, il existe plusieurs façons de procéder. Nous pourrions simplement appliquer le comportement de poussée tout en redimensionnant un par un chaque conteneur jusqu'à ce qu'ils s'adaptent, mais cela peut ne pas avoir de solution et coûtera un total de O(n2) de complexité.
|
|
||||||
|
|
||||||
L'autre moyen est d'utiliser la programmation linéaire puisque nous pouvons traduire ce problème comme un programme linéaire : la fonction objectif serait de maximiser la largeur de tous les conteneurs sans déborder du conteneur parent `max sum(width_i) <= wantedWidth`. Les inéquations supplémentaires sont les contraintes de largeur minimale et maximale.
|
|
||||||
|
|
||||||
L'algorithme que nous allons utiliser est l'algorithme du simplexe. Il s'agit d'un algorithme populaire dans le domaine de l'optimisation mathématique.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Comportement de poussée
|
|
||||||
|
|
||||||
Lorsqu'on a un conteneur à droite et qu'on en ajoute un nouveau à sa droite, on pousse ce conteneur à gauche s'il y a assez de place.
|
|
||||||
|
|
||||||
|
|
||||||
## Application
|
|
||||||
|
|
||||||
Permet d'ajouter un nouveau conteneur sans empiéter sur les conteneurs existants lorsqu'il y a suffisamment d'espace à leur gauche.
|
|
||||||
|
|
||||||
|
|
||||||
## Références de code et algorithmes
|
|
||||||
|
|
||||||
Afin de pousser, nous devons trouver les trous à la gauche du nouveau conteneur. Lorsque nous trouvons un trou, nous devons pousser le conteneur à la droite du trou jusqu'à ce qu'il n'y ait plus de place.
|
|
||||||
|
|
||||||
Pour trouver un trou, nous devons itérer par paire (deux par deux) de la droite vers la gauche.
|
|
||||||
|
|
||||||
Pour pousser le conteneur de droite, nous pouvons simplement soustraire sa position de la taille du trou.
|
|
||||||
|
|
||||||
Lorsque l'espace restant est égal à 0, nous pouvons arrêter de pousser.
|
|
||||||
|
|
||||||
Comme vous pouvez le constater, cet algorithme est très lent et peut coûter jusqu'à O(n2). Et comme les calculs se chevauchent, il est également possible qu'en raison de la précision de la virgule flottante, il reste un espace minuscule.
|
|
||||||
|
|
||||||
|
|
||||||
# Comportement de swap
|
|
||||||
|
|
||||||
Lorsque deux conteneurs sont en collision, la position des deux conteneurs est permutée.
|
|
||||||
|
|
||||||
|
|
||||||
## Application
|
|
||||||
|
|
||||||
Lorsqu'il n'y a plus de place et que le corps rigide est activé, pour déplacer un conteneur dans la ligne, nous pouvons permuter deux conteneurs en augmentant x.
|
|
||||||
|
|
||||||
|
|
||||||
## Références de code et algorithmes
|
|
||||||
|
|
||||||
Le code pour ce comportement est très simple :
|
|
||||||
- Lorsque deux conteneurs se chevauchent, on permute leur position.
|
|
BIN
docs/#Project/Pages/ComponentStructure.drawio
(Stored with Git LFS)
BIN
docs/#Project/Pages/ComponentStructure.drawio
(Stored with Git LFS)
Binary file not shown.
|
@ -1,55 +0,0 @@
|
||||||
> TL;DR: Menu.tsx est le composant qui affiche et qui traite l'event contextmenu sur la page
|
|
||||||
> InitActions de Editor.tsx prépare le modèle pour Menu.tsx
|
|
||||||
> ContextMenuActions définient les actions de l'API
|
|
||||||
|
|
||||||
# Context Menu
|
|
||||||
|
|
||||||
Ce document présente comment le menu contextuel est implémenté.
|
|
||||||
|
|
||||||
|
|
||||||
# Event listener
|
|
||||||
|
|
||||||
Pour implémenter le menu contextuel, il faut en premier ajouter un event listener sur `contextmenu`.
|
|
||||||
|
|
||||||
Cela se fait dans le composant `Menu.tsx` via la fonction `UseMouseEvents()`.
|
|
||||||
|
|
||||||
Elle équipe plusieurs events sur la page en plus de `contextmenu` afin de fermer correctement lorsque l'on clique ailleurs.
|
|
||||||
|
|
||||||
Il n'existe donc qu'un seul menu contextuel pour toute la page.
|
|
||||||
|
|
||||||
|
|
||||||
# Affichage du contenu
|
|
||||||
|
|
||||||
On a vu que `Menu.tsx` s'occupe de traiter l'event `contextmenu`. Regardons maintenant comment elle affiche le menu.
|
|
||||||
|
|
||||||
Ce composant utilise une hashmap `actions: Map<string, IMenuAction[]>` pour lire les différentes actions possible, la clé servant d'identifiant et de pattern.
|
|
||||||
|
|
||||||
En effet, la fonction `AddClassSpecificActions`, obtenant le composant html lit les *classes* et vérifie s'il est présent dans le dictionnaire avec `props.actions.get(className)`.
|
|
||||||
|
|
||||||
S'il est présent, alors on itère sur les différentes actions possible pour cette classe pour ajouter des `MenuItem` représentant une ligne du menu contextuel. Chaque `MenuItem` possède un fonction qui sera exécutée lorsque la ligne est cliquée. Il possède également un texte, un titre qui sera affiché si le curseur survole la ligne, et, optionnellement, un raccourci qui sera affiché à droite de la ligne.
|
|
||||||
|
|
||||||
En plus des actions de classes, il y a aussi des actions universelles comme le `undo` ou `redo` qui sont affichées n'importe où on clique. Celle-ci ont pour id `''`, une chaine de caractères vide. On itère sur cette liste d'action pour ajouter les lignes.
|
|
||||||
|
|
||||||
L'ordre d'affichage est donc défini :
|
|
||||||
|
|
||||||
1) actions de classes
|
|
||||||
2) actions universelles
|
|
||||||
|
|
||||||
L'ordre des classes est l'ordre d'ajout dans le dictionnaire.
|
|
||||||
|
|
||||||
|
|
||||||
# Création du dictionnaire
|
|
||||||
|
|
||||||
Parlons de l'initialisation du dictionnaire.
|
|
||||||
|
|
||||||
Le composant `Menu` est utilisé dans `Editor`. C'est aussi ici que l'ont crée le dictionnaire.
|
|
||||||
|
|
||||||
La fonction `InitActions` s'occupe d'enrichîr le dictionnaire des différentes actions.
|
|
||||||
|
|
||||||
On peut voir qu'au début de la fonction que les actions universelles y sont initialisées. Ensuite, les actions spécifiques aux classes y sont ajoutés avec au début les actions définies dans SVGLayoutDesigner et après, les actions définies dans la configuration de l'API (donc `Diviser remplissage par exemple`).
|
|
||||||
|
|
||||||
Chaque action provenant de l'API utilise la fonction `GetAction` du fichier utilitaire `ContextMenuActions.ts`.
|
|
||||||
|
|
||||||
Cette fonction équipe l'action qui sera exécutée d'une autre fonction appelé `SetContainerList`. Cette autre fonction s'occupe de faire un appel REST vers l'api sur le point d'accès `Configuration.APIConfiguration.apiSetContainerListUrl` ou, si elle n'est pas définie, sur `VITE_API_SET_CONTAINER_LIST_URL`.
|
|
||||||
|
|
||||||
Cela veut dire que pour l'instant toutes les actions provenant de l'API a pour but de remplacer, d'ajouter ou de supprimer des conteneurs.
|
|
|
@ -1,220 +0,0 @@
|
||||||
|
|
||||||
> TL;DR: Ce projet utilise un dictionnaire pour représenter un arbre/graphe
|
|
||||||
|
|
||||||
# Préface
|
|
||||||
|
|
||||||
Ce document explique la structure de données utilisée pour les conteneurs.
|
|
||||||
|
|
||||||
|
|
||||||
# Graphe
|
|
||||||
|
|
||||||
Avant de parler de la structure principale il faut comprendre ce qu'est un graphe.
|
|
||||||
|
|
||||||
Pourquoi ? Parce que la plupart des algorithmes utilisés dans ce projets découle de la théorie des graphes.
|
|
||||||
|
|
||||||
> Definition : Un graphe est une structure composée d'objets dans laquelle certaines paries d'objets sont en relation. (...) On distingue les graphes non orientés et les graphes orientés. (src: wikipedia)
|
|
||||||
|
|
||||||
Ces objets sont appelé Container dans notre projet. Mais dans cette documentation, nous appelerons cela un *Noeud*
|
|
||||||
|
|
||||||
En programmation un graphe peut être représenter par des divers structure de données (Vector/List/Array, Dictionaries, Tree, Pointer etc.).
|
|
||||||
|
|
||||||
Dans notre projet, nous utilisons les pointeurs, les listes et les dictionnaires.
|
|
||||||
|
|
||||||
# Structures de données
|
|
||||||
|
|
||||||
## Pointeur
|
|
||||||
|
|
||||||
Un graphe représenté par des pointeurs peut être représenté de cette manière.
|
|
||||||
|
|
||||||
Un exemple de graphe peut être représenté de la manière suivante :
|
|
||||||
```
|
|
||||||
type Node = {
|
|
||||||
child: Node
|
|
||||||
}
|
|
||||||
|
|
||||||
A: Node = {
|
|
||||||
child: B
|
|
||||||
}
|
|
||||||
|
|
||||||
B: Node = {
|
|
||||||
child: A
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Donc le graphe est simplement `A <-> B`
|
|
||||||
|
|
||||||
Ceci est un graphe cyclique que nous ne verrons pas souvent dans ce projet.
|
|
||||||
En effet, nous avons plutôt quelque chose comme ça:
|
|
||||||
|
|
||||||
```
|
|
||||||
type Node = {
|
|
||||||
parent: Node
|
|
||||||
child: Node
|
|
||||||
}
|
|
||||||
|
|
||||||
A: Node = {
|
|
||||||
parent: null
|
|
||||||
child: B
|
|
||||||
}
|
|
||||||
|
|
||||||
B: Node = {
|
|
||||||
parent: A
|
|
||||||
child: null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Cela permet de représenter un graphe avec deux liens au lieu d'un seul et d'avoir une information de **hiérarchie**.
|
|
||||||
On le représente donc comme cela: `A -> B`. Donc par définition, un graphe orienté.
|
|
||||||
|
|
||||||
|
|
||||||
## Liste
|
|
||||||
|
|
||||||
Un noeud dans un graphe peut avoir plusieurs voisins/enfants. On peut simplement représenter cela par une *liste* de pointeurs.
|
|
||||||
|
|
||||||
Reprenons l'exemple précédent :
|
|
||||||
|
|
||||||
```
|
|
||||||
type Node = {
|
|
||||||
parent: Node
|
|
||||||
children: Node[]
|
|
||||||
}
|
|
||||||
|
|
||||||
A: Node {
|
|
||||||
parent: null,
|
|
||||||
children: [
|
|
||||||
B,
|
|
||||||
C,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
B: Node {
|
|
||||||
parent: A
|
|
||||||
children: []
|
|
||||||
}
|
|
||||||
|
|
||||||
C: Node {
|
|
||||||
parent: A
|
|
||||||
children: []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Ici, A contient donc deux enfants dans une liste: B et C.
|
|
||||||
On peut représenter cela de la manière suivante:
|
|
||||||
|
|
||||||
```
|
|
||||||
A -> B
|
|
||||||
A -> C
|
|
||||||
```
|
|
||||||
|
|
||||||
A partir d'ici vous pouvez voir ce qu'on appelle un *arbre*. Cette structure de données d'arbre est la base fondamentale dont repose les Conteneurs.
|
|
||||||
|
|
||||||
Pour des examples réels, vous pouvez voir qu'un livre peut être représenté comme des arbres :
|
|
||||||
|
|
||||||
> Un livre contiennent des pages, et des pages contiennent des lignes, des lignes contiennent des lettres, etc.
|
|
||||||
|
|
||||||
|
|
||||||
## Dictionnaire
|
|
||||||
|
|
||||||
### Contexte
|
|
||||||
|
|
||||||
Normalement l'arbre devrait être suffisant pour développer SVGLayoutDesigner mais on va s'en rendre compte que ce n'est pas la meilleure solution.
|
|
||||||
|
|
||||||
Depuis le début du projet, la structure utilisée était celle d'un arbre avec relation parent-enfant comme précédemment montré.
|
|
||||||
|
|
||||||
Mon on a remarqué tardivement que cela commençait à avoir un coût très important sur toutes les opérations d'arbres. En effet, car toutes les opérations d'arbre repose sur une fonction principale: la recherche.
|
|
||||||
|
|
||||||
Pour trouver un noeud dans un arbre, il faut parcours l'arbre grâce à des algorithme de parcours d'arbre appelé Depth-First Search et Breath-First Search, qui sont d'excellent algorithme de recherche dans un graphe (oui! car ça ne se limite pas aux arbres).
|
|
||||||
|
|
||||||
Cependant, cela possède un coût au pire cas de O(N). Il est possible que le noeud que le cherche se trouve tout à la fin de l'arbre et ces deux algorithmes n'aident pas du tout dans ce cas là. Imaginez 1000 conteneurs, et on veut juste changer la position d'un seul conteneur. Cela veut dire qu'il faudrait parcourir les 1000 conteneurs !
|
|
||||||
|
|
||||||
Et c'est ce qui c'est passé jusqu'au commit `9f4796d0` (10 oct 2022). Faites une recherche globale de `FindContainerById`, cela montrera toutes les opérations impactées par l'arbre. Au jour de l'écriture de ce document, il y a environ 60 fonctions utilisant cette méthode utilisée dans divers opérations actives ou passives. C'est très COUTEUX !
|
|
||||||
|
|
||||||
Réduisons cela à O(1), le meilleur cas. Pour cela nous allons utiliser ce qu'on appelle un dictionnaire ou aussi appelé aussi HashMap.
|
|
||||||
|
|
||||||
|
|
||||||
### Qu'est-ce qu'un dictionnaire ?
|
|
||||||
|
|
||||||
> Un dictionnaire est une structure de données qui implémente un tableau associatif. C'est un type de données abstrait qui permet l'association de clé-valeur.
|
|
||||||
|
|
||||||
Exemples :
|
|
||||||
|
|
||||||
```
|
|
||||||
Dict {
|
|
||||||
key: value
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
Dict = [
|
|
||||||
[key, value],
|
|
||||||
...
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mise en pratique
|
|
||||||
|
|
||||||
On veut accéder aux conteneurs rapidement, nous allons sauvegarder chaque conteneur dans le dictionnaire. Donc dès que l'ont crée un conteneur, on ajoute son id pour clé et le conteneur en tant que valeur. Dès qu'on le supprime, on supprime sa clé.
|
|
||||||
|
|
||||||
Cependant pour éviter la duplication de données, il faut aussi changer comment on représente l'arbre. Il n'est plus nécessaire de sauvegarder la référence de l'enfant en tant qu'enfant, on peut juste utiliser son id.
|
|
||||||
|
|
||||||
Ainsi on obtient la structure suivante utilisée dans le projet :
|
|
||||||
|
|
||||||
```
|
|
||||||
type Conteneur = {
|
|
||||||
parent: string
|
|
||||||
children: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const dict = new Map<string, Conteneur>();
|
|
||||||
|
|
||||||
dict = {
|
|
||||||
"conteneur1": Conteneur {
|
|
||||||
parent: null,
|
|
||||||
children: [
|
|
||||||
"conteneur2",
|
|
||||||
"conteneur3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"conteneur3": Conteneur {
|
|
||||||
parent: "conteneur1",
|
|
||||||
children: []
|
|
||||||
},
|
|
||||||
"conteneur2": Conteneur {
|
|
||||||
parent: "conteneur1",
|
|
||||||
children: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Ainsi, `FindContainerById` utilisant précédemment depth-first search, peut être refactoré par une seule ligne :
|
|
||||||
|
|
||||||
```ts
|
|
||||||
function FindContainerById(
|
|
||||||
containers: Map<string, Conteneur>,
|
|
||||||
id: string
|
|
||||||
): Conteneur {
|
|
||||||
return containers.get(id)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Et maintenant déplacer un seul conteneur ne coûte plus aussi cher pas vrai ?
|
|
||||||
|
|
||||||
# FAQ
|
|
||||||
|
|
||||||
Pourquoi est-ce important d'utiliser un dictionnaire dans notre cas ?
|
|
||||||
|
|
||||||
> Cela permet d'accéder un conteneur directement par une clé par exemple son id
|
|
||||||
|
|
||||||
|
|
||||||
Pourquoi ne pas l'utiliser tout le temps ?
|
|
||||||
|
|
||||||
> Car ce n'est pas très intuitif, on aime voir les arbres comme des arbres et pas comme des listes. Le coût est parfois suffisamment mineur pour ignorer cela.
|
|
||||||
|
|
||||||
|
|
||||||
Pourquoi ne pas utiliser l'arbre avec le dictionnaire en même temps ?
|
|
||||||
|
|
||||||
> Car dans notre projet, la sérialisation des données ne permet pas d'avoir deux instances à deux endroits différents. C'est pourquoi nous utilisons un *replacer*, pour supprimer les références de parents. Mais il serait difficile de faire cela pour tous les enfants, il est plus simple de supprimer entièrement l'arbre et de juste conserver le dictionnaire. Et puis, pourquoi dupliquer les données alors que l'on l'accéder avec un coût minimal O(1) avec juste le dictionnaire sans aucun coût supplémentaire ?
|
|
||||||
|
|
||||||
|
|
||||||
Et si je veux itérer sur tout les chassis ?
|
|
||||||
|
|
||||||
> Depth-first search et Breath-first search sont toujours valables. Il faut juste adapter légèrement l'algorithme pour qu'il lit le dictionnaire à chaque fois que l'on veut accéder aux enfants. Voir `MakeDFSIterator` ou `MakeBFSIterator` dans `src/utils/itertools.ts` pour l'exemple.
|
|
|
@ -1,50 +0,0 @@
|
||||||
# Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
.
|
|
||||||
├── .vscode/ VSCode config folder
|
|
||||||
│ ├── launch.json VSCode debug config
|
|
||||||
│ └── settings.json VSCode project config
|
|
||||||
├── csharp/ C# models and test project
|
|
||||||
├── docs/ Documentation folder
|
|
||||||
├── public/ Public folder in which the index.html
|
|
||||||
│ │ import its resources
|
|
||||||
│ ├── smartcomponent/ SmartComponent folder
|
|
||||||
│ └── workers/ Webworkers folder
|
|
||||||
├── src/ Source folder for the react app
|
|
||||||
│ ├── assets/ Assets folder in which the react app
|
|
||||||
│ │ import its resources
|
|
||||||
│ ├── Components/ Components folder
|
|
||||||
│ ├── Enums/ Enums folder
|
|
||||||
│ ├── Events/ API Events folder
|
|
||||||
│ ├── Interfaces/ Interface (+ types folder)
|
|
||||||
│ ├── test/ Setup folder for the tests
|
|
||||||
│ ├── tests/ Other tests + resources
|
|
||||||
│ ├── utils/ Utilities folder
|
|
||||||
│ ├── index.scss Tailwind CSS extends
|
|
||||||
│ ├── main.tsx Entrypoint for App injection
|
|
||||||
│ └── vite-env.d.ts Types for .env files
|
|
||||||
├── test-server/ Tests servers to test the API
|
|
||||||
│ ├── http.js Test server for bun.sh
|
|
||||||
│ └── node-http.js Test server for Node.js
|
|
||||||
├── .drone.yml Drone.io ci configuration
|
|
||||||
├── .env.development Vite development environment config
|
|
||||||
├── .env.production Vite production environment config
|
|
||||||
├── .env.test Vite test environment config
|
|
||||||
├── .eslintrc.cjs eslint config file
|
|
||||||
├── .gitattributes git-lfs config file
|
|
||||||
├── .gitignore git ignore config file
|
|
||||||
├── azure-pipelines.yml Azure Pipelines YAML config file
|
|
||||||
├── index.html HTML Page
|
|
||||||
├── package-lock.json Describe the node_modules tree for npm
|
|
||||||
├── package.json Node.JS config file
|
|
||||||
├── pnpm-lock.yaml Describe the node_modules tree for pnpm
|
|
||||||
├── postcss.config.cjs Postcss config file for SCSS processing
|
|
||||||
├── README.md
|
|
||||||
├── tailwind.config.cjs Tailwind CSS config file
|
|
||||||
├── tsconfig.json Typescript config file
|
|
||||||
├── tsconfig.node.json Typescript config file for Node modules
|
|
||||||
├── vite.config.ts Vite config file
|
|
||||||
└── vitest.config.ts Vitest config file
|
|
||||||
```
|
|
||||||
|
|
BIN
docs/#Project/Pages/SVGLD_Cotes.pdf
(Stored with Git LFS)
BIN
docs/#Project/Pages/SVGLD_Cotes.pdf
(Stored with Git LFS)
Binary file not shown.
BIN
docs/#Project/Pages/SVGLD_Cotes.pptx
(Stored with Git LFS)
BIN
docs/#Project/Pages/SVGLD_Cotes.pptx
(Stored with Git LFS)
Binary file not shown.
|
@ -1,159 +0,0 @@
|
||||||
Source: https://dev.azure.com/techformsa/SmartConfigurator/_wiki?pageId=122&friendlyName=Int%C3%A9grer-le-projet-en-tant-que-composant-dans-Modeler#
|
|
||||||
|
|
||||||
- [Préface](#préface)
|
|
||||||
- [Générer des fichiers de définition pour SmartModeler](#générer-des-fichiers-de-définition-pour-smartmodeler)
|
|
||||||
- [Customiser et build le projet (recommandé)](#customiser-et-build-le-projet-recommandé)
|
|
||||||
- [Configurer les options de build](#configurer-les-options-de-build)
|
|
||||||
- [Build le projet](#build-le-projet)
|
|
||||||
- [Télécharger les défauts](#télécharger-les-défauts)
|
|
||||||
- [Télécharger les ressources](#télécharger-les-ressources)
|
|
||||||
- [Utiliser dans modeler](#utiliser-dans-modeler)
|
|
||||||
|
|
||||||
# Préface
|
|
||||||
|
|
||||||
Ce document a pour but d'expliquer comment intégrer SVGLayoutDesigner dans un projet.
|
|
||||||
|
|
||||||
Le projet se compile avec `npm run build` grace à Vite.
|
|
||||||
Avec les fichiers générés dans `dist` nous allons créer un nouveau composant modeler par injection.
|
|
||||||
(Plus tard les fichiers "Release" seront fournies par azure-pipeline par artefacts).
|
|
||||||
|
|
||||||
Il utilise le framework JS React pour faire le rendu.
|
|
||||||
|
|
||||||
Les projets modeler vont communiquer avec le composant par [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent) et par REST via un webservice dans les options de build.
|
|
||||||
|
|
||||||
Il y deux manières de récupérer les builds du projets:
|
|
||||||
- Rebuild le projet (recommandé)
|
|
||||||
- Récupérer des prébuild
|
|
||||||
|
|
||||||
Mais avant tout cela il faut mettre à jour le fichier de types à fournir.
|
|
||||||
|
|
||||||
|
|
||||||
# Générer des fichiers de définition pour SmartModeler
|
|
||||||
|
|
||||||
Pré-requis : `typescript`, `python3`
|
|
||||||
|
|
||||||
Allez dans le répertoire `src/dts` et exécutez la commande suivante
|
|
||||||
|
|
||||||
```
|
|
||||||
npx tsc --project tsconfig.dts.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Ou si l'installation est globale
|
|
||||||
|
|
||||||
```
|
|
||||||
tsc --projet tsconfig.dts.json
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Ensuite, exécutez `python3` (ou `py` sous Windows) sur `generate_dts.py` :
|
|
||||||
|
|
||||||
```
|
|
||||||
python3 generate_dts.py SVGLD svgld.d.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Une définition sera générée sous la forme `svgld.d.ts` avec l'espace de nom `SVGLD`.
|
|
||||||
|
|
||||||
Copiez-le enfin dans `public/` et écrasez le fichier existant.
|
|
||||||
|
|
||||||
Lorsque vous ferez `npm run build`, ce fichier sera copié dans `build`.
|
|
||||||
|
|
||||||
|
|
||||||
# Customiser et build le projet (recommandé)
|
|
||||||
|
|
||||||
Customiser le build du projet permet de modifier les urls de l'API et de personnaliser des fonctionnalités.
|
|
||||||
|
|
||||||
## Configurer les options de build
|
|
||||||
|
|
||||||
Il y a deux fichiers principaux à configurer :
|
|
||||||
- `.env.production.local`: pour configurer les URLs d'API
|
|
||||||
- `src/utils/default.ts`: pour configurer les fonctionnalités
|
|
||||||
|
|
||||||
Copiez `.env.production` vers `.env.production.local` et modifiez-le comme bon vous semble :
|
|
||||||
|
|
||||||
```
|
|
||||||
VITE_API_FETCH_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/GetSVGLayoutConfiguration
|
|
||||||
VITE_API_SET_CONTAINER_LIST_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/SetContainerList
|
|
||||||
VITE_API_GET_FEEDBACK_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/GetFeedback
|
|
||||||
```
|
|
||||||
|
|
||||||
Vous pouvez modifiez `src/utils/default.ts` mais ne le committez pas.
|
|
||||||
|
|
||||||
## Build le projet
|
|
||||||
|
|
||||||
Lancer la commande `npm run build` dans le projet.
|
|
||||||
|
|
||||||
Les fichiers seront fournis dans `dist`.
|
|
||||||
|
|
||||||
|
|
||||||
# Télécharger les défauts
|
|
||||||
|
|
||||||
Les defauts utilise l'API de SmartMenuiserieTemplate sur `localhost`
|
|
||||||
|
|
||||||
## Télécharger les ressources
|
|
||||||
|
|
||||||
Sur Azure DevOps, dans ce projet *SmartConfigurator*, allez dans **Pipelines**. Cliquez sur la pipeline [SVGLayoutDesignerReact](https://techformsa.visualstudio.com/SmartConfigurator/_build?definitionId=9)
|
|
||||||
|
|
||||||
Cliquez sur le dernier run de la branche `dev` ou (`master` pour une version stable).
|
|
||||||
|
|
||||||
Cliquez sur Job (s'il a un statut **Success**)
|
|
||||||
|
|
||||||
Cliquez sur `1 artifact`:
|
|
||||||

|
|
||||||
|
|
||||||
Cliquez sur le menu hamburger et cliquez sur **Download Artifacts** :
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
# Utiliser dans modeler
|
|
||||||
|
|
||||||
- Créez un dossier `svg-layout-designer` dans le dossier `Component`.
|
|
||||||
|
|
||||||
- Puis copier les fichiers de build dans le dossier. (copiez l'entièreté du dossier `dist` ou extrayez le zip selon la méthode).
|
|
||||||
|
|
||||||
- Si **besoin**, modifier le fichier `smartcomponent/svg-layout-designer.html` pour avoir la bonne url relative à la basename de l'url :
|
|
||||||
|
|
||||||
```diff
|
|
||||||
-<iframe src="./Components/svg-layout-designer/index.html" style="border:none; width:100%; height:100%">
|
|
||||||
+<iframe src="./Components/SVGLayoutDesigner/index.html" style="border:none; width:100%; height:100%">
|
|
||||||
</iframe>
|
|
||||||
```
|
|
||||||
|
|
||||||
- Ensuite dans Visual Studio, incluez les fichiers et compilez.
|
|
||||||
|
|
||||||
- Enfin sur Modeler, un nouveau composant devrait être disponible.
|
|
||||||
|
|
||||||
- Pour l'utiliser dans un viewModel, créez un observable de type `SVGLayoutDesigner` et assignez-le dans les propriétés du composant :
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
ConfiguratorViewModel.ts
|
|
||||||
...
|
|
||||||
|
|
||||||
private OSVGLayoutDesigner: KnockoutObservable<Components.SVGLayoutDesigner> = ko.observable<Components.SVGLayoutDesigner>(null);
|
|
||||||
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
- Exemple d'utilisation:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
public AddContainer() {
|
|
||||||
// Récupère la configuration de l'éditeur
|
|
||||||
this.OSVGLayoutDesigner().GetEditorState((state: SVGLD.IEditorState) => {
|
|
||||||
// Création d'un nouveau conteneur du premier type de container de la config dans le conteneur sélectionné
|
|
||||||
const config = state.configuration;
|
|
||||||
const containerConfig = config.AvailableContainers.shift();
|
|
||||||
const type = containerConfig.Type;
|
|
||||||
this.OSVGLayoutDesigner().AppendContainerToSelectedContainer(type);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public DeleteContainer() {
|
|
||||||
// Supprime le conteneur sélectionné
|
|
||||||
this.OSVGLayoutDesigner().GetCurrentHistoryState((state: SVGLD.IHistoryState) => {
|
|
||||||
const selectedContainer = state.selectedContainerId;
|
|
||||||
this.OSVGLayoutDesigner().DeleteContainer(selectedContainer);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
BIN
docs/#Project/Pages/Translations.drawio
(Stored with Git LFS)
BIN
docs/#Project/Pages/Translations.drawio
(Stored with Git LFS)
Binary file not shown.
|
@ -1,38 +0,0 @@
|
||||||
# Web workers
|
|
||||||
|
|
||||||
Cette page explique la raison d'utiliser un web worker.
|
|
||||||
|
|
||||||
|
|
||||||
# Qu'est-ce qu'un web worker ?
|
|
||||||
|
|
||||||
Rapidement, c'est juste un fichier js qui est exécuté côté dans un Thread différent.
|
|
||||||
|
|
||||||
Il attend un réponse, la traite et peut répondre ensuite.
|
|
||||||
|
|
||||||
|
|
||||||
# Pourquoi en utiliser ?
|
|
||||||
|
|
||||||
Cela permet du véritable code asynchrone évitant le freeze du navigateur lorsqu'il fait des calculs compliqués ou lorsqu'il attend.
|
|
||||||
|
|
||||||
Exemple: https://mdn.github.io/dom-examples/web-workers/fibonacci-worker/
|
|
||||||
|
|
||||||
|
|
||||||
# Comment sont-ils utilisés ?
|
|
||||||
|
|
||||||
## Sauvegarde
|
|
||||||
|
|
||||||
Le premier web worker, situé dans [`public/workers/worker.js`](../../../public/workers/worker.js) s'occupe de faire une seule est unique tâche : `JSON.stringify` cependant on fonction de la taille de l'objet à stringifier en JSON cela peut prendre plusieurs secondes auquel cas l'utilisateur peut croire que son navigateur à bloqué.
|
|
||||||
|
|
||||||
On le met donc dans un web worker pour éviter cela.
|
|
||||||
|
|
||||||
Dans `Save.ts`, on crée le web worker avec `new Worker('workers/worker.js')` et on le termine quand il a fini sa tache avec `terminate()`
|
|
||||||
|
|
||||||
## Envois de message
|
|
||||||
|
|
||||||
Le deuxième web worker, situé dans [`public/workers/worker.js`](../../../public/workers/message_worker.js) s'occupe de faire des appels REST en stringifiant l'état de l'application.
|
|
||||||
|
|
||||||
Pour la même raison que la sauvegarde, on le met pour éviter un freeze.
|
|
||||||
|
|
||||||
Il est évidemment moins utile que la sauvegarde qui prends un objet beaucoup plus lourd.
|
|
||||||
|
|
||||||
Contrairement à la sauvegarde, le web worker est crée dans `UI.tsx` avec `UseWorker()` et existe sur tout le long de la durée de vie de l'application. Il est initialisé dans l'utilisation du module `UseWorker.tsx`.
|
|
|
@ -5,9 +5,8 @@ This project uses Azure Pipelines to runs automatic tests.
|
||||||
Its `azure-pipelines.yml` configuration file can be found at the root project folder.
|
Its `azure-pipelines.yml` configuration file can be found at the root project folder.
|
||||||
|
|
||||||
|
|
||||||
# Drone.io (deprecated)
|
# Drone.io
|
||||||
|
|
||||||
Due to the limitations of Azure Pipelines (limited free usage, no parallel, no dockerhub...), it might be more useful to use Drone.io.
|
Due to the limitations of Azure Pipelines (limited free usage, no parallel, no dockerhub...), it might be more useful to use Drone.io.
|
||||||
However `pnpm` will not be as useful as in Azure Pipelines since we cannot cache on the parent machine.
|
|
||||||
|
|
||||||
Its config file can be found in `.drone.yml`.
|
Its config file can be found in `.drone.yml`.
|
|
@ -13,8 +13,7 @@ It depends on Vite in order to build the project.
|
||||||
|
|
||||||
Others dependencies:
|
Others dependencies:
|
||||||
- [react-dom](https://reactjs.org/docs/react-dom.html): library used to inject the app to `#root` html element.
|
- [react-dom](https://reactjs.org/docs/react-dom.html): library used to inject the app to `#root` html element.
|
||||||
- [react-window](https://www.npmjs.com/package/react-window): component that offers component dynamic loading over scroll (very useful++)
|
- [react-svg-pan-zoom](https://www.npmjs.com/package/react-svg-pan-zoom): component that offers pan + zoom to a svg element
|
||||||
- [react-svg-pan-zoom](https://www.npmjs.com/package/react-svg-pan-zoom): component that offers pan + zoom to a svg element (if this gets deprecated, please try to migrate to HTML Canvas before trying a new library)
|
|
||||||
|
|
||||||
|
|
||||||
# [Vite](https://vitejs.dev/)
|
# [Vite](https://vitejs.dev/)
|
||||||
|
@ -49,12 +48,6 @@ Other dependencies:
|
||||||
|
|
||||||
SVG Icons that can be used as JSX elements with Tailwind CSS
|
SVG Icons that can be used as JSX elements with Tailwind CSS
|
||||||
|
|
||||||
# [Interweave](https://interweave.dev/)
|
|
||||||
|
|
||||||
React library to render HTML from string.
|
|
||||||
In this project, it is particularly used for the CustomSVG property.
|
|
||||||
|
|
||||||
If this dependencies gets deprecated please revert [PR#18 e96e4f12](https://dev.azure.com/enguyen0660/SVGLayoutDesignerReact/_git/SVGLayoutDesignerReact/commit/e96e4f123b4aa4c9cdb327d4d617ab0e63dc4d0f?refName=refs%2Fheads%2Fdev)
|
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
|
|
BIN
docs/Documents/01_141017-WG-11328-SYME-VERNUCCI-DET BF ind A.pdf
(Stored with Git LFS)
BIN
docs/Documents/01_141017-WG-11328-SYME-VERNUCCI-DET BF ind A.pdf
(Stored with Git LFS)
Binary file not shown.
BIN
docs/Documents/02_141017-WG-11328-SYME-VERNUCCI-DET BF ind B.pdf
(Stored with Git LFS)
BIN
docs/Documents/02_141017-WG-11328-SYME-VERNUCCI-DET BF ind B.pdf
(Stored with Git LFS)
Binary file not shown.
BIN
docs/Documents/03_SYME KLINE cde BV.pdf
(Stored with Git LFS)
BIN
docs/Documents/03_SYME KLINE cde BV.pdf
(Stored with Git LFS)
Binary file not shown.
BIN
docs/Documents/04_ARC KL_K1485985-ARC-K1485985.pdf
(Stored with Git LFS)
BIN
docs/Documents/04_ARC KL_K1485985-ARC-K1485985.pdf
(Stored with Git LFS)
Binary file not shown.
BIN
docs/Documents/05_DT_K1485985.pdf
(Stored with Git LFS)
BIN
docs/Documents/05_DT_K1485985.pdf
(Stored with Git LFS)
Binary file not shown.
BIN
docs/Documents/06_ Photo IMG_1406.jpg
(Stored with Git LFS)
BIN
docs/Documents/06_ Photo IMG_1406.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
docs/Documents/131421 KALIA - 15541 - PARVIS DE RODE - D0371837 - BANDE FILANTE PLAN 01 à 07 -IND B.dwg
(Stored with Git LFS)
BIN
docs/Documents/131421 KALIA - 15541 - PARVIS DE RODE - D0371837 - BANDE FILANTE PLAN 01 à 07 -IND B.dwg
(Stored with Git LFS)
Binary file not shown.
BIN
docs/Documents/131421 KALIA - 15541 - PARVIS DE RODE - D0371837 - BANDE FILANTE PLAN 01 à 07 -IND B.pdf
(Stored with Git LFS)
BIN
docs/Documents/131421 KALIA - 15541 - PARVIS DE RODE - D0371837 - BANDE FILANTE PLAN 01 à 07 -IND B.pdf
(Stored with Git LFS)
Binary file not shown.
BIN
docs/Documents/image0000001.jpg
(Stored with Git LFS)
BIN
docs/Documents/image0000001.jpg
(Stored with Git LFS)
Binary file not shown.
38
docs/Project_Structure.md
Normal file
38
docs/Project_Structure.md
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Project Structure
|
||||||
|
|
||||||
|
The project is structured this way
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── docs Documentation folder
|
||||||
|
├── public Public folder in which the index.html
|
||||||
|
│ import its resources
|
||||||
|
├── src Source folder for the react app
|
||||||
|
│ ├── assets Assets folder in which the react app
|
||||||
|
│ │ import its resources
|
||||||
|
│ ├── Components Components folder
|
||||||
|
│ ├── Enums Enums folder
|
||||||
|
│ ├── Interfaces Interface (+ types folder)
|
||||||
|
│ ├── test Setup folder for the tests
|
||||||
|
│ ├── tests Other tests + resources
|
||||||
|
│ ├── utils Utilities folder
|
||||||
|
│ ├── index.scss Tailwind CSS extends
|
||||||
|
│ ├── main.tsx Entrypoint for App injection
|
||||||
|
│ └── vite-env.d.ts Types for .env files
|
||||||
|
├── test-server Tests servers to test the API
|
||||||
|
│ ├── http.js Test server for bun.sh
|
||||||
|
│ └── node-http.js Test server for Node.js
|
||||||
|
├── azure-pipelines.yml Azure Pipelines YAML config file
|
||||||
|
├── index.html HTML Page
|
||||||
|
├── package-lock.json Describe the node_modules tree for npm
|
||||||
|
├── package.json Node.JS config file
|
||||||
|
├── pnpm-lock.yaml Describe the node_modules tree for pnpm
|
||||||
|
├── postcss.config.cjs Postcss config file for SCSS processing
|
||||||
|
├── README.md
|
||||||
|
├── tailwind.config.cjs Tailwind CSS config file
|
||||||
|
├── tsconfig.json Typescript config file
|
||||||
|
├── tsconfig.node.json Typescript config file for Node modules
|
||||||
|
├── vite.config.ts Vite config file
|
||||||
|
└── vitest.config.ts Vitest config file
|
||||||
|
```
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
|
|
||||||
Bienvenue au tutoriel de SVGLayoutDesigner.
|
|
||||||
|
|
||||||
Cette documentation a pour objectif de familiariser les nouveaux développeur aux outils du projet
|
|
||||||
et à apprendre à développer des composants sous React.
|
|
||||||
|
|
||||||
Si vous êtes prêt allez sur la page [Pour commencer](Pages/PourCommencer.md)
|
|
|
@ -1,251 +0,0 @@
|
||||||
# Les bases de React
|
|
||||||
|
|
||||||
Pour commencer, je vous recommande fortement de regarder cette vidéo qui expliquera mieux que ce document sur comment fonctionne React : https://www.youtube.com/watch?v=Tn6-PIqc4UM
|
|
||||||
|
|
||||||
Ce document fera quand même de son mieux pour expliquer les bases de React et permettra d'expliquer comment intégrer un composant dans le projet.
|
|
||||||
|
|
||||||
## Qu'est-ce qu'un composant React ?
|
|
||||||
|
|
||||||
Un composant React peut être décrite comme une classe (`class component`) ou une fonction (`functional component`) retournant une description de composant en JSX, un langage permettant de combiner du HTML et du JavaScript. Ces composants ont par convention l'extension de fichier `.jsx` (ou `.tsx` avec TypeScript)
|
|
||||||
|
|
||||||
Un composant est principalement constituer de `props` (propriétés) et d'un `state` (état). Cela est universel pour tous les composants.
|
|
||||||
|
|
||||||
Voici un basique exemple de composant :
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
function Welcome(props) {
|
|
||||||
return <h1>Hello, {props.name}</h1>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> La règle d'or est que le rendu change lorsqu'un props ou un state change.
|
|
||||||
|
|
||||||
Selon comment vous écrivez le composant, classe ou fonctionnelle, la manière de changer les props ou state est différente:
|
|
||||||
|
|
||||||
Pour les props :
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// functional
|
|
||||||
function Welcome(props) {
|
|
||||||
return <h1>Hello, {props.name}</h1>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// class
|
|
||||||
class Welcome extends React.Component {
|
|
||||||
render() {
|
|
||||||
return <h1>Hello, {this.props.name}</h1>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Pour l'état:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
// Functionnal
|
|
||||||
const useTicking = (setDate) => {
|
|
||||||
React.useEffect(() => {
|
|
||||||
const timerID = setInterval(() => {
|
|
||||||
setDate(new Date());
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearInterval(timerID)
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Clock = (props) => {
|
|
||||||
const [date, setDate] = React.useState(new Date());
|
|
||||||
|
|
||||||
useTicking(setDate);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{ date.toLocaleTimeString() }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// class
|
|
||||||
class Clock extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {date: new Date()};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.timerID = setInterval(
|
|
||||||
() => this.tick(),
|
|
||||||
1000
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
clearInterval(this.timerID);
|
|
||||||
}
|
|
||||||
|
|
||||||
tick() {
|
|
||||||
this.setState({
|
|
||||||
date: new Date()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{this.state.date.toLocaleTimeString()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Dans ce projet, il est de convention d'écrire en fonctionnel, car cela a pour avantage de fractionner les composants possèdant beaucoup de fonctions (voir par exemple `ContainerOperations.ts`) et de séparer les les propriétés d'états par des `state hooks` (crochet d'états).
|
|
||||||
|
|
||||||
La documentation qui suivra utilisera donc l'écriture fonctionnelle. Pour en savoir plus sur l'écriture de classe, vous pouvez essayer [le tutoriel de React](https://reactjs.org/tutorial/tutorial.html).
|
|
||||||
|
|
||||||
|
|
||||||
## Props
|
|
||||||
|
|
||||||
Les props sont des données entrer d'un composant. Comme pour un composant html ils décrivent simplement ce que le composant doit représenter.
|
|
||||||
|
|
||||||
Exemple :
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
function Welcome(props) {
|
|
||||||
return <h1>Hello, {props.name}</h1>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## State vs Stateless
|
|
||||||
|
|
||||||
Un composant possédant un état interne utilise `React.setState(newState)` dans un composant de classe ou `React.useState<Type>(defaultValue)` dans un composant fonctionnel.
|
|
||||||
Un composant ne possédant pas d'état est dit `stateless`.
|
|
||||||
|
|
||||||
Voici donc un exemple de composant React **avec état** :
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Clock.tsx
|
|
||||||
interface IClockProps {
|
|
||||||
}
|
|
||||||
|
|
||||||
const useTicking = (setTime: React.Dispatch<React.SetStateAction<Date>>) => {
|
|
||||||
React.useEffect(() => {
|
|
||||||
const timerID = setInterval(() => {
|
|
||||||
setTime(new Date());
|
|
||||||
}, 1000);
|
|
||||||
return () => {
|
|
||||||
clearInterval(timerID)
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Clock: React.FC<IClockProps> = (props) => {
|
|
||||||
const [time, setTime] = React.useState<Date>(new Date());
|
|
||||||
|
|
||||||
useTicking(setTime);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{ time.toLocaleTimeString() }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Et voici la version **sans état** :
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Clock.tsx
|
|
||||||
interface IClockProps {
|
|
||||||
time: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Clock: React.FC<IClockProps> = ({ time }) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{ time.toLocaleTimeString() }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parent.tsx
|
|
||||||
const useTicking = (setTime: React.Dispatch<React.SetStateAction<Date>>) => {
|
|
||||||
React.useEffect(() => {
|
|
||||||
const timerID = setInterval(() => {
|
|
||||||
setTime(new Date());
|
|
||||||
}, 1000);
|
|
||||||
return () => {
|
|
||||||
clearInterval(timerID);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Parent: React.FC<IClockProps> = ({ time }) => {
|
|
||||||
const [time, setTime] = React.useState<Date>(new Date());
|
|
||||||
|
|
||||||
useTicking(setTime);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Clock time={time}>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
Comme vous pouvez le voir, la différence est que la gestion d'état est donnée au composant parent. Cela permet de réduire les mises à jours de rendu en parallèles et de faire le rendu seulement si l'état du composant parent change.
|
|
||||||
|
|
||||||
Vous avez probablement remarqué l'usage de `React.useState()` et `React.useEffect()`. Lorsque l'on utilise une fonction avec le mot clé `use` de React, on utilise ce qu'on appelle un `hook` (crochet).
|
|
||||||
|
|
||||||
Il existe plusieurs type de hook et chacun ont leurs propre effet. Dans ce projet, nous utilisont principalement `useState` et `useEffect` mais il y a aucune interdiction pour utiliser d'autres types.
|
|
||||||
|
|
||||||
### `useState`
|
|
||||||
|
|
||||||
`useState` permet de lier à l'état interne du composant une variable.
|
|
||||||
|
|
||||||
La fonction nous fourni 2 paramètres dans une liste. La valeur courante et le setter de cette valeur :
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const [value, setValue] = React.useState(defaultValue);
|
|
||||||
```
|
|
||||||
|
|
||||||
### `useEffect`
|
|
||||||
|
|
||||||
`useEffect` permet de lancer une fonction d'effet lors d'un rendu. Il prend en paramètres :
|
|
||||||
- une fonction callback d'effet lors du rendu qui retourne lui aussi retourne une fonction d'effet lors de la suppression du rendu.
|
|
||||||
- Il prend aussi optionnellement un liste de dépendance permettant de déterminer les conditions de rendu
|
|
||||||
|
|
||||||
Revenons sur l'exemple de `useTicking`:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const useTicking = (setTime) => {
|
|
||||||
React.useEffect(() => {
|
|
||||||
// componentDidMount
|
|
||||||
const timerID = setInterval(() => {
|
|
||||||
setTime(new Date());
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
// componentWillUnmount
|
|
||||||
clearInterval(timerID);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
Décrivons ce quelle fait :
|
|
||||||
- La callback de `useEffect` appelle `setInterval` afin d'exécuter une fonction `setTime` toute les seconde.
|
|
||||||
- Cette callback retourne la fonction de suppression de `setInterval` qui sera exécutée si le composant est disposé.
|
|
||||||
- `useEffect` possède un tableu de dépendance mais elle est vide. Cela veut dire que la callback sera exécuté qu'une seule est unique fois sur toute la durée de vie du composant
|
|
||||||
|
|
||||||
Si on avait enlevé le tableau de dépendance, on aurait eu une boucle infinie ! Car rien n'aurait limité l'exécution de la callback à chaque rendu créé par `setTime`.
|
|
||||||
|
|
||||||
> Une règle d'or est qu'il faut mettre un tableau de dépendance lorsque l'on utilise un setteur d'état car il y aura a coup sûr une boucle infinie s'il y en a pas
|
|
||||||
|
|
||||||
> Contraposée: Il n'est pas nécessaire de mettre un tableau de dépendances s'il n'y pas de setteur d'état dans la callback
|
|
||||||
|
|
||||||
> Il est de convention de mettre les fonctions `useEffect` dans une fonction nommée afin de déterminer ce qu'elle fait. Il est possible d'appeler plusieurs fois `useEffect`, donc il n'est pas dangereux de dissocier les effets.
|
|
||||||
|
|
||||||
|
|
||||||
Si vous avez compris, essayons de créer notre [premier composant React](PremierComposantReact.md).
|
|
|
@ -1,41 +0,0 @@
|
||||||
# Heroicon
|
|
||||||
|
|
||||||
Cette page est totalement optionnel pour apprendre à faire un composant. Mais elle vous permettra d'apprendre à utiliser [Heroicon](https://heroicons.com/).
|
|
||||||
|
|
||||||
Heroicon est un ensemble de SVG utilisable sous forme JSX avec React et Tailwind CSS.
|
|
||||||
|
|
||||||
|
|
||||||
# Continuation de l'exemple de Premier Composant React
|
|
||||||
|
|
||||||
L'icône Home peut être importé comme tout composant React.
|
|
||||||
|
|
||||||
Il existe plusieurs type de chaque icône de Heroicon : `outline`, `solid`, `mini`. Utilisons donc `outline`.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { HomeIcon } from '@heroicons/react/outline';
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
export function Home({ goHome }: IHomeProps): JSX.Element {
|
|
||||||
const defaultIndex = Math.floor(Math.random() * (colors.length - 1));
|
|
||||||
const [index, setIndex] = useState<number>(defaultIndex);
|
|
||||||
|
|
||||||
const selectedColor = colors[index];
|
|
||||||
const className = `${selectedColor}`;
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={className}
|
|
||||||
type='button'
|
|
||||||
onClick={goHome}
|
|
||||||
onMouseEnter={() => {
|
|
||||||
const newIndex = Math.floor(Math.random() * (colors.length - 1));
|
|
||||||
setIndex(newIndex);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<HomeIcon />
|
|
||||||
Home
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
|
@ -1,43 +0,0 @@
|
||||||
# Pour commencer
|
|
||||||
|
|
||||||
Je vous recommande de lire [README.md](../../../README.md) si ce n'est pas encore fait
|
|
||||||
et de mettre en place le projet selon ce qui est écrit.
|
|
||||||
|
|
||||||
La documentation fera référence à la configuration suivante :
|
|
||||||
- [VSCode](https://code.visualstudio.com/) pour l'IDE
|
|
||||||
- [pNPm](https://pnpm.io/fr/) pour le gestionnaire de paquet
|
|
||||||
- [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) pour l'outil d'analyse de projet React
|
|
||||||
- [Typescript React code snippets](https://marketplace.visualstudio.com/items?itemName=infeng.vscode-react-typescript) pour les snippets React
|
|
||||||
|
|
||||||
Si vous n'avez pas installé l'un de ces outils, je vous le recommande **fortement**.
|
|
||||||
|
|
||||||
# Structure du projet
|
|
||||||
|
|
||||||
Commençons par nous familiariser avec la structure du projet: 90% du temps, les modifications que vous allez faire seront dans `src/`.
|
|
||||||
Si vous souhaitez vous familiariser avec le dossier du projet, lisez [Project_Structure](../../Project_Structure.md).
|
|
||||||
|
|
||||||
Le dossier est composé de la manière suivante:
|
|
||||||
|
|
||||||
```
|
|
||||||
./src
|
|
||||||
├── assets
|
|
||||||
├── Components
|
|
||||||
├── dts
|
|
||||||
├── Enums
|
|
||||||
├── Events
|
|
||||||
├── Interfaces
|
|
||||||
├── test
|
|
||||||
├── tests
|
|
||||||
├── utils
|
|
||||||
├── index.scss
|
|
||||||
├── main.tsx
|
|
||||||
└── vite-env.d.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Le point d'entrer est le `main.tsx`, mais vous aurez rarement le besoin de le modifier sauf pour ajouter des fonctionnalité sur le scope global.
|
|
||||||
|
|
||||||
Les dossiers principaux que vous aurez besoin d'utiliser pour développer un nouveau composant seront : `Components`, `Enums`, `Interfaces`, `utils` (et `assets` si vous mettez des images ou polices d'écriture).
|
|
||||||
|
|
||||||
Les autres dossiers ne sont pas nécessaire pour faire un composant, mais sont utilisés pour développer d'autres systèmes comme les évents, les définitions typescript et les tests.
|
|
||||||
|
|
||||||
Si vous avez compris, allons à la suite et regardons [les bases de React](BasesReact.md).
|
|
|
@ -1,346 +0,0 @@
|
||||||
# Notre premier composant React
|
|
||||||
|
|
||||||
Si vous êtes arrivé ici, c'est que vous avez compris ce qu'est un composant React et qu'il est composant de `props` et d'un `state`.
|
|
||||||
|
|
||||||
Mettons donc en pratique ce que vous avez vu.
|
|
||||||
|
|
||||||
Voici le cahier de charge fonctionnel du composant à implémenter :
|
|
||||||
|
|
||||||
Fonction:
|
|
||||||
- On veut ajouter un bouton `Home` dans l'éditeur pour revenir au menu principal
|
|
||||||
|
|
||||||
Contraintes principales :
|
|
||||||
- Le bouton `home` ne doit pas rafraîchir la page
|
|
||||||
|
|
||||||
Contraites secondaires :
|
|
||||||
- Le bouton est positionné en bas sur la barre
|
|
||||||
- Le bouton change de couleur à chaque fois que le curseur entre dedans
|
|
||||||
- Le bouton possède un icône home
|
|
||||||
|
|
||||||
## Initialiser un composant
|
|
||||||
|
|
||||||
Commençont par créer le composant sans le lier forcément au reste du projet.
|
|
||||||
|
|
||||||
Par convention comme il est dit dans la section [Pour commencer](PourCommencer.md), créons le fichier `Home.tsx` dans un dossier `Home/` dans `src/Components`. Il est recommandé de toujours créer un dossier pour le composant pour plus tard si on veut ajouter des styles propres au composant ou si on veut faire des tests.
|
|
||||||
|
|
||||||
Maintenant créons le squelette du composant. Pour rappel, on utilise la syntaxe *fonctionnelle* (i.e. une fonction = un composant).
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Home.tsx
|
|
||||||
export function Home(): JSX.Element {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: vous pouvez utiliser le snippet `tsrfc` si vous utilisez l'extension de VSCode.
|
|
||||||
|
|
||||||
La convention de nommage indique qu'il faut prioriser les fonctions nommées (donc pas de flèches fonctions).
|
|
||||||
|
|
||||||
Mettons le bouton html et mettons du texte dedans :
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
export function Home(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<button>
|
|
||||||
Home
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Appeler une fonction
|
|
||||||
|
|
||||||
Pour que le bouton appelle une fonction nous allons utiliser la propriété `onClick`. Pour lui équiper d'une variable/fonction, on la met entre accolades `prop={var}`.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
function GoHome(): void {
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Home(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={GoHome}
|
|
||||||
>
|
|
||||||
Home
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Cela devrait être suffisant pour revenir au menu principal. Appelons-le dans l'editeur.
|
|
||||||
|
|
||||||
|
|
||||||
## Ajouter le composant dans l'editeur
|
|
||||||
|
|
||||||
Tout ce qui est UI doit se mettre dans `UI.tsx`.
|
|
||||||
On sait que l'on doit l'ajouter dans la **barre** à gauche, soit `Bar.tsx`
|
|
||||||
|
|
||||||
Commençons par importer le composant dans `Bar.tsx`:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Bar.tsx
|
|
||||||
import { Home } from '../Home/Home';
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Puis ajoutons-le dans la vue :
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Bar.tsx
|
|
||||||
export function Bar(props: IBarProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div className='bar'>
|
|
||||||
...
|
|
||||||
<Home />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Testez avec `pnpm dev`. Et ouvrez la page sur `http://localhost:5173`.
|
|
||||||
|
|
||||||
Et voilà ! Vous avez fini !
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
Sauf que vous n'avez respecté aucune des contraintes.
|
|
||||||
|
|
||||||
Faisons mieux. Utilisons les `props` et le `state` de l'application.
|
|
||||||
|
|
||||||
|
|
||||||
## Props et state
|
|
||||||
|
|
||||||
Le composant doit restaurer l'état du menu principal.
|
|
||||||
|
|
||||||
Dans le menu principal, lorsque l'on clique sur `Start from scratch`, le composant doit forcément changer un état pour cacher le menu.
|
|
||||||
|
|
||||||
Ce que l'on veut faire est donc de renverser cette opération, autrement dit changer l'état qui permet de cacher le menu pour le remontrer.
|
|
||||||
|
|
||||||
Allons donc dans le composant `MainMenu.tsx` et analysons le contenu du bouton `Start from scratch`.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// MainMenu.tsx
|
|
||||||
...
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<div className='absolute bg-blue-50 p-12 rounded-lg drop-shadow-lg grid grid-cols-1 md:grid-cols-2 gap-8 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
|
|
||||||
<button type="button" className='mainmenu-btn' onClick={() => {
|
|
||||||
props.newEditor();
|
|
||||||
}}>Start from scratch</button>
|
|
||||||
<button type="button" className='mainmenu-btn' onClick={() => setWindowState(WindowState.Load)}>Load a configuration file</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
On trouve la fonction `onClick` suivante :
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// MainMenu.tsx
|
|
||||||
<button type="button" className='mainmenu-btn' onClick={() => {
|
|
||||||
props.newEditor();
|
|
||||||
}}>
|
|
||||||
```
|
|
||||||
|
|
||||||
On trouve la
|
|
||||||
|
|
||||||
`newEditor` étant une fonction donnée par `props`, doit donc correspondre à la fonctionalité que l'on veut renverser.
|
|
||||||
|
|
||||||
Regardons donc le composant parent de `MainMenu`: `App`.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// App.tsx
|
|
||||||
...
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={appRef}
|
|
||||||
className='App mainmenu-bg'
|
|
||||||
>
|
|
||||||
<MainMenu
|
|
||||||
newEditor={() => {
|
|
||||||
setAppState(AppState.Loading);
|
|
||||||
NewEditor(
|
|
||||||
editorState,
|
|
||||||
(newEditor) => setEditorState(newEditor),
|
|
||||||
() => setAppState(AppState.Loaded)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
loadEditor={(files: FileList | null) => LoadEditor(
|
|
||||||
files,
|
|
||||||
setEditorState,
|
|
||||||
setAppState
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Cette fonction `newEditor` appelle donc une autre fonction `NewEditor`.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
export function NewEditor(
|
|
||||||
editorState: IEditorState,
|
|
||||||
setEditorState: (newState: IEditorState) => void,
|
|
||||||
enableLoaded: () => void
|
|
||||||
): void {
|
|
||||||
if (DISABLE_API) {
|
|
||||||
enableLoaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editorState.configuration.APIConfiguration !== undefined) {
|
|
||||||
enableLoaded();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch the configuration from the API
|
|
||||||
FetchConfiguration()
|
|
||||||
.then((configuration: IConfiguration) => {
|
|
||||||
// Set the editor from the given properties of the API
|
|
||||||
const editorState: IEditorState = GetDefaultEditorState(configuration);
|
|
||||||
|
|
||||||
setEditorState(editorState);
|
|
||||||
enableLoaded();
|
|
||||||
}, (error) => {
|
|
||||||
console.debug('[NewEditor] Could not fetch resource from API. Using default.', error);
|
|
||||||
enableLoaded();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Cette fonction à l'air assez compliquée, mais on sait qu'elle utilise `setEditorState` et `enableLoaded()`. Et c'est tout ce qui nous intéresse. On observe dans `App.tsx` que `setEditorState` sert à changer l'état de `editorState` et que `enableLoaded` est appelle `setAppState` qui lui sert à changer l'état de `appState`.
|
|
||||||
|
|
||||||
En analysant un peu plus près `App.tsx`, on remarque l'évidante condition permettant d'afficher ou non le menu principal :
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// App.tsx
|
|
||||||
switch (appState) {
|
|
||||||
case AppState.Loaded:
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={appRef}
|
|
||||||
className='App'
|
|
||||||
>
|
|
||||||
<Editor
|
|
||||||
root={props.root}
|
|
||||||
configuration={editorState.configuration}
|
|
||||||
history={editorState.history}
|
|
||||||
historyCurrentStep={editorState.historyCurrentStep}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case AppState.Loading:
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={appRef}
|
|
||||||
className='App mainmenu-bg'
|
|
||||||
>
|
|
||||||
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
|
|
||||||
<Loader />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={appRef}
|
|
||||||
className='App mainmenu-bg'
|
|
||||||
>
|
|
||||||
<MainMenu
|
|
||||||
newEditor={() => {
|
|
||||||
setAppState(AppState.Loading);
|
|
||||||
NewEditor(
|
|
||||||
editorState,
|
|
||||||
(newEditor) => setEditorState(newEditor),
|
|
||||||
() => setAppState(AppState.Loaded)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
loadEditor={(files: FileList | null) => LoadEditor(
|
|
||||||
files,
|
|
||||||
setEditorState,
|
|
||||||
setAppState
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Ainsi, on sait qu'il faut appeler `setAppState` le setteur d'état de `appState`:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// App.tsx
|
|
||||||
const [appState, setAppState] = useState<AppState>(FAST_BOOT);
|
|
||||||
```
|
|
||||||
|
|
||||||
Cependant comment appeler cette fonction depuis `Home` ? Et bien nous allons utiliser les `props` pour passer la fonction en dessous.
|
|
||||||
|
|
||||||
Comme pour `MainMenu`, dans `Home.tsx` nous allons mettre la fonction `GoHome` dans les props :
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface IHomeProps {
|
|
||||||
goHome: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Home({ goHome }: IHomeProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={goHome}
|
|
||||||
>
|
|
||||||
Home
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: vous pouvez utiliser `Home(props: IHomeProps)` et appeler `props.goHome` comme tout le reste du projet au lieu de le destructurer en `{ goHome }`, mais le destructuring d'objet est autorisé (et dans certains cas, c'est mieux avec).
|
|
||||||
|
|
||||||
Home est utilisé dans les composants Bar, UI, et Editor avant d'être appelé dans App. Il faut faire la même chose, ajouter la fonction dans les props et l'appeler dans le composant :
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Bar.tsx
|
|
||||||
interface IBarProps {
|
|
||||||
goHome: () => void
|
|
||||||
}
|
|
||||||
...
|
|
||||||
export function Bar(props: IBarProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div className='bar'>
|
|
||||||
...
|
|
||||||
<Home
|
|
||||||
goHome={props.goHome}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
*Faites la même chose pour UI et Editor.*
|
|
||||||
|
|
||||||
Enfin dans `App.tsx`, créez la fonction permettant de changer `appState` vers `AppState.MainMenu`:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
...
|
|
||||||
case AppState.Loaded:
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={appRef}
|
|
||||||
className='App'
|
|
||||||
>
|
|
||||||
<Editor
|
|
||||||
root={props.root}
|
|
||||||
configuration={editorState.configuration}
|
|
||||||
history={editorState.history}
|
|
||||||
historyCurrentStep={editorState.historyCurrentStep}
|
|
||||||
goHome={() => setAppState(AppState.MainMenu)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Félicitation vous avez correctement développé votre premier composant Home !
|
|
||||||
|
|
||||||
Essayons maintenant de faire le reste dans le prochain chapitre avec [`TailwindCSS`](Tailwind.md)
|
|
|
@ -1,240 +0,0 @@
|
||||||
# Tailwind CSS
|
|
||||||
|
|
||||||
Tailwind CSS est un framework CSS proposant des classes utilitaires comme `flex, pt-4, text-center`.
|
|
||||||
Ce framework permet d'optimiser notament la génération de css et d'éviter la duplication de code lorsque `Vite` compile le projet.
|
|
||||||
|
|
||||||
On peut confirmer cela en regardant le fichier `.css` généré après avoir lancé la commande `pnpm build`.
|
|
||||||
|
|
||||||
Il utilise `index.scss` si on veut regrouper les propriétés de tailwind dans une classe.
|
|
||||||
|
|
||||||
Il utilise `tailwind.config.cjs` pour configurer et étendre quelques propriétés comme la couleur.
|
|
||||||
|
|
||||||
|
|
||||||
# Continuation de l'exemple de Premier Composant React
|
|
||||||
|
|
||||||
Rappelons les contraintes secondaires du bouton Home :
|
|
||||||
- Le bouton est positionné en bas sur la barre
|
|
||||||
- Le bouton change de couleur à chaque fois que le curseur entre dedans
|
|
||||||
- Le bouton possède un icône home
|
|
||||||
|
|
||||||
Essayons Tailwind avec React.
|
|
||||||
|
|
||||||
Je vous recommande fortement d'avoir [la documentation de Tailwind](https://tailwindcss.com/) sous les yeux pour suivre ce que font les propriétés.
|
|
||||||
|
|
||||||
## Positionner avec Tailwind
|
|
||||||
|
|
||||||
Pour positionner le bouton tout en bas il faut modifier le layout de la barre
|
|
||||||
|
|
||||||
Voici le layout actuel du composant Bar.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Bar.tsx
|
|
||||||
<div className='bar'>
|
|
||||||
<BarIcon>(...)</BarIcon>
|
|
||||||
<BarIcon>(...)</BarIcon>
|
|
||||||
<BarIcon>(...)</BarIcon>
|
|
||||||
<BarIcon>(...)</BarIcon>
|
|
||||||
<Home goHome={props.goHome} />
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
```scss
|
|
||||||
// index.scss
|
|
||||||
.bar {
|
|
||||||
@apply fixed z-20 flex flex-col top-0 left-0
|
|
||||||
h-full w-16 bg-slate-100
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Traduisont donc ceci en css:
|
|
||||||
|
|
||||||
```css
|
|
||||||
.bar {
|
|
||||||
display: flex; /* flex */
|
|
||||||
flex-direction: column; /* flex-col */
|
|
||||||
position: fixed; /* fixed */
|
|
||||||
top: 0; /* top-0 */
|
|
||||||
left: 0; /* left-0 */
|
|
||||||
z-index: 20; /* z-20 */
|
|
||||||
width: 4rem; /* w-16 */
|
|
||||||
height: 100%; /* h-full */
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
On va juste nous interesser sur `flex` et `flex-col` du coup car elles decident du layout des éléments enfants.
|
|
||||||
|
|
||||||
Pour espacer deux éléments dans un layout flex, il y a plusieurs manière de le faire :
|
|
||||||
- On peut rajout un `div` d'espacement avec la propriété `grow`
|
|
||||||
- On peut wrapper tous les `BarIcon` et utiliser `place-content-between` dans `.bar`
|
|
||||||
|
|
||||||
On va utiliser la premiere méthode, mais libre à vous la méthode.
|
|
||||||
|
|
||||||
Pour ajouter une classe sur React, on utilise le mot-clé `className`. C'est pour ne pas rentrer en conflit avec le mot-clé `class` servant de classe d'objet en JavaScript que ce mot-clé a été choisi.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Bar.tsx
|
|
||||||
<div className='bar'>
|
|
||||||
<BarIcon>(...)</BarIcon>
|
|
||||||
<BarIcon>(...)</BarIcon>
|
|
||||||
<BarIcon>(...)</BarIcon>
|
|
||||||
<BarIcon>(...)</BarIcon>
|
|
||||||
<div className='grow'></div>
|
|
||||||
<Home goHome={props.goHome} />
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
Et voilà ! Aussi simple que cela.
|
|
||||||
|
|
||||||
Continuons avec les couleurs avec le curseur. Cette fois ci, on va utiliser React.
|
|
||||||
|
|
||||||
|
|
||||||
# Utiliser Tailwind avec React
|
|
||||||
|
|
||||||
Revenons sur le composant Home définie par un simple bouton :
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Home.tsx
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface IHomeProps {
|
|
||||||
goHome: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Home({ goHome }: IHomeProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={goHome}
|
|
||||||
>
|
|
||||||
Home
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Dans ce composant, initialisons la liste des couleurs que l'on veut utiliser. Les couleurs de Tailwind sont définies sur cette page : `https://tailwindcss.com/docs/customizing-colors`.
|
|
||||||
|
|
||||||
Il nous suffit donc de juste mettre une liste de mots-clés de couleurs :
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Home.tsx
|
|
||||||
...
|
|
||||||
|
|
||||||
const colors = [
|
|
||||||
'bg-state-500',
|
|
||||||
'bg-gray-500',
|
|
||||||
'bg-zinc-500',
|
|
||||||
'bg-neutral-500',
|
|
||||||
'bg-stone-500',
|
|
||||||
'bg-red-500',
|
|
||||||
'bg-orange-500',
|
|
||||||
'bg-amber-500',
|
|
||||||
'bg-yellow-500',
|
|
||||||
'bg-lime-500',
|
|
||||||
'bg-emerald-500',
|
|
||||||
'bg-teal-500',
|
|
||||||
'bg-cyan-500',
|
|
||||||
'bg-sky-500',
|
|
||||||
'bg-blue-500',
|
|
||||||
'bg-indigo-500',
|
|
||||||
'bg-violet-500',
|
|
||||||
'bg-purple-500',
|
|
||||||
'bg-fuchsia-500',
|
|
||||||
'bg-pink-500',
|
|
||||||
'bg-rose-500'
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
Et d'en sélectionner une au hazard.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Home.tsx
|
|
||||||
const colors = [
|
|
||||||
...
|
|
||||||
];
|
|
||||||
|
|
||||||
export function Home({ goHome }: IHomeProps): JSX.Element {
|
|
||||||
const index = Math.floor(Math.random() * (colors.length - 1));
|
|
||||||
const selectedColor = colors[index];
|
|
||||||
const className = `${selectedColor}`;
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={className}
|
|
||||||
type='button'
|
|
||||||
onClick={goHome}
|
|
||||||
>
|
|
||||||
Home
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Vous vous demandez peut-être pourquoi je n'ai pas juste interpolé une liste de couleurs (`red`, `blue` etc.) au lieux de la *liste de propriétés de couleurs d'arrière-plan* (`bg-red-500`, `bg-blue-500`...).
|
|
||||||
Cela est expliqué par l'algorithme d'optimisation de fichier CSS de Tailwind.
|
|
||||||
|
|
||||||
Tailwind CSS purge les classes non utilisées dans son framework en scannant tous les fichiers, cela permet d'avoir un fichier css final léger mais en contrepartie, les opérations d'interpolation ne fonctionne pas.
|
|
||||||
Vous pouvez vérifier la liste de type fichier scanné dans `tailwind.config.cjs`.
|
|
||||||
|
|
||||||
Note : Cela veut dire que l'on aurait pas créer un fichier de preload appelé `colors.tw` et ajouter ce format `.tw` dans le fichier de config pour qu'il le scanne. Ainsi pouvoir enfin interpoler dans le fichier `.tsx` mais cela revient au même finalement que de faire la liste.
|
|
||||||
|
|
||||||
On a donc enfin un arrière-plan qui change mais n'avons nous pas oublié quelque chose ?
|
|
||||||
Oui ! Il faut changer la couleur quand la souris passe par dessus !
|
|
||||||
|
|
||||||
Cette fois-ci, pouvez-vous le faire sans lire la solution ?
|
|
||||||
|
|
||||||
Indice: Cela implique l'utilisation de `state` ;)
|
|
||||||
|
|
||||||
# Réponse
|
|
||||||
|
|
||||||
Suivons donc l'indice et créons un hook d'état afin de sélectionner la couleur et de pouvoir la changer quand la souris passe dans le composant
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Home.tsx
|
|
||||||
export function Home({ goHome }: IHomeProps): JSX.Element {
|
|
||||||
const defaultIndex = Math.floor(Math.random() * (colors.length - 1));
|
|
||||||
const [index, setIndex] = useState<number>(defaultIndex);
|
|
||||||
|
|
||||||
const selectedColor = colors[index];
|
|
||||||
const className = `${selectedColor}`;
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={className}
|
|
||||||
type='button'
|
|
||||||
onClick={goHome}
|
|
||||||
>
|
|
||||||
Home
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Ensuite utilisons la propriété `onMouseEnter` pour jeter l'évent.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Home.tsx
|
|
||||||
export function Home({ goHome }: IHomeProps): JSX.Element {
|
|
||||||
const defaultIndex = Math.floor(Math.random() * (colors.length - 1));
|
|
||||||
const [index, setIndex] = useState<number>(defaultIndex);
|
|
||||||
|
|
||||||
const selectedColor = colors[index];
|
|
||||||
const className = `${selectedColor}`;
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={className}
|
|
||||||
type='button'
|
|
||||||
onClick={goHome}
|
|
||||||
onMouseEnter={() => {
|
|
||||||
const newIndex = Math.floor(Math.random() * (colors.length - 1));
|
|
||||||
setIndex(newIndex);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Home
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Et voilà vous avez enfin terminé avec Tailwind CSS !
|
|
||||||
|
|
||||||
Amusez-vous un peu avec Tailwind CSS pour ajuster le style du bouton (padding, margin, transitions etc.) et découvrir quelques propriétés.
|
|
||||||
|
|
||||||
Finissons ce tutoriel avec [Heroicon](Heroicon.md).
|
|
|
@ -3,12 +3,11 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<link rel="stylesheet" href="style.css">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React + TS</title>
|
<title>Vite + React + TS</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="./src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
procs:
|
|
||||||
vite:
|
|
||||||
shell: "npx vite"
|
|
||||||
test-server:
|
|
||||||
shell: "npx nodemon ./test-server/http.js"
|
|
5833
package-lock.json
generated
5833
package-lock.json
generated
File diff suppressed because it is too large
Load diff
80
package.json
80
package.json
|
@ -1,72 +1,50 @@
|
||||||
{
|
{
|
||||||
"name": "svg-layout-designer-react",
|
"name": "svg-layout-designer-react",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "v2.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"postinstall": "npx patch-package",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"d": "mprocs",
|
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"build:dotnet": "dotnet build ./csharp/SVGLDLibs/SVGLDLibs/SVGLDLibs.csproj",
|
"preview": "vite preview",
|
||||||
"linter": "eslint src",
|
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:ui": "vitest --ui",
|
"test:ui": "vitest --ui",
|
||||||
"test:nowatch": "vitest run",
|
"test:nowatch": "vitest run",
|
||||||
"test:full": "(node ./test-server/http.js &) && (dotnet run --project=./csharp/SVGLDLibs/SVGLDWebAPI/SVGLDWebAPI.csproj &) && sleep 3 && vitest ; pkill dotnet ; pkill node",
|
|
||||||
"test:full:nowatch": "(node ./test-server/http.js &) && (dotnet run --project=./csharp/SVGLDLibs/SVGLDWebAPI/SVGLDWebAPI.csproj &) && sleep 3 && vitest run ; pkill dotnet ; pkill node",
|
|
||||||
"coverage": "vitest run coverage"
|
"coverage": "vitest run coverage"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/react": "^2.0.16",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@react-hook/size": "^2.1.2",
|
"framer-motion": "^6.5.1",
|
||||||
"@types/node": "^18.15.5",
|
|
||||||
"interweave": "^13.1.0",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-svg-pan-zoom": "^3.12.1",
|
"react-svg-pan-zoom": "^3.11.0"
|
||||||
"react-window": "^1.8.8",
|
|
||||||
"sweetalert2": "^11.7.3",
|
|
||||||
"sweetalert2-react-content": "^5.0.7",
|
|
||||||
"transformation-matrix": "^2.15.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/dom": "^9.0.1",
|
"@testing-library/dom": "^8.16.1",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.4.1",
|
||||||
"@types/react": "^18.0.28",
|
"@types/react": "^18.0.15",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@types/react-svg-pan-zoom": "^3.3.5",
|
"@types/react-svg-pan-zoom": "^3.3.5",
|
||||||
"@types/react-window": "^1.8.5",
|
"@typescript-eslint/eslint-plugin": "^5.31.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.56.0",
|
"@typescript-eslint/parser": "^5.31.0",
|
||||||
"@typescript-eslint/parser": "^5.56.0",
|
"@vitejs/plugin-react": "^2.0.0",
|
||||||
"@vitejs/plugin-react": "^3.1.0",
|
"@vitest/ui": "^0.20.3",
|
||||||
"@vitejs/plugin-react-swc": "^3.2.0",
|
"autoprefixer": "^10.4.8",
|
||||||
"@vitest/ui": "^0.29.7",
|
"eslint": "^8.20.0",
|
||||||
"autoprefixer": "^10.4.14",
|
|
||||||
"eslint": "^8.36.0",
|
|
||||||
"eslint-config-standard": "^17.0.0",
|
"eslint-config-standard": "^17.0.0",
|
||||||
"eslint-config-standard-with-typescript": "^34.0.1",
|
"eslint-config-standard-with-typescript": "^22.0.0",
|
||||||
"eslint-plugin-import": "^2.27.5",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-n": "^15.6.1",
|
"eslint-plugin-n": "^15.2.4",
|
||||||
"eslint-plugin-only-warn": "^1.1.0",
|
"eslint-plugin-promise": "^6.0.0",
|
||||||
"eslint-plugin-promise": "^6.1.1",
|
"eslint-plugin-react": "^7.30.1",
|
||||||
"eslint-plugin-react": "^7.32.2",
|
"jsdom": "^20.0.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"postcss": "^8.4.14",
|
||||||
"jsdom": "^21.1.1",
|
"sass": "^1.54.0",
|
||||||
"mprocs": "^0.6.4",
|
"tailwindcss": "^3.1.7",
|
||||||
"nodemon": "^2.0.22",
|
"typescript": "^4.6.4",
|
||||||
"postcss": "^8.4.21",
|
"vite": "^3.0.0",
|
||||||
"sass": "^1.59.3",
|
"vitest": "^0.20.3"
|
||||||
"tailwindcss": "^3.2.7",
|
|
||||||
"typescript": "^5.0.2",
|
|
||||||
"vite": "^4.2.1",
|
|
||||||
"vitest": "^0.29.7"
|
|
||||||
},
|
|
||||||
"pnpm": {
|
|
||||||
"patchedDependencies": {
|
|
||||||
"@types/react-svg-pan-zoom@3.3.5": "patches/@types__react-svg-pan-zoom@3.3.5.patch"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
diff --git a/node_modules/@types/react-svg-pan-zoom/index.d.ts b/node_modules/@types/react-svg-pan-zoom/index.d.ts
|
|
||||||
index a57d545..83ace9f 100644
|
|
||||||
--- a/node_modules/@types/react-svg-pan-zoom/index.d.ts
|
|
||||||
+++ b/node_modules/@types/react-svg-pan-zoom/index.d.ts
|
|
||||||
@@ -256,7 +256,11 @@ export function zoom(value: Value, SVGPointX: number, SVGPointY: number, scaleFa
|
|
||||||
export function fitSelection(
|
|
||||||
value: Value, selectionSVGPointX: number, selectionSVGPointY: number, selectionWidth: number, selectionHeight: number): Value;
|
|
||||||
|
|
||||||
-export function fitToViewer(value: Value): Value;
|
|
||||||
+export function fitToViewer(
|
|
||||||
+ value: Value,
|
|
||||||
+ SVGAlignX?: typeof ALIGN_CENTER | typeof ALIGN_LEFT | typeof ALIGN_RIGHT | undefined,
|
|
||||||
+ SVGAlignY?: typeof ALIGN_CENTER | typeof ALIGN_TOP | typeof ALIGN_BOTTOM | undefined
|
|
||||||
+): Value;
|
|
||||||
|
|
||||||
export function zoomOnViewerCenter(value: Value, scaleFactor: number): Value;
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
diff --git a/index.d.ts b/index.d.ts
|
|
||||||
index a57d545d33b2798024b9762d3d3513e58a38e19d..83ace9fc85b7354e128948402a50e00083eacd8c 100644
|
|
||||||
--- a/index.d.ts
|
|
||||||
+++ b/index.d.ts
|
|
||||||
@@ -256,7 +256,11 @@ export function zoom(value: Value, SVGPointX: number, SVGPointY: number, scaleFa
|
|
||||||
export function fitSelection(
|
|
||||||
value: Value, selectionSVGPointX: number, selectionSVGPointY: number, selectionWidth: number, selectionHeight: number): Value;
|
|
||||||
|
|
||||||
-export function fitToViewer(value: Value): Value;
|
|
||||||
+export function fitToViewer(
|
|
||||||
+ value: Value,
|
|
||||||
+ SVGAlignX?: typeof ALIGN_CENTER | typeof ALIGN_LEFT | typeof ALIGN_RIGHT | undefined,
|
|
||||||
+ SVGAlignY?: typeof ALIGN_CENTER | typeof ALIGN_TOP | typeof ALIGN_BOTTOM | undefined
|
|
||||||
+): Value;
|
|
||||||
|
|
||||||
export function zoomOnViewerCenter(value: Value, scaleFactor: number): Value;
|
|
||||||
|
|
2926
pnpm-lock.yaml
generated
2926
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -1,4 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25L12 21m0 0l-3.75-3.75M12 21V3" />
|
|
||||||
</svg>
|
|
||||||
|
|
Before Width: | Height: | Size: 246 B |
|
@ -1,2 +0,0 @@
|
||||||
<iframe src="./Components/svg-layout-designer/index.html" style="border:none; width:100%; height:100%">
|
|
||||||
</iframe>
|
|
|
@ -1,490 +0,0 @@
|
||||||
namespace SmartBusiness.Web.Components {
|
|
||||||
/**
|
|
||||||
* Types macros
|
|
||||||
*/
|
|
||||||
type IHistoryState = SVGLD.IHistoryState;
|
|
||||||
type IEditorState = SVGLD.IEditorState;
|
|
||||||
type IConfiguration = SVGLD.IConfiguration;
|
|
||||||
type ILanguage = SVGLD.ILanguage;
|
|
||||||
|
|
||||||
export class SVGLayoutDesigner extends Components.ComponentBase {
|
|
||||||
|
|
||||||
private _hooks: Record<string, (e: CustomEvent) => void>;
|
|
||||||
public App: AppController;
|
|
||||||
public Editor: EditorController;
|
|
||||||
|
|
||||||
public constructor(componentInfo: KnockoutComponentTypes.ComponentInfo, params: any) {
|
|
||||||
super(componentInfo, params);
|
|
||||||
this.App = new AppController(this, this.$component);
|
|
||||||
this.Editor = new EditorController(this, this.$component);
|
|
||||||
this._hooks = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the root HTML component of the SmartComponent
|
|
||||||
* In the iframe, it would be the document.
|
|
||||||
*/
|
|
||||||
public GetRootComponent() {
|
|
||||||
return this.$component[0]
|
|
||||||
.querySelector('iframe')
|
|
||||||
.contentDocument;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the a new event listener that will be delete on call,
|
|
||||||
* optionnally call a callback
|
|
||||||
* @param callback Callback function to call in the event listener
|
|
||||||
* @param eventType Event type for the listener to listen to
|
|
||||||
*/
|
|
||||||
public AddEventListener(eventType: string, callback: ((...args: any[]) => void) | undefined) {
|
|
||||||
const root = this.GetRootComponent();
|
|
||||||
const listener = (e: CustomEvent) => {
|
|
||||||
e.target.removeEventListener(e.type, listener);
|
|
||||||
callback && callback(e.detail);
|
|
||||||
};
|
|
||||||
root.addEventListener(eventType, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hooks ///
|
|
||||||
|
|
||||||
private static EDITOR_LISTENER_TYPE = 'editorListener';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a hook to the editor state change.
|
|
||||||
* After every time an action is perform on the editor, the callback will be called
|
|
||||||
* @param callback Callback to add that listen to the event
|
|
||||||
*/
|
|
||||||
public AddEditorListenerHook(hookId: string, callback: (state: IEditorState) => void): void {
|
|
||||||
const root = this.GetRootComponent();
|
|
||||||
const customEvent = (e: CustomEvent) => {
|
|
||||||
callback(e.detail);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._hooks[hookId] !== undefined) {
|
|
||||||
console.error(`HookId is already occupied. Please use a different HookId: ${hookId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._hooks[hookId] = customEvent;
|
|
||||||
root.addEventListener(SVGLayoutDesigner.EDITOR_LISTENER_TYPE, customEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a hook to the editor state change.
|
|
||||||
* @param callback Callback to remove that listen to the event
|
|
||||||
*/
|
|
||||||
public RemoveEditorListenerHook(hookId): void {
|
|
||||||
const root = this.GetRootComponent();
|
|
||||||
root.removeEventListener(SVGLayoutDesigner.EDITOR_LISTENER_TYPE, this._hooks[hookId]);
|
|
||||||
delete this._hooks[hookId];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Macros ///
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset to the first state and clear all history
|
|
||||||
*/
|
|
||||||
public Reset(): void {
|
|
||||||
this.Editor.GetEditorState((state) => {
|
|
||||||
this.Editor.SetHistory({ history: [state.history[0]] });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all previous history but the last state
|
|
||||||
*/
|
|
||||||
public ClearHistory(): void {
|
|
||||||
this.Editor.GetEditorState((state) => {
|
|
||||||
this.Editor.SetHistory({ history: [state.history[state.history.length - 1]] });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AppController {
|
|
||||||
app: SVGLayoutDesigner;
|
|
||||||
$component: JQuery;
|
|
||||||
|
|
||||||
constructor(app: SVGLayoutDesigner, $component: JQuery) {
|
|
||||||
this.app = app;
|
|
||||||
this.$component = $component;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the HTML component handling the editor
|
|
||||||
*/
|
|
||||||
public GetAppComponent() {
|
|
||||||
const component = this.$component[0]
|
|
||||||
.querySelector('iframe')
|
|
||||||
.contentDocument
|
|
||||||
.querySelector('.App');
|
|
||||||
|
|
||||||
if (component === undefined) {
|
|
||||||
throw new Error('[SVGLD] Cannot hook the event because the editor is not yet open')
|
|
||||||
}
|
|
||||||
|
|
||||||
return component;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// App Events ///
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load an existing editor state and its history
|
|
||||||
* This is a macro function for
|
|
||||||
* ReviveEditorState -> SetEditor -> SetHistory
|
|
||||||
* @param editorState
|
|
||||||
*/
|
|
||||||
public LoadEditor(editorState: IEditorState) {
|
|
||||||
this.ReviveEditorState(editorState, (state) => {
|
|
||||||
this.SetEditor(state, () => {
|
|
||||||
this.app.App.SetAppState(2, () => {
|
|
||||||
this.app.Editor.SetHistory({ history: state.history, historyCurrentStep: state.historyCurrentStep });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Not to be confused with setHistory,
|
|
||||||
* change the default configuration for the new containers, symbols etc.
|
|
||||||
* @param newEditor New editor configuration to set
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
public SetEditor(newEditor: IEditorState, callback?: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'setEditor';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const component = this.GetAppComponent();
|
|
||||||
component.dispatchEvent(new CustomEvent(eventType, { detail: newEditor }))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide the main menu to go to the application.
|
|
||||||
* SetEditor must be called first or the application will crash.
|
|
||||||
* @param appState
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
public SetAppState(appState: SVGLD.AppState, callback?: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'setAppState';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const component = this.GetAppComponent();
|
|
||||||
component.dispatchEvent(new CustomEvent(eventType, { detail: appState }))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Revive the references in the editor state by mutation
|
|
||||||
* Useful after using JSON.stringify with a replacer
|
|
||||||
* @param editorState Editor state to revive
|
|
||||||
* @param callback Callback with the revived state
|
|
||||||
*/
|
|
||||||
public ReviveEditorState(editorState: IEditorState, callback: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'reviveEditorState';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const component = this.GetAppComponent();
|
|
||||||
component.dispatchEvent(new CustomEvent(eventType, { detail: editorState }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Revive the references in the history by mutation
|
|
||||||
* Useful after using JSON.stringify with a replacer
|
|
||||||
* @param history History to revive
|
|
||||||
* @param callback Callback with the revived state
|
|
||||||
*/
|
|
||||||
public ReviveHistory(history: IHistoryState[], callback: (state: IHistoryState[]) => void) {
|
|
||||||
const eventType = 'reviveHistory';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const component = this.GetAppComponent();
|
|
||||||
component.dispatchEvent(new CustomEvent(eventType, { detail: history }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a default EditorState from a given configuration.
|
|
||||||
* This is the same method used when loading the editor
|
|
||||||
* for the first time after fetch the configuration from the API.
|
|
||||||
* @param configuration History to revive
|
|
||||||
* @param callback Callback with the revived state
|
|
||||||
*/
|
|
||||||
public GetDefaultEditorState(configuration: IConfiguration, callback: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'getDefaultEditorState';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const component = this.GetAppComponent();
|
|
||||||
component.dispatchEvent(new CustomEvent(eventType, { detail: configuration }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a language to the app
|
|
||||||
* @param option Displayed string of the language
|
|
||||||
* @param language Language containing an id and a dictionary
|
|
||||||
* @param callback Callback
|
|
||||||
*/
|
|
||||||
public AddLanguage(option: string, language: ILanguage, callback?: () => void) {
|
|
||||||
const eventType = 'addLanguage';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const component = this.GetAppComponent();
|
|
||||||
component.dispatchEvent(new CustomEvent(eventType, { detail: { language, option } }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the language of the app (defaults: ['fr', 'en'])
|
|
||||||
* @param languageId Language identifier for the language to select
|
|
||||||
* @param callback Callback
|
|
||||||
*/
|
|
||||||
public SetLanguage(languageId: string, callback?: (success: boolean) => void) {
|
|
||||||
const eventType = 'setLanguage';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const component = this.GetAppComponent();
|
|
||||||
component.dispatchEvent(new CustomEvent(eventType, { detail: languageId }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the language of the app (defaults: ['fr', 'en'])
|
|
||||||
* @param languageId Language identifier for the language to select
|
|
||||||
* @param callback Callback
|
|
||||||
*/
|
|
||||||
public GetLanguages(callback: (languages: Record<string, Record<string, string>>) => void) {
|
|
||||||
const eventType = 'getLanguages';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const component = this.GetAppComponent();
|
|
||||||
component.dispatchEvent(new CustomEvent(eventType));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class EditorController {
|
|
||||||
app: SVGLayoutDesigner;
|
|
||||||
$component: JQuery;
|
|
||||||
|
|
||||||
constructor(app: SVGLayoutDesigner, $component: JQuery) {
|
|
||||||
this.app = app;
|
|
||||||
this.$component = $component;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the HTML component handling the editor
|
|
||||||
*/
|
|
||||||
public GetEditorComponent() {
|
|
||||||
const component = this.$component[0]
|
|
||||||
.querySelector('iframe')
|
|
||||||
.contentDocument
|
|
||||||
.querySelector('.Editor');
|
|
||||||
|
|
||||||
if (component === undefined) {
|
|
||||||
throw new Error('[SVGLD] Cannot hook the event because the editor is not yet open')
|
|
||||||
}
|
|
||||||
|
|
||||||
return component;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Editor Events ///
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return in a callback the current state in the history of the editor
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
public GetCurrentHistoryState(callback: (state: IHistoryState) => void) {
|
|
||||||
const eventType = 'getCurrentHistoryState';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const component = this.GetEditorComponent();
|
|
||||||
component.dispatchEvent(new CustomEvent(eventType));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return in a callback the current history of the editor
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
public GetEditorState(callback: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'getEditorState';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const component = this.GetEditorComponent();
|
|
||||||
component.dispatchEvent(new CustomEvent(eventType));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return in a callback the current history of the editor as string
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
public GetEditorStateAsString(callback: (state: string) => void) {
|
|
||||||
const eventType = 'getEditorStateAsString';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const component = this.GetEditorComponent();
|
|
||||||
component.dispatchEvent(new CustomEvent(eventType));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the current history of the editor
|
|
||||||
* @param history Whole history of the editor
|
|
||||||
* @param callback (optional)
|
|
||||||
*/
|
|
||||||
public SetHistory(
|
|
||||||
{ history, historyCurrentStep }: { history: IHistoryState[], historyCurrentStep?: number},
|
|
||||||
callback?: (state: IEditorState) => void
|
|
||||||
): void {
|
|
||||||
const eventType = 'setHistory';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const component = this.GetEditorComponent();
|
|
||||||
component.dispatchEvent(new CustomEvent(eventType, { detail: { history, historyCurrentStep }}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new state to the editor
|
|
||||||
* @param historyState New history state to append
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
public AppendNewHistoryState(historyState: IHistoryState, callback?: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'appendNewState';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail: historyState }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new container at the given index position in a given parent container
|
|
||||||
* @param index Position to insert the container
|
|
||||||
* @param type Container type to create
|
|
||||||
* @param parentId Parent container of the new container
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
public AddContainer(index: number, type: string, parentId: string, callback?: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'addContainer';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const detail = {
|
|
||||||
index,
|
|
||||||
type,
|
|
||||||
parentId
|
|
||||||
}
|
|
||||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new container at the given index position in the current selected container
|
|
||||||
* @param index Position to insert the container
|
|
||||||
* @param type Container type to create
|
|
||||||
* @param callback
|
|
||||||
* @deprecated Do not use this function, use AddContainer instead
|
|
||||||
*/
|
|
||||||
public AddContainerToSelectedContainer(index: number, type: string, callback?: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'addContainerToSelectedContainer';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const detail = {
|
|
||||||
index,
|
|
||||||
type
|
|
||||||
}
|
|
||||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a new container in a given parent container
|
|
||||||
* @param type Container type to create
|
|
||||||
* @param parentId Parent container of the new container
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
public AppendContainer(type: string, parentId: string, callback?: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'appendContainer';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const detail = {
|
|
||||||
type,
|
|
||||||
parentId
|
|
||||||
}
|
|
||||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail }));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a new container in the current selected container
|
|
||||||
* @param type Container type to create
|
|
||||||
* @param callback
|
|
||||||
* @deprecated Do not use this function, use AppendContainer instead
|
|
||||||
*/
|
|
||||||
public AppendContainerToSelectedContainer(type: string, callback?: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'appendContainerToSelectedContainer';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const detail = {
|
|
||||||
type
|
|
||||||
}
|
|
||||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select a container by id
|
|
||||||
* @param containerId Container's id to select
|
|
||||||
* @param callback
|
|
||||||
* @deprecated Do not use this function
|
|
||||||
*/
|
|
||||||
public SelectContainer(containerId: string, callback?: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'selectContainer';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const detail = {
|
|
||||||
containerId
|
|
||||||
}
|
|
||||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a container by id
|
|
||||||
* @param containerId Container's id to delete
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
public DeleteContainer(containerId: string, callback?: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'deleteContainer';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const detail = {
|
|
||||||
containerId
|
|
||||||
}
|
|
||||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail }));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new symbol
|
|
||||||
* @param name Name of the symbol present in the config
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
public AddSymbol(name: string, callback?: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'addSymbol';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const detail = {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select a symbol by id
|
|
||||||
* @param symbolId Symbol's id to select
|
|
||||||
* @param callback
|
|
||||||
* @deprecated Do not use this function
|
|
||||||
*/
|
|
||||||
public SelectSymbol(symbolId: string, callback?: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'selectSymbol';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const detail = {
|
|
||||||
symbolId
|
|
||||||
}
|
|
||||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a symbol by id
|
|
||||||
* @param symbolId Symbol's id to delete
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
public DeleteSymbol(symbolId: string, callback?: (state: IEditorState) => void) {
|
|
||||||
const eventType = 'deleteSymbol';
|
|
||||||
this.app.AddEventListener(eventType, callback);
|
|
||||||
const detail = {
|
|
||||||
symbolId
|
|
||||||
}
|
|
||||||
this.GetEditorComponent().dispatchEvent(new CustomEvent(eventType, { detail }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ko.components.register('svg-layout-designer', {
|
|
||||||
viewModel: {
|
|
||||||
createViewModel: function (params, componentInfo) {
|
|
||||||
return new SmartBusiness.Web.Components.SVGLayoutDesigner(componentInfo, params);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
template: { element: 'svg-layout-designer' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ComponentModel xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.Techform.com/SmartExpert/2009/05">
|
|
||||||
<HasContent>false</HasContent>
|
|
||||||
<Id>0A61000D-FC2D-4490-BB3E-0FAED2AF3FDC</Id>
|
|
||||||
<ImageUrl />
|
|
||||||
<ItemName>svg-layout-designer</ItemName>
|
|
||||||
<Parameters>
|
|
||||||
<ParameterModel>
|
|
||||||
<ItemName>viewModel</ItemName>
|
|
||||||
<Text>ViewModel</Text>
|
|
||||||
</ParameterModel>
|
|
||||||
</Parameters>
|
|
||||||
<Text>svg-layout-designer</Text>
|
|
||||||
</ComponentModel>
|
|
583
public/svgld.d.ts
vendored
583
public/svgld.d.ts
vendored
|
@ -1,583 +0,0 @@
|
||||||
declare namespace SVGLD {
|
|
||||||
export interface IMargin {
|
|
||||||
left?: number;
|
|
||||||
bottom?: number;
|
|
||||||
top?: number;
|
|
||||||
right?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPoint {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model of available symbol to configure the application */
|
|
||||||
export interface IAvailableSymbol {
|
|
||||||
Name: string;
|
|
||||||
Image: IImage;
|
|
||||||
/** displayed text */
|
|
||||||
DisplayedText?: string;
|
|
||||||
isVertical?: boolean;
|
|
||||||
offset?: number;
|
|
||||||
Width?: number;
|
|
||||||
Height?: number;
|
|
||||||
PositionReference?: PositionReference;
|
|
||||||
/** An existing or new available container */
|
|
||||||
AssociatedContainer?: IAvailableContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAPIConfiguration {
|
|
||||||
apiFetchUrl?: string;
|
|
||||||
apiSetContainerListUrl?: string;
|
|
||||||
apiGetFeedbackUrl?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface ISymbolModel {
|
|
||||||
/** Identifier */
|
|
||||||
id: string;
|
|
||||||
/** Displayed Text */
|
|
||||||
displayedText: string;
|
|
||||||
/** Type */
|
|
||||||
type: string;
|
|
||||||
/** Configuration of the symbol */
|
|
||||||
config: IAvailableSymbol;
|
|
||||||
isVertical: boolean;
|
|
||||||
/** offset */
|
|
||||||
offset: number;
|
|
||||||
/** Width */
|
|
||||||
width: number;
|
|
||||||
/** Height */
|
|
||||||
height: number;
|
|
||||||
/** List of linked container id */
|
|
||||||
linkedContainers: Set<string>;
|
|
||||||
/** Dimensions options */
|
|
||||||
showDimension: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface IPattern {
|
|
||||||
/**
|
|
||||||
* Unique id for the pattern
|
|
||||||
*/
|
|
||||||
id: string;
|
|
||||||
/**
|
|
||||||
* Text to display in the sidebar
|
|
||||||
*/
|
|
||||||
text: string;
|
|
||||||
/**
|
|
||||||
* IAvailableContainer id used to wrap the children.
|
|
||||||
*/
|
|
||||||
wrapper: string;
|
|
||||||
/**
|
|
||||||
* List of ids of Pattern or IAvailableContainer
|
|
||||||
* If a IAvailableContainer and a Pattern have the same id,
|
|
||||||
* IAvailableContainer will be prioritized
|
|
||||||
*/
|
|
||||||
children: string[];
|
|
||||||
}
|
|
||||||
export type ContainerOrPattern = IAvailableContainer | IPattern;
|
|
||||||
export function GetPattern(id: string, configs: Map<string, IAvailableContainer>, patterns: Map<string, IPattern>): ContainerOrPattern | undefined;
|
|
||||||
export function IsPattern(id: string, configs: Map<string, IAvailableContainer>, patterns: Map<string, IPattern>): boolean;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface IHistoryState {
|
|
||||||
/** Last editor action */
|
|
||||||
lastAction: string;
|
|
||||||
/** Reference to the main container */
|
|
||||||
mainContainer: string;
|
|
||||||
containers: Map<string, IContainerModel>;
|
|
||||||
/** Counter of type of container. Used for ids. */
|
|
||||||
typeCounters: Record<string, number>;
|
|
||||||
/** List of symbols */
|
|
||||||
symbols: Map<string, ISymbolModel>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IKeyValue {
|
|
||||||
Key: string;
|
|
||||||
Value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface IGetFeedbackRequest {
|
|
||||||
/** Current application state */
|
|
||||||
ApplicationState: IHistoryState;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface IInputGroup {
|
|
||||||
key: string;
|
|
||||||
text: React.ReactNode;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface IAction {
|
|
||||||
Id: string;
|
|
||||||
CustomLogo: IImage;
|
|
||||||
Label: string;
|
|
||||||
Description: string;
|
|
||||||
Action: string;
|
|
||||||
AddingBehavior: AddMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ILanguage {
|
|
||||||
language: string;
|
|
||||||
dictionary: Record<string, string>;
|
|
||||||
languageChange?: (selected: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface ISetContainerListResponse {
|
|
||||||
Containers: IAvailableContainer[];
|
|
||||||
AddingBehavior?: AddMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface IContainerModel {
|
|
||||||
children: string[];
|
|
||||||
properties: IContainerProperties;
|
|
||||||
userData: Record<string, string | number>;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Macro for creating the interface
|
|
||||||
* Do not add methods since they will be lost during serialization
|
|
||||||
*/
|
|
||||||
export class ContainerModel implements IContainerModel {
|
|
||||||
children: string[];
|
|
||||||
properties: IContainerProperties;
|
|
||||||
userData: Record<string, string | number>;
|
|
||||||
constructor(properties: IContainerProperties, children?: string[], userData?: {});
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IStyle {
|
|
||||||
stroke?: string;
|
|
||||||
strokeOpacity?: number;
|
|
||||||
strokeWidth?: number;
|
|
||||||
fill?: string;
|
|
||||||
fillOpacity?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A SizePointer is a pointer in a 1 dimensional array of width/space
|
|
||||||
* x being the address where the pointer is pointing
|
|
||||||
* width being the overall (un)allocated space affected to the address
|
|
||||||
*/
|
|
||||||
export interface ISizePointer {
|
|
||||||
x: number;
|
|
||||||
width: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface IGetFeedbackResponse {
|
|
||||||
messages: IMessage[];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Model of configuration for the application to configure it */
|
|
||||||
export interface IConfiguration {
|
|
||||||
AvailableContainers: IAvailableContainer[];
|
|
||||||
AvailableSymbols: IAvailableSymbol[];
|
|
||||||
Categories: ICategory[];
|
|
||||||
Patterns: IPattern[];
|
|
||||||
MainContainer: IAvailableContainer;
|
|
||||||
APIConfiguration?: IAPIConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface ISetContainerListRequest {
|
|
||||||
/** Name of the action declared in the API */
|
|
||||||
Action: IAction;
|
|
||||||
/** Selected container */
|
|
||||||
Container: IContainerModel;
|
|
||||||
/** The previous sibling container */
|
|
||||||
PreviousContainer: IContainerModel | undefined;
|
|
||||||
/** The next sibling container */
|
|
||||||
NextContainer: IContainerModel | undefined;
|
|
||||||
/** Current application state */
|
|
||||||
ApplicationState: IHistoryState;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Properties of a container
|
|
||||||
*/
|
|
||||||
export interface IContainerProperties {
|
|
||||||
/** id of the container */
|
|
||||||
id: string;
|
|
||||||
/** type matching the configuration on construction */
|
|
||||||
type: string;
|
|
||||||
/** id of the parent container (null when there is no parent) */
|
|
||||||
parentId: string;
|
|
||||||
/** id of the linked symbol ('' when there is no parent) */
|
|
||||||
linkedSymbolId: string;
|
|
||||||
/** Text displayed in the container */
|
|
||||||
displayedText: string;
|
|
||||||
/** orientation */
|
|
||||||
orientation: Orientation;
|
|
||||||
/** horizontal offset */
|
|
||||||
x: number;
|
|
||||||
/** vertical offset */
|
|
||||||
y: number;
|
|
||||||
/** margin */
|
|
||||||
margin: IMargin;
|
|
||||||
/** width */
|
|
||||||
width: number;
|
|
||||||
/** height */
|
|
||||||
height: number;
|
|
||||||
/**
|
|
||||||
* Minimum width (min=1)
|
|
||||||
*/
|
|
||||||
minWidth: number;
|
|
||||||
/**
|
|
||||||
* Maximum width
|
|
||||||
*/
|
|
||||||
maxWidth: number;
|
|
||||||
/**
|
|
||||||
* Minimum height (min=1)
|
|
||||||
*/
|
|
||||||
minHeight: number;
|
|
||||||
/**
|
|
||||||
* Maximum height
|
|
||||||
*/
|
|
||||||
maxHeight: number;
|
|
||||||
/** true if anchor, false otherwise */
|
|
||||||
isAnchor: boolean;
|
|
||||||
/** true if flex, false otherwise */
|
|
||||||
isFlex: boolean;
|
|
||||||
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
|
||||||
positionReference: PositionReference;
|
|
||||||
/** Hide the children in the treeview */
|
|
||||||
hideChildrenInTreeview: boolean;
|
|
||||||
/** Dimensions options */
|
|
||||||
dimensionOptions: IDimensions;
|
|
||||||
/**
|
|
||||||
* Warnings of a container
|
|
||||||
*/
|
|
||||||
warning: string;
|
|
||||||
/**
|
|
||||||
* (optional)
|
|
||||||
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
|
|
||||||
* to draw some patterns that can be bind to the properties of the container
|
|
||||||
* Use {prop} to bind a property. Use {{ styleProp }} to use an object.
|
|
||||||
* Example :
|
|
||||||
* ```
|
|
||||||
* `<rect width="{width}" height="{height}" style="{style}"></rect>
|
|
||||||
* <rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
|
|
||||||
* <line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
|
|
||||||
* <line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
|
|
||||||
* `
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
customSVG?: string;
|
|
||||||
/**
|
|
||||||
* (optional)
|
|
||||||
* Style of the <rect>
|
|
||||||
*/
|
|
||||||
style?: IStyle;
|
|
||||||
/**
|
|
||||||
* (optional)
|
|
||||||
* User data that can be used for data storage or custom SVG
|
|
||||||
*/
|
|
||||||
userData?: IKeyValue[];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Model of available container used in application configuration */
|
|
||||||
export interface IAvailableContainer {
|
|
||||||
/** type */
|
|
||||||
Type: string;
|
|
||||||
/** displayed text */
|
|
||||||
DisplayedText?: string;
|
|
||||||
/** category */
|
|
||||||
Category?: string;
|
|
||||||
/** orientation */
|
|
||||||
Orientation?: Orientation;
|
|
||||||
/** horizontal offset */
|
|
||||||
X?: number;
|
|
||||||
/** vertical offset */
|
|
||||||
Y?: number;
|
|
||||||
/** width */
|
|
||||||
Width?: number;
|
|
||||||
/** height */
|
|
||||||
Height?: number;
|
|
||||||
/**
|
|
||||||
* Minimum width (min=1)
|
|
||||||
*/
|
|
||||||
MinWidth?: number;
|
|
||||||
/**
|
|
||||||
* Maximum width
|
|
||||||
*/
|
|
||||||
MaxWidth?: number;
|
|
||||||
/**
|
|
||||||
* Minimum height (min=1)
|
|
||||||
*/
|
|
||||||
MinHeight?: number;
|
|
||||||
/**
|
|
||||||
* Maximum height
|
|
||||||
*/
|
|
||||||
MaxHeight?: number;
|
|
||||||
/** margin */
|
|
||||||
Margin?: IMargin;
|
|
||||||
/** true if anchor, false otherwise */
|
|
||||||
IsAnchor?: boolean;
|
|
||||||
/** true if flex, false otherwise */
|
|
||||||
IsFlex?: boolean;
|
|
||||||
/** Method used on container add */
|
|
||||||
AddMethod?: AddMethod;
|
|
||||||
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
|
||||||
PositionReference?: PositionReference;
|
|
||||||
/**
|
|
||||||
* (optional)
|
|
||||||
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
|
|
||||||
* to draw some patterns that can be bind to the properties of the container
|
|
||||||
* Use {prop} to bind a property. Use {{ styleProp }} to use an object.
|
|
||||||
* Example :
|
|
||||||
* ```
|
|
||||||
* `<rect width="{width}" height="{height}" style="{style}"></rect>
|
|
||||||
* <rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
|
|
||||||
* <line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
|
|
||||||
* <line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
|
|
||||||
* `
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
CustomSVG?: string;
|
|
||||||
/**
|
|
||||||
* (optional)
|
|
||||||
* Disabled when Pattern is used.
|
|
||||||
*
|
|
||||||
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
|
|
||||||
* to draw some patterns that can be bind to the properties of the container
|
|
||||||
* Use {prop} to bind a property. Use {{ styleProp }} to use an object.
|
|
||||||
* Example :
|
|
||||||
* ```
|
|
||||||
* `<rect width="{width}" height="{height}" style="{style}"></rect>
|
|
||||||
* <rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
|
|
||||||
* <line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
|
|
||||||
* <line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
|
|
||||||
* `
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
DefaultChildType?: string;
|
|
||||||
/**
|
|
||||||
* Allow to use a Pattern to create the list of children
|
|
||||||
* Cannot be used with DefaultChildType,
|
|
||||||
* DefaultChildType will be disabled for this container and the children
|
|
||||||
*/
|
|
||||||
Pattern?: string;
|
|
||||||
/** Hide the children in the treeview */
|
|
||||||
HideChildrenInTreeview?: boolean;
|
|
||||||
/** Dimensions options */
|
|
||||||
DimensionOptions?: IDimensions;
|
|
||||||
/**
|
|
||||||
* if true, hide the entry in the sidebar (default: false)
|
|
||||||
*/
|
|
||||||
IsHidden?: boolean;
|
|
||||||
/**
|
|
||||||
* Disable a list of available container to be added inside
|
|
||||||
*/
|
|
||||||
Blacklist?: string[];
|
|
||||||
/**
|
|
||||||
* Cannot be used with blacklist. Whitelist will be prioritized.
|
|
||||||
* To disable the whitelist, Whitelist must be undefined.
|
|
||||||
* Only allow a set of available container to be added inside
|
|
||||||
*/
|
|
||||||
Whitelist?: string[];
|
|
||||||
/**
|
|
||||||
* (optional)
|
|
||||||
* Style of the <rect>
|
|
||||||
*/
|
|
||||||
Style?: IStyle;
|
|
||||||
/**
|
|
||||||
* List of possible actions shown on right-click
|
|
||||||
*/
|
|
||||||
Actions?: IAction[];
|
|
||||||
/**
|
|
||||||
* (optional)
|
|
||||||
* User data that can be used for data storage or custom SVG
|
|
||||||
*/
|
|
||||||
UserData?: IKeyValue[];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface IMessage {
|
|
||||||
text: string;
|
|
||||||
type: MessageType;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface IEditorState {
|
|
||||||
history: IHistoryState[];
|
|
||||||
historyCurrentStep: number;
|
|
||||||
configuration: IConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICategory {
|
|
||||||
Type: string;
|
|
||||||
DisplayedText?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IReplaceContainer {
|
|
||||||
id: string | undefined;
|
|
||||||
isReplacing: boolean;
|
|
||||||
category: string | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface IDimensionOptions {
|
|
||||||
positions: Position[];
|
|
||||||
/**
|
|
||||||
* Stroke color
|
|
||||||
*/
|
|
||||||
color?: string;
|
|
||||||
/** stroke-width */
|
|
||||||
width?: number;
|
|
||||||
/** stroke-dasharray */
|
|
||||||
dashArray?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface IDimensions {
|
|
||||||
/** if true, show the dimension of the container */
|
|
||||||
selfDimensions: IDimensionOptions;
|
|
||||||
/** if true, show the dimension of the container */
|
|
||||||
selfMarginsDimensions: IDimensionOptions;
|
|
||||||
/** if true show the overall dimensions of its children */
|
|
||||||
childrenDimensions: IDimensionOptions;
|
|
||||||
/**
|
|
||||||
* if true, allows a parent dimension borrower to borrow its x coordinate
|
|
||||||
* as a reference point for a dimension
|
|
||||||
*/
|
|
||||||
markPosition: Orientation[];
|
|
||||||
/**
|
|
||||||
* if true, show a dimension from the edge of the container to end
|
|
||||||
* and insert dimensions marks at lift up children (see liftDimensionToBorrower)
|
|
||||||
*/
|
|
||||||
dimensionWithMarks: IDimensionOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model of an image with multiple source
|
|
||||||
* It must at least have one source.
|
|
||||||
*
|
|
||||||
* If Url/Base64Image and Svg are set,
|
|
||||||
* Url/Base64Image will be shown in the menu while SVG will be drawn
|
|
||||||
*/
|
|
||||||
export interface IImage {
|
|
||||||
/** Name of the image */
|
|
||||||
Name: string;
|
|
||||||
/** (optional) Url of the image */
|
|
||||||
Url?: string;
|
|
||||||
/** (optional) base64 data of the image */
|
|
||||||
Base64Image?: string;
|
|
||||||
/** (optional) SVG string */
|
|
||||||
Svg?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum MessageType {
|
|
||||||
Normal = 0,
|
|
||||||
Success = 1,
|
|
||||||
Warning = 2,
|
|
||||||
Error = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add method when creating a container
|
|
||||||
* - Append will append to the last children in list
|
|
||||||
* - Insert will always place it at the begining
|
|
||||||
* - Replace will remove the selected container and insert a new one
|
|
||||||
* (default: Append)
|
|
||||||
*/
|
|
||||||
export enum AddMethod {
|
|
||||||
Append = 0,
|
|
||||||
Insert = 1,
|
|
||||||
Replace = 2,
|
|
||||||
ReplaceParent = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Describe the type of the property.
|
|
||||||
* Used for the assignation in the OnPropertyChange function
|
|
||||||
* See ContainerOperations.ts's OnPropertyChange
|
|
||||||
*/
|
|
||||||
export enum PropertyType {
|
|
||||||
/**
|
|
||||||
* Simple property: is not inside any object: id, x, width... (default)
|
|
||||||
*/
|
|
||||||
Simple = 0,
|
|
||||||
/**
|
|
||||||
* Style property: is inside the style object: stroke, fillOpacity...
|
|
||||||
*/
|
|
||||||
Style = 1,
|
|
||||||
/**
|
|
||||||
* Margin property: is inside the margin property: left, bottom, top, right...
|
|
||||||
*/
|
|
||||||
Margin = 2,
|
|
||||||
/**
|
|
||||||
* Dimension options
|
|
||||||
*/
|
|
||||||
SelfDimension = 3,
|
|
||||||
SelfMarginDimension = 4,
|
|
||||||
ChildrenDimensions = 5,
|
|
||||||
DimensionWithMarks = 6,
|
|
||||||
DimensionOptions = 7
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PositionReference {
|
|
||||||
TopLeft = 0,
|
|
||||||
TopCenter = 1,
|
|
||||||
TopRight = 2,
|
|
||||||
CenterLeft = 3,
|
|
||||||
CenterCenter = 4,
|
|
||||||
CenterRight = 5,
|
|
||||||
BottomLeft = 6,
|
|
||||||
BottomCenter = 7,
|
|
||||||
BottomRight = 8
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AppState {
|
|
||||||
MainMenu = 0,
|
|
||||||
Loading = 1,
|
|
||||||
Loaded = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Orientation {
|
|
||||||
Horizontal = 0,
|
|
||||||
Vertical = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Position {
|
|
||||||
Left = 0,
|
|
||||||
Down = 1,
|
|
||||||
Up = 2,
|
|
||||||
Right = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
/**
|
|
||||||
* Web worker handling message API.
|
|
||||||
* When sending quickly multiple request,
|
|
||||||
* a single packet of message merging all request' responses is returned
|
|
||||||
*/
|
|
||||||
|
|
||||||
const DELAY_BEFORE_SEND = 200;
|
|
||||||
const queue = [];
|
|
||||||
let messagePacket = [];
|
|
||||||
onmessage = async(e) => {
|
|
||||||
let packetLength
|
|
||||||
|
|
||||||
const url = e.data.url;
|
|
||||||
const state = e.data.state;
|
|
||||||
const request = {
|
|
||||||
ApplicationState: state
|
|
||||||
};
|
|
||||||
const dataParsed = JSON.stringify(request, GetCircularReplacerKeepDataStructure());
|
|
||||||
queue.push(request);
|
|
||||||
fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: new Headers({
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}),
|
|
||||||
body: dataParsed
|
|
||||||
})
|
|
||||||
.then((response) =>
|
|
||||||
response.json()
|
|
||||||
)
|
|
||||||
.then(async(json) => {
|
|
||||||
messagePacket.push.apply(messagePacket, json.messages);
|
|
||||||
queue.pop();
|
|
||||||
|
|
||||||
// The sleep allow the message packet to be filled by
|
|
||||||
// others requests before being sent as a single batch
|
|
||||||
// Reducing the wait time will reduce latency but increase error rate
|
|
||||||
let doLoop = true;
|
|
||||||
do {
|
|
||||||
packetLength = messagePacket.length;
|
|
||||||
await sleep(DELAY_BEFORE_SEND);
|
|
||||||
const newPacketLength = messagePacket.length;
|
|
||||||
doLoop = newPacketLength !== packetLength;
|
|
||||||
packetLength = newPacketLength;
|
|
||||||
} while (doLoop);
|
|
||||||
|
|
||||||
if (queue.length <= 0 && messagePacket.length > 0) {
|
|
||||||
console.debug(`[GetFeedback] Packet size before sent: ${messagePacket.length}`)
|
|
||||||
postMessage(messagePacket)
|
|
||||||
messagePacket.splice(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function sleep(ms) {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetCircularReplacerKeepDataStructure()
|
|
||||||
{
|
|
||||||
return (key, value) => {
|
|
||||||
if (key === 'parent') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'containers') {
|
|
||||||
return [...value.entries()].map((keyPair) => {
|
|
||||||
return {
|
|
||||||
Key: keyPair[0],
|
|
||||||
Value: keyPair[1]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'symbols') {
|
|
||||||
return [...value.entries()].map((keyPair) => {
|
|
||||||
return {
|
|
||||||
Key: keyPair[0],
|
|
||||||
Value: keyPair[1]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'linkedContainers') {
|
|
||||||
return Array.from(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
/**
|
|
||||||
* Web worker handling message API.
|
|
||||||
* Return a message of the request.
|
|
||||||
*/
|
|
||||||
|
|
||||||
onmessage = async(e) => {
|
|
||||||
const url = e.data.url;
|
|
||||||
const state = e.data.state;
|
|
||||||
const request = {
|
|
||||||
ApplicationState: state
|
|
||||||
};
|
|
||||||
const dataParsed = JSON.stringify(request, GetCircularReplacerKeepDataStructure());
|
|
||||||
fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: new Headers({
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}),
|
|
||||||
body: dataParsed
|
|
||||||
})
|
|
||||||
.then((response) =>
|
|
||||||
response.json()
|
|
||||||
)
|
|
||||||
.then(async(json) => {
|
|
||||||
postMessage(json.messages)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function GetCircularReplacerKeepDataStructure()
|
|
||||||
{
|
|
||||||
return (key, value) => {
|
|
||||||
if (key === 'parent') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'containers') {
|
|
||||||
const containers = Array.from(value).map(([Key, Value]) => ({ Key, Value }));
|
|
||||||
return containers;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'symbols') {
|
|
||||||
const symbols = Array.from(value).map(([Key, Value]) => ({ Key, Value }));
|
|
||||||
return symbols;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
onmessage = (e) => {
|
|
||||||
const data = JSON.stringify(e.data.editorState, getCircularReplacer(), e.data.spaces);
|
|
||||||
postMessage(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCircularReplacer = () => {
|
|
||||||
return (key, value) => {
|
|
||||||
if (key === 'containers') {
|
|
||||||
return [...value.entries()]
|
|
||||||
.map(([Key, Value]) => ({ Key, Value }));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'symbols') {
|
|
||||||
return [...value.entries()]
|
|
||||||
.map(([Key, Value]) => ({ Key, Value }));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'linkedContainers') {
|
|
||||||
return Array.from(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,31 +1,14 @@
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { AddMethod } from '../../Enums/AddMethod';
|
import { fetchConfiguration } from './api';
|
||||||
import { PositionReference } from '../../Enums/PositionReference';
|
|
||||||
import { type IAction } from '../../Interfaces/IAction';
|
|
||||||
import { type IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
|
||||||
import { type IAvailableSymbol } from '../../Interfaces/IAvailableSymbol';
|
|
||||||
import { type ICategory } from '../../Interfaces/ICategory';
|
|
||||||
import { type IConfiguration } from '../../Interfaces/IConfiguration';
|
|
||||||
import { type IContainerModel, ContainerModel } from '../../Interfaces/IContainerModel';
|
|
||||||
import { type IHistoryState } from '../../Interfaces/IHistoryState';
|
|
||||||
import { type IPattern } from '../../Interfaces/IPattern';
|
|
||||||
import { DEFAULT_DIMENSION_OPTION, DEFAULT_MAINCONTAINER_PROPS, GetDefaultContainerProps } from '../../utils/default';
|
|
||||||
import { FetchConfiguration } from './api';
|
|
||||||
|
|
||||||
const CSHARP_WEB_API_BASE_URL = 'http://localhost:5209/';
|
describe.concurrent('API test', () => {
|
||||||
const CHARP_WEB_API_RESOURCE_URL = 'SVGLD';
|
|
||||||
const CSHARP_WEB_API_URL = `${CSHARP_WEB_API_BASE_URL + CHARP_WEB_API_RESOURCE_URL}/`;
|
|
||||||
|
|
||||||
// TODO: Migrate this test to SVGLDWebAPI rather than using test-server/
|
|
||||||
describe.concurrent('Test server test', () => {
|
|
||||||
it('Load environment', () => {
|
it('Load environment', () => {
|
||||||
const url = import.meta.env.VITE_API_FETCH_URL;
|
const url = import.meta.env.VITE_API_URL;
|
||||||
expect(url).toBe('http://localhost:5000');
|
expect(url).toBe('http://localhost:5000');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Fetch configuration', async () => {
|
it('Fetch configuration', async() => {
|
||||||
const configuration = await FetchConfiguration();
|
const configuration = await fetchConfiguration();
|
||||||
expect(configuration.MainContainer).toBeDefined();
|
expect(configuration.MainContainer).toBeDefined();
|
||||||
expect(configuration.MainContainer.Height).toBeGreaterThan(0);
|
expect(configuration.MainContainer.Height).toBeGreaterThan(0);
|
||||||
expect(configuration.MainContainer.Width).toBeGreaterThan(0);
|
expect(configuration.MainContainer.Width).toBeGreaterThan(0);
|
||||||
|
@ -33,244 +16,3 @@ describe.concurrent('Test server test', () => {
|
||||||
expect(configuration.AvailableSymbols.length).toBeGreaterThan(-1);
|
expect(configuration.AvailableSymbols.length).toBeGreaterThan(-1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
async function Post2API(resource: string, body: string, method = 'POST'): Promise<boolean> {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.open(method, CSHARP_WEB_API_URL + resource, true);
|
|
||||||
xhr.onreadystatechange = () => { // Call a function when the state changes.
|
|
||||||
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
|
|
||||||
resolve(xhr.response === 'true');
|
|
||||||
}
|
|
||||||
if (xhr.status === 400) {
|
|
||||||
reject(xhr);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.onerror = () => {
|
|
||||||
reject(xhr);
|
|
||||||
};
|
|
||||||
xhr.setRequestHeader('Content-type', 'application/json');
|
|
||||||
xhr.send(body);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialization tests for type compatibility of csharp classes
|
|
||||||
* See csharp/SVGLDWebAPI/README.md
|
|
||||||
*/
|
|
||||||
describe.concurrent('Models test suite', () => {
|
|
||||||
it('ActionContainerModel', async () => {
|
|
||||||
const model: IAction = {
|
|
||||||
Id: 'string',
|
|
||||||
CustomLogo: {
|
|
||||||
Name: 'string',
|
|
||||||
Url: 'string',
|
|
||||||
Base64Image: 'string',
|
|
||||||
Svg: 'string'
|
|
||||||
},
|
|
||||||
Label: 'string',
|
|
||||||
Description: 'string',
|
|
||||||
Action: 'string',
|
|
||||||
AddingBehavior: 0
|
|
||||||
};
|
|
||||||
const res = await Post2API('ActionContainerModel', JSON.stringify(model));
|
|
||||||
expect(res).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('AddingBehaviorEnumModel', async () => {
|
|
||||||
let model: AddMethod = AddMethod.Append;
|
|
||||||
const res0 = await Post2API('AddingBehaviorEnumModel', JSON.stringify(model));
|
|
||||||
model = AddMethod.Insert;
|
|
||||||
const res1 = await Post2API('AddingBehaviorEnumModel', JSON.stringify(model));
|
|
||||||
model = AddMethod.Replace;
|
|
||||||
const res2 = await Post2API('AddingBehaviorEnumModel', JSON.stringify(model));
|
|
||||||
model = AddMethod.ReplaceParent;
|
|
||||||
const res3 = await Post2API('AddingBehaviorEnumModel', JSON.stringify(model));
|
|
||||||
model = 1;
|
|
||||||
const res4 = await Post2API('AddingBehaviorEnumModel', JSON.stringify(model));
|
|
||||||
expect(res0).toBe(true);
|
|
||||||
expect(res1).toBe(true);
|
|
||||||
expect(res2).toBe(true);
|
|
||||||
expect(res3).toBe(true);
|
|
||||||
expect(res4).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
const mainContainer = new ContainerModel(DEFAULT_MAINCONTAINER_PROPS);
|
|
||||||
|
|
||||||
const containers = new Map<string, IContainerModel>();
|
|
||||||
const historyState: IHistoryState = {
|
|
||||||
lastAction: 'string',
|
|
||||||
mainContainer: mainContainer.properties.id,
|
|
||||||
containers,
|
|
||||||
typeCounters: {
|
|
||||||
main: 1
|
|
||||||
},
|
|
||||||
symbols: new Map()
|
|
||||||
};
|
|
||||||
|
|
||||||
it('ApplicationStateModel', async () => {
|
|
||||||
const res = await Post2API('ApplicationStateModel', JSON.stringify(historyState));
|
|
||||||
expect(res).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
const availableContainerModel: IAvailableContainer = {
|
|
||||||
Type: 'string',
|
|
||||||
DisplayedText: 'string',
|
|
||||||
Category: 'string',
|
|
||||||
Orientation: 0,
|
|
||||||
X: 0,
|
|
||||||
Y: 0,
|
|
||||||
Width: 0,
|
|
||||||
Height: 0,
|
|
||||||
MinWidth: 0,
|
|
||||||
MaxWidth: 0,
|
|
||||||
MinHeight: 0,
|
|
||||||
MaxHeight: 0,
|
|
||||||
Margin: {
|
|
||||||
left: 0,
|
|
||||||
bottom: 0,
|
|
||||||
top: 0,
|
|
||||||
right: 0
|
|
||||||
},
|
|
||||||
IsAnchor: true,
|
|
||||||
IsFlex: true,
|
|
||||||
Pattern: 'string',
|
|
||||||
AddMethod: 0,
|
|
||||||
DefaultChildType: 'string',
|
|
||||||
PositionReference: 0,
|
|
||||||
HideChildrenInTreeview: true,
|
|
||||||
DimensionOptions: {
|
|
||||||
childrenDimensions: DEFAULT_DIMENSION_OPTION,
|
|
||||||
selfDimensions: DEFAULT_DIMENSION_OPTION,
|
|
||||||
selfMarginsDimensions: DEFAULT_DIMENSION_OPTION,
|
|
||||||
markPosition: [],
|
|
||||||
dimensionWithMarks: DEFAULT_DIMENSION_OPTION
|
|
||||||
},
|
|
||||||
IsHidden: true,
|
|
||||||
Blacklist: [
|
|
||||||
'string'
|
|
||||||
],
|
|
||||||
Whitelist: [
|
|
||||||
'string'
|
|
||||||
],
|
|
||||||
Actions: [
|
|
||||||
{
|
|
||||||
Id: 'string',
|
|
||||||
CustomLogo: {
|
|
||||||
Name: 'string',
|
|
||||||
Url: 'string',
|
|
||||||
Base64Image: 'string',
|
|
||||||
Svg: 'string'
|
|
||||||
},
|
|
||||||
Label: 'string',
|
|
||||||
Description: 'string',
|
|
||||||
Action: 'string',
|
|
||||||
AddingBehavior: 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
CustomSVG: 'string',
|
|
||||||
Style: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
it('AvailableContainerModel', async () => {
|
|
||||||
const res = await Post2API('ApplicationStateModel', JSON.stringify(availableContainerModel));
|
|
||||||
expect(res).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
const availableSymbolModel: IAvailableSymbol = {
|
|
||||||
Name: 'string',
|
|
||||||
Image: {
|
|
||||||
Name: 'string',
|
|
||||||
Url: 'string',
|
|
||||||
Base64Image: 'string',
|
|
||||||
Svg: 'string'
|
|
||||||
},
|
|
||||||
PositionReference: PositionReference.BottomCenter,
|
|
||||||
Width: 0,
|
|
||||||
Height: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
it('AvailableSymbolModel', async () => {
|
|
||||||
const res = await Post2API('AvailableSymbolModel', JSON.stringify(availableSymbolModel));
|
|
||||||
expect(res).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
const category: ICategory = {
|
|
||||||
Type: 'string',
|
|
||||||
DisplayedText: 'string'
|
|
||||||
};
|
|
||||||
|
|
||||||
it('Category', async () => {
|
|
||||||
const res = await Post2API('Category', JSON.stringify(category));
|
|
||||||
expect(res).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
const pattern: IPattern = {
|
|
||||||
children: ['string'],
|
|
||||||
id: 'string',
|
|
||||||
text: 'string',
|
|
||||||
wrapper: 'string'
|
|
||||||
};
|
|
||||||
|
|
||||||
it('Configuration', async () => {
|
|
||||||
const model: IConfiguration = {
|
|
||||||
AvailableContainers: [
|
|
||||||
availableContainerModel
|
|
||||||
],
|
|
||||||
AvailableSymbols: [
|
|
||||||
availableSymbolModel
|
|
||||||
],
|
|
||||||
Categories: [
|
|
||||||
category
|
|
||||||
],
|
|
||||||
MainContainer: availableContainerModel,
|
|
||||||
Patterns: [pattern],
|
|
||||||
APIConfiguration: {
|
|
||||||
apiFetchUrl: '',
|
|
||||||
apiGetFeedbackUrl: '',
|
|
||||||
apiSetContainerListUrl: ''
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await Post2API('Configuration', JSON.stringify(model));
|
|
||||||
expect(res).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
const containerProperties = GetDefaultContainerProps(
|
|
||||||
'container',
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
availableContainerModel
|
|
||||||
);
|
|
||||||
|
|
||||||
it('ContainerModel', async () => {
|
|
||||||
const model: IContainerModel = {
|
|
||||||
children: [],
|
|
||||||
properties: containerProperties,
|
|
||||||
userData: {}
|
|
||||||
};
|
|
||||||
const res = await Post2API('ContainerModel', JSON.stringify(model));
|
|
||||||
expect(res).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('ContainerProperties', async () => {
|
|
||||||
const res = await Post2API('ContainerModel', JSON.stringify(containerProperties));
|
|
||||||
expect(res).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('CSSStyle', async () => {
|
|
||||||
const model: React.CSSProperties = {
|
|
||||||
strokeWidth: '0',
|
|
||||||
fillOpacity: '0',
|
|
||||||
stroke: 'black',
|
|
||||||
fill: 'black'
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await Post2API('CSSStyle', JSON.stringify(model));
|
|
||||||
expect(res).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
import { type IConfiguration } from '../../Interfaces/IConfiguration';
|
import { Configuration } from '../../Interfaces/Configuration';
|
||||||
import { type ISetContainerListRequest } from '../../Interfaces/ISetContainerListRequest';
|
|
||||||
import { type 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<Configuration> {
|
||||||
const url = import.meta.env.VITE_API_FETCH_URL;
|
const url = `${import.meta.env.VITE_API_URL}`;
|
||||||
// @ts-expect-error The test library cannot use the Fetch API
|
// The test library cannot use the Fetch API
|
||||||
|
// @ts-expect-error
|
||||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||||
if (window.fetch) {
|
if (window.fetch) {
|
||||||
return await fetch(url, {
|
return await fetch(url, {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
})
|
})
|
||||||
.then(async(response) =>
|
.then(async(response) =>
|
||||||
await response.json()) as IConfiguration;
|
await response.json()
|
||||||
|
) as Configuration;
|
||||||
}
|
}
|
||||||
return await new Promise((resolve) => {
|
return await new Promise((resolve) => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
|
@ -26,40 +25,6 @@ export async function FetchConfiguration(): Promise<IConfiguration> {
|
||||||
resolve(JSON.parse(this.responseText));
|
resolve(JSON.parse(this.responseText));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
xhr.timeout = 5000;
|
|
||||||
xhr.send();
|
xhr.send();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function SetContainerList(
|
|
||||||
request: ISetContainerListRequest,
|
|
||||||
configurationUrl?: string
|
|
||||||
): Promise<ISetContainerListResponse> {
|
|
||||||
const url = configurationUrl ?? import.meta.env.VITE_API_SET_CONTAINER_LIST_URL;
|
|
||||||
const dataParsed = JSON.stringify(request, GetCircularReplacer());
|
|
||||||
// @ts-expect-error The test library cannot use the Fetch API
|
|
||||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
||||||
if (window.fetch) {
|
|
||||||
return await fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: new Headers({
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}),
|
|
||||||
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.setRequestHeader('Content-type', 'application/json');
|
|
||||||
xhr.send(dataParsed);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue