diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index a73261f1..94d22997 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -6,25 +6,29 @@
"version": "8.0.3",
"commands": [
"paket"
- ]
+ ],
+ "rollForward": false
},
"fable": {
- "version": "4.9.0",
+ "version": "4.19.0",
"commands": [
"fable"
- ]
+ ],
+ "rollForward": false
},
"femto": {
"version": "0.19.0",
"commands": [
"femto"
- ]
+ ],
+ "rollForward": false
},
"fantomas": {
"version": "6.2.3",
"commands": [
"fantomas"
- ]
+ ],
+ "rollForward": false
}
}
}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
index 5f282702..30fa4c7b 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1 +1,12 @@
-
\ No newline at end of file
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = false
+
+[*.fs]
+fsharp_multiline_bracket_style = stroustrup
+fsharp_newline_before_multiline_computation_expression = false
\ No newline at end of file
diff --git a/.github/workflows/PublishDocker.yaml b/.github/workflows/PublishDocker.yaml
index 0c4f50b9..28b78692 100644
--- a/.github/workflows/PublishDocker.yaml
+++ b/.github/workflows/PublishDocker.yaml
@@ -39,7 +39,7 @@ jobs:
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref, event=branch
- type=semver,pattern={{version}}
+ type=semver,pattern={{raw}}
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
diff --git a/build/Build.fs b/build/Build.fs
index 499b22c4..d59c3b94 100644
--- a/build/Build.fs
+++ b/build/Build.fs
@@ -38,7 +38,7 @@ module ReleaseNoteTasks =
open Fake.Extensions.Release
- let createVersionFile(version: string) =
+ let createVersionFile(version: string, commit: bool) =
let releaseDate = System.DateTime.UtcNow.ToShortDateString()
Fake.DotNet.AssemblyInfoFile.createFSharp "src/Server/Version.fs"
[ Fake.DotNet.AssemblyInfo.Title "Swate"
@@ -46,6 +46,9 @@ module ReleaseNoteTasks =
Fake.DotNet.AssemblyInfo.Metadata ("Version",version)
Fake.DotNet.AssemblyInfo.Metadata ("ReleaseDate",releaseDate)
]
+ if commit then
+ run git ["add"; "."] ""
+ run git ["commit"; "-m"; (sprintf "Release %s :bookmark:" ProjectInfo.prereleaseTag)] ""
let updateReleaseNotes = Target.create "releasenotes" (fun config ->
ReleaseNotes.ensure()
@@ -53,7 +56,7 @@ module ReleaseNoteTasks =
ReleaseNotes.update(ProjectInfo.gitOwner, ProjectInfo.project, config)
let newRelease = ReleaseNotes.load "RELEASE_NOTES.md"
- createVersionFile(newRelease.AssemblyVersion)
+ createVersionFile(newRelease.AssemblyVersion, false)
Trace.trace "Update Version.fs done!"
@@ -223,22 +226,21 @@ module Release =
open System.Diagnostics
- let private executeCommand (command: string) : string =
- let p = new Process()
- p.StartInfo.FileName <- "git"
- p.StartInfo.Arguments <- command
- p.StartInfo.RedirectStandardOutput <- true
- p.StartInfo.UseShellExecute <- false
- p.StartInfo.CreateNoWindow <- true
-
- p.Start() |> ignore
+ let GetLatestGitTag () : string =
+ let executeCommand (command: string) : string =
+ let p = new Process()
+ p.StartInfo.FileName <- "git"
+ p.StartInfo.Arguments <- command
+ p.StartInfo.RedirectStandardOutput <- true
+ p.StartInfo.UseShellExecute <- false
+ p.StartInfo.CreateNoWindow <- true
- let output = p.StandardOutput.ReadToEnd()
- p.WaitForExit()
+ p.Start() |> ignore
- output
+ let output = p.StandardOutput.ReadToEnd()
+ p.WaitForExit()
- let GetLatestGitTag () : string =
+ output
executeCommand "describe --abbrev=0 --tags"
|> String.trim
@@ -246,7 +248,7 @@ module Release =
printfn "Please enter pre-release package suffix"
let suffix = System.Console.ReadLine()
ProjectInfo.prereleaseSuffix <- suffix
- ProjectInfo.prereleaseTag <- (sprintf "%i.%i.%i-%s" ProjectInfo.release.SemVer.Major ProjectInfo.release.SemVer.Minor ProjectInfo.release.SemVer.Patch suffix)
+ ProjectInfo.prereleaseTag <- (sprintf "v%i.%i.%i-%s" ProjectInfo.release.SemVer.Major ProjectInfo.release.SemVer.Minor ProjectInfo.release.SemVer.Patch suffix)
ProjectInfo.isPrerelease <- true
let CreateTag() =
@@ -258,15 +260,14 @@ module Release =
let CreatePrereleaseTag() =
if promptYesNo (sprintf "Tagging branch with %s OK?" ProjectInfo.prereleaseTag ) then
- Git.Branches.tag "" ProjectInfo.prereleaseTag
+ run git ["tag"; "-f"; ProjectInfo.prereleaseTag; ] ""
Git.Branches.pushTag "" ProjectInfo.projectRepo ProjectInfo.prereleaseTag
else
failwith "aborted"
let ForcePushNightly() =
if promptYesNo "Ready to force push release to nightly branch?" then
- Git.Commit.exec "." (sprintf "Release v%s" ProjectInfo.prereleaseTag)
- run git ["push"; "-f"; "origin"; "HEAD:nightly"] __SOURCE_DIRECTORY__
+ run git ["push"; "-f"; "origin"; "HEAD:nightly"] ""
else
failwith "aborted"
@@ -404,7 +405,7 @@ let main args =
Release.SetPrereleaseTag()
Release.CreatePrereleaseTag()
let version = Release.GetLatestGitTag()
- ReleaseNoteTasks.createVersionFile(version)
+ ReleaseNoteTasks.createVersionFile(version, true)
Release.ForcePushNightly()
0
| _ ->
@@ -420,8 +421,12 @@ let main args =
| _ -> runOrDefault args
| "version" :: a ->
match a with
- | "create-file" :: version :: a -> ReleaseNoteTasks.createVersionFile(version); 0
+ | "create-file" :: version :: a -> ReleaseNoteTasks.createVersionFile(version, false); 0
| _ -> runOrDefault args
+ | "cmdtest" :: a ->
+ run git ["add"; "."] ""
+ run git ["commit"; "-m"; (sprintf "Release v%s" ProjectInfo.prereleaseTag)] ""
+ 0
| _ -> runOrDefault args
\ No newline at end of file
diff --git a/build/Build.fsproj b/build/Build.fsproj
index 10a1e12c..b24e0e8f 100644
--- a/build/Build.fsproj
+++ b/build/Build.fsproj
@@ -15,5 +15,4 @@
-
\ No newline at end of file
diff --git a/build/manifest.xml b/build/manifest.xml
index 969174f7..98a5568c 100644
--- a/build/manifest.xml
+++ b/build/manifest.xml
@@ -1,4 +1,4 @@
-
+
5d6f5462-3401-48ec-9406-d12882e9ad83
1.0.0
@@ -16,7 +16,7 @@
-
+
ReadWriteDocument
@@ -65,7 +65,7 @@
-
+
@@ -116,8 +116,8 @@
-
-
+
+
diff --git a/build/paket.references b/build/paket.references
deleted file mode 100644
index 08bfadf2..00000000
--- a/build/paket.references
+++ /dev/null
@@ -1,3 +0,0 @@
-ARCtrl
-FsSpreadsheet.Exceljs
-Feliz.Bulma.Checkradio
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 63fd122d..315784ae 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,3658 +1,4138 @@
{
- "name": "Swate",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "dependencies": {
- "@creativebulma/bulma-tooltip": "^1.2.0",
- "@nfdi4plants/exceljs": "^0.3.0",
- "bulma": "^0.9.4",
- "bulma-checkradio": "^2.1.3",
- "bulma-slider": "^2.0.5",
- "bulma-switch": "^2.0.4",
- "cytoscape": "^3.27.0",
- "human-readable-ids": "^1.0.4",
- "isomorphic-fetch": "^3.0.0",
- "jsonschema": "^1.4.1",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "use-sync-external-store": "^1.2.0"
- },
- "devDependencies": {
- "@types/node": "^20.10.3",
- "@vitejs/plugin-basic-ssl": "^1.0.2",
- "@vitejs/plugin-react": "^4.2.1",
- "autoprefixer": "^10.4.16",
- "core-js": "^3.33.3",
- "postcss": "^8.4.32",
- "remotedev": "^0.2.9",
- "sass": "^1.69.5",
- "selfsigned": "^2.4.1",
- "tailwindcss": "^3.3.6",
- "vite": "^5.0.5"
- },
- "engines": {
- "node": "~18 || ~20",
- "npm": "~9 || ~10"
- }
- },
- "node_modules/@alloc/quick-lru": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
- "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@ampproject/remapping": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
- "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
- "dev": true,
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.0",
- "@jridgewell/trace-mapping": "^0.3.9"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/code-frame": {
- "version": "7.23.5",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
- "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
- "dev": true,
- "dependencies": {
- "@babel/highlight": "^7.23.4",
- "chalk": "^2.4.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/compat-data": {
- "version": "7.23.5",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
- "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "7.23.5",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz",
- "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==",
- "dev": true,
- "dependencies": {
- "@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.23.5",
- "@babel/generator": "^7.23.5",
- "@babel/helper-compilation-targets": "^7.22.15",
- "@babel/helper-module-transforms": "^7.23.3",
- "@babel/helpers": "^7.23.5",
- "@babel/parser": "^7.23.5",
- "@babel/template": "^7.22.15",
- "@babel/traverse": "^7.23.5",
- "@babel/types": "^7.23.5",
- "convert-source-map": "^2.0.0",
- "debug": "^4.1.0",
- "gensync": "^1.0.0-beta.2",
- "json5": "^2.2.3",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
- }
- },
- "node_modules/@babel/generator": {
- "version": "7.23.5",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz",
- "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==",
- "dev": true,
- "dependencies": {
- "@babel/types": "^7.23.5",
- "@jridgewell/gen-mapping": "^0.3.2",
- "@jridgewell/trace-mapping": "^0.3.17",
- "jsesc": "^2.5.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.22.15",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz",
- "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==",
- "dev": true,
- "dependencies": {
- "@babel/compat-data": "^7.22.9",
- "@babel/helper-validator-option": "^7.22.15",
- "browserslist": "^4.21.9",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-environment-visitor": {
- "version": "7.22.20",
- "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
- "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-function-name": {
- "version": "7.23.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
- "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
- "dev": true,
- "dependencies": {
- "@babel/template": "^7.22.15",
- "@babel/types": "^7.23.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-hoist-variables": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
- "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
- "dev": true,
- "dependencies": {
- "@babel/types": "^7.22.5"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-imports": {
- "version": "7.22.15",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
- "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
- "dev": true,
- "dependencies": {
- "@babel/types": "^7.22.15"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
- "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
- "dev": true,
- "dependencies": {
- "@babel/helper-environment-visitor": "^7.22.20",
- "@babel/helper-module-imports": "^7.22.15",
- "@babel/helper-simple-access": "^7.22.5",
- "@babel/helper-split-export-declaration": "^7.22.6",
- "@babel/helper-validator-identifier": "^7.22.20"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-plugin-utils": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
- "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-simple-access": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
- "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
- "dev": true,
- "dependencies": {
- "@babel/types": "^7.22.5"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-split-export-declaration": {
- "version": "7.22.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
- "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
- "dev": true,
- "dependencies": {
- "@babel/types": "^7.22.5"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.23.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
- "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.22.20",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
- "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-option": {
- "version": "7.23.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
- "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "7.23.5",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz",
- "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==",
- "dev": true,
- "dependencies": {
- "@babel/template": "^7.22.15",
- "@babel/traverse": "^7.23.5",
- "@babel/types": "^7.23.5"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/highlight": {
- "version": "7.23.4",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
- "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
- "dev": true,
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.22.20",
- "chalk": "^2.4.2",
- "js-tokens": "^4.0.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.23.5",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz",
- "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==",
- "dev": true,
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-self": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz",
- "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==",
- "dev": true,
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.22.5"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-source": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz",
- "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==",
- "dev": true,
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.22.5"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/template": {
- "version": "7.22.15",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
- "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
- "dev": true,
- "dependencies": {
- "@babel/code-frame": "^7.22.13",
- "@babel/parser": "^7.22.15",
- "@babel/types": "^7.22.15"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse": {
- "version": "7.23.5",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz",
- "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==",
- "dev": true,
- "dependencies": {
- "@babel/code-frame": "^7.23.5",
- "@babel/generator": "^7.23.5",
- "@babel/helper-environment-visitor": "^7.22.20",
- "@babel/helper-function-name": "^7.23.0",
- "@babel/helper-hoist-variables": "^7.22.5",
- "@babel/helper-split-export-declaration": "^7.22.6",
- "@babel/parser": "^7.23.5",
- "@babel/types": "^7.23.5",
- "debug": "^4.1.0",
- "globals": "^11.1.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.23.5",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz",
- "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==",
- "dev": true,
- "dependencies": {
- "@babel/helper-string-parser": "^7.23.4",
- "@babel/helper-validator-identifier": "^7.22.20",
- "to-fast-properties": "^2.0.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@creativebulma/bulma-tooltip": {
- "version": "1.2.0",
- "license": "MIT"
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz",
- "integrity": "sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz",
- "integrity": "sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.8.tgz",
- "integrity": "sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz",
- "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz",
- "integrity": "sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz",
- "integrity": "sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz",
- "integrity": "sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz",
- "integrity": "sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz",
- "integrity": "sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz",
- "integrity": "sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz",
- "integrity": "sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz",
- "integrity": "sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz",
- "integrity": "sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz",
- "integrity": "sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz",
- "integrity": "sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz",
- "integrity": "sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz",
- "integrity": "sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz",
- "integrity": "sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz",
- "integrity": "sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz",
- "integrity": "sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz",
- "integrity": "sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz",
- "integrity": "sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@fast-csv/format": {
- "version": "4.3.5",
- "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz",
- "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==",
- "dependencies": {
- "@types/node": "^14.0.1",
- "lodash.escaperegexp": "^4.1.2",
- "lodash.isboolean": "^3.0.3",
- "lodash.isequal": "^4.5.0",
- "lodash.isfunction": "^3.0.9",
- "lodash.isnil": "^4.0.0"
- }
- },
- "node_modules/@fast-csv/format/node_modules/@types/node": {
- "version": "14.18.63",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
- "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="
- },
- "node_modules/@fast-csv/parse": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz",
- "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==",
- "dependencies": {
- "@types/node": "^14.0.1",
- "lodash.escaperegexp": "^4.1.2",
- "lodash.groupby": "^4.6.0",
- "lodash.isfunction": "^3.0.9",
- "lodash.isnil": "^4.0.0",
- "lodash.isundefined": "^3.0.1",
- "lodash.uniq": "^4.5.0"
- }
- },
- "node_modules/@fast-csv/parse/node_modules/@types/node": {
- "version": "14.18.63",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
- "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/set-array": "^1.0.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.9"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/set-array": {
- "version": "1.1.2",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/source-map": {
- "version": "0.3.2",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.0",
- "@jridgewell/trace-mapping": "^0.3.9"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.4.14",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.19",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
- "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
- "dev": true,
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@nfdi4plants/exceljs": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@nfdi4plants/exceljs/-/exceljs-0.3.0.tgz",
- "integrity": "sha512-/IvHS3ozGyZ2jG1pYpMoUn2vz+GMzkdo8zUnhsfnn2175ajnjlQKQi7qVhp8Kgpvt/FtthcysrloOjlttbyJQQ==",
- "dependencies": {
- "archiver": "^5.0.0",
- "dayjs": "^1.8.34",
- "fast-csv": "^4.3.1",
- "jszip": "^3.10.1",
- "readable-stream": "^3.6.0",
- "saxes": "^5.0.1",
- "tmp": "^0.2.0",
- "unzipper": "^0.10.11",
- "uuid": "^8.3.0"
- },
- "engines": {
- "node": ">=8.3.0"
- }
- },
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
- "dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz",
- "integrity": "sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.6.1.tgz",
- "integrity": "sha512-1TKm25Rn20vr5aTGGZqo6E4mzPicCUD79k17EgTLAsXc1zysyi4xXKACfUbwyANEPAEIxkzwue6JZ+stYzWUTA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.6.1.tgz",
- "integrity": "sha512-cEXJQY/ZqMACb+nxzDeX9IPLAg7S94xouJJCNVE5BJM8JUEP4HeTF+ti3cmxWeSJo+5D+o8Tc0UAWUkfENdeyw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.6.1.tgz",
- "integrity": "sha512-LoSU9Xu56isrkV2jLldcKspJ7sSXmZWkAxg7sW/RfF7GS4F5/v4EiqKSMCFbZtDu2Nc1gxxFdQdKwkKS4rwxNg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.6.1.tgz",
- "integrity": "sha512-EfI3hzYAy5vFNDqpXsNxXcgRDcFHUWSx5nnRSCKwXuQlI5J9dD84g2Usw81n3FLBNsGCegKGwwTVsSKK9cooSQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.6.1.tgz",
- "integrity": "sha512-9lhc4UZstsegbNLhH0Zu6TqvDfmhGzuCWtcTFXY10VjLLUe4Mr0Ye2L3rrtHaDd/J5+tFMEuo5LTCSCMXWfUKw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.6.1.tgz",
- "integrity": "sha512-FfoOK1yP5ksX3wwZ4Zk1NgyGHZyuRhf99j64I5oEmirV8EFT7+OhUZEnP+x17lcP/QHJNWGsoJwrz4PJ9fBEXw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.1.tgz",
- "integrity": "sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.6.1.tgz",
- "integrity": "sha512-RkJVNVRM+piYy87HrKmhbexCHg3A6Z6MU0W9GHnJwBQNBeyhCJG9KDce4SAMdicQnpURggSvtbGo9xAWOfSvIQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.6.1.tgz",
- "integrity": "sha512-v2FVT6xfnnmTe3W9bJXl6r5KwJglMK/iRlkKiIFfO6ysKs0rDgz7Cwwf3tjldxQUrHL9INT/1r4VA0n9L/F1vQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.6.1.tgz",
- "integrity": "sha512-YEeOjxRyEjqcWphH9dyLbzgkF8wZSKAKUkldRY6dgNR5oKs2LZazqGB41cWJ4Iqqcy9/zqYgmzBkRoVz3Q9MLw==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.6.1.tgz",
- "integrity": "sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
- "dev": true,
- "dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
- }
- },
- "node_modules/@types/babel__generator": {
- "version": "7.6.7",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.7.tgz",
- "integrity": "sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==",
- "dev": true,
- "dependencies": {
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
- "dev": true,
- "dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__traverse": {
- "version": "7.20.4",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz",
- "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==",
- "dev": true,
- "dependencies": {
- "@babel/types": "^7.20.7"
- }
- },
- "node_modules/@types/node": {
- "version": "20.10.3",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz",
- "integrity": "sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==",
- "dev": true,
- "dependencies": {
- "undici-types": "~5.26.4"
- }
- },
- "node_modules/@types/node-forge": {
- "version": "1.3.10",
- "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.10.tgz",
- "integrity": "sha512-y6PJDYN4xYBxwd22l+OVH35N+1fCYWiuC3aiP2SlXVE6Lo7SS+rSx9r89hLxrP4pn6n1lBGhHJ12pj3F3Mpttw==",
- "dev": true,
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@vitejs/plugin-basic-ssl": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.0.2.tgz",
- "integrity": "sha512-DKHKVtpI+eA5fvObVgQ3QtTGU70CcCnedalzqmGSR050AzKZMdUzgC8KmlOneHWH8dF2hJ3wkC9+8FDVAaDRCw==",
- "dev": true,
- "engines": {
- "node": ">=14.6.0"
- },
- "peerDependencies": {
- "vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
- }
- },
- "node_modules/@vitejs/plugin-react": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz",
- "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==",
- "dev": true,
- "dependencies": {
- "@babel/core": "^7.23.5",
- "@babel/plugin-transform-react-jsx-self": "^7.23.3",
- "@babel/plugin-transform-react-jsx-source": "^7.23.3",
- "@types/babel__core": "^7.20.5",
- "react-refresh": "^0.14.0"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "peerDependencies": {
- "vite": "^4.2.0 || ^5.0.0"
- }
- },
- "node_modules/acorn": {
- "version": "8.6.0",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
- "dependencies": {
- "color-convert": "^1.9.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/any-promise": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
- "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
- "dev": true
- },
- "node_modules/anymatch": {
- "version": "3.1.2",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/archiver": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz",
- "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
- "dependencies": {
- "archiver-utils": "^2.1.0",
- "async": "^3.2.4",
- "buffer-crc32": "^0.2.1",
- "readable-stream": "^3.6.0",
- "readdir-glob": "^1.1.2",
- "tar-stream": "^2.2.0",
- "zip-stream": "^4.1.0"
- },
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/archiver-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz",
- "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
- "dependencies": {
- "glob": "^7.1.4",
- "graceful-fs": "^4.2.0",
- "lazystream": "^1.0.0",
- "lodash.defaults": "^4.2.0",
- "lodash.difference": "^4.5.0",
- "lodash.flatten": "^4.4.0",
- "lodash.isplainobject": "^4.0.6",
- "lodash.union": "^4.6.0",
- "normalize-path": "^3.0.0",
- "readable-stream": "^2.0.0"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/archiver-utils/node_modules/readable-stream": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
- "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
- "dependencies": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "node_modules/archiver-utils/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
- },
- "node_modules/archiver-utils/node_modules/string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dependencies": {
- "safe-buffer": "~5.1.0"
- }
- },
- "node_modules/arg": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
- "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
- "dev": true
- },
- "node_modules/async": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
- "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
- },
- "node_modules/async-limiter": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
- "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
- "dev": true
- },
- "node_modules/autoprefixer": {
- "version": "10.4.16",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz",
- "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/autoprefixer"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "browserslist": "^4.21.10",
- "caniuse-lite": "^1.0.30001538",
- "fraction.js": "^4.3.6",
- "normalize-range": "^0.1.2",
- "picocolors": "^1.0.0",
- "postcss-value-parser": "^4.2.0"
- },
- "bin": {
- "autoprefixer": "bin/autoprefixer"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- },
- "peerDependencies": {
- "postcss": "^8.1.0"
- }
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
- },
- "node_modules/base-64": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
- "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==",
- "dev": true
- },
- "node_modules/base64-js": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
- "node_modules/big-integer": {
- "version": "1.6.51",
- "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
- "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
- "engines": {
- "node": ">=0.6"
- }
- },
- "node_modules/binary": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
- "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==",
- "dependencies": {
- "buffers": "~0.1.1",
- "chainsaw": "~0.1.0"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/binary-extensions": {
- "version": "2.2.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/bl": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
- "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
- "dependencies": {
- "buffer": "^5.5.0",
- "inherits": "^2.0.4",
- "readable-stream": "^3.4.0"
- }
- },
- "node_modules/bluebird": {
- "version": "3.4.7",
- "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
- "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA=="
- },
- "node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/braces": {
- "version": "3.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fill-range": "^7.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/browserslist": {
- "version": "4.21.10",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz",
- "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "caniuse-lite": "^1.0.30001517",
- "electron-to-chromium": "^1.4.477",
- "node-releases": "^2.0.13",
- "update-browserslist-db": "^1.0.11"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- }
- },
- "node_modules/buffer": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
- "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.1.13"
- }
- },
- "node_modules/buffer-crc32": {
- "version": "0.2.13",
- "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
- "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/buffer-from": {
- "version": "1.1.2",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true
- },
- "node_modules/buffer-indexof-polyfill": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz",
- "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==",
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/buffers": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
- "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==",
- "engines": {
- "node": ">=0.2.0"
- }
- },
- "node_modules/bulma": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/bulma/-/bulma-0.9.4.tgz",
- "integrity": "sha512-86FlT5+1GrsgKbPLRRY7cGDg8fsJiP/jzTqXXVqiUZZ2aZT8uemEOHlU1CDU+TxklPEZ11HZNNWclRBBecP4CQ=="
- },
- "node_modules/bulma-checkradio": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/bulma-checkradio/-/bulma-checkradio-2.1.3.tgz",
- "integrity": "sha512-8OmZ7PURyftNLGXSTNAYNTJHIe0OkoH/8z9iWfSXGxiv3AlrKneMtiVpBKofXsvc9ZHBUI1YjefiW5WFhgFgAQ==",
- "dependencies": {
- "bulma": "^0.9.3"
- }
- },
- "node_modules/bulma-slider": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/bulma-slider/-/bulma-slider-2.0.5.tgz",
- "integrity": "sha512-6woD/1E7q1o5bfEaQjNqpWZaCItC1oHe9bN15WYB2ELqz2gDaJYZkf+rlozGpAYOXQGDQGCCv3y+QuKjx6sQuw=="
- },
- "node_modules/bulma-switch": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/bulma-switch/-/bulma-switch-2.0.4.tgz",
- "integrity": "sha512-kMu4H0Pr0VjvfsnT6viRDCgptUq0Rvy7y7PX6q+IHg1xUynsjszPjhAdal5ysAlCG5HNO+5YXxeiu92qYGQolw=="
- },
- "node_modules/camelcase-css": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
- "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
- "dev": true,
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001566",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz",
- "integrity": "sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ]
- },
- "node_modules/chainsaw": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
- "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==",
- "dependencies": {
- "traverse": ">=0.3.0 <0.4"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dev": true,
- "dependencies": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/chokidar": {
- "version": "3.5.3",
- "dev": true,
- "funding": [
- {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
- },
- "engines": {
- "node": ">= 8.10.0"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/clone": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz",
- "integrity": "sha512-h5FLmEMFHeuzqmpVRcDayNlVZ+k4uK1niyKQN6oUMe7ieJihv44Vc3dY/kDnnWX4PDQSwes48s965PG/D4GntQ==",
- "dev": true,
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
- "dependencies": {
- "color-name": "1.1.3"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
- "dev": true
- },
- "node_modules/commander": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
- "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
- "dev": true,
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/component-emitter": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
- "integrity": "sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA==",
- "dev": true
- },
- "node_modules/compress-commons": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
- "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
- "dependencies": {
- "buffer-crc32": "^0.2.13",
- "crc32-stream": "^4.0.2",
- "normalize-path": "^3.0.0",
- "readable-stream": "^3.6.0"
- },
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true
- },
- "node_modules/core-js": {
- "version": "3.33.3",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz",
- "integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==",
- "dev": true,
- "hasInstallScript": true,
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/core-js"
- }
- },
- "node_modules/core-util-is": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
- "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
- },
- "node_modules/crc-32": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
- "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
- "bin": {
- "crc32": "bin/crc32.njs"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/crc32-stream": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz",
- "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
- "dependencies": {
- "crc-32": "^1.2.0",
- "readable-stream": "^3.4.0"
- },
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/cssesc": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
- "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
- "dev": true,
- "bin": {
- "cssesc": "bin/cssesc"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/cytoscape": {
- "version": "3.27.0",
- "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.27.0.tgz",
- "integrity": "sha512-pPZJilfX9BxESwujODz5pydeGi+FBrXq1rcaB1mfhFXXFJ9GjE6CNndAk+8jPzoXGD+16LtSS4xlYEIUiW4Abg==",
- "dependencies": {
- "heap": "^0.2.6",
- "lodash": "^4.17.21"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/dayjs": {
- "version": "1.11.10",
- "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
- "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
- },
- "node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dev": true,
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/didyoumean": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
- "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
- "dev": true
- },
- "node_modules/dlv": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
- "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
- "dev": true
- },
- "node_modules/duplexer2": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
- "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==",
- "dependencies": {
- "readable-stream": "^2.0.2"
- }
- },
- "node_modules/duplexer2/node_modules/readable-stream": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
- "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
- "dependencies": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "node_modules/duplexer2/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
- },
- "node_modules/duplexer2/node_modules/string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dependencies": {
- "safe-buffer": "~5.1.0"
- }
- },
- "node_modules/electron-to-chromium": {
- "version": "1.4.488",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.488.tgz",
- "integrity": "sha512-Dv4sTjiW7t/UWGL+H8ZkgIjtUAVZDgb/PwGWvMsCT7jipzUV/u5skbLXPFKb6iV0tiddVi/bcS2/kUrczeWgIQ==",
- "dev": true
- },
- "node_modules/end-of-stream": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
- "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
- "dependencies": {
- "once": "^1.4.0"
- }
- },
- "node_modules/esbuild": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz",
- "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==",
- "dev": true,
- "hasInstallScript": true,
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
- },
- "optionalDependencies": {
- "@esbuild/android-arm": "0.19.8",
- "@esbuild/android-arm64": "0.19.8",
- "@esbuild/android-x64": "0.19.8",
- "@esbuild/darwin-arm64": "0.19.8",
- "@esbuild/darwin-x64": "0.19.8",
- "@esbuild/freebsd-arm64": "0.19.8",
- "@esbuild/freebsd-x64": "0.19.8",
- "@esbuild/linux-arm": "0.19.8",
- "@esbuild/linux-arm64": "0.19.8",
- "@esbuild/linux-ia32": "0.19.8",
- "@esbuild/linux-loong64": "0.19.8",
- "@esbuild/linux-mips64el": "0.19.8",
- "@esbuild/linux-ppc64": "0.19.8",
- "@esbuild/linux-riscv64": "0.19.8",
- "@esbuild/linux-s390x": "0.19.8",
- "@esbuild/linux-x64": "0.19.8",
- "@esbuild/netbsd-x64": "0.19.8",
- "@esbuild/openbsd-x64": "0.19.8",
- "@esbuild/sunos-x64": "0.19.8",
- "@esbuild/win32-arm64": "0.19.8",
- "@esbuild/win32-ia32": "0.19.8",
- "@esbuild/win32-x64": "0.19.8"
- }
- },
- "node_modules/escalade": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
- "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "dev": true,
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/fast-csv": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz",
- "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==",
- "dependencies": {
- "@fast-csv/format": "4.3.5",
- "@fast-csv/parse": "4.3.6"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/fast-glob": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
- "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
- "dev": true,
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.4"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fastq": {
- "version": "1.15.0",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
- "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
- "dev": true,
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
- "node_modules/fill-range": {
- "version": "7.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/fraction.js": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
- "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
- "dev": true,
- "engines": {
- "node": "*"
- },
- "funding": {
- "type": "patreon",
- "url": "https://github.com/sponsors/rawify"
- }
- },
- "node_modules/fs-constants": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
- "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
- },
- "node_modules/fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/fstream": {
- "version": "1.0.12",
- "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
- "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
- "dependencies": {
- "graceful-fs": "^4.1.2",
- "inherits": "~2.0.0",
- "mkdirp": ">=0.5 0",
- "rimraf": "2"
- },
- "engines": {
- "node": ">=0.6"
- }
- },
- "node_modules/fstream/node_modules/rimraf": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
- "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-parent": {
- "version": "5.1.2",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/globals": {
- "version": "11.12.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
- "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
- },
- "node_modules/has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
- "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
- "dev": true,
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/heap": {
- "version": "0.2.7",
- "license": "MIT"
- },
- "node_modules/human-readable-ids": {
- "version": "1.0.4",
- "license": "Apache2",
- "dependencies": {
- "knuth-shuffle": "^1.0.0"
- }
- },
- "node_modules/ieee754": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
- "node_modules/immediate": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
- "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
- },
- "node_modules/immutable": {
- "version": "4.0.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "dependencies": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
- },
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "binary-extensions": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-core-module": {
- "version": "2.13.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
- "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
- "dev": true,
- "dependencies": {
- "hasown": "^2.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-glob": {
- "version": "4.0.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-number": {
- "version": "7.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.12.0"
- }
- },
- "node_modules/isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
- },
- "node_modules/isomorphic-fetch": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz",
- "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==",
- "dependencies": {
- "node-fetch": "^2.6.1",
- "whatwg-fetch": "^3.4.1"
- }
- },
- "node_modules/jiti": {
- "version": "1.21.0",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
- "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
- "dev": true,
- "bin": {
- "jiti": "bin/jiti.js"
- }
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "license": "MIT"
- },
- "node_modules/jsan": {
- "version": "3.1.13",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/jsesc": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
- "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
- "dev": true,
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "dev": true,
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/jsonschema": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz",
- "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/jszip": {
- "version": "3.10.1",
- "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
- "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
- "dependencies": {
- "lie": "~3.3.0",
- "pako": "~1.0.2",
- "readable-stream": "~2.3.6",
- "setimmediate": "^1.0.5"
- }
- },
- "node_modules/jszip/node_modules/readable-stream": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
- "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
- "dependencies": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "node_modules/jszip/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
- },
- "node_modules/jszip/node_modules/string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dependencies": {
- "safe-buffer": "~5.1.0"
- }
- },
- "node_modules/knuth-shuffle": {
- "version": "1.0.8",
- "license": "(MIT OR Apache-2.0)"
- },
- "node_modules/lazystream": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
- "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
- "dependencies": {
- "readable-stream": "^2.0.5"
- },
- "engines": {
- "node": ">= 0.6.3"
- }
- },
- "node_modules/lazystream/node_modules/readable-stream": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
- "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
- "dependencies": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "node_modules/lazystream/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
- },
- "node_modules/lazystream/node_modules/string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dependencies": {
- "safe-buffer": "~5.1.0"
- }
- },
- "node_modules/lie": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
- "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
- "dependencies": {
- "immediate": "~3.0.5"
- }
- },
- "node_modules/lilconfig": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
- "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
- "dev": true,
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/lines-and-columns": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
- "dev": true
- },
- "node_modules/linked-list": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz",
- "integrity": "sha512-Zr4ovrd0ODzF3ut2TWZMdHIxb8iFdJc/P3QM4iCJdlxxGHXo69c9hGIHzLo8/FtuR9E6WUZc5irKhtPUgOKMAg==",
- "dev": true
- },
- "node_modules/listenercount": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz",
- "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ=="
- },
- "node_modules/lodash": {
- "version": "4.17.21",
- "license": "MIT"
- },
- "node_modules/lodash.defaults": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
- "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
- },
- "node_modules/lodash.difference": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
- "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA=="
- },
- "node_modules/lodash.escaperegexp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
- "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="
- },
- "node_modules/lodash.flatten": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
- "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="
- },
- "node_modules/lodash.groupby": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz",
- "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw=="
- },
- "node_modules/lodash.isboolean": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
- "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
- },
- "node_modules/lodash.isequal": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
- "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
- },
- "node_modules/lodash.isfunction": {
- "version": "3.0.9",
- "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz",
- "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw=="
- },
- "node_modules/lodash.isnil": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz",
- "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng=="
- },
- "node_modules/lodash.isplainobject": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
- "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
- },
- "node_modules/lodash.isundefined": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz",
- "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA=="
- },
- "node_modules/lodash.union": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
- "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="
- },
- "node_modules/lodash.uniq": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
- "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="
- },
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "license": "MIT",
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
- "node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
- "dependencies": {
- "yallist": "^3.0.2"
- }
- },
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/micromatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
- "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
- "dev": true,
- "dependencies": {
- "braces": "^3.0.2",
- "picomatch": "^2.3.1"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/minimist": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/mkdirp": {
- "version": "0.5.6",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
- "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
- "dependencies": {
- "minimist": "^1.2.6"
- },
- "bin": {
- "mkdirp": "bin/cmd.js"
- }
- },
- "node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "node_modules/mz": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
- "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
- "dev": true,
- "dependencies": {
- "any-promise": "^1.0.0",
- "object-assign": "^4.0.1",
- "thenify-all": "^1.0.0"
- }
- },
- "node_modules/nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/node-fetch": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
- "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
- "dependencies": {
- "whatwg-url": "^5.0.0"
- },
- "engines": {
- "node": "4.x || >=6.0.0"
- },
- "peerDependencies": {
- "encoding": "^0.1.0"
- },
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
- }
- },
- "node_modules/node-forge": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
- "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
- "dev": true,
- "engines": {
- "node": ">= 6.13.0"
- }
- },
- "node_modules/node-releases": {
- "version": "2.0.13",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
- "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
- "dev": true
- },
- "node_modules/normalize-path": {
- "version": "3.0.0",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/normalize-range": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
- "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-hash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
- "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
- "dev": true,
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/pako": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
- "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
- },
- "node_modules/path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true
- },
- "node_modules/picocolors": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
- "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
- "dev": true
- },
- "node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/pirates": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
- "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
- "dev": true,
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/postcss": {
- "version": "8.4.32",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
- "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "nanoid": "^3.3.7",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/postcss-import": {
- "version": "15.1.0",
- "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
- "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
- "dev": true,
- "dependencies": {
- "postcss-value-parser": "^4.0.0",
- "read-cache": "^1.0.0",
- "resolve": "^1.1.7"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "postcss": "^8.0.0"
- }
- },
- "node_modules/postcss-js": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
- "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
- "dev": true,
- "dependencies": {
- "camelcase-css": "^2.0.1"
- },
- "engines": {
- "node": "^12 || ^14 || >= 16"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- "peerDependencies": {
- "postcss": "^8.4.21"
- }
- },
- "node_modules/postcss-load-config": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
- "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "lilconfig": "^3.0.0",
- "yaml": "^2.3.4"
- },
- "engines": {
- "node": ">= 14"
- },
- "peerDependencies": {
- "postcss": ">=8.0.9",
- "ts-node": ">=9.0.0"
- },
- "peerDependenciesMeta": {
- "postcss": {
- "optional": true
- },
- "ts-node": {
- "optional": true
- }
- }
- },
- "node_modules/postcss-load-config/node_modules/lilconfig": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz",
- "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==",
- "dev": true,
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/postcss-nested": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
- "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
- "dev": true,
- "dependencies": {
- "postcss-selector-parser": "^6.0.11"
- },
- "engines": {
- "node": ">=12.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- "peerDependencies": {
- "postcss": "^8.2.14"
- }
- },
- "node_modules/postcss-selector-parser": {
- "version": "6.0.13",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
- "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
- "dev": true,
- "dependencies": {
- "cssesc": "^3.0.0",
- "util-deprecate": "^1.0.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/postcss-value-parser": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
- "dev": true
- },
- "node_modules/process-nextick-args": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
- "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
- },
- "node_modules/querystring": {
- "version": "0.2.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.4.x"
- }
- },
- "node_modules/queue-microtask": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
- "node_modules/react": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
- "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
- "dependencies": {
- "loose-envify": "^1.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
- "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
- "dependencies": {
- "loose-envify": "^1.1.0",
- "scheduler": "^0.23.0"
- },
- "peerDependencies": {
- "react": "^18.2.0"
- }
- },
- "node_modules/react-refresh": {
- "version": "0.14.0",
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
- "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/read-cache": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
- "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
- "dev": true,
- "dependencies": {
- "pify": "^2.3.0"
- }
- },
- "node_modules/readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/readdir-glob": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
- "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
- "dependencies": {
- "minimatch": "^5.1.0"
- }
- },
- "node_modules/readdir-glob/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/readdir-glob/node_modules/minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/readdirp": {
- "version": "3.6.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "picomatch": "^2.2.1"
- },
- "engines": {
- "node": ">=8.10.0"
- }
- },
- "node_modules/remotedev": {
- "version": "0.2.9",
- "resolved": "https://registry.npmjs.org/remotedev/-/remotedev-0.2.9.tgz",
- "integrity": "sha512-W8dHOv9BcFnetFEd08yNb5O9Hd+zkTFFnf9FRjNCkb4u+JgQ/U152Aw4q83AmY3m34d6KZwhK5ip/Qc331+4vA==",
- "dev": true,
- "dependencies": {
- "jsan": "^3.1.3",
- "querystring": "^0.2.0",
- "rn-host-detect": "^1.0.1",
- "socketcluster-client": "^13.0.0"
- }
- },
- "node_modules/resolve": {
- "version": "1.22.8",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
- "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
- "dev": true,
- "dependencies": {
- "is-core-module": "^2.13.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/reusify": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
- "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
- "dev": true,
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
- "node_modules/rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/rn-host-detect": {
- "version": "1.2.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/rollup": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.1.tgz",
- "integrity": "sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==",
- "dev": true,
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.6.1",
- "@rollup/rollup-android-arm64": "4.6.1",
- "@rollup/rollup-darwin-arm64": "4.6.1",
- "@rollup/rollup-darwin-x64": "4.6.1",
- "@rollup/rollup-linux-arm-gnueabihf": "4.6.1",
- "@rollup/rollup-linux-arm64-gnu": "4.6.1",
- "@rollup/rollup-linux-arm64-musl": "4.6.1",
- "@rollup/rollup-linux-x64-gnu": "4.6.1",
- "@rollup/rollup-linux-x64-musl": "4.6.1",
- "@rollup/rollup-win32-arm64-msvc": "4.6.1",
- "@rollup/rollup-win32-ia32-msvc": "4.6.1",
- "@rollup/rollup-win32-x64-msvc": "4.6.1",
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/run-parallel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "dependencies": {
- "queue-microtask": "^1.2.2"
- }
- },
- "node_modules/sass": {
- "version": "1.69.5",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz",
- "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==",
- "dev": true,
- "dependencies": {
- "chokidar": ">=3.0.0 <4.0.0",
- "immutable": "^4.0.0",
- "source-map-js": ">=0.6.2 <2.0.0"
- },
- "bin": {
- "sass": "sass.js"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/saxes": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
- "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==",
- "dependencies": {
- "xmlchars": "^2.2.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/sc-channel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz",
- "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==",
- "dev": true,
- "dependencies": {
- "component-emitter": "1.2.1"
- }
- },
- "node_modules/sc-errors": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.1.tgz",
- "integrity": "sha512-dBn92iIonpChTxYLgKkIT/PCApvmYT6EPIbRvbQKTgY6tbEbIy8XVUv4pGyKwEK4nCmvX4TKXcN0iXC6tNW6rQ==",
- "dev": true
- },
- "node_modules/sc-formatter": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-3.0.3.tgz",
- "integrity": "sha512-lYI/lTs1u1c0geKElcj+bmEUfcP/HuKg2iDeTijPSjiTNFzN3Cf8Qh6tVd65oi7Qn+2/oD7LP4s6GC13v/9NiQ==",
- "dev": true
- },
- "node_modules/scheduler": {
- "version": "0.23.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
- "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
- "dependencies": {
- "loose-envify": "^1.1.0"
- }
- },
- "node_modules/selfsigned": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz",
- "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==",
- "dev": true,
- "dependencies": {
- "@types/node-forge": "^1.3.0",
- "node-forge": "^1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/setimmediate": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
- "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
- },
- "node_modules/socketcluster-client": {
- "version": "13.0.1",
- "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-13.0.1.tgz",
- "integrity": "sha512-hxiE2xz6mgaBlhXbtBa4POgWVEvIcjCoHzf5LTUVhI9IL8V2ltV3Ze8pQsi9egqTjSz4RHPfyrJ7BiETe5Kthw==",
- "dev": true,
- "dependencies": {
- "base-64": "0.1.0",
- "clone": "2.1.1",
- "component-emitter": "1.2.1",
- "linked-list": "0.1.0",
- "querystring": "0.2.0",
- "sc-channel": "^1.2.0",
- "sc-errors": "^1.4.0",
- "sc-formatter": "^3.0.1",
- "uuid": "3.2.1",
- "ws": "5.1.1"
- }
- },
- "node_modules/socketcluster-client/node_modules/querystring": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
- "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==",
- "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
- "dev": true,
- "engines": {
- "node": ">=0.4.x"
- }
- },
- "node_modules/socketcluster-client/node_modules/uuid": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
- "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==",
- "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
- "dev": true,
- "bin": {
- "uuid": "bin/uuid"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
- "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-support": {
- "version": "0.5.21",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- }
- },
- "node_modules/source-map-support/node_modules/source-map": {
- "version": "0.6.1",
- "dev": true,
- "license": "BSD-3-Clause",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "dependencies": {
- "safe-buffer": "~5.2.0"
- }
- },
- "node_modules/string_decoder/node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
- "node_modules/sucrase": {
- "version": "3.34.0",
- "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz",
- "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==",
- "dev": true,
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.2",
- "commander": "^4.0.0",
- "glob": "7.1.6",
- "lines-and-columns": "^1.1.6",
- "mz": "^2.7.0",
- "pirates": "^4.0.1",
- "ts-interface-checker": "^0.1.9"
- },
- "bin": {
- "sucrase": "bin/sucrase",
- "sucrase-node": "bin/sucrase-node"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/sucrase/node_modules/glob": {
- "version": "7.1.6",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
- "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
- "dev": true,
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
- "dependencies": {
- "has-flag": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/tailwindcss": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.6.tgz",
- "integrity": "sha512-AKjF7qbbLvLaPieoKeTjG1+FyNZT6KaJMJPFeQyLfIp7l82ggH1fbHJSsYIvnbTFQOlkh+gBYpyby5GT1LIdLw==",
- "dev": true,
- "dependencies": {
- "@alloc/quick-lru": "^5.2.0",
- "arg": "^5.0.2",
- "chokidar": "^3.5.3",
- "didyoumean": "^1.2.2",
- "dlv": "^1.1.3",
- "fast-glob": "^3.3.0",
- "glob-parent": "^6.0.2",
- "is-glob": "^4.0.3",
- "jiti": "^1.19.1",
- "lilconfig": "^2.1.0",
- "micromatch": "^4.0.5",
- "normalize-path": "^3.0.0",
- "object-hash": "^3.0.0",
- "picocolors": "^1.0.0",
- "postcss": "^8.4.23",
- "postcss-import": "^15.1.0",
- "postcss-js": "^4.0.1",
- "postcss-load-config": "^4.0.1",
- "postcss-nested": "^6.0.1",
- "postcss-selector-parser": "^6.0.11",
- "resolve": "^1.22.2",
- "sucrase": "^3.32.0"
- },
- "bin": {
- "tailwind": "lib/cli.js",
- "tailwindcss": "lib/cli.js"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/tailwindcss/node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/tar-stream": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
- "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
- "dependencies": {
- "bl": "^4.0.3",
- "end-of-stream": "^1.4.1",
- "fs-constants": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^3.1.1"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/terser": {
- "version": "5.14.2",
- "dev": true,
- "license": "BSD-2-Clause",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@jridgewell/source-map": "^0.3.2",
- "acorn": "^8.5.0",
- "commander": "^2.20.0",
- "source-map-support": "~0.5.20"
- },
- "bin": {
- "terser": "bin/terser"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/terser/node_modules/commander": {
- "version": "2.20.3",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true
- },
- "node_modules/thenify": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
- "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
- "dev": true,
- "dependencies": {
- "any-promise": "^1.0.0"
- }
- },
- "node_modules/thenify-all": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
- "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
- "dev": true,
- "dependencies": {
- "thenify": ">= 3.1.0 < 4"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/tmp": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
- "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
- "dependencies": {
- "rimraf": "^3.0.0"
- },
- "engines": {
- "node": ">=8.17.0"
- }
- },
- "node_modules/to-fast-properties": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
- "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
- "engines": {
- "node": ">=8.0"
- }
- },
- "node_modules/tr46": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
- },
- "node_modules/traverse": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
- "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/ts-interface-checker": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
- "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
- "dev": true
- },
- "node_modules/undici-types": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
- "dev": true
- },
- "node_modules/unzipper": {
- "version": "0.10.14",
- "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz",
- "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==",
- "dependencies": {
- "big-integer": "^1.6.17",
- "binary": "~0.3.0",
- "bluebird": "~3.4.1",
- "buffer-indexof-polyfill": "~1.0.0",
- "duplexer2": "~0.1.4",
- "fstream": "^1.0.12",
- "graceful-fs": "^4.2.2",
- "listenercount": "~1.0.1",
- "readable-stream": "~2.3.6",
- "setimmediate": "~1.0.4"
- }
- },
- "node_modules/unzipper/node_modules/readable-stream": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
- "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
- "dependencies": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "node_modules/unzipper/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
- },
- "node_modules/unzipper/node_modules/string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dependencies": {
- "safe-buffer": "~5.1.0"
- }
- },
- "node_modules/update-browserslist-db": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
- "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "escalade": "^3.1.1",
- "picocolors": "^1.0.0"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
- }
- },
- "node_modules/use-sync-external-store": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
- "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
- }
- },
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
- },
- "node_modules/uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
- "node_modules/vite": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.5.tgz",
- "integrity": "sha512-OekeWqR9Ls56f3zd4CaxzbbS11gqYkEiBtnWFFgYR2WV8oPJRRKq0mpskYy/XaoCL3L7VINDhqqOMNDiYdGvGg==",
- "dev": true,
- "dependencies": {
- "esbuild": "^0.19.3",
- "postcss": "^8.4.32",
- "rollup": "^4.2.0"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^18.0.0 || >=20.0.0",
- "less": "*",
- "lightningcss": "^1.21.0",
- "sass": "*",
- "stylus": "*",
- "sugarss": "*",
- "terser": "^5.4.0"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- }
- }
- },
- "node_modules/webidl-conversions": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
- },
- "node_modules/whatwg-fetch": {
- "version": "3.6.20",
- "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
- "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="
- },
- "node_modules/whatwg-url": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
- "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
- "dependencies": {
- "tr46": "~0.0.3",
- "webidl-conversions": "^3.0.0"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
- },
- "node_modules/ws": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz",
- "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==",
- "dev": true,
- "dependencies": {
- "async-limiter": "~1.0.0"
- }
- },
- "node_modules/xmlchars": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
- "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
- },
- "node_modules/yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true
- },
- "node_modules/yaml": {
- "version": "2.3.4",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
- "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
- "dev": true,
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/zip-stream": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz",
- "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
- "dependencies": {
- "archiver-utils": "^3.0.4",
- "compress-commons": "^4.1.2",
- "readable-stream": "^3.6.0"
- },
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/zip-stream/node_modules/archiver-utils": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz",
- "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
- "dependencies": {
- "glob": "^7.2.3",
- "graceful-fs": "^4.2.0",
- "lazystream": "^1.0.0",
- "lodash.defaults": "^4.2.0",
- "lodash.difference": "^4.5.0",
- "lodash.flatten": "^4.4.0",
- "lodash.isplainobject": "^4.0.6",
- "lodash.union": "^4.6.0",
- "normalize-path": "^3.0.0",
- "readable-stream": "^3.6.0"
- },
- "engines": {
- "node": ">= 10"
- }
+ "name": "Swate",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "@creativebulma/bulma-tooltip": "^1.2.0",
+ "@nfdi4plants/exceljs": "^0.3.0",
+ "bulma": "^1.0.1",
+ "bulma-checkradio": "^2.1.3",
+ "bulma-slider": "^2.0.5",
+ "bulma-switch": "^2.0.4",
+ "cytoscape": "^3.27.0",
+ "human-readable-ids": "^1.0.4",
+ "isomorphic-fetch": "^3.0.0",
+ "jsonschema": "^1.4.1",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "use-sync-external-store": "^1.2.0"
+ },
+ "devDependencies": {
+ "@types/node": "^20.10.3",
+ "@vitejs/plugin-basic-ssl": "^1.0.2",
+ "@vitejs/plugin-react": "^4.2.1",
+ "autoprefixer": "^10.4.16",
+ "core-js": "^3.33.3",
+ "postcss": "^8.4.32",
+ "remotedev": "^0.2.7",
+ "sass": "^1.69.5",
+ "selfsigned": "^2.4.1",
+ "tailwindcss": "^3.3.6",
+ "vite": "^5.0.5"
+ },
+ "engines": {
+ "node": "~18 || ~20",
+ "npm": "~9 || ~10"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
+ "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.24.7",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz",
+ "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz",
+ "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==",
+ "dev": true,
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.24.7",
+ "@babel/generator": "^7.24.7",
+ "@babel/helper-compilation-targets": "^7.24.7",
+ "@babel/helper-module-transforms": "^7.24.7",
+ "@babel/helpers": "^7.24.7",
+ "@babel/parser": "^7.24.7",
+ "@babel/template": "^7.24.7",
+ "@babel/traverse": "^7.24.7",
+ "@babel/types": "^7.24.7",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz",
+ "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.7",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^2.5.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz",
+ "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.24.7",
+ "@babel/helper-validator-option": "^7.24.7",
+ "browserslist": "^4.22.2",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-environment-visitor": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
+ "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-function-name": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz",
+ "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.24.7",
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-hoist-variables": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz",
+ "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
+ "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/traverse": "^7.24.7",
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz",
+ "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-environment-visitor": "^7.24.7",
+ "@babel/helper-module-imports": "^7.24.7",
+ "@babel/helper-simple-access": "^7.24.7",
+ "@babel/helper-split-export-declaration": "^7.24.7",
+ "@babel/helper-validator-identifier": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz",
+ "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-simple-access": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz",
+ "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/traverse": "^7.24.7",
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz",
+ "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz",
+ "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
+ "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz",
+ "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz",
+ "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.24.7",
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
+ "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.24.7",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz",
+ "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==",
+ "dev": true,
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz",
+ "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz",
+ "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz",
+ "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.24.7",
+ "@babel/parser": "^7.24.7",
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz",
+ "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.24.7",
+ "@babel/generator": "^7.24.7",
+ "@babel/helper-environment-visitor": "^7.24.7",
+ "@babel/helper-function-name": "^7.24.7",
+ "@babel/helper-hoist-variables": "^7.24.7",
+ "@babel/helper-split-export-declaration": "^7.24.7",
+ "@babel/parser": "^7.24.7",
+ "@babel/types": "^7.24.7",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz",
+ "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.24.7",
+ "@babel/helper-validator-identifier": "^7.24.7",
+ "to-fast-properties": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@creativebulma/bulma-tooltip": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@creativebulma/bulma-tooltip/-/bulma-tooltip-1.2.0.tgz",
+ "integrity": "sha512-ooImbeXEBxf77cttbzA7X5rC5aAWm9UsXIGViFOnsqB+6M944GkB28S5R4UWRqjFd2iW4zGEkEifAU+q43pt2w=="
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@fast-csv/format": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz",
+ "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==",
+ "dependencies": {
+ "@types/node": "^14.0.1",
+ "lodash.escaperegexp": "^4.1.2",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isequal": "^4.5.0",
+ "lodash.isfunction": "^3.0.9",
+ "lodash.isnil": "^4.0.0"
+ }
+ },
+ "node_modules/@fast-csv/format/node_modules/@types/node": {
+ "version": "14.18.63",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
+ "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="
+ },
+ "node_modules/@fast-csv/parse": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz",
+ "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==",
+ "dependencies": {
+ "@types/node": "^14.0.1",
+ "lodash.escaperegexp": "^4.1.2",
+ "lodash.groupby": "^4.6.0",
+ "lodash.isfunction": "^3.0.9",
+ "lodash.isnil": "^4.0.0",
+ "lodash.isundefined": "^3.0.1",
+ "lodash.uniq": "^4.5.0"
+ }
+ },
+ "node_modules/@fast-csv/parse/node_modules/@types/node": {
+ "version": "14.18.63",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
+ "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@nfdi4plants/exceljs": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@nfdi4plants/exceljs/-/exceljs-0.3.0.tgz",
+ "integrity": "sha512-/IvHS3ozGyZ2jG1pYpMoUn2vz+GMzkdo8zUnhsfnn2175ajnjlQKQi7qVhp8Kgpvt/FtthcysrloOjlttbyJQQ==",
+ "dependencies": {
+ "archiver": "^5.0.0",
+ "dayjs": "^1.8.34",
+ "fast-csv": "^4.3.1",
+ "jszip": "^3.10.1",
+ "readable-stream": "^3.6.0",
+ "saxes": "^5.0.1",
+ "tmp": "^0.2.0",
+ "unzipper": "^0.10.11",
+ "uuid": "^8.3.0"
+ },
+ "engines": {
+ "node": ">=8.3.0"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz",
+ "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz",
+ "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz",
+ "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz",
+ "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz",
+ "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz",
+ "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz",
+ "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz",
+ "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz",
+ "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz",
+ "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz",
+ "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz",
+ "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz",
+ "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz",
+ "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz",
+ "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz",
+ "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.6.8",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
+ "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.20.6",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
+ "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.20.7"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "20.14.9",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz",
+ "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==",
+ "dev": true,
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/@types/node-forge": {
+ "version": "1.3.11",
+ "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz",
+ "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@vitejs/plugin-basic-ssl": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz",
+ "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.6.0"
+ },
+ "peerDependencies": {
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz",
+ "integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.24.5",
+ "@babel/plugin-transform-react-jsx-self": "^7.24.5",
+ "@babel/plugin-transform-react-jsx-source": "^7.24.1",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.14.2"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/archiver": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz",
+ "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
+ "dependencies": {
+ "archiver-utils": "^2.1.0",
+ "async": "^3.2.4",
+ "buffer-crc32": "^0.2.1",
+ "readable-stream": "^3.6.0",
+ "readdir-glob": "^1.1.2",
+ "tar-stream": "^2.2.0",
+ "zip-stream": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/archiver-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz",
+ "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
+ "dependencies": {
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.2.0",
+ "lazystream": "^1.0.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.difference": "^4.5.0",
+ "lodash.flatten": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.union": "^4.6.0",
+ "normalize-path": "^3.0.0",
+ "readable-stream": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/archiver-utils/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/archiver-utils/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "node_modules/archiver-utils/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true
+ },
+ "node_modules/async": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
+ "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
+ },
+ "node_modules/async-limiter": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
+ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
+ "dev": true
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.19",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
+ "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "browserslist": "^4.23.0",
+ "caniuse-lite": "^1.0.30001599",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.0.0",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "node_modules/base-64": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
+ "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==",
+ "dev": true
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/big-integer": {
+ "version": "1.6.52",
+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
+ "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/binary": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
+ "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==",
+ "dependencies": {
+ "buffers": "~0.1.1",
+ "chainsaw": "~0.1.0"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/bluebird": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
+ "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA=="
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.23.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz",
+ "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001629",
+ "electron-to-chromium": "^1.4.796",
+ "node-releases": "^2.0.14",
+ "update-browserslist-db": "^1.0.16"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/buffer-indexof-polyfill": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz",
+ "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/buffers": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
+ "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==",
+ "engines": {
+ "node": ">=0.2.0"
+ }
+ },
+ "node_modules/bulma": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/bulma/-/bulma-1.0.1.tgz",
+ "integrity": "sha512-+xv/BIAEQakHkR0QVz+s+RjNqfC53Mx9ZYexyaFNFo9wx5i76HXArNdwW7bccyJxa5mgV/T5DcVGqsAB19nBJQ=="
+ },
+ "node_modules/bulma-checkradio": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/bulma-checkradio/-/bulma-checkradio-2.1.3.tgz",
+ "integrity": "sha512-8OmZ7PURyftNLGXSTNAYNTJHIe0OkoH/8z9iWfSXGxiv3AlrKneMtiVpBKofXsvc9ZHBUI1YjefiW5WFhgFgAQ==",
+ "dependencies": {
+ "bulma": "^0.9.3"
+ }
+ },
+ "node_modules/bulma-checkradio/node_modules/bulma": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/bulma/-/bulma-0.9.4.tgz",
+ "integrity": "sha512-86FlT5+1GrsgKbPLRRY7cGDg8fsJiP/jzTqXXVqiUZZ2aZT8uemEOHlU1CDU+TxklPEZ11HZNNWclRBBecP4CQ=="
+ },
+ "node_modules/bulma-slider": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/bulma-slider/-/bulma-slider-2.0.5.tgz",
+ "integrity": "sha512-6woD/1E7q1o5bfEaQjNqpWZaCItC1oHe9bN15WYB2ELqz2gDaJYZkf+rlozGpAYOXQGDQGCCv3y+QuKjx6sQuw=="
+ },
+ "node_modules/bulma-switch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/bulma-switch/-/bulma-switch-2.0.4.tgz",
+ "integrity": "sha512-kMu4H0Pr0VjvfsnT6viRDCgptUq0Rvy7y7PX6q+IHg1xUynsjszPjhAdal5ysAlCG5HNO+5YXxeiu92qYGQolw=="
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001638",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001638.tgz",
+ "integrity": "sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ]
+ },
+ "node_modules/chainsaw": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
+ "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==",
+ "dependencies": {
+ "traverse": ">=0.3.0 <0.4"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/clone": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz",
+ "integrity": "sha512-h5FLmEMFHeuzqmpVRcDayNlVZ+k4uK1niyKQN6oUMe7ieJihv44Vc3dY/kDnnWX4PDQSwes48s965PG/D4GntQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/component-emitter": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+ "integrity": "sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA==",
+ "dev": true
+ },
+ "node_modules/compress-commons": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
+ "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
+ "dependencies": {
+ "buffer-crc32": "^0.2.13",
+ "crc32-stream": "^4.0.2",
+ "normalize-path": "^3.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true
+ },
+ "node_modules/core-js": {
+ "version": "3.37.1",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz",
+ "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
+ },
+ "node_modules/crc-32": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+ "bin": {
+ "crc32": "bin/crc32.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/crc32-stream": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz",
+ "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
+ "dependencies": {
+ "crc-32": "^1.2.0",
+ "readable-stream": "^3.4.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/cytoscape": {
+ "version": "3.30.0",
+ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.30.0.tgz",
+ "integrity": "sha512-l590mjTHT6/Cbxp13dGPC2Y7VXdgc+rUeF8AnF/JPzhjNevbDJfObnJgaSjlldOgBQZbue+X6IUZ7r5GAgvauQ==",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.11",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz",
+ "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg=="
+ },
+ "node_modules/debug": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
+ "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true
+ },
+ "node_modules/duplexer2": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
+ "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==",
+ "dependencies": {
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "node_modules/duplexer2/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/duplexer2/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "node_modules/duplexer2/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.812",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.812.tgz",
+ "integrity": "sha512-7L8fC2Ey/b6SePDFKR2zHAy4mbdp1/38Yk5TsARO66W3hC5KEaeKMMHoxwtuH+jcu2AYLSn9QX04i95t6Fl1Hg==",
+ "dev": true
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
+ "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/fast-csv": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz",
+ "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==",
+ "dependencies": {
+ "@fast-csv/format": "4.3.5",
+ "@fast-csv/parse": "4.3.6"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+ "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
+ "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/fstream": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
+ "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
+ "deprecated": "This package is no longer supported.",
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "inherits": "~2.0.0",
+ "mkdirp": ">=0.5 0",
+ "rimraf": "2"
+ },
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/human-readable-ids": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/human-readable-ids/-/human-readable-ids-1.0.4.tgz",
+ "integrity": "sha512-h1zwThTims8A/SpqFGWyTx+jG1+WRMJaEeZgbtPGrIpj2AZjsOgy8Y+iNzJ0yAyN669Q6F02EK66WMWcst+2FA==",
+ "dependencies": {
+ "knuth-shuffle": "^1.0.0"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
+ },
+ "node_modules/immutable": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz",
+ "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==",
+ "dev": true
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz",
+ "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/isomorphic-fetch": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz",
+ "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==",
+ "dependencies": {
+ "node-fetch": "^2.6.1",
+ "whatwg-fetch": "^3.4.1"
+ }
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz",
+ "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==",
+ "dev": true,
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "1.21.6",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
+ "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
+ "dev": true,
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/jsan": {
+ "version": "3.1.14",
+ "resolved": "https://registry.npmjs.org/jsan/-/jsan-3.1.14.tgz",
+ "integrity": "sha512-wStfgOJqMv4QKktuH273f5fyi3D3vy2pHOiSDGPvpcS/q+wb/M7AK3vkCcaHbkZxDOlDU/lDJgccygKSG2OhtA==",
+ "dev": true
+ },
+ "node_modules/jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonschema": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz",
+ "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/jszip": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+ "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+ "dependencies": {
+ "lie": "~3.3.0",
+ "pako": "~1.0.2",
+ "readable-stream": "~2.3.6",
+ "setimmediate": "^1.0.5"
+ }
+ },
+ "node_modules/jszip/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/jszip/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "node_modules/jszip/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/knuth-shuffle": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/knuth-shuffle/-/knuth-shuffle-1.0.8.tgz",
+ "integrity": "sha512-IdC4Hpp+mx53zTt6VAGsAtbGM0g4BV9fP8tTcviCosSwocHcRDw9uG5Rnv6wLWckF4r72qeXFoK9NkvV1gUJCQ=="
+ },
+ "node_modules/lazystream": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
+ "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
+ "dependencies": {
+ "readable-stream": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.6.3"
+ }
+ },
+ "node_modules/lazystream/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/lazystream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "node_modules/lazystream/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
+ },
+ "node_modules/linked-list": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz",
+ "integrity": "sha512-Zr4ovrd0ODzF3ut2TWZMdHIxb8iFdJc/P3QM4iCJdlxxGHXo69c9hGIHzLo8/FtuR9E6WUZc5irKhtPUgOKMAg==",
+ "dev": true
+ },
+ "node_modules/listenercount": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz",
+ "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ=="
+ },
+ "node_modules/lodash.defaults": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
+ "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
+ },
+ "node_modules/lodash.difference": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
+ "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA=="
+ },
+ "node_modules/lodash.escaperegexp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
+ "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="
+ },
+ "node_modules/lodash.flatten": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
+ "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="
+ },
+ "node_modules/lodash.groupby": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz",
+ "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw=="
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
+ },
+ "node_modules/lodash.isequal": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
+ },
+ "node_modules/lodash.isfunction": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz",
+ "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw=="
+ },
+ "node_modules/lodash.isnil": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz",
+ "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng=="
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+ },
+ "node_modules/lodash.isundefined": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz",
+ "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA=="
+ },
+ "node_modules/lodash.union": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
+ "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="
+ },
+ "node_modules/lodash.uniq": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
+ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
+ "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-forge": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
+ "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6.13.0"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
+ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
+ "dev": true
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
+ "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
+ "dev": true
+ },
+ "node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
+ "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
+ "dev": true,
+ "engines": {
+ "node": "14 || >=16.14"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
+ "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
+ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.38",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+ "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.2.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "dev": true,
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
+ "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "lilconfig": "^3.0.0",
+ "yaml": "^2.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-load-config/node_modules/lilconfig": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
+ "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
+ "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
+ "dev": true,
+ "dependencies": {
+ "postcss-selector-parser": "^6.0.11"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz",
+ "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==",
+ "dev": true,
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true
+ },
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+ },
+ "node_modules/querystring": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz",
+ "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==",
+ "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.x"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
+ "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/readdir-glob": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
+ "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
+ "dependencies": {
+ "minimatch": "^5.1.0"
+ }
+ },
+ "node_modules/readdir-glob/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/readdir-glob/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/remotedev": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/remotedev/-/remotedev-0.2.9.tgz",
+ "integrity": "sha512-W8dHOv9BcFnetFEd08yNb5O9Hd+zkTFFnf9FRjNCkb4u+JgQ/U152Aw4q83AmY3m34d6KZwhK5ip/Qc331+4vA==",
+ "dev": true,
+ "dependencies": {
+ "jsan": "^3.1.3",
+ "querystring": "^0.2.0",
+ "rn-host-detect": "^1.0.1",
+ "socketcluster-client": "^13.0.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/rn-host-detect": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/rn-host-detect/-/rn-host-detect-1.2.0.tgz",
+ "integrity": "sha512-btNg5kzHcjZZ7t7mvvV/4wNJ9e3MPgrWivkRgWURzXL0JJ0pwWlU4zrbmdlz3HHzHOxhBhHB4D+/dbMFfu4/4A==",
+ "dev": true
+ },
+ "node_modules/rollup": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz",
+ "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "1.0.5"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.18.0",
+ "@rollup/rollup-android-arm64": "4.18.0",
+ "@rollup/rollup-darwin-arm64": "4.18.0",
+ "@rollup/rollup-darwin-x64": "4.18.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.18.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.18.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.18.0",
+ "@rollup/rollup-linux-arm64-musl": "4.18.0",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.18.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.18.0",
+ "@rollup/rollup-linux-x64-gnu": "4.18.0",
+ "@rollup/rollup-linux-x64-musl": "4.18.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.18.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.18.0",
+ "@rollup/rollup-win32-x64-msvc": "4.18.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/sass": {
+ "version": "1.77.6",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz",
+ "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==",
+ "dev": true,
+ "dependencies": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/saxes": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
+ "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sc-channel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz",
+ "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==",
+ "dev": true,
+ "dependencies": {
+ "component-emitter": "1.2.1"
+ }
+ },
+ "node_modules/sc-errors": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.1.tgz",
+ "integrity": "sha512-dBn92iIonpChTxYLgKkIT/PCApvmYT6EPIbRvbQKTgY6tbEbIy8XVUv4pGyKwEK4nCmvX4TKXcN0iXC6tNW6rQ==",
+ "dev": true
+ },
+ "node_modules/sc-formatter": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-3.0.3.tgz",
+ "integrity": "sha512-lYI/lTs1u1c0geKElcj+bmEUfcP/HuKg2iDeTijPSjiTNFzN3Cf8Qh6tVd65oi7Qn+2/oD7LP4s6GC13v/9NiQ==",
+ "dev": true
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/selfsigned": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz",
+ "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==",
+ "dev": true,
+ "dependencies": {
+ "@types/node-forge": "^1.3.0",
+ "node-forge": "^1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/socketcluster-client": {
+ "version": "13.0.1",
+ "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-13.0.1.tgz",
+ "integrity": "sha512-hxiE2xz6mgaBlhXbtBa4POgWVEvIcjCoHzf5LTUVhI9IL8V2ltV3Ze8pQsi9egqTjSz4RHPfyrJ7BiETe5Kthw==",
+ "dev": true,
+ "dependencies": {
+ "base-64": "0.1.0",
+ "clone": "2.1.1",
+ "component-emitter": "1.2.1",
+ "linked-list": "0.1.0",
+ "querystring": "0.2.0",
+ "sc-channel": "^1.2.0",
+ "sc-errors": "^1.4.0",
+ "sc-formatter": "^3.0.1",
+ "uuid": "3.2.1",
+ "ws": "5.1.1"
+ }
+ },
+ "node_modules/socketcluster-client/node_modules/querystring": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+ "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==",
+ "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.x"
+ }
+ },
+ "node_modules/socketcluster-client/node_modules/uuid": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
+ "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==",
+ "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
+ "dev": true,
+ "bin": {
+ "uuid": "bin/uuid"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "glob": "^10.3.10",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/sucrase/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/sucrase/node_modules/glob": {
+ "version": "10.4.2",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz",
+ "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==",
+ "dev": true,
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/sucrase/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.4",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
+ "integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==",
+ "dev": true,
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.5.3",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.0",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.0",
+ "lilconfig": "^2.1.0",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.23",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.1",
+ "postcss-nested": "^6.0.1",
+ "postcss-selector-parser": "^6.0.11",
+ "resolve": "^1.22.2",
+ "sucrase": "^3.32.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/tmp": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
+ "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ },
+ "node_modules/traverse": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
+ "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true
+ },
+ "node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "dev": true
+ },
+ "node_modules/unzipper": {
+ "version": "0.10.14",
+ "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz",
+ "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==",
+ "dependencies": {
+ "big-integer": "^1.6.17",
+ "binary": "~0.3.0",
+ "bluebird": "~3.4.1",
+ "buffer-indexof-polyfill": "~1.0.0",
+ "duplexer2": "~0.1.4",
+ "fstream": "^1.0.12",
+ "graceful-fs": "^4.2.2",
+ "listenercount": "~1.0.1",
+ "readable-stream": "~2.3.6",
+ "setimmediate": "~1.0.4"
+ }
+ },
+ "node_modules/unzipper/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/unzipper/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "node_modules/unzipper/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.0.16",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz",
+ "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.1.2",
+ "picocolors": "^1.0.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
+ "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
+ },
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.2.tgz",
+ "integrity": "sha512-6lA7OBHBlXUxiJxbO5aAY2fsHHzDr1q7DvXYnyZycRs2Dz+dXBWuhpWHvmljTRTpQC2uvGmUFFkSHF2vGo90MA==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.38",
+ "rollup": "^4.13.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
}
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ },
+ "node_modules/whatwg-fetch": {
+ "version": "3.6.20",
+ "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
+ "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
+ },
+ "node_modules/ws": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz",
+ "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==",
+ "dev": true,
+ "dependencies": {
+ "async-limiter": "~1.0.0"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
+ "node_modules/yaml": {
+ "version": "2.4.5",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz",
+ "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==",
+ "dev": true,
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/zip-stream": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz",
+ "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
+ "dependencies": {
+ "archiver-utils": "^3.0.4",
+ "compress-commons": "^4.1.2",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/zip-stream/node_modules/archiver-utils": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz",
+ "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
+ "dependencies": {
+ "glob": "^7.2.3",
+ "graceful-fs": "^4.2.0",
+ "lazystream": "^1.0.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.difference": "^4.5.0",
+ "lodash.flatten": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.union": "^4.6.0",
+ "normalize-path": "^3.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
}
+ }
}
diff --git a/package.json b/package.json
index 5c84c1f9..a48e3a1b 100644
--- a/package.json
+++ b/package.json
@@ -3,38 +3,38 @@
"ie 11"
],
"private": true,
- "type": "module",
- "engines": {
- "node": "~18 || ~20",
- "npm": "~9 || ~10"
- },
- "scripts": {},
- "devDependencies": {
- "@types/node": "^20.10.3",
- "@vitejs/plugin-basic-ssl": "^1.0.2",
- "@vitejs/plugin-react": "^4.2.1",
- "autoprefixer": "^10.4.16",
- "core-js": "^3.33.3",
- "postcss": "^8.4.32",
- "remotedev": "^0.2.9",
- "sass": "^1.69.5",
- "selfsigned": "^2.4.1",
- "tailwindcss": "^3.3.6",
- "vite": "^5.0.5"
- },
- "dependencies": {
- "@creativebulma/bulma-tooltip": "^1.2.0",
- "@nfdi4plants/exceljs": "^0.3.0",
- "bulma": "^0.9.4",
- "bulma-checkradio": "^2.1.3",
- "bulma-slider": "^2.0.5",
- "bulma-switch": "^2.0.4",
- "cytoscape": "^3.27.0",
- "human-readable-ids": "^1.0.4",
- "isomorphic-fetch": "^3.0.0",
- "jsonschema": "^1.4.1",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "use-sync-external-store": "^1.2.0"
- }
+ "type": "module",
+ "engines": {
+ "node": "~18 || ~20",
+ "npm": "~9 || ~10"
+ },
+ "scripts": {},
+ "devDependencies": {
+ "@types/node": "^20.10.3",
+ "@vitejs/plugin-basic-ssl": "^1.0.2",
+ "@vitejs/plugin-react": "^4.2.1",
+ "autoprefixer": "^10.4.16",
+ "core-js": "^3.33.3",
+ "postcss": "^8.4.32",
+ "remotedev": "^0.2.7",
+ "sass": "^1.69.5",
+ "selfsigned": "^2.4.1",
+ "tailwindcss": "^3.3.6",
+ "vite": "^5.0.5"
+ },
+ "dependencies": {
+ "@creativebulma/bulma-tooltip": "^1.2.0",
+ "@nfdi4plants/exceljs": "^0.3.0",
+ "bulma": "^1.0.1",
+ "bulma-checkradio": "^2.1.3",
+ "bulma-slider": "^2.0.5",
+ "bulma-switch": "^2.0.4",
+ "cytoscape": "^3.27.0",
+ "human-readable-ids": "^1.0.4",
+ "isomorphic-fetch": "^3.0.0",
+ "jsonschema": "^1.4.1",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "use-sync-external-store": "^1.2.0"
+ }
}
diff --git a/paket.dependencies b/paket.dependencies
index 7ef8ff1e..5b76225c 100644
--- a/paket.dependencies
+++ b/paket.dependencies
@@ -2,12 +2,19 @@ source https://api.nuget.org/v3/index.json
framework: net8.0
storage: none
-nuget ARCtrl 1.0.5
+nuget ARCtrl 2.0.0-alpha.7.swate alpha
+nuget ARCtrl.Contract 2.0.0-alpha.7.swate alpha
+nuget ARCtrl.Core 2.0.0-alpha.7.swate alpha
+nuget ARCtrl.CWL 2.0.0-alpha.7.swate alpha
+nuget ARCtrl.FileSystem 2.0.0-alpha.7.swate alpha
+nuget ARCtrl.Json 2.0.0-alpha.7.swate alpha
+nuget ARCtrl.Spreadsheet 2.0.0-alpha.7.swate alpha
+nuget Fable.Fetch 2.7.0
nuget Feliz.Bulma.Checkradio
nuget Feliz.Bulma.Switch
nuget Fsharp.Core ~> 8
nuget Fable.Remoting.Giraffe ~> 5
-nuget FsSpreadsheet.Exceljs ~> 5.0.2
+nuget FsSpreadsheet.Js ~> 6.1.3
nuget Saturn ~> 0
nuget Fable.Core ~> 4
diff --git a/paket.lock b/paket.lock
index f622e315..1ceec7ed 100644
--- a/paket.lock
+++ b/paket.lock
@@ -2,38 +2,41 @@ STORAGE: NONE
RESTRICTION: == net8.0
NUGET
remote: https://api.nuget.org/v3/index.json
- ARCtrl (1.0.5)
- ARCtrl.Contract (>= 1.0.5)
- ARCtrl.CWL (>= 1.0.5)
- ARCtrl.FileSystem (>= 1.0.5)
- ARCtrl.ISA (>= 1.0.5)
- ARCtrl.ISA.Json (>= 1.0.5)
- ARCtrl.ISA.Spreadsheet (>= 1.0.5)
+ ARCtrl (2.0.0-alpha.7.swate)
+ ARCtrl.Contract (>= 2.0.0-alpha.7.swate)
+ ARCtrl.CWL (>= 2.0.0-alpha.7.swate)
+ ARCtrl.FileSystem (>= 2.0.0-alpha.7.swate)
+ ARCtrl.Json (>= 2.0.0-alpha.7.swate)
+ ARCtrl.Spreadsheet (>= 2.0.0-alpha.7.swate)
Fable.Fetch (>= 2.6)
Fable.SimpleHttp (>= 3.5)
+ FSharp.Core (>= 7.0.401)
+ ARCtrl.Contract (2.0.0-alpha.7.swate)
+ ARCtrl.Core (>= 2.0.0-alpha.7.swate)
+ ARCtrl.Json (>= 2.0.0-alpha.7.swate)
+ ARCtrl.Spreadsheet (>= 2.0.0-alpha.7.swate)
+ FSharp.Core (>= 7.0.401)
+ ARCtrl.Core (2.0.0-alpha.7.swate)
+ ARCtrl.CWL (>= 2.0.0-alpha.7.swate)
+ ARCtrl.FileSystem (>= 2.0.0-alpha.7.swate)
+ FSharp.Core (>= 7.0.401)
+ ARCtrl.CWL (2.0.0-alpha.7.swate)
FSharp.Core (>= 6.0.7)
- ARCtrl.Contract (1.0.5)
- ARCtrl.ISA (>= 1.0.5)
- FSharp.Core (>= 6.0.7)
- ARCtrl.CWL (1.0.5)
- FSharp.Core (>= 6.0.7)
- ARCtrl.FileSystem (1.0.5)
+ ARCtrl.FileSystem (2.0.0-alpha.7.swate)
Fable.Core (>= 4.2)
FSharp.Core (>= 6.0.7)
- ARCtrl.ISA (1.0.5)
- ARCtrl.FileSystem (>= 1.0.5)
- FSharp.Core (>= 6.0.7)
- ARCtrl.ISA.Json (1.0.5)
- ARCtrl.ISA (>= 1.0.5)
- FSharp.Core (>= 6.0.7)
+ ARCtrl.Json (2.0.0-alpha.7.swate)
+ ARCtrl.Core (>= 2.0.0-alpha.7.swate)
+ FSharp.Core (>= 7.0.401)
NJsonSchema (>= 10.8)
- Thoth.Json (>= 10.1)
- Thoth.Json.Net (>= 11.0)
- ARCtrl.ISA.Spreadsheet (1.0.5)
- ARCtrl.FileSystem (>= 1.0.5)
- ARCtrl.ISA (>= 1.0.5)
- FSharp.Core (>= 6.0.7)
- FsSpreadsheet (>= 5.0.1)
+ Thoth.Json.Core (>= 0.2.1)
+ Thoth.Json.JavaScript (>= 0.1)
+ Thoth.Json.Newtonsoft (>= 0.1)
+ ARCtrl.Spreadsheet (2.0.0-alpha.7.swate)
+ ARCtrl.Core (>= 2.0.0-alpha.7.swate)
+ ARCtrl.FileSystem (>= 2.0.0-alpha.7.swate)
+ FSharp.Core (>= 7.0.401)
+ FsSpreadsheet (>= 6.1.2)
ExcelJS.Fable (0.3)
Fable.Core (>= 3.2.8)
Fable.React (>= 7.4.1)
@@ -41,38 +44,38 @@ NUGET
Expecto (9.0.4)
FSharp.Core (>= 4.6)
Mono.Cecil (>= 0.11.3)
- Fable.AST (4.3)
- Fable.Browser.Blob (1.3)
- Fable.Core (>= 3.0)
+ Fable.AST (4.5)
+ Fable.Browser.Blob (1.4)
+ Fable.Core (>= 3.2.8)
FSharp.Core (>= 4.7.2)
- Fable.Browser.Dom (2.15)
+ Fable.Browser.Dom (2.16)
Fable.Browser.Blob (>= 1.3)
Fable.Browser.Event (>= 1.5)
Fable.Browser.WebStorage (>= 1.2)
Fable.Core (>= 3.2.8)
FSharp.Core (>= 4.7.2)
- Fable.Browser.Event (1.5)
- Fable.Browser.Gamepad (>= 1.1)
- Fable.Core (>= 3.0)
+ Fable.Browser.Event (1.6)
+ Fable.Browser.Gamepad (>= 1.3)
+ Fable.Core (>= 3.2.8)
FSharp.Core (>= 4.7.2)
- Fable.Browser.Gamepad (1.2)
- Fable.Core (>= 3.0)
+ Fable.Browser.Gamepad (1.3)
+ Fable.Core (>= 3.2.8)
FSharp.Core (>= 4.7.2)
- Fable.Browser.MediaQueryList (1.4)
- Fable.Browser.Dom (>= 2.11)
- Fable.Browser.Event (>= 1.5)
- Fable.Core (>= 3.0)
+ Fable.Browser.MediaQueryList (1.5)
+ Fable.Browser.Dom (>= 2.16)
+ Fable.Browser.Event (>= 1.6)
+ Fable.Core (>= 3.2.8)
FSharp.Core (>= 4.7.2)
- Fable.Browser.WebStorage (1.2)
- Fable.Browser.Event (>= 1.5)
- Fable.Core (>= 3.0)
+ Fable.Browser.WebStorage (1.3)
+ Fable.Browser.Event (>= 1.6)
+ Fable.Core (>= 3.2.8)
FSharp.Core (>= 4.7.2)
- Fable.Browser.XMLHttpRequest (1.3)
- Fable.Browser.Blob (>= 1.3)
- Fable.Browser.Event (>= 1.5)
- Fable.Core (>= 3.0)
+ Fable.Browser.XMLHttpRequest (1.4)
+ Fable.Browser.Blob (>= 1.4)
+ Fable.Browser.Event (>= 1.6)
+ Fable.Core (>= 3.2.8)
FSharp.Core (>= 4.7.2)
- Fable.Core (4.2)
+ Fable.Core (4.3)
Fable.Elmish (4.1)
Fable.Core (>= 3.7.1)
FSharp.Core (>= 4.7.2)
@@ -98,7 +101,7 @@ NUGET
Fable.Exceljs (1.6)
Fable.Core (>= 4.0)
FSharp.Core (>= 6.0.7)
- Fable.Fetch (2.6)
+ Fable.Fetch (2.7)
Fable.Browser.Blob (>= 1.2)
Fable.Browser.Event (>= 1.5)
Fable.Core (>= 3.7.1)
@@ -113,7 +116,7 @@ NUGET
Fable.Promise (3.2)
Fable.Core (>= 3.7.1)
FSharp.Core (>= 4.7.2)
- Fable.React (9.3)
+ Fable.React (9.4)
Fable.React.Types (>= 18.3)
Fable.ReactDom.Types (>= 18.2)
FSharp.Core (>= 4.7.2)
@@ -124,25 +127,25 @@ NUGET
Fable.ReactDom.Types (18.2)
Fable.React.Types (>= 18.3)
FSharp.Core (>= 4.7.2)
- Fable.Remoting.Client (7.30)
+ Fable.Remoting.Client (7.32)
Fable.Browser.XMLHttpRequest (>= 1.0)
Fable.Core (>= 3.1.5)
- Fable.Remoting.MsgPack (>= 1.21)
+ Fable.Remoting.MsgPack (>= 1.24)
Fable.SimpleJson (>= 3.24)
FSharp.Core (>= 4.7.2)
- Fable.Remoting.Giraffe (5.18)
- Fable.Remoting.Server (>= 5.36)
+ Fable.Remoting.Giraffe (5.19)
+ Fable.Remoting.Server (>= 5.37)
FSharp.Core (>= 6.0)
Giraffe (>= 5.0)
Microsoft.IO.RecyclableMemoryStream (>= 3.0 < 4.0)
Fable.Remoting.Json (2.23)
FSharp.Core (>= 6.0)
Newtonsoft.Json (>= 12.0.2)
- Fable.Remoting.MsgPack (1.21)
+ Fable.Remoting.MsgPack (1.24)
FSharp.Core (>= 4.7.2)
- Fable.Remoting.Server (5.36)
+ Fable.Remoting.Server (5.37)
Fable.Remoting.Json (>= 2.23)
- Fable.Remoting.MsgPack (>= 1.21)
+ Fable.Remoting.MsgPack (>= 1.24)
FSharp.Core (>= 6.0)
Microsoft.IO.RecyclableMemoryStream (>= 3.0 < 4.0)
Fable.SimpleHttp (3.6)
@@ -182,27 +185,28 @@ NUGET
Feliz.UseElmish (2.5)
Fable.Elmish (>= 4.0)
FSharp.Core (>= 4.7.2)
- FSharp.Control.Websockets (0.2.3)
+ FSharp.Control.Websockets (0.3)
FSharp.Core (>= 6.0)
- Microsoft.IO.RecyclableMemoryStream (>= 2.2.1)
- FSharp.Core (8.0.101)
- FsSpreadsheet (5.0.2)
- Fable.Core (>= 4.0)
+ Microsoft.IO.RecyclableMemoryStream (>= 3.0)
+ FSharp.Core (8.0.300)
+ FsSpreadsheet (6.1.3)
FSharp.Core (>= 6.0.7)
- FsSpreadsheet.Exceljs (5.0.2)
+ Thoth.Json.Core (>= 0.2.1)
+ FsSpreadsheet.Js (6.1.3)
Fable.Exceljs (>= 1.6)
Fable.Promise (>= 3.2)
FSharp.Core (>= 6.0.7)
- FsSpreadsheet (>= 5.0.2)
- Giraffe (6.2)
+ FsSpreadsheet (>= 6.1.3)
+ Thoth.Json.JavaScript (>= 0.1)
+ Giraffe (6.4)
FSharp.Core (>= 6.0)
Giraffe.ViewEngine (>= 1.4)
- Microsoft.IO.RecyclableMemoryStream (>= 2.2.1)
+ Microsoft.IO.RecyclableMemoryStream (>= 3.0)
Newtonsoft.Json (>= 13.0.3)
- System.Text.Json (>= 7.0.3)
+ System.Text.Json (>= 8.0.3)
Giraffe.ViewEngine (1.4)
FSharp.Core (>= 5.0)
- Microsoft.AspNetCore.Authentication.JwtBearer (6.0.26)
+ Microsoft.AspNetCore.Authentication.JwtBearer (6.0.31)
Microsoft.IdentityModel.Protocols.OpenIdConnect (>= 6.35)
Microsoft.Bcl.AsyncInterfaces (8.0)
Microsoft.CSharp (4.7)
@@ -236,24 +240,23 @@ NUGET
Microsoft.Extensions.Primitives (>= 8.0)
Microsoft.Extensions.FileSystemGlobbing (8.0)
Microsoft.Extensions.Primitives (8.0)
- Microsoft.IdentityModel.Abstractions (7.2)
- Microsoft.IdentityModel.JsonWebTokens (7.2)
- Microsoft.IdentityModel.Tokens (>= 7.2)
- Microsoft.IdentityModel.Logging (7.2)
- Microsoft.IdentityModel.Abstractions (>= 7.2)
- Microsoft.IdentityModel.Protocols (7.2)
- Microsoft.IdentityModel.Logging (>= 7.2)
- Microsoft.IdentityModel.Tokens (>= 7.2)
- Microsoft.IdentityModel.Protocols.OpenIdConnect (7.2)
- Microsoft.IdentityModel.Protocols (>= 7.2)
- System.IdentityModel.Tokens.Jwt (>= 7.2)
- Microsoft.IdentityModel.Tokens (7.2)
- Microsoft.IdentityModel.Logging (>= 7.2)
- Microsoft.IO.RecyclableMemoryStream (3.0)
+ Microsoft.IdentityModel.Abstractions (7.6)
+ Microsoft.IdentityModel.JsonWebTokens (7.6)
+ Microsoft.IdentityModel.Tokens (>= 7.6)
+ Microsoft.IdentityModel.Logging (7.6)
+ Microsoft.IdentityModel.Abstractions (>= 7.6)
+ Microsoft.IdentityModel.Protocols (7.6)
+ Microsoft.IdentityModel.Tokens (>= 7.6)
+ Microsoft.IdentityModel.Protocols.OpenIdConnect (7.6)
+ Microsoft.IdentityModel.Protocols (>= 7.6)
+ System.IdentityModel.Tokens.Jwt (>= 7.6)
+ Microsoft.IdentityModel.Tokens (7.6)
+ Microsoft.IdentityModel.Logging (>= 7.6)
+ Microsoft.IO.RecyclableMemoryStream (3.0.1)
Mono.Cecil (0.11.5)
Namotion.Reflection (3.1.1)
Microsoft.CSharp (>= 4.3)
- Neo4j.Driver (5.16)
+ Neo4j.Driver (5.21)
Microsoft.Bcl.AsyncInterfaces (>= 5.0)
System.IO.Pipelines (>= 7.0)
System.ValueTuple (>= 4.5)
@@ -263,19 +266,19 @@ NUGET
Newtonsoft.Json (>= 13.0.3)
NJsonSchema.Annotations (>= 11.0)
NJsonSchema.Annotations (11.0)
- Saturn (0.16.1)
+ Saturn (0.17)
FSharp.Control.Websockets (>= 0.2.2)
- Giraffe (>= 6.0)
+ Giraffe (>= 6.4)
Microsoft.AspNetCore.Authentication.JwtBearer (>= 6.0.3)
- System.IdentityModel.Tokens.Jwt (7.2)
- Microsoft.IdentityModel.JsonWebTokens (>= 7.2)
- Microsoft.IdentityModel.Tokens (>= 7.2)
+ System.IdentityModel.Tokens.Jwt (7.6)
+ Microsoft.IdentityModel.JsonWebTokens (>= 7.6)
+ Microsoft.IdentityModel.Tokens (>= 7.6)
System.IO.Pipelines (8.0)
System.Text.Encodings.Web (8.0)
- System.Text.Json (8.0.1)
+ System.Text.Json (8.0.3)
System.Text.Encodings.Web (>= 8.0)
System.ValueTuple (4.5)
- Thoth.Elmish.Debouncer (2.0)
+ Thoth.Elmish.Debouncer (2.1)
Fable.Core (>= 4.0)
Fable.Elmish (>= 4.0.1)
Fable.Promise (>= 3.2)
@@ -284,7 +287,15 @@ NUGET
Thoth.Json (10.2)
Fable.Core (>= 3.6.2)
FSharp.Core (>= 4.7.2)
- Thoth.Json.Net (11.0)
- Fable.Core (>= 3.1.6)
- FSharp.Core (>= 4.7.2)
- Newtonsoft.Json (>= 11.0.2)
+ Thoth.Json.Core (0.2.1)
+ Fable.Core (>= 4.1)
+ FSharp.Core (>= 5.0)
+ Thoth.Json.JavaScript (0.1)
+ Fable.Core (>= 4.1)
+ FSharp.Core (>= 5.0)
+ Thoth.Json.Core (>= 0.1)
+ Thoth.Json.Newtonsoft (0.1)
+ Fable.Core (>= 4.1)
+ FSharp.Core (>= 5.0)
+ Newtonsoft.Json (>= 13.0.1)
+ Thoth.Json.Core (>= 0.1)
diff --git a/src/Client/ARCitect/ARCitect.fs b/src/Client/ARCitect/ARCitect.fs
index fd09db01..fa1f86c8 100644
--- a/src/Client/ARCitect/ARCitect.fs
+++ b/src/Client/ARCitect/ARCitect.fs
@@ -6,22 +6,25 @@ open Model.ARCitect
open Shared
open Messages
open Elmish
-open ARCtrl.ISA
-open ARCtrl.ISA.Json
+open ARCtrl
+open ARCtrl.Json
let send (msg:ARCitect.Msg) =
let (data: obj) =
match msg with
| Init ->
"Hello from Swate!"
- | TriggerSwateClose ->
- null
| AssayToARCitect assay ->
- let assay = ArcAssay.toArcJsonString assay
+ let assay = ArcAssay.toJsonString 0 assay
assay
| StudyToARCitect study ->
- let json = ArcStudy.toArcJsonString study
+ let json = ArcStudy.toJsonString 0 study
json
+ | InvestigationToARCitect inv ->
+ let json = ArcInvestigation.toJsonString 0 inv
+ json
+ | RequestPaths selectDirectories ->
+ selectDirectories
| Error exn ->
exn
postMessageToARCitect(msg, data)
@@ -29,14 +32,20 @@ let send (msg:ARCitect.Msg) =
let EventHandler (dispatch: Messages.Msg -> unit) : IEventHandler =
{
AssayToSwate = fun data ->
- let assay = ArcAssay.fromArcJsonString data.ArcAssayJsonString
+ let assay = ArcAssay.fromJsonString data.ArcAssayJsonString
log($"Received Assay {assay.Identifier} from ARCitect!")
Spreadsheet.InitFromArcFile (ArcFiles.Assay assay) |> SpreadsheetMsg |> dispatch
StudyToSwate = fun data ->
- let study = ArcStudy.fromArcJsonString data.ArcStudyJsonString
+ let study = ArcStudy.fromJsonString data.ArcStudyJsonString
Spreadsheet.InitFromArcFile (ArcFiles.Study (study, [])) |> SpreadsheetMsg |> dispatch
log($"Received Study {study.Identifier} from ARCitect!")
- Browser.Dom.console.log(study)
+ InvestigationToSwate = fun data ->
+ let inv = ArcInvestigation.fromJsonString data.ArcInvestigationJsonString
+ Spreadsheet.InitFromArcFile (ArcFiles.Investigation inv) |> SpreadsheetMsg |> dispatch
+ log($"Received Investigation {inv.Title} from ARCitect!")
+ PathsToSwate = fun paths ->
+ log $"Received {paths.paths.Length} paths from ARCitect!"
+ FilePicker.LoadNewFiles (List.ofArray paths.paths) |> FilePickerMsg |> dispatch
Error = fun exn ->
GenericError (Cmd.none, exn) |> DevMsg |> dispatch
}
\ No newline at end of file
diff --git a/src/Client/ARCitect/Interop.fs b/src/Client/ARCitect/Interop.fs
index 2331a275..2f496733 100644
--- a/src/Client/ARCitect/Interop.fs
+++ b/src/Client/ARCitect/Interop.fs
@@ -45,6 +45,7 @@ let initEventListener (eventHandler: IEventHandler) : unit -> unit =
let e = e :?> Browser.Types.MessageEvent
match verifyARCitectMsg e with
| Some content ->
+ log ("Message from ARCitect: " + content.api)
runApiFromName eventHandler content.api content.data
| None ->
()
diff --git a/src/Client/Client.fable-temp.csproj b/src/Client/Client.fable-temp.csproj
new file mode 100644
index 00000000..9f7bb983
--- /dev/null
+++ b/src/Client/Client.fable-temp.csproj
@@ -0,0 +1,123 @@
+
+
+
+
+ net8.0
+ FABLE_COMPILER
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Client/Client.fs b/src/Client/Client.fs
index f6bb79bc..a3092a20 100644
--- a/src/Client/Client.fs
+++ b/src/Client/Client.fs
@@ -15,10 +15,11 @@ let _ = importSideEffects "./style.scss"
let sayHello name = $"Hello {name}"
open Feliz
+open Feliz.Bulma
let private split_container model dispatch =
- let mainWindow = Seq.singleton <| MainWindowView.Main model dispatch
- let sideWindow = Seq.singleton <| SidebarView.SidebarView model dispatch
+ let mainWindow = Seq.singleton <| MainWindowView.Main (model, dispatch)
+ let sideWindow = Seq.singleton <| SidebarView.SidebarView.Main(model, dispatch)
SplitWindowView.Main
mainWindow
sideWindow
@@ -28,13 +29,18 @@ let private split_container model dispatch =
[]
let View (model : Model) (dispatch : Msg -> unit) =
let (colorstate, setColorstate) = React.useState(LocalStorage.Darkmode.State.init)
+ // Make ARCitect always use lighttheme
+ let makeColorSchemeLight = fun _ ->
+ if model.PersistentStorageState.Host.IsSome && model.PersistentStorageState.Host.Value = Swatehost.ARCitect then
+ setColorstate {colorstate with Theme = LocalStorage.Darkmode.DataTheme.Light}
+ LocalStorage.Darkmode.DataTheme.SET LocalStorage.Darkmode.DataTheme.Light
+ React.useEffect(makeColorSchemeLight, [|box model.PersistentStorageState.Host|])
let v = {colorstate with SetTheme = setColorstate}
React.contextProvider(LocalStorage.Darkmode.themeContext, v,
Html.div [
- Html.div [prop.id "modal-container"]
match model.PersistentStorageState.Host with
| Some Swatehost.Excel ->
- SidebarView.SidebarView model dispatch
+ SidebarView.SidebarView.Main(model, dispatch)
| _ ->
split_container model dispatch
]
diff --git a/src/Client/Client.fsproj b/src/Client/Client.fsproj
index 46d78fe7..02614367 100644
--- a/src/Client/Client.fsproj
+++ b/src/Client/Client.fsproj
@@ -6,6 +6,7 @@
+
@@ -13,6 +14,7 @@
+
@@ -24,21 +26,17 @@
-
-
+
-
-
-
@@ -53,28 +51,18 @@
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -89,15 +77,31 @@
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Client/Helper.fs b/src/Client/Helper.fs
index 9803ffb3..5e43dcb5 100644
--- a/src/Client/Helper.fs
+++ b/src/Client/Helper.fs
@@ -15,8 +15,8 @@ let debounce<'T> (storage:Dictionary) (key: string) (timeout: int)
let key = key // fn.ToString()
// Cancel previous debouncer
match storage.TryGetValue(key) with
- | true, timeoutId -> printfn "CLEAR"; Fable.Core.JS.clearTimeout timeoutId
- | _ -> printfn "Not clear";()
+ | true, timeoutId -> Fable.Core.JS.clearTimeout timeoutId
+ | _ -> ()
// Create a new timeout and memoize it
let timeoutId =
@@ -50,4 +50,33 @@ let debouncel<'T> (storage:Dictionary) (key: string) (timeout: int)
timeout
storage.[key] <- timeoutId
-let newDebounceStorage = fun () -> Dictionary(HashIdentity.Structural)
\ No newline at end of file
+let newDebounceStorage = fun () -> Dictionary(HashIdentity.Structural)
+
+type Clipboard =
+ abstract member writeText: string -> JS.Promise
+ abstract member readText: unit -> JS.Promise
+
+type Navigator =
+ abstract member clipboard: Clipboard
+
+[]
+let navigator : Navigator = jsNative
+
+///
+/// take "count" many items from array if existing. if not enough items return as many as possible
+///
+///
+///
+let takeFromArray (count: int) (array: 'a []) =
+ let exit (acc: 'a list) = List.rev acc |> Array.ofList
+ let rec takeRec (l2: 'a list) (acc: 'a list) index =
+ if index >= count then
+ exit acc
+ else
+ match l2 with
+ | [] -> exit acc
+ | item::tail ->
+ let newAcc = item::acc
+ takeRec tail newAcc (index+1)
+
+ takeRec (Array.toList array) [] 0
\ No newline at end of file
diff --git a/src/Client/Host.fs b/src/Client/Host.fs
index 3d84d091..33fd146f 100644
--- a/src/Client/Host.fs
+++ b/src/Client/Host.fs
@@ -5,11 +5,13 @@ module Host
type Swatehost =
| Browser
| Excel
-| ARCitect //WIP
-
+| ARCitect
with
static member ofQueryParam (queryInteger: int option) =
match queryInteger with
| Some 1 -> Swatehost.ARCitect
| Some 2 -> Swatehost.Excel
- | _ -> Browser
\ No newline at end of file
+ | _ -> Browser
+
+ member this.IsStandalone =
+ this = Swatehost.Browser || this = Swatehost.ARCitect
\ No newline at end of file
diff --git a/src/Client/Init.fs b/src/Client/Init.fs
index 99e233b2..a55faf06 100644
--- a/src/Client/Init.fs
+++ b/src/Client/Init.fs
@@ -12,22 +12,15 @@ let initializeModel () =
let dt = LocalStorage.Darkmode.DataTheme.GET()
LocalStorage.Darkmode.DataTheme.SET dt
{
- DebouncerState = Debouncer .create ()
PageState = PageState .init ()
PersistentStorageState = PersistentStorageState .init ()
DevState = DevState .init ()
TermSearchState = TermSearch.Model .init ()
ExcelState = OfficeInterop.Model .init ()
- ApiState = ApiState .init ()
FilePickerState = FilePicker.Model .init ()
AddBuildingBlockState = BuildingBlock.Model .init ()
- ValidationState = Validation.Model .init ()
ProtocolState = Protocol.Model .init ()
BuildingBlockDetailsState = BuildingBlockDetailsState .init ()
- SettingsXmlState = SettingsXml.Model .init ()
- JsonExporterModel = JsonExporter.Model .init ()
- TemplateMetadataModel = TemplateMetadata.Model .init ()
- DagModel = Dag.Model .init ()
CytoscapeModel = Cytoscape.Model .init ()
SpreadsheetModel = Spreadsheet.Model .fromLocalStorage()
History = LocalHistory.Model .init().UpdateFromSessionStorage()
diff --git a/src/Client/LocalStorage/Widgets.fs b/src/Client/LocalStorage/Widgets.fs
new file mode 100644
index 00000000..1495a4cd
--- /dev/null
+++ b/src/Client/LocalStorage/Widgets.fs
@@ -0,0 +1,68 @@
+module LocalStorage.Widgets
+
+open Feliz
+open Fable.Core.JsInterop
+
+///
+/// Is not only used to store position but also size.
+///
+type Rect = {
+ X: int
+ Y: int
+} with
+ static member init () = {
+ X = 0
+ Y = 0
+ }
+
+open Fable.SimpleJson
+
+let [] BuildingBlockWidgets = "BuildingBlock"
+let [] TemplatesWidgets = "Templates"
+let [] FilePickerWidgets = "FilerPicker"
+
+[]
+module Position =
+
+ open Browser
+
+ let [] private Key_Prefix = "WidgetsPosition_"
+
+ let write(modalName:string, dt: Rect) =
+ let s = Json.serialize dt
+ WebStorage.localStorage.setItem(Key_Prefix + modalName, s)
+
+ let load(modalName:string) =
+ let key = Key_Prefix + modalName
+ try
+ WebStorage.localStorage.getItem(key)
+ |> Json.parseAs
+ |> Some
+ with
+ |_ ->
+ WebStorage.localStorage.removeItem(key)
+ printfn "Could not find %s" key
+ None
+
+
+[]
+module Size =
+ open Browser
+
+ let [] private Key_Prefix = "WidgetsSize_"
+
+ let write(modalName:string, dt: Rect) =
+ let s = Json.serialize dt
+ WebStorage.localStorage.setItem(Key_Prefix + modalName, s)
+
+ let load(modalName:string) =
+ let key = Key_Prefix + modalName
+ try
+ WebStorage.localStorage.getItem(key)
+ |> Json.parseAs
+ |> Some
+ with
+ |_ ->
+ WebStorage.localStorage.removeItem(key)
+ printfn "Could not find %s" key
+ None
diff --git a/src/Client/MainComponents/AddRows.fs b/src/Client/MainComponents/AddRows.fs
index 07857199..6ad69ce6 100644
--- a/src/Client/MainComponents/AddRows.fs
+++ b/src/Client/MainComponents/AddRows.fs
@@ -12,7 +12,6 @@ let Main (dispatch: Messages.Msg -> unit) =
let state_rows, setState_rows = React.useState(init_RowsToAdd)
Html.div [
prop.id "ExpandTable"
- prop.title "Add rows"
prop.style [
style.flexGrow 1; style.justifyContent.center; style.display.inheritFromParent; style.padding(length.rem 1)
style.position.sticky; style.left 0
@@ -25,11 +24,13 @@ let Main (dispatch: Messages.Msg -> unit) =
prop.id "n_row_input"
prop.min init_RowsToAdd
prop.onChange(fun e -> setState_rows e)
+ prop.onKeyDown(key.enter, fun _ -> Spreadsheet.AddRows state_rows |> SpreadsheetMsg |> dispatch)
prop.defaultValue init_RowsToAdd
- prop.style [style.width(50)]
+ prop.style [style.width(100)]
]
Bulma.button.a [
Bulma.button.isRounded
+ prop.title "Add rows"
prop.onClick(fun _ ->
let inp = Browser.Dom.document.getElementById "n_row_input"
inp?Value <- init_RowsToAdd
diff --git a/src/Client/MainComponents/Cells.fs b/src/Client/MainComponents/Cells.fs
index 6753f765..28dc6627 100644
--- a/src/Client/MainComponents/Cells.fs
+++ b/src/Client/MainComponents/Cells.fs
@@ -7,97 +7,114 @@ open Spreadsheet
open MainComponents
open Messages
open Shared
-open ARCtrl.ISA
+open ARCtrl
+open Components
-type private CellState = {
- Active: bool
- /// This value is used to show during input cell editing. After confirming edit it will be used to push update
- Value: string
-} with
- static member init() =
- {
- Active = false
- Value = ""
- }
- static member init(v: string) =
- {
- Active = false
- Value = v
- }
+module private CellComponents =
-let private cellStyle (specificStyle: IStyleAttribute list) = prop.style [
- style.minWidth 100
- style.height 22
- style.border(length.px 1, borderStyle.solid, "darkgrey")
- yield! specificStyle
- ]
-
-let private cellInnerContainerStyle (specificStyle: IStyleAttribute list) = prop.style [
- style.display.flex;
- style.justifyContent.spaceBetween;
- style.height(length.percent 100);
- style.minHeight(35)
- style.width(length.percent 100)
- style.alignItems.center
- yield! specificStyle
- ]
+ let cellStyle (specificStyle: IStyleAttribute list) = prop.style [
+ style.minWidth 100
+ style.height 22
+ style.border(length.px 1, borderStyle.solid, "darkgrey")
+ yield! specificStyle
+ ]
-let private cellInputElement (isHeader: bool, updateMainStateTable: unit -> unit, setState_cell, state_cell, cell_value) =
- Bulma.input.text [
- prop.autoFocus true
- prop.style [
- if isHeader then style.fontWeight.bold
+ let cellInnerContainerStyle (specificStyle: IStyleAttribute list) = prop.style [
+ style.display.flex;
+ style.justifyContent.spaceBetween;
+ style.height(length.percent 100);
+ style.minHeight(35)
style.width(length.percent 100)
- style.height.unset
- style.borderRadius(0)
- style.border(0,borderStyle.none,"")
- style.backgroundColor.transparent
- style.margin (0)
- style.padding(length.em 0.5,length.em 0.75)
- //if isHeader then
- // style.color(NFDIColors.white)
+ style.alignItems.center
+ yield! specificStyle
]
- // Update main spreadsheet state when leaving focus or...
- prop.onBlur(fun _ ->
- updateMainStateTable()
- )
- // .. when pressing "ENTER". "ESCAPE" will negate changes.
- prop.onKeyDown(fun e ->
- match e.which with
- | 13. -> //enter
- updateMainStateTable()
- | 27. -> //escape
- setState_cell {Active = false; Value = cell_value}
- | _ -> ()
- )
- // Only change cell value while typing to increase performance.
- prop.onChange(fun e ->
- setState_cell {state_cell with Value = e}
- )
- prop.defaultValue cell_value
- ]
-let private basicValueDisplayCell (v: string) =
- Html.span [
- prop.style [
- style.flexGrow 1
- style.padding(length.em 0.5,length.em 0.75)
+ let basicValueDisplayCell (v: string) =
+ Html.span [
+ prop.style [
+ style.flexGrow 1
+ style.padding(length.em 0.5,length.em 0.75)
+ ]
+ prop.text v
+ ]
+
+ let compositeCellDisplay (cc: CompositeCell) =
+ let hasValidOA = match cc with | CompositeCell.Term oa -> oa.TermAccessionShort <> "" | CompositeCell.Unitized (v, oa) -> oa.TermAccessionShort <> "" | CompositeCell.FreeText _ -> false
+ let v = cc.ToString()
+ Html.div [
+ prop.classes ["is-flex"]
+ prop.style [
+ style.flexGrow 1
+ style.padding(length.em 0.5,length.em 0.75)
+ ]
+ prop.children [
+ Html.span [
+ prop.style [
+ style.flexGrow 1
+ ]
+ prop.text v
+ ]
+ if hasValidOA then
+ Bulma.icon [Html.i [
+ prop.style [style.custom("marginLeft", "auto")]
+ prop.className ["fa-solid"; "fa-check"]
+ ]]
+ ]
]
- prop.text v
- ]
+
+ let extendHeaderButton (state_extend: Set, columnIndex, setState_extend) =
+ let isExtended = state_extend.Contains(columnIndex)
+ Bulma.icon [
+ prop.style [
+ style.height (length.perc 100)
+ style.minWidth 25
+ style.cursor.pointer
+ ]
+ prop.onDoubleClick(fun e ->
+ e.stopPropagation()
+ e.preventDefault()
+ ()
+ )
+ prop.onClick(fun e ->
+ e.stopPropagation()
+ e.preventDefault()
+ let nextState = if isExtended then state_extend.Remove(columnIndex) else state_extend.Add(columnIndex)
+ setState_extend nextState
+ )
+ prop.children [Html.i [prop.classes ["fa-sharp"; "fa-solid"; "fa-angles-up"; if isExtended then "fa-rotate-270" else "fa-rotate-90"]; prop.style [style.fontSize(length.em 1)]]]
+ ]
+
+
+module private CellAux =
+
+ let headerTANSetter (columnIndex: int, s: string, header: CompositeHeader, dispatch) =
+ match header.TryOA(), s with
+ | Some oa, "" -> oa.TermAccessionNumber <- None; Some oa
+ | Some oa, s1 -> oa.TermAccessionNumber <- Some s1; Some oa
+ | None, _ -> None
+ |> Option.map header.UpdateWithOA
+ |> Option.iter (fun nextHeader -> Msg.UpdateHeader (columnIndex, nextHeader) |> SpreadsheetMsg |> dispatch)
+
+ let oasetter (index, nextCell: CompositeCell, dispatch) = Msg.UpdateCell (index, nextCell) |> SpreadsheetMsg |> dispatch
+
+
+open CellComponents
+open CellAux
module private EventPresets =
open Shared
- let onClickSelect (index: int*int, state_cell, selectedCells: Set, model:Messages.Model, dispatch)=
+ let onClickSelect (index: int*int, isIdle:bool, selectedCells: Set, model:Messages.Model, dispatch)=
fun (e: Browser.Types.MouseEvent) ->
// don't select cell if active(editable)
- if not state_cell.Active then
+ if isIdle then
let set =
- match e.ctrlKey with
- | true ->
+ match e.shiftKey, selectedCells.Count with
+ | true, 0 ->
+ selectedCells
+ | true, _ ->
let createSetOfIndex (columnMin:int, columnMax, rowMin:int, rowMax: int) =
[
for c in columnMin .. columnMax do
@@ -110,7 +127,7 @@ module private EventPresets =
let rowMin, rowMax = System.Math.Min(snd source, snd target), System.Math.Max(snd source, snd target)
let set = createSetOfIndex (columnMin,columnMax,rowMin,rowMax)
set
- | false ->
+ | false, _ ->
let next = if selectedCells = Set([index]) then Set.empty else Set([index])
next
UpdateSelectedCells set |> SpreadsheetMsg |> dispatch
@@ -123,270 +140,264 @@ module private EventPresets =
else
None
TermSearch.UpdateParentTerm oa |> TermSearchMsg |> dispatch
-///// Only apply this element to SwateCell if header has term.
-//[]
-//let TANCell(index: (int*int), model: Model, dispatch) =
-// let columnIndex = fst index
-// let rowIndex = snd index
-// let state = model.SpreadsheetModel
-// let cell = state.ActiveTable.[index]
-// let isHeader = cell.isHeader
-// let cellValue =
-// if isHeader then
-// let tan = cell.Header.Term |> Option.map (fun x -> x.TermAccession) |> Option.defaultValue ""
-// tan
-// elif cell.isUnit then
-// cell.Unit.Unit.TermAccession
-// elif cell.isTerm then
-// cell.Term.Term.TermAccession
-// else ""
-// let state_cell, setState_cell = React.useState(CellState.init(cellValue))
-// let isSelected = state.SelectedCells.Contains index
-// let cell_element : IReactProperty list -> ReactElement = if isHeader then Html.th else Html.td
-// cell_element [
-// prop.key $"Cell_{state.ActiveTableIndex}-{columnIndex}-{rowIndex}_TAN"
-// cellStyle [
-// if isHeader then
-// style.color(NFDIColors.white)
-// style.backgroundColor(NFDIColors.DarkBlue.Lighter20)
-// if isSelected then style.backgroundColor(NFDIColors.Mint.Lighter80)
-// ]
-// prop.onContextMenu <| ContextMenu.onContextMenu (index, model, dispatch)
-// prop.children [
-// Html.div [
-// cellInnerContainerStyle []
-// prop.onDoubleClick(fun e ->
-// e.preventDefault()
-// e.stopPropagation()
-// UpdateSelectedCells Set.empty |> SpreadsheetMsg |> dispatch
-// if not state_cell.Active then setState_cell {state_cell with Active = true}
-// )
-// prop.onClick <| EventPresets.onClickSelect(index, state_cell, state.SelectedCells, dispatch)
-// prop.children [
-// if state_cell.Active then
-// let updateMainStateTable() =
-// // Only update if changed
-// if state_cell.Value <> cellValue then
-// // Updating unit name should remove unit tsr/tan
-// let nextTerm =
-// match cell with
-// | IsHeader header ->
-// let nextTerm = header.Term |> Option.map (fun t -> {t with TermAccession = state_cell.Value}) |> Option.defaultValue (TermMinimal.create "" state_cell.Value )
-// let nextHeader = {header with Term = Some nextTerm}
-// IsHeader nextHeader
-// | IsTerm t_cell ->
-// let nextTermCell =
-// let term = { t_cell.Term with TermAccession = state_cell.Value }
-// { t_cell with Term = term }
-// IsTerm nextTermCell
-// | IsUnit u_cell ->
-// let nextUnitCell =
-// let unit = { u_cell.Unit with TermAccession = state_cell.Value }
-// { u_cell with Unit = unit }
-// IsUnit nextUnitCell
-// | IsFreetext _ ->
-// let t_cell = cell.toTermCell().Term
-// let term = {t_cell.Term with TermAccession = state_cell.Value}
-// let nextCell = {t_cell with Term = term}
-// IsTerm nextCell
-// Msg.UpdateTable (index, nextTerm) |> SpreadsheetMsg |> dispatch
-// setState_cell {state_cell with Active = false}
-// cellInputElement(isHeader, updateMainStateTable, setState_cell, state_cell, cellValue)
-// else
-// let displayValue =
-// if isHeader then
-// $"{ColumnCoreNames.TermAccessionNumber.toString} ({cellValue})"
-// else
-// cellValue
-// Html.span [
-// prop.style [
-// style.flexGrow 1
-// ]
-// prop.text displayValue
-// ]
-// ]
-// ]
-// ]
-// ]
-//[]
-//let UnitCell(index: (int*int), model: Model, dispatch) =
-// let columnIndex = fst index
-// let rowIndex = snd index
-// let state = model.SpreadsheetModel
-// let cell = state.ActiveTable.[index]
-// let isHeader = cell.isHeader
-// let cellValue = if isHeader then ColumnCoreNames.Unit.toString elif cell.isUnit then cell.Unit.Unit.Name else "Unknown"
-// let state_cell, setState_cell = React.useState(CellState.init(cellValue))
-// let isSelected = state.SelectedCells.Contains index
-// let cell_element : IReactProperty list -> ReactElement = if isHeader then Html.th else Html.td
-// cell_element [
-// prop.key $"Cell_{state.ActiveTableIndex}-{columnIndex}-{rowIndex}_Unit"
-// cellStyle [
-// if isHeader then
-// style.color(NFDIColors.white)
-// style.backgroundColor(NFDIColors.DarkBlue.Lighter20)
-// if isSelected then style.backgroundColor(NFDIColors.Mint.Lighter80)
-// ]
-// prop.onContextMenu <| ContextMenu.onContextMenu (index, model, dispatch)
-// prop.children [
-// Html.div [
-// cellInnerContainerStyle []
-// prop.onDoubleClick(fun e ->
-// e.preventDefault()
-// e.stopPropagation()
-// UpdateSelectedCells Set.empty |> SpreadsheetMsg |> dispatch
-// if not state_cell.Active then setState_cell {state_cell with Active = true}
-// )
-// prop.onClick <| EventPresets.onClickSelect(index, state_cell, state.SelectedCells, dispatch)
-// prop.children [
-// if not isHeader && state_cell.Active then
-// let updateMainStateTable() =
-// // Only update if changed
-// if state_cell.Value <> cellValue then
-// // This column only exists for unit cells
-// let nextTerm = {cell.Unit.Unit with Name = state_cell.Value}
-// let nextBody = IsUnit { cell.Unit with Unit = nextTerm }
-// Msg.UpdateTable (index, nextBody) |> SpreadsheetMsg |> dispatch
-// setState_cell {state_cell with Active = false}
-// cellInputElement(isHeader, updateMainStateTable, setState_cell, state_cell, cellValue)
-// else
-// Html.span [
-// prop.style [
-// style.flexGrow 1
-// ]
-// prop.text cellValue
-// ]
-// ]
-// ]
-// ]
-// ]
+open Shared
+open Fable.Core.JsInterop
-//let private extendHeaderButton (state_extend: Set, columnIndex, setState_extend) =
-// let isExtended = state_extend.Contains(columnIndex)
-// Bulma.icon [
-// prop.style [
-// style.cursor.pointer
-// ]
-// prop.onDoubleClick(fun e ->
-// e.stopPropagation()
-// e.preventDefault()
-// ()
-// )
-// prop.onClick(fun e ->
-// e.stopPropagation()
-// e.preventDefault()
-// let nextState = if isExtended then state_extend.Remove(columnIndex) else state_extend.Add(columnIndex)
-// setState_extend nextState
-// )
-// prop.children [Html.i [prop.classes ["fa-sharp"; "fa-solid"; "fa-angles-up"; if isExtended then "fa-rotate-270" else "fa-rotate-90"]; prop.style [style.fontSize(length.em 1)]]]
-// ]
+type Cell =
-[]
-let HeaderCell(columnIndex: int, state_extend: Set, setState_extend, model: Model, dispatch) =
- let state = model.SpreadsheetModel
- let header = state.ActiveTable.Headers.[columnIndex]
- let cellValue = header.ToString()
- let state_cell, setState_cell = React.useState(CellState.init(cellValue))
- Html.th [
- prop.key $"Header_{state.ActiveView.TableIndex}-{columnIndex}"
- cellStyle [
- //if isHeader && cell.Header.isInputColumn then
- // style.color(NFDIColors.white)
- // style.backgroundColor(NFDIColors.LightBlue.Base)
- //elif isHeader && cell.Header.isOutputColumn then
- // style.color(NFDIColors.white)
- // style.backgroundColor(NFDIColors.Red.Lighter30)
- //elif isHeader then
- // style.color(NFDIColors.white)
- // style.backgroundColor(NFDIColors.DarkBlue.Base)
- //if isSelected then style.backgroundColor(NFDIColors.Mint.Lighter80)
- ]
- //prop.onContextMenu <| ContextMenu.onContextMenu (index, model, dispatch)
- prop.children [
- Html.div [
- cellInnerContainerStyle []
- prop.onDoubleClick(fun e ->
- e.preventDefault()
- e.stopPropagation()
- UpdateSelectedCells Set.empty |> SpreadsheetMsg |> dispatch
- if not state_cell.Active then setState_cell {state_cell with Active = true}
- )
- //prop.onClick <| EventPresets.onClickSelect(index, state_cell, state.SelectedCells, dispatch)
+ []
+ static member CellInputElement (input: string, isHeader: bool, isReadOnly: bool, setter: string -> unit, makeIdle) =
+ let state, setState = React.useState(input)
+ React.useEffect((fun () -> setState input), [|box input|])
+ let debounceStorage = React.useRef(newDebounceStorage())
+ let loading, setLoading = React.useState(false)
+ let dsetter (inp) = debouncel debounceStorage.current "TextChange" 1000 setLoading setter inp
+ let input =
+ Bulma.control.div [
+ Bulma.control.isExpanded
+ if loading then Bulma.control.isLoading
prop.children [
- if state_cell.Active then
- /// Update change to mainState and exit active input.
- let updateMainStateTable() =
- // Only update if changed
- if state_cell.Value <> cellValue then
- let nextHeader = CompositeHeader.OfHeaderString state_cell.Value
- Msg.UpdateHeader (columnIndex, nextHeader) |> SpreadsheetMsg |> dispatch
- setState_cell {state_cell with Active = false}
- cellInputElement(true, updateMainStateTable, setState_cell, state_cell, cellValue)
- else
- basicValueDisplayCell cellValue
- //if isHeader && cell.Header.isTermColumn then
- // extendHeaderButton(state_extend, columnIndex, setState_extend)
+ Bulma.input.text [
+ prop.defaultValue input
+ prop.readOnly isReadOnly
+ prop.autoFocus true
+ prop.style [
+ if isHeader then style.fontWeight.bold
+ style.width(length.percent 100)
+ style.height.unset
+ style.borderRadius(0)
+ style.border(0,borderStyle.none,"")
+ style.backgroundColor.transparent
+ style.margin (0)
+ style.padding(length.em 0.5,length.em 0.75)
+ ]
+ prop.onBlur(fun _ ->
+ if isHeader then setter state;
+ makeIdle()
+ )
+ prop.onKeyDown(fun e ->
+ e.stopPropagation()
+ match e.which with
+ | 13. -> //enter
+ if isHeader then setter state
+ makeIdle()
+ | 27. -> //escape
+ makeIdle()
+ | _ -> ()
+ )
+ // Only change cell value while typing to increase performance.
+ prop.onChange(fun e ->
+ if isHeader then setState e else dsetter e
+ )
+ ]
]
]
+ Bulma.field.div [
+ Bulma.field.hasAddons
+ prop.className "is-flex-grow-1 m-0"
+ prop.children [ input ]
]
- ]
-[]
-let BodyCell(index: (int*int), state_extend: Set, setState_extend, model: Model, dispatch) =
- let columnIndex, rowIndex = index
- let state = model.SpreadsheetModel
- let cell = state.ActiveTable.TryGetCellAt index |> Option.defaultValue CompositeCell.emptyFreeText
- let cellValue = cell.GetContent().[0]
- let state_cell, setState_cell = React.useState(CellState.init(cellValue))
- let isSelected = state.SelectedCells.Contains index
- Html.td [
- prop.key $"Cell_{state.ActiveView.TableIndex}-{columnIndex}-{rowIndex}"
- cellStyle [
- //if isHeader && cell.Header.isInputColumn then
- // style.color(NFDIColors.white)
- // style.backgroundColor(NFDIColors.LightBlue.Base)
- //elif isHeader && cell.Header.isOutputColumn then
- // style.color(NFDIColors.white)
- // style.backgroundColor(NFDIColors.Red.Lighter30)
- //elif isHeader then
- // style.color(NFDIColors.white)
- // style.backgroundColor(NFDIColors.DarkBlue.Base)
- if isSelected then style.backgroundColor(NFDIColors.Mint.Lighter80)
+ []
+ static member private HeaderBase(columnType: ColumnType, setter: string -> unit, cellValue: string, columnIndex: int, header: CompositeHeader, state_extend: Set, setState_extend, model: Model, dispatch) =
+ let state = model.SpreadsheetModel
+ let isReadOnly = columnType = Unit
+ let makeIdle() = UpdateActiveCell None |> SpreadsheetMsg |> dispatch
+ let makeActive() = UpdateActiveCell (Some (!^columnIndex, columnType)) |> SpreadsheetMsg |> dispatch
+ let isIdle = state.CellIsIdle (!^columnIndex, columnType)
+ let isActive = not isIdle
+ Html.th [
+ if columnType.IsRefColumn then Bulma.color.hasBackgroundGreyLighter
+ prop.key $"Header_{state.ActiveView.TableIndex}-{columnIndex}-{columnType}"
+ prop.id $"Header_{columnIndex}_{columnType}"
+ cellStyle []
+ prop.className "main-contrast-bg"
+ prop.children [
+ Html.div [
+ cellInnerContainerStyle [style.custom("backgroundColor","inherit")]
+ if not isReadOnly then prop.onDoubleClick(fun e ->
+ e.preventDefault()
+ e.stopPropagation()
+ UpdateSelectedCells Set.empty |> SpreadsheetMsg |> dispatch
+ if isIdle then makeActive()
+ )
+ prop.children [
+ if isActive then
+ Cell.CellInputElement(cellValue, true, isReadOnly, setter, makeIdle)
+ else
+ let cellValue = // shadow cell value for tsr and tan to add columnType
+ match columnType with
+ | TSR | TAN -> $"{columnType} ({cellValue})"
+ | _ -> cellValue
+ basicValueDisplayCell cellValue
+ if columnType = Main && not header.IsSingleColumn then
+ extendHeaderButton(state_extend, columnIndex, setState_extend)
+ ]
+ ]
+ ]
]
- prop.onContextMenu <| ContextMenu.onContextMenu (index, model, dispatch)
- prop.children [
+
+ static member Header(columnIndex: int, header: CompositeHeader, state_extend: Set, setState_extend, model: Model, dispatch) =
+ let cellValue = header.ToString()
+ let setter =
+ fun (s: string) ->
+ let mutable nextHeader = CompositeHeader.OfHeaderString s
+ // update header with ref columns if term column
+ if header.IsTermColumn && not header.IsFeaturedColumn then
+ let updatedOA =
+ match nextHeader.TryOA() ,header.TryOA() with
+ | Some oa1, Some oa2 -> oa1.TermAccessionNumber <- oa2.TermAccessionNumber; oa1.TermSourceREF <- oa2.TermSourceREF; oa1
+ | _ -> failwith "this should never happen"
+ nextHeader <- nextHeader.UpdateWithOA updatedOA
+ Msg.UpdateHeader (columnIndex, nextHeader) |> SpreadsheetMsg |> dispatch
+ Cell.HeaderBase(Main, setter, cellValue, columnIndex, header, state_extend, setState_extend, model, dispatch)
+
+ static member HeaderUnit(columnIndex: int, header: CompositeHeader, state_extend: Set, setState_extend, model: Model, dispatch) =
+ let cellValue = "Unit"
+ let setter = fun (s: string) -> ()
+ Cell.HeaderBase(Unit, setter, cellValue, columnIndex, header, state_extend, setState_extend, model, dispatch)
+
+ static member HeaderTSR(columnIndex: int, header: CompositeHeader, state_extend: Set, setState_extend, model: Model, dispatch) =
+ let cellValue = header.TryOA() |> Option.map (fun oa -> oa.TermAccessionShort) |> Option.defaultValue ""
+ let setter = fun (s: string) -> headerTANSetter(columnIndex, s, header, dispatch)
+ Cell.HeaderBase(TSR, setter, cellValue, columnIndex, header, state_extend, setState_extend, model, dispatch)
+
+ static member HeaderTAN(columnIndex: int, header: CompositeHeader, state_extend: Set, setState_extend, model: Model, dispatch) =
+ let cellValue = header.TryOA() |> Option.map (fun oa -> oa.TermAccessionShort) |> Option.defaultValue ""
+ let setter = fun (s: string) -> headerTANSetter(columnIndex, s, header, dispatch)
+ Cell.HeaderBase(TAN, setter, cellValue, columnIndex, header, state_extend, setState_extend, model, dispatch)
+
+ static member Empty() =
+ Html.td [ cellStyle []; prop.readOnly true; prop.children [
Html.div [
- cellInnerContainerStyle []
- prop.onDoubleClick(fun e ->
- e.preventDefault()
- e.stopPropagation()
- UpdateSelectedCells Set.empty |> SpreadsheetMsg |> dispatch
- if not state_cell.Active then setState_cell {state_cell with Active = true}
- )
- prop.onClick <| EventPresets.onClickSelect(index, state_cell, state.SelectedCells, model, dispatch)
+ prop.style [style.height (length.perc 100); style.cursor.notAllowed; style.userSelect.none]
+ prop.className "is-flex is-align-items-center is-justify-content-center has-background-grey-lighter"
prop.children [
- if state_cell.Active then
- /// Update change to mainState and exit active input.
- let updateMainStateTable() =
- // Only update if changed
- if state_cell.Value <> cellValue then
- let nextCell = cell.UpdateMainField state_cell.Value
- Msg.UpdateCell (index, nextCell) |> SpreadsheetMsg |> dispatch
- setState_cell {state_cell with Active = false}
- cellInputElement(false, updateMainStateTable, setState_cell, state_cell, cellValue)
- else
- let displayName =
- if cell.isUnitized then
- let oaName = cell.ToOA().NameText
- let unitizedValue = if cellValue = "" then "" else cellValue + " " + oaName
- unitizedValue
+ Html.div "-"
+ ]
+ ]
+ ]]
+
+ []
+ static member private BodyBase(columnType: ColumnType, cellValue: string, setter: string -> unit, index: (int*int), cell: CompositeCell, model: Model, dispatch, ?oasetter: OntologyAnnotation -> unit) =
+ let columnIndex, rowIndex = index
+ let state = model.SpreadsheetModel
+ let isSelected = state.SelectedCells.Contains index
+ let isIdle = state.CellIsIdle (!^index, columnType)
+ let isActive = not isIdle
+ let ref = React.useElementRef()
+ let makeIdle() =
+ UpdateActiveCell None |> SpreadsheetMsg |> dispatch
+ let ele = Browser.Dom.document.getElementById("SPREADSHEET_MAIN_VIEW")
+ ele.focus()
+ let makeActive() = UpdateActiveCell (Some (!^index, columnType)) |> SpreadsheetMsg |> dispatch
+ React.useEffect((fun () ->
+ if isSelected then
+ let options = createEmpty
+ options.behavior <- Browser.Types.ScrollBehavior.Auto
+ options.``inline`` <- Browser.Types.ScrollAlignment.Nearest
+ options.block <- Browser.Types.ScrollAlignment.Nearest
+ if ref.current.IsSome then ref.current.Value.scrollIntoView(options)),
+ [|box isSelected|]
+ )
+ Html.td [
+ prop.key $"Cell_{state.ActiveView.TableIndex}-{columnIndex}-{rowIndex}"
+ cellStyle [
+ if isSelected then style.backgroundColor(NFDIColors.Mint.Lighter80)
+ ]
+ prop.ref ref
+ prop.onContextMenu <| ContextMenu.onContextMenu (index, model, dispatch)
+ prop.children [
+ Html.div [
+ cellInnerContainerStyle []
+ prop.onDoubleClick(fun e ->
+ e.preventDefault()
+ e.stopPropagation()
+ if isIdle then makeActive()
+ UpdateSelectedCells Set.empty |> SpreadsheetMsg |> dispatch
+ )
+ if isIdle then prop.onClick <| EventPresets.onClickSelect(index, isIdle, state.SelectedCells, model, dispatch)
+ prop.onMouseDown(fun e -> if isIdle && e.shiftKey then e.preventDefault())
+ prop.children [
+ if isActive then
+ // Update change to mainState and exit active input.
+ if oasetter.IsSome then
+ let oa = cell.ToOA()
+ let onBlur = fun e -> makeIdle()
+ let onEscape = fun e -> makeIdle()
+ let onEnter = fun e -> makeIdle()
+ let headerOA = state.ActiveTable.Headers.[columnIndex].TryOA()
+ let setter = fun (oa: OntologyAnnotation option) ->
+ if oa.IsSome then oasetter.Value oa.Value else setter ""
+ Components.TermSearch.Input(setter, input=oa, fullwidth=true, ?parent=headerOA, displayParent=false, debounceSetter=1000, onBlur=onBlur, onEscape=onEscape, onEnter=onEnter, autofocus=true, borderRadius=0, border="unset", searchableToggle=true, minWidth=length.px 400)
+ else
+ Cell.CellInputElement(cellValue, false, false, setter, makeIdle)
+ else
+ if columnType = Main then
+ compositeCellDisplay cell
else
- cellValue
- basicValueDisplayCell displayName
- //if isHeader && cell.Header.isTermColumn then
- // extendHeaderButton(state_extend, columnIndex, setState_extend)
+ basicValueDisplayCell cellValue
+ ]
]
]
]
- ]
\ No newline at end of file
+
+ static member Body(index: (int*int), cell: CompositeCell, model: Model, dispatch) =
+ let cellValue = cell.GetContent().[0]
+ let setter = fun (s: string) ->
+ let nextCell = cell.UpdateMainField s
+ Msg.UpdateCell (index, nextCell) |> SpreadsheetMsg |> dispatch
+ let oasetter =
+ if cell.isTerm then
+ fun (oa:OntologyAnnotation) ->
+ let nextCell = cell.UpdateWithOA oa
+ CellAux.oasetter(index, nextCell, dispatch)
+ |> Some
+ else
+ None
+ Cell.BodyBase(Main, cellValue, setter, index, cell, model, dispatch, ?oasetter=oasetter)
+
+ static member BodyUnit(index: (int*int), cell: CompositeCell, model: Model, dispatch) =
+ let cellValue = cell.GetContent().[1]
+ let setter = fun (s: string) ->
+ let oa = cell.ToOA()
+ let newName = if s = "" then None else Some s
+ oa.Name <- newName
+ let nextCell = cell.UpdateWithOA oa
+ Msg.UpdateCell (index, nextCell) |> SpreadsheetMsg |> dispatch
+ let oasetter =
+ if cell.isUnitized then
+ log "IsUnitized"
+ fun (oa:OntologyAnnotation) ->
+ log ("oa", oa)
+ let nextCell = cell.UpdateWithOA oa
+ log ("nextCell", nextCell)
+ CellAux.oasetter(index, nextCell, dispatch)
+ |> Some
+ else
+ None
+ Cell.BodyBase(Unit, cellValue, setter, index, cell, model, dispatch, ?oasetter=oasetter)
+
+ static member BodyTSR(index: (int*int), cell: CompositeCell, model: Model, dispatch) =
+ let contentIndex = if cell.isUnitized then 2 else 1
+ let cellValue = cell.GetContent().[contentIndex]
+ let setter = fun (s: string) ->
+ let oa = cell.ToOA()
+ let newTSR = if s = "" then None else Some s
+ oa.TermSourceREF <- newTSR
+ let nextCell = cell.UpdateWithOA oa
+ Msg.UpdateCell (index, nextCell) |> SpreadsheetMsg |> dispatch
+ Cell.BodyBase(TSR, cellValue, setter, index, cell, model, dispatch)
+
+ static member BodyTAN(index: (int*int), cell: CompositeCell, model: Model, dispatch) =
+ let contentIndex = if cell.isUnitized then 3 else 2
+ let cellValue = cell.GetContent().[contentIndex]
+ let setter = fun (s: string) ->
+ let oa = cell.ToOA()
+ let newTAN = if s = "" then None else Some s
+ oa.TermAccessionNumber <- newTAN
+ let nextCell = cell.UpdateWithOA oa
+ Msg.UpdateCell (index, nextCell) |> SpreadsheetMsg |> dispatch
+ Cell.BodyBase(TAN, cellValue, setter, index, cell, model, dispatch)
+
\ No newline at end of file
diff --git a/src/Client/MainComponents/ContextMenu.fs b/src/Client/MainComponents/ContextMenu.fs
index c89af432..0da1034d 100644
--- a/src/Client/MainComponents/ContextMenu.fs
+++ b/src/Client/MainComponents/ContextMenu.fs
@@ -3,22 +3,30 @@ module MainComponents.ContextMenu
open Feliz
open Feliz.Bulma
open Spreadsheet
-open ARCtrl.ISA
+open ARCtrl
open Messages
type private ContextFunctions = {
DeleteRow : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit
DeleteColumn : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit
+ MoveColumn : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit
Copy : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit
Cut : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit
Paste : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit
+ PasteAll : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit
FillColumn : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit
+ Clear : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit
+ TransformCell : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit
+ UpdateAllCells : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit
//EditColumn : (Browser.Types.MouseEvent -> unit) -> Browser.Types.MouseEvent -> unit
RowIndex : int
ColumnIndex : int
}
-let private contextmenu (mousex: int, mousey: int) (funcs:ContextFunctions) (selectedCell: CompositeCell option ) (rmv: _ -> unit) =
+let private isUnitOrTermCell (cell: CompositeCell option) =
+ cell.IsSome && not cell.Value.isFreeText
+
+let private contextmenu (mousex: int, mousey: int) (funcs:ContextFunctions) (contextCell: CompositeCell option) (rmv: _ -> unit) =
/// This element will remove the contextmenu when clicking anywhere else
let rmv_element = Html.div [
prop.onClick rmv
@@ -35,8 +43,9 @@ let private contextmenu (mousex: int, mousey: int) (funcs:ContextFunctions) (sel
]
let button (name:string, icon: string, msg, props) = Html.li [
Bulma.button.button [
- prop.style [style.borderRadius 0; style.justifyContent.spaceBetween]
+ prop.style [style.borderRadius 0; style.justifyContent.spaceBetween; style.fontSize (length.rem 0.9)]
prop.onClick msg
+ prop.className "py-1"
Bulma.button.isFullWidth
//Bulma.button.isSmall
Bulma.color.isBlack
@@ -49,18 +58,26 @@ let private contextmenu (mousex: int, mousey: int) (funcs:ContextFunctions) (sel
]
]
let divider = Html.li [
- Html.div [ prop.style [style.border(2, borderStyle.solid, NFDIColors.DarkBlue.Base); style.margin(2,0)] ]
+ Html.div [ prop.style [style.border(1, borderStyle.solid, NFDIColors.DarkBlue.Base); style.margin(2,0); style.width (length.perc 75); style.marginLeft length.auto] ]
]
let buttonList = [
//button ("Edit Column", "fa-solid fa-table-columns", funcs.EditColumn rmv, [])
- button ("Fill Column", "fa-solid fa-file-signature", funcs.FillColumn rmv, [])
+ button ("Fill Column", "fa-solid fa-pen", funcs.FillColumn rmv, [])
+ if isUnitOrTermCell contextCell then
+ let text = if contextCell.Value.isTerm then "As Unit Cell" else "As Term Cell"
+ button (text, "fa-solid fa-arrow-right-arrow-left", funcs.TransformCell rmv, [])
+ else
+ button ("Update Column", "fa-solid fa-ellipsis-vertical", funcs.UpdateAllCells rmv, [])
+ button ("Clear", "fa-solid fa-eraser", funcs.Clear rmv, [])
divider
button ("Copy", "fa-solid fa-copy", funcs.Copy rmv, [])
button ("Cut", "fa-solid fa-scissors", funcs.Cut rmv, [])
- button ("Paste", "fa-solid fa-paste", funcs.Paste rmv, [prop.disabled selectedCell.IsNone])
+ button ("Paste", "fa-solid fa-paste", funcs.Paste rmv, [])
+ button ("Paste All", "fa-solid fa-paste", funcs.PasteAll rmv, [])
divider
button ("Delete Row", "fa-solid fa-delete-left", funcs.DeleteRow rmv, [])
button ("Delete Column", "fa-solid fa-delete-left fa-rotate-270", funcs.DeleteColumn rmv, [])
+ button ("Move Column", "fa-solid fa-arrow-right-arrow-left", funcs.MoveColumn rmv, [])
]
Html.div [
prop.style [
@@ -78,6 +95,8 @@ let private contextmenu (mousex: int, mousey: int) (funcs:ContextFunctions) (sel
]
]
+open Shared
+
let onContextMenu (index: int*int, model: Model, dispatch) = fun (e: Browser.Types.MouseEvent) ->
e.stopPropagation()
e.preventDefault()
@@ -90,18 +109,45 @@ let onContextMenu (index: int*int, model: Model, dispatch) = fun (e: Browser.Typ
Spreadsheet.DeleteRows indexArr |> Messages.SpreadsheetMsg |> dispatch
else
Spreadsheet.DeleteRow (snd index) |> Messages.SpreadsheetMsg |> dispatch
+ let cell = model.SpreadsheetModel.ActiveTable.TryGetCellAt(fst index, snd index)
+ let isSelectedCell = model.SpreadsheetModel.SelectedCells.Contains index
//let editColumnEvent _ = Modals.Controller.renderModal("EditColumn_Modal", Modals.EditColumn.Main (fst index) model dispatch)
+ let triggerMoveColumnModal _ = Modals.Controller.renderModal("MoveColumn_Modal", Modals.MoveColumn.Main(fst index, model, dispatch))
+ let triggerUpdateColumnModal _ =
+ let columnIndex = fst index
+ let column = model.SpreadsheetModel.ActiveTable.GetColumn columnIndex
+ Modals.Controller.renderModal("UpdateColumn_Modal", Modals.UpdateColumn.Main(fst index, column, dispatch))
let funcs = {
DeleteRow = fun rmv e -> rmv e; deleteRowEvent e
DeleteColumn = fun rmv e -> rmv e; Spreadsheet.DeleteColumn (fst index) |> Messages.SpreadsheetMsg |> dispatch
- Copy = fun rmv e -> rmv e; Spreadsheet.CopyCell index |> Messages.SpreadsheetMsg |> dispatch
+ MoveColumn = fun rmv e -> rmv e; triggerMoveColumnModal e
+ Copy = fun rmv e ->
+ rmv e;
+ if isSelectedCell then
+ Spreadsheet.CopySelectedCells |> Messages.SpreadsheetMsg |> dispatch
+ else
+ Spreadsheet.CopyCell index |> Messages.SpreadsheetMsg |> dispatch
Cut = fun rmv e -> rmv e; Spreadsheet.CutCell index |> Messages.SpreadsheetMsg |> dispatch
- Paste = fun rmv e -> rmv e; Spreadsheet.PasteCell index |> Messages.SpreadsheetMsg |> dispatch
+ Paste = fun rmv e ->
+ rmv e;
+ if isSelectedCell then
+ Spreadsheet.PasteSelectedCells |> Messages.SpreadsheetMsg |> dispatch
+ else
+ Spreadsheet.PasteCell index |> Messages.SpreadsheetMsg |> dispatch
+ PasteAll = fun rmv e ->
+ rmv e;
+ Spreadsheet.PasteCellsExtend index |> Messages.SpreadsheetMsg |> dispatch
FillColumn = fun rmv e -> rmv e; Spreadsheet.FillColumnWithTerm index |> Messages.SpreadsheetMsg |> dispatch
+ Clear = fun rmv e -> rmv e; if isSelectedCell then Spreadsheet.ClearSelected |> Messages.SpreadsheetMsg |> dispatch else Spreadsheet.Clear [|index|] |> Messages.SpreadsheetMsg |> dispatch
+ TransformCell = fun rmv e ->
+ if cell.IsSome && (cell.Value.isTerm || cell.Value.isUnitized) then
+ let nextCell = if cell.Value.isTerm then cell.Value.ToUnitizedCell() else cell.Value.ToTermCell()
+ rmv e; Spreadsheet.UpdateCell (index, nextCell) |> Messages.SpreadsheetMsg |> dispatch
+ UpdateAllCells = fun rmv e -> rmv e; triggerUpdateColumnModal e
//EditColumn = fun rmv e -> rmv e; editColumnEvent e
RowIndex = snd index
ColumnIndex = fst index
}
- let child = contextmenu mousePosition funcs model.SpreadsheetModel.Clipboard.Cell
+ let child = contextmenu mousePosition funcs cell
let name = $"context_{mousePosition}"
Modals.Controller.renderModal(name, child)
\ No newline at end of file
diff --git a/src/Client/MainComponents/EmptyTableElement.fs b/src/Client/MainComponents/EmptyTableElement.fs
new file mode 100644
index 00000000..56b74273
--- /dev/null
+++ b/src/Client/MainComponents/EmptyTableElement.fs
@@ -0,0 +1,54 @@
+namespace MainComponents
+
+open Feliz
+open Feliz.Bulma
+open ARCtrl
+
+type EmptyTableElement =
+ static member Main(openBuildingBlockWidget: unit -> unit, openTemplateWidget: unit -> unit) =
+ Html.div [
+ prop.className "is-flex is-justify-content-center is-align-items-center"
+ prop.style [style.height (length.perc 100)]
+ prop.children [
+ Bulma.box [
+ Bulma.content [
+ prop.children [
+ Html.h3 [
+ prop.className "title"
+ prop.text "New Table!"
+ ]
+ Bulma.field.div [
+ prop.className "is-flex is-justify-content-space-between is-align-items-center gap-3"
+ prop.children [
+ Html.text "Start from an existing template!"
+ Bulma.button.span [
+ prop.onClick (fun _ -> openTemplateWidget())
+ prop.children [
+ Bulma.icon [
+ Html.i [prop.className "fa-solid fa-circle-plus" ]
+ Html.i [prop.className "fa-solid fa-table" ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ Bulma.field.div [
+ prop.className "is-flex is-justify-content-space-between is-align-items-center gap-3"
+ prop.children [
+ Html.text "Or start from scratch!"
+ Bulma.button.span [
+ prop.onClick (fun _ -> openBuildingBlockWidget())
+ prop.children [
+ Bulma.icon [
+ Html.i [prop.className "fa-solid fa-circle-plus" ]
+ Html.i [prop.className "fa-solid fa-table-columns" ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
\ No newline at end of file
diff --git a/src/Client/MainComponents/FooterTabs.fs b/src/Client/MainComponents/FooterTabs.fs
index a8bb4cc5..60a96953 100644
--- a/src/Client/MainComponents/FooterTabs.fs
+++ b/src/Client/MainComponents/FooterTabs.fs
@@ -2,7 +2,7 @@ module MainComponents.FooterTabs
open Feliz
open Feliz.Bulma
-open ARCtrl.ISA
+open ARCtrl
type private FooterTab = {
IsEditable: bool
@@ -95,11 +95,9 @@ let private dragleave_handler(state, setState) = fun (e: Browser.Types.DragEvent
setState {state with IsDraggedOver = false}
[]
-let Main (input: {|index: int; tables: ArcTables; model: Messages.Model; dispatch: Messages.Msg -> unit|}) =
- let index = input.index
- let table = input.tables.GetTableAt(index)
+let Main (index: int, tables: ArcTables, model: Messages.Model, dispatch: Messages.Msg -> unit) =
+ let table = tables.GetTableAt(index)
let state, setState = React.useState(FooterTab.init(table.Name))
- let dispatch = input.dispatch
let id = $"ReorderMe_{index}_{table.Name}"
Bulma.tab [
if state.IsDraggedOver then prop.className "dragover-footertab"
@@ -120,7 +118,7 @@ let Main (input: {|index: int; tables: ArcTables; model: Messages.Model; dispatc
// Use this to ensure updating reactelement correctly
prop.key id
prop.id id
- if input.model.SpreadsheetModel.ActiveView = Spreadsheet.ActiveView.Table index then Bulma.tab.isActive
+ if model.SpreadsheetModel.ActiveView = Spreadsheet.ActiveView.Table index then Bulma.tab.isActive
prop.onClick (fun _ -> Spreadsheet.UpdateActiveView (Spreadsheet.ActiveView.Table index) |> Messages.SpreadsheetMsg |> dispatch)
prop.onContextMenu(fun e ->
e.stopPropagation()
@@ -136,7 +134,8 @@ let Main (input: {|index: int; tables: ArcTables; model: Messages.Model; dispatc
prop.children [
if state.IsEditable then
let updateName = fun e ->
- Spreadsheet.RenameTable (index, state.Name) |> Messages.SpreadsheetMsg |> dispatch
+ if state.Name <> table.Name then
+ Spreadsheet.RenameTable (index, state.Name) |> Messages.SpreadsheetMsg |> dispatch
setState {state with IsEditable = false}
Bulma.input.text [
prop.autoFocus(true)
@@ -162,13 +161,12 @@ let Main (input: {|index: int; tables: ArcTables; model: Messages.Model; dispatc
]
[]
-let MainMetadata(input:{|model: Messages.Model; dispatch: Messages.Msg -> unit|}) =
- let dispatch = input.dispatch
+let MainMetadata(model: Messages.Model, dispatch: Messages.Msg -> unit) =
let order = 0
let id = "Metadata-Tab"
let nav = Spreadsheet.ActiveView.Metadata
Bulma.tab [
- if input.model.SpreadsheetModel.ActiveView = nav then Bulma.tab.isActive
+ if model.SpreadsheetModel.ActiveView = nav then Bulma.tab.isActive
prop.key id
prop.id id
prop.onClick (fun _ -> Spreadsheet.UpdateActiveView nav |> Messages.SpreadsheetMsg |> dispatch)
@@ -179,10 +177,9 @@ let MainMetadata(input:{|model: Messages.Model; dispatch: Messages.Msg -> unit|}
]
[]
-let MainPlus(input:{|dispatch: Messages.Msg -> unit|}) =
- let dispatch = input.dispatch
+let MainPlus(model: Messages.Model, dispatch: Messages.Msg -> unit) =
let state, setState = React.useState(FooterTab.init())
- let order = System.Int32.MaxValue
+ let order = System.Int32.MaxValue-1 // MaxValue will be sidebar toggle
let id = "Add-Spreadsheet-Button"
Bulma.tab [
prop.key id
@@ -207,4 +204,28 @@ let MainPlus(input:{|dispatch: Messages.Msg -> unit|}) =
]
]
]
+ ]
+
+let ToggleSidebar(model: Messages.Model, dispatch: Messages.Msg -> unit) =
+ let show = model.PersistentStorageState.ShowSideBar
+ let order = System.Int32.MaxValue
+ let id = "Toggle-Sidebar-Button"
+ Bulma.tab [
+ prop.key id
+ prop.id id
+ prop.onClick (fun e -> Messages.PersistentStorage.UpdateShowSidebar (not show) |> Messages.PersistentStorageMsg |> dispatch)
+ prop.style [style.custom ("order", order); style.height (length.percent 100); style.cursor.pointer; style.marginLeft length.auto]
+ prop.children [
+ Html.a [
+ prop.style [style.height.inheritFromParent; style.pointerEvents.none]
+ prop.children [
+ Bulma.icon [
+ Bulma.icon.isSmall
+ prop.children [
+ Html.i [prop.className ["fa-solid"; if show then "fa-chevron-right" else "fa-chevron-left"]]
+ ]
+ ]
+ ]
+ ]
+ ]
]
\ No newline at end of file
diff --git a/src/Client/MainComponents/KeyboardShortcuts.fs b/src/Client/MainComponents/KeyboardShortcuts.fs
index 3ffeaf41..8f51397f 100644
--- a/src/Client/MainComponents/KeyboardShortcuts.fs
+++ b/src/Client/MainComponents/KeyboardShortcuts.fs
@@ -1,26 +1,31 @@
module Spreadsheet.KeyboardShortcuts
-let private onKeydownEvent (dispatch: Messages.Msg -> unit) =
+let onKeydownEvent (dispatch: Messages.Msg -> unit) =
fun (e: Browser.Types.Event) ->
- //e.preventDefault()
- //e.stopPropagation()
let e = e :?> Browser.Types.KeyboardEvent
- match e.ctrlKey, e.which with
- | false, _ -> ()
+ match (e.ctrlKey || e.metaKey), e.which with
+ | false, 27. | false, 13. | false, 9. | false, 16. -> // escape, enter, tab, shift
+ ()
+ | false, 46. -> // del
+ Spreadsheet.ClearSelected |> Messages.SpreadsheetMsg |> dispatch
+ | false, 37. -> // arrow left
+ MoveSelectedCell Key.Left |> Messages.SpreadsheetMsg |> dispatch
+ | false, 38. -> // arrow up
+ MoveSelectedCell Key.Up |> Messages.SpreadsheetMsg |> dispatch
+ | false, 39. -> // arrow right
+ MoveSelectedCell Key.Right |> Messages.SpreadsheetMsg |> dispatch
+ | false, 40. -> // arrow down
+ MoveSelectedCell Key.Down |> Messages.SpreadsheetMsg |> dispatch
+ | false, key ->
+ SetActiveCellFromSelected |> Messages.SpreadsheetMsg |> dispatch
// Ctrl + c
- | _, _ ->
- match e.ctrlKey, e.which with
- | true, 67. ->
- Spreadsheet.CopySelectedCell |> Messages.SpreadsheetMsg |> dispatch
- // Ctrl + c
- | true, 88. ->
- Spreadsheet.CutSelectedCell |> Messages.SpreadsheetMsg |> dispatch
- // Ctrl + v
- | true, 86. ->
- Spreadsheet.PasteSelectedCell |> Messages.SpreadsheetMsg |> dispatch
- | _, _ -> ()
+ | true, _ ->
+ match e.which with
+ | 67. -> // Ctrl + c
+ Spreadsheet.CopySelectedCells |> Messages.SpreadsheetMsg |> dispatch
+ | 88. -> // Ctrl + x
+ Spreadsheet.CutSelectedCells |> Messages.SpreadsheetMsg |> dispatch
+ | 86. -> // Ctrl + v
+ Spreadsheet.PasteSelectedCells |> Messages.SpreadsheetMsg |> dispatch
+ | _ -> ()
-///These events only get reapplied on reload, not during hot reload
-let addOnKeydownEvent dispatch =
- Browser.Dom.document.body.removeEventListener("keydown", onKeydownEvent dispatch)
- Browser.Dom.document.body.addEventListener("keydown", onKeydownEvent dispatch)
diff --git a/src/Client/MainComponents/MainViewContainer.fs b/src/Client/MainComponents/MainViewContainer.fs
new file mode 100644
index 00000000..48f29d9f
--- /dev/null
+++ b/src/Client/MainComponents/MainViewContainer.fs
@@ -0,0 +1,19 @@
+module MainComponents.MainViewContainer
+
+
+open Feliz
+open Feliz.Bulma
+open Spreadsheet
+open Messages
+
+let Main(minWidth: int, left: ReactElement seq) =
+ Html.div [
+ prop.style [
+ style.minWidth(minWidth)
+ style.flexGrow 1
+ style.flexShrink 1
+ style.height(length.vh 100)
+ style.width(length.perc 100)
+ ]
+ prop.children left
+ ]
\ No newline at end of file
diff --git a/src/Client/MainComponents/Metadata/Assay.fs b/src/Client/MainComponents/Metadata/Assay.fs
index 99878715..15064d6d 100644
--- a/src/Client/MainComponents/Metadata/Assay.fs
+++ b/src/Client/MainComponents/Metadata/Assay.fs
@@ -3,9 +3,10 @@
open Feliz
open Feliz.Bulma
open Messages
-open ARCtrl.ISA
+open ARCtrl
open Shared
+[]
let Main(assay: ArcAssay, model: Messages.Model, dispatch: Msg -> unit) =
Bulma.section [
FormComponents.TextInput (
@@ -17,41 +18,41 @@ let Main(assay: ArcAssay, model: Messages.Model, dispatch: Msg -> unit) =
fullwidth=true
)
FormComponents.OntologyAnnotationInput(
- assay.MeasurementType |> Option.defaultValue OntologyAnnotation.empty,
- "Measurement Type",
- fun oa ->
- let oa = if oa = OntologyAnnotation.empty then None else Some oa
+ assay.MeasurementType |> Option.defaultValue (OntologyAnnotation.empty()),
+ (fun oa ->
+ let oa = if oa = (OntologyAnnotation.empty()) then None else Some oa
assay.MeasurementType <- oa
- assay |> Assay |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
+ assay |> Assay |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch),
+ "Measurement Type"
)
FormComponents.OntologyAnnotationInput(
- assay.TechnologyType |> Option.defaultValue OntologyAnnotation.empty,
- "Technology Type",
- fun oa ->
- let oa = if oa = OntologyAnnotation.empty then None else Some oa
+ assay.TechnologyType |> Option.defaultValue (OntologyAnnotation.empty()),
+ (fun oa ->
+ let oa = if oa = (OntologyAnnotation.empty()) then None else Some oa
assay.TechnologyType <- oa
- assay |> Assay |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
+ assay |> Assay |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch),
+ "Technology Type"
)
FormComponents.OntologyAnnotationInput(
- assay.TechnologyPlatform |> Option.defaultValue OntologyAnnotation.empty,
- "Technology Platform",
- fun oa ->
- let oa = if oa = OntologyAnnotation.empty then None else Some oa
+ assay.TechnologyPlatform |> Option.defaultValue (OntologyAnnotation.empty()),
+ (fun oa ->
+ let oa = if oa = (OntologyAnnotation.empty()) then None else Some oa
assay.TechnologyPlatform <- oa
- assay |> Assay |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
+ assay |> Assay |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch),
+ "Technology Platform"
)
FormComponents.PersonsInput(
- assay.Performers,
+ Array.ofSeq assay.Performers,
"Performers",
fun persons ->
- assay.Performers <- persons
+ assay.Performers <- ResizeArray persons
assay |> Assay |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
)
FormComponents.CommentsInput(
- assay.Comments,
+ Array.ofSeq assay.Comments,
"Comments",
fun comments ->
- assay.Comments <- comments
+ assay.Comments <- ResizeArray comments
assay |> Assay |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
)
]
\ No newline at end of file
diff --git a/src/Client/MainComponents/Metadata/Forms.fs b/src/Client/MainComponents/Metadata/Forms.fs
index 5ee4244c..90b2ae5d 100644
--- a/src/Client/MainComponents/Metadata/Forms.fs
+++ b/src/Client/MainComponents/Metadata/Forms.fs
@@ -1,4 +1,4 @@
-namespace MainComponents.Metadata
+namespace MainComponents.Metadata
open Feliz
open Feliz.Bulma
@@ -7,139 +7,260 @@ open Spreadsheet
open Messages
open Browser.Types
open Fable.Core.JsInterop
-open ARCtrl.ISA
+open ARCtrl
open Shared
+open Fetch
+open ARCtrl.Json
+module private API =
-module Helper =
- type PersonMutable(?firstname, ?lastname, ?midinitials, ?orcid, ?address, ?affiliation, ?email, ?phone, ?fax, ?roles) =
- member val FirstName : string option = firstname with get, set
- member val LastName : string option = lastname with get, set
- member val MidInitials : string option = midinitials with get, set
- member val ORCID : string option = orcid with get, set
- member val Address : string option = address with get, set
- member val Affiliation : string option = affiliation with get, set
- member val EMail : string option = email with get, set
- member val Phone : string option = phone with get, set
- member val Fax : string option = fax with get, set
- member val Roles : OntologyAnnotation [] option = roles with get, set
+ []
+ type Request<'A> =
+ | Ok of 'A
+ | Error of exn
+ | Loading
+ | Idle
+
+ module Null =
+ let defaultValue (def:'A) (x:'A) = if isNull x then def else x
+
+ let requestAsJson (url) =
+ promise {
+ let! response = fetch url [
+ requestHeaders [Accept "application/json"]
+ ]
+ let! json = response.json()
+ return Some json
+ }
+
+ let private createAuthorString (authors: Person []) =
+ authors |> Array.map (fun x -> $"{x.FirstName} {x.LastName}") |> String.concat ", "
+
+ let private createAffiliationString (org_department: (string*string) []) : string option =
+ if org_department.Length = 0 then
+ None
+ else
+ org_department |> Array.map (fun (org,department) -> $"{org}, {department}") |> String.concat ";"
+ |> Some
+
+ let requestByORCID (orcid: string) =
+ let url = $"https://pub.orcid.org/v3.0/{orcid}/record"
+ promise {
+ let! json = requestAsJson url
+ let name: string = json?person?name?("given-names")?value
+ let lastName: string = json?person?name?("family-name")?value
+ let emails: obj [] = json?person?emails?email
+ let email = if emails.Length = 0 then None else Some (emails.[0]?email)
+ let groups: obj [] = json?("activities-summary")?employments?("affiliation-group")
+ let groupsParsed =
+ groups |> Array.choose (fun json ->
+ let summaries : obj [] = json?summaries
+ let summary =
+ summaries
+ |> Array.tryHead
+ |> Option.map (fun s0 ->
+ let s = s0?("employment-summary")
+ let department = s?("department-name") |> Null.defaultValue ""
+ let org = s?organization?name |> Null.defaultValue ""
+ org, department
+ )
+ summary
+ )
+ |> createAffiliationString
+ let person = Person.create(orcid=orcid,lastName=lastName, firstName=name, ?email=email, ?affiliation=groupsParsed)
+ return person
+ }
+
+
+ let requestByPubMedID (id: string) =
+ let url = @"https://api.ncbi.nlm.nih.gov/lit/ctxp/v1/pubmed/?format=csl&id=" + id
+ promise {
+ let! json = requestAsJson url
+ let doi: string = json?DOI
+ let pmid: string = json?PMID
+ let authors : Person [] =
+ [|
+ for pj in json?author do
+ Person.create(LastName=pj?family, FirstName=pj?given)
+ |]
+ let authorString = createAuthorString authors
+ let title = json?title
+ let publication = Publication.create(pmid, doi, authorString, title, TermCollection.Published)
+ return publication
+ }
+
+ let requestByDOI_FromPubMed (doi: string) =
+ /// https://academia.stackexchange.com/questions/67103/is-there-any-api-service-to-retrieve-abstract-of-a-journal-article
+ let url_pubmed = $"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term={doi}&retmode=JSON"
+ promise {
+ let! json = requestAsJson url_pubmed
+ let errorList = json?esearchresult?errorlist
+ if isNull errorList then
+ let idList : string [] = json?esearchresult?idlist
+ let pubmedID = if idList.Length <> 1 then None else Some idList.[0]
+ return pubmedID
+ else
+ return None
+ }
+
+ let requestByDOI (doi: string) =
+ let url_crossref = $"https://api.crossref.org/works/{doi}"
+ promise {
+ let! json = requestAsJson url_crossref
+ let titles: string [] = json?message?title
+ let title = if titles.Length = 0 then None else Some titles.[0]
+ let authors : Person [] = [|
+ for pj in json?message?author do
+ let affiliationsJson: obj [] = pj?affiliation
+ let affiliations : string [] =
+ [|
+ for aff in affiliationsJson do
+ yield aff?("name")
+ |]
+ let affString = affiliations |> String.concat ", "
+ Person.create(ORCID=pj?ORCID, LastName=pj?family, FirstName=pj?given, affiliation=affString)
+ |]
+ let! pubmedId = requestByDOI_FromPubMed doi
+ let authorString = createAuthorString authors
+ let publication = Publication.create(?pubMedID=pubmedId, doi=doi, authors=authorString, ?title=title, status=TermCollection.Published)
+ return publication
+ }
+
+ let start (call: 't -> Fable.Core.JS.Promise<'a>) (args:'t) (success) (fail) =
+ call args
+ |> Promise.either
+ success
+ fail
+ |> Promise.start
+
+module private Helper =
+ //type PersonMutable(?firstname, ?lastname, ?midinitials, ?orcid, ?address, ?affiliation, ?email, ?phone, ?fax, ?roles) =
+ // member val FirstName : string option = firstname with get, set
+ // member val LastName : string option = lastname with get, set
+ // member val MidInitials : string option = midinitials with get, set
+ // member val ORCID : string option = orcid with get, set
+ // member val Address : string option = address with get, set
+ // member val Affiliation : string option = affiliation with get, set
+ // member val EMail : string option = email with get, set
+ // member val Phone : string option = phone with get, set
+ // member val Fax : string option = fax with get, set
+ // member val Roles : OntologyAnnotation [] option = roles with get, set
- static member fromPerson(person:Person) =
- PersonMutable(
- ?firstname=person.FirstName,
- ?lastname=person.LastName,
- ?midinitials=person.MidInitials,
- ?orcid=person.ORCID,
- ?address=person.Address,
- ?affiliation=person.Affiliation,
- ?email=person.EMail,
- ?phone=person.Phone,
- ?fax=person.Fax,
- ?roles=person.Roles
- )
+ // static member fromPerson(person:Person) =
+ // PersonMutable(
+ // ?firstname=person.FirstName,
+ // ?lastname=person.LastName,
+ // ?midinitials=person.MidInitials,
+ // ?orcid=person.ORCID,
+ // ?address=person.Address,
+ // ?affiliation=person.Affiliation,
+ // ?email=person.EMail,
+ // ?phone=person.Phone,
+ // ?fax=person.Fax,
+ // ?roles=person.Roles
+ // )
- member this.ToPerson() =
- Person.create(
- ?FirstName=this.FirstName,
- ?LastName=this.LastName,
- ?MidInitials=this.MidInitials,
- ?ORCID=this.ORCID,
- ?Address=this.Address,
- ?Affiliation=this.Affiliation,
- ?Email=this.EMail,
- ?Phone=this.Phone,
- ?Fax=this.Fax,
- ?Roles=this.Roles
- )
+ // member this.ToPerson() =
+ // Person.create(
+ // ?FirstName=this.FirstName,
+ // ?LastName=this.LastName,
+ // ?MidInitials=this.MidInitials,
+ // ?ORCID=this.ORCID,
+ // ?Address=this.Address,
+ // ?Affiliation=this.Affiliation,
+ // ?Email=this.EMail,
+ // ?Phone=this.Phone,
+ // ?Fax=this.Fax,
+ // ?Roles=this.Roles
+ // )
- type OntologyAnnotationMutable(?name,?tsr,?tan) =
- member val Name : string option = name with get, set
- member val TSR : string option = tsr with get, set
- member val TAN : string option = tan with get, set
-
- static member fromOntologyAnnotation(oa: OntologyAnnotation) =
- let name = if oa.NameText = "" then None else Some oa.NameText
- OntologyAnnotationMutable(?name=name, ?tsr=oa.TermSourceREF, ?tan=oa.TermAccessionNumber)
-
- member this.ToOntologyAnnotation() =
- OntologyAnnotation.fromString(?termName=this.Name,?tsr=this.TSR,?tan=this.TAN)
-
- type PublicationMutable(?pubmedid: string, ?doi: string, ?authors: string, ?title: string, ?status: OntologyAnnotation, ?comments: Comment []) =
- member val PubmedId = pubmedid with get, set
- member val Doi = doi with get, set
- member val Authors = authors with get, set
- member val Title = title with get, set
- member val Status = status with get, set
- member val Comments = comments with get, set
-
- static member fromPublication(pub:Publication) =
- PublicationMutable(
- ?pubmedid=pub.PubMedID,
- ?doi=pub.DOI,
- ?authors=pub.Authors,
- ?title=pub.Title,
- ?status=pub.Status,
- ?comments=pub.Comments
- )
+ //type OntologyAnnotationMutable(?name,?tsr,?tan) =
+ // member val Name : string option = name with get, set
+ // member val TSR : string option = tsr with get, set
+ // member val TAN : string option = tan with get, set
- member this.ToPublication() =
- Publication.create(
- ?PubMedID=this.PubmedId,
- ?Doi=this.Doi,
- ?Authors=this.Authors,
- ?Title=this.Title,
- ?Status=this.Status,
- ?Comments=this.Comments
- )
+ // static member fromOntologyAnnotation(oa: OntologyAnnotation) =
+ // let name = if oa.NameText = "" then None else Some oa.NameText
+ // OntologyAnnotationMutable(?name=name, ?tsr=oa.TermSourceREF, ?tan=oa.TermAccessionNumber)
- type FactorMutable(?name,?factortype,?comments) =
- member val Name = name with get, set
- member val FactorType = factortype with get, set
- member val Comments = comments with get, set
+ // member this.ToOntologyAnnotation() =
+ // OntologyAnnotation.fromString(?termName=this.Name,?tsr=this.TSR,?tan=this.TAN)
- static member fromFactor(f:Factor) =
- FactorMutable(
- ?name=f.Name,
- ?factortype=f.FactorType,
- ?comments=f.Comments
- )
- member this.ToFactor() =
- Factor.create(
- ?Name=this.Name,
- ?FactorType=this.FactorType,
- ?Comments=this.Comments
- )
+ //type PublicationMutable(?pubmedid: string, ?doi: string, ?authors: string, ?title: string, ?status: OntologyAnnotation, ?comments: Comment []) =
+ // member val PubmedId = pubmedid with get, set
+ // member val Doi = doi with get, set
+ // member val Authors = authors with get, set
+ // member val Title = title with get, set
+ // member val Status = status with get, set
+ // member val Comments = comments with get, set
- type OntologySourceReferenceMutable(?name,?description,?file,?version,?comments) =
- member val Name = name with get, set
- member val Description = description with get, set
- member val File = file with get, set
- member val Version = version with get, set
- member val Comments = comments with get, set
-
- static member fromOntologySourceReference(o:OntologySourceReference) =
- OntologySourceReferenceMutable(
- ?name=o.Name,
- ?description= o.Description,
- ?file=o.File,
- ?version=o.Version,
- ?comments=o.Comments
- )
- member this.ToOntologySourceReference() =
- OntologySourceReference.create(
- ?Name=this.Name,
- ?Description=this.Description,
- ?File=this.File,
- ?Version=this.Version,
- ?Comments=this.Comments
- )
+ // static member fromPublication(pub:Publication) =
+ // PublicationMutable(
+ // ?pubmedid=pub.PubMedID,
+ // ?doi=pub.DOI,
+ // ?authors=pub.Authors,
+ // ?title=pub.Title,
+ // ?status=pub.Status,
+ // ?comments=pub.Comments
+ // )
+
+ // member this.ToPublication() =
+ // Publication.create(
+ // ?PubMedID=this.PubmedId,
+ // ?Doi=this.Doi,
+ // ?Authors=this.Authors,
+ // ?Title=this.Title,
+ // ?Status=this.Status,
+ // ?Comments=this.Comments
+ // )
+
+ //type FactorMutable(?name,?factortype,?comments) =
+ // member val Name = name with get, set
+ // member val FactorType = factortype with get, set
+ // member val Comments = comments with get, set
+
+ // static member fromFactor(f:Factor) =
+ // FactorMutable(
+ // ?name=f.Name,
+ // ?factortype=f.FactorType,
+ // ?comments=f.Comments
+ // )
+ // member this.ToFactor() =
+ // Factor.create(
+ // ?Name=this.Name,
+ // ?FactorType=this.FactorType,
+ // ?Comments=this.Comments
+ // )
+
+ //type OntologySourceReferenceMutable(?name,?description,?file,?version,?comments) =
+ // member val Name = name with get, set
+ // member val Description = description with get, set
+ // member val File = file with get, set
+ // member val Version = version with get, set
+ // member val Comments = comments with get, set
+
+ // static member fromOntologySourceReference(o:OntologySourceReference) =
+ // OntologySourceReferenceMutable(
+ // ?name=o.Name,
+ // ?description= o.Description,
+ // ?file=o.File,
+ // ?version=o.Version,
+ // ?comments=o.Comments
+ // )
+ // member this.ToOntologySourceReference() =
+ // OntologySourceReference.create(
+ // ?Name=this.Name,
+ // ?Description=this.Description,
+ // ?File=this.File,
+ // ?Version=this.Version,
+ // ?Comments=this.Comments
+ // )
let addButton (clickEvent: MouseEvent -> unit) =
Html.div [
prop.classes ["is-flex"; "is-justify-content-center"]
prop.children [
Bulma.button.button [
- prop.className "is-ghost"
prop.text "+"
prop.onClick clickEvent
]
@@ -167,6 +288,133 @@ module Helper =
]
]
+ let readOnlyFormElement(v: string option, label: string) =
+ let v = defaultArg v "-"
+ Bulma.field.div [
+ prop.className "is-flex is-flex-direction-column is-flex-grow-1"
+ prop.children [
+ Bulma.label label
+ Bulma.control.div [
+ Bulma.control.isExpanded
+ prop.children [
+ Bulma.input.text [
+ prop.readOnly true
+ prop.valueOrDefault v
+ ]
+ ]
+ ]
+ ]
+ ]
+
+ let personModal (person: Person, confirm, back) =
+ Bulma.modal [
+ Bulma.modal.isActive
+ prop.children [
+ Bulma.modalBackground []
+ Bulma.modalClose []
+ Bulma.modalContent [
+ Bulma.container [
+ prop.className "p-1"
+ prop.children [
+ Bulma.box [
+ cardFormGroup [
+ readOnlyFormElement(person.FirstName, "Given Name")
+ readOnlyFormElement(person.LastName, "Family Name")
+ ]
+ cardFormGroup [
+ readOnlyFormElement(person.EMail, "Email")
+ readOnlyFormElement(person.ORCID, "ORCID")
+ ]
+ cardFormGroup [
+ readOnlyFormElement(person.Affiliation, "Affiliation")
+ ]
+ Bulma.field.div [
+ prop.className "is-flex is-justify-content-flex-end"
+ prop.style [style.gap (length.rem 1)]
+ prop.children [
+ Bulma.button.button [
+ prop.text "back"
+ prop.onClick back
+ ]
+ Bulma.button.button [
+ Bulma.color.isSuccess
+ prop.text "confirm"
+ prop.onClick confirm
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+
+ let publicationModal (pub: Publication, confirm, back) =
+ Bulma.modal [
+ Bulma.modal.isActive
+ prop.children [
+ Bulma.modalBackground []
+ Bulma.modalClose []
+ Bulma.modalContent [
+ Bulma.container [
+ prop.className "p-1"
+ prop.children [
+ Bulma.box [
+ cardFormGroup [
+ readOnlyFormElement(pub.Title, "Title")
+ ]
+ cardFormGroup [
+ readOnlyFormElement(pub.DOI, "DOI")
+ readOnlyFormElement(pub.PubMedID, "PubMedID")
+ ]
+ cardFormGroup [
+ readOnlyFormElement(pub.Authors, "Authors")
+ ]
+ cardFormGroup [
+ readOnlyFormElement(pub.Status |> Option.map _.ToString(), "Status")
+ ]
+ Bulma.field.div [
+ prop.className "is-flex is-justify-content-flex-end"
+ prop.style [style.gap (length.rem 1)]
+ prop.children [
+ Bulma.button.button [
+ prop.text "back"
+ prop.onClick back
+ ]
+ Bulma.button.button [
+ Bulma.color.isSuccess
+ prop.text "confirm"
+ prop.onClick confirm
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+
+ let errorModal (error: exn, back) =
+ Bulma.modal [
+ Bulma.modal.isActive
+ prop.children [
+ Bulma.modalBackground [prop.onClick back]
+ Bulma.modalClose [prop.onClick back]
+ Bulma.modalContent [
+ Bulma.notification [
+ Bulma.color.isDanger
+ prop.children [
+ Bulma.delete [prop.onClick back]
+ Html.div error.Message
+ ]
+ ]
+ ]
+ ]
+ ]
+
+
type FormComponents =
[]
@@ -176,7 +424,7 @@ type FormComponents =
let fullwidth = defaultArg fullwidth false
let loading, setLoading = React.useState(false)
let state, setState = React.useState(input)
- let debounceStorage, setdebounceStorage = React.useState(newDebounceStorage)
+ let debounceStorage = React.useRef(newDebounceStorage())
React.useEffect((fun () -> setState input), dependencies=[|box input|])
Bulma.field.div [
prop.style [if fullwidth then style.flexGrow 1]
@@ -193,7 +441,7 @@ type FormComponents =
prop.valueOrDefault state
prop.onChange(fun (e: string) ->
setState e
- debouncel debounceStorage label 1000 setLoading setter e
+ debouncel debounceStorage.current label 1000 setLoading setter e
)
]
]
@@ -337,44 +585,179 @@ type FormComponents =
]
[]
- static member OntologyAnnotationInput (input: OntologyAnnotation, label: string, setter: OntologyAnnotation -> unit, ?showTextLabels: bool, ?removebutton: MouseEvent -> unit) =
+ static member PublicationRequestInput (id: string option,searchAPI: string -> Fable.Core.JS.Promise, doisetter, searchsetter: Publication -> unit, ?label:string) =
+ let id = defaultArg id ""
+ let state, setState = React.useState(API.Request.Idle)
+ let resetState = fun _ -> setState API.Request.Idle
+ Bulma.field.div [
+ prop.className "is-flex-grow-1"
+ prop.children [
+ if label.IsSome then Bulma.label label.Value
+ Bulma.field.div [
+ Bulma.field.hasAddons
+ prop.children [
+ //if state.IsSome || error.IsSome then
+ match state with
+ | API.Request.Ok pub -> Helper.publicationModal(pub,(fun _ -> searchsetter pub; resetState()), resetState)
+ | API.Request.Error e -> Helper.errorModal(e, resetState)
+ | API.Request.Loading -> Modals.Loading.loadingModal
+ | _ -> Html.none
+ Bulma.control.div [
+ Bulma.control.isExpanded
+ prop.children [
+ FormComponents.TextInput(
+ id,
+ "",
+ setter=doisetter,
+ fullwidth=true
+ )
+ ]
+ ]
+ Bulma.control.div [
+ Bulma.button.button [
+ Bulma.color.isInfo
+ prop.text "Search"
+ prop.onClick (fun _ ->
+ //API.requestByORCID ("0000-0002-8510-6810") |> Promise.start
+ setState API.Request.Loading
+ API.start
+ searchAPI
+ id
+ (API.Request.Ok >> setState)
+ (API.Request.Error >> setState)
+ )
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+
+ []
+ static member PersonRequestInput (orcid: string option, doisetter, searchsetter: Person -> unit, ?label:string) =
+ let orcid = defaultArg orcid ""
+ let state, setState = React.useState(API.Request.Idle)
+ let resetState = fun _ -> setState API.Request.Idle
+ Bulma.field.div [
+ prop.className "is-flex-grow-1"
+ prop.children [
+ if label.IsSome then Bulma.label label.Value
+ Bulma.field.div [
+ Bulma.field.hasAddons
+ prop.children [
+ //if state.IsSome || error.IsSome then
+ match state with
+ | API.Request.Ok p -> Helper.personModal (p, (fun _ -> searchsetter p; resetState()), resetState)
+ | API.Request.Error e -> Helper.errorModal(e, resetState)
+ | API.Request.Loading -> Modals.Loading.loadingModal
+ | _ -> Html.none
+ Bulma.control.div [
+ Bulma.control.isExpanded
+ prop.children [
+ FormComponents.TextInput(
+ orcid,
+ "",
+ setter=doisetter,
+ fullwidth=true
+ )
+ ]
+ ]
+ Bulma.control.div [
+ Bulma.button.button [
+ Bulma.color.isInfo
+ prop.text "Search"
+ prop.onClick (fun _ ->
+ //API.requestByORCID ("0000-0002-8510-6810") |> Promise.start
+ setState API.Request.Loading
+ API.start
+ API.requestByORCID
+ orcid
+ (API.Request.Ok >> setState)
+ (API.Request.Error >> setState)
+ )
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+
+ []
+ static member DOIInput (id: string option, doisetter, searchsetter: Publication -> unit, ?label:string) =
+ FormComponents.PublicationRequestInput(
+ id,
+ API.requestByDOI,//"10.3390/ijms24087444"//"10.3390/ijms2408741d"//
+ doisetter,
+ searchsetter,
+ ?label=label
+ )
+
+ []
+ static member PubMedIDInput (id: string option, doisetter, searchsetter: Publication -> unit, ?label:string) =
+ FormComponents.PublicationRequestInput(
+ id,
+ API.requestByPubMedID,
+ doisetter,
+ searchsetter,
+ ?label=label
+ )
+
+ []
+ static member OntologyAnnotationInput (input: OntologyAnnotation, setter: OntologyAnnotation -> unit, ?label: string, ?showTextLabels: bool, ?removebutton: MouseEvent -> unit, ?parent: OntologyAnnotation) =
let showTextLabels = defaultArg showTextLabels true
- let state, setState = React.useState(Helper.OntologyAnnotationMutable.fromOntologyAnnotation input)
- React.useEffect((fun () -> setState <| Helper.OntologyAnnotationMutable.fromOntologyAnnotation input), dependencies=[|box input|])
- let hasLabel = label <> ""
+ let state, setState = React.useState(input)
+ let element = React.useElementRef()
+ React.useEffect((fun () -> setState input), dependencies=[|box input|])
Bulma.field.div [
- if hasLabel then Bulma.label label
+ //if label.IsSome then Bulma.label label.Value
Bulma.field.div [
+ //prop.ref element
+ prop.style [style.position.relative]
prop.classes ["is-flex"; "is-flex-direction-row"; "is-justify-content-space-between"]
prop.children [
Html.div [
prop.classes ["form-container"; if removebutton.IsSome then "pr-2"]
prop.children [
+ Bulma.field.div [
+ prop.style [style.flexGrow 1]
+ prop.children [
+ let label = defaultArg label "Term Name"
+ Bulma.label label
+ let innersetter =
+ fun (oaOpt: OntologyAnnotation option) ->
+ if oaOpt.IsSome then
+ setter oaOpt.Value
+ setState oaOpt.Value
+ Components.TermSearch.Input(
+ innersetter,
+ input=state,
+ fullwidth=true,
+ ?portalTermSelectArea=element.current,
+ ?parent=parent,
+ debounceSetter=1000
+ )
+ ]
+ ]
+ Html.div [
+ prop.classes ["form-input-term-search-positioner"]
+ prop.ref element
+ ]
FormComponents.TextInput(
- Option.defaultValue "" state.Name,
- (if showTextLabels then $"Term Name" else ""),
- (fun s ->
- let s = if s = "" then None else Some s
- state.Name <- s
- state.ToOntologyAnnotation() |> setter),
- fullwidth = true
- )
- FormComponents.TextInput(
- Option.defaultValue "" state.TSR,
+ Option.defaultValue "" state.TermSourceREF,
(if showTextLabels then $"TSR" else ""),
(fun s ->
let s = if s = "" then None else Some s
- state.TSR <- s
- state.ToOntologyAnnotation() |> setter),
+ state.TermSourceREF <- s
+ state |> setter),
fullwidth = true
)
FormComponents.TextInput(
- Option.defaultValue "" state.TAN,
+ Option.defaultValue "" state.TermAccessionNumber,
(if showTextLabels then $"TAN" else ""),
(fun s ->
let s = if s = "" then None else Some s
- state.TAN <- s
- state.ToOntologyAnnotation() |> setter),
+ state.TermAccessionNumber <- s
+ state |> setter),
fullwidth = true
)
]
@@ -394,18 +777,18 @@ type FormComponents =
]
[]
- static member OntologyAnnotationsInput (oas: OntologyAnnotation [], label: string, setter: OntologyAnnotation [] -> unit, ?showTextLabels: bool) =
+ static member OntologyAnnotationsInput (oas: OntologyAnnotation [], label: string, setter: OntologyAnnotation [] -> unit, ?showTextLabels: bool, ?parent: OntologyAnnotation) =
FormComponents.InputSequence(
- oas, OntologyAnnotation.empty, label, setter,
- (fun (a,b,c,d) -> FormComponents.OntologyAnnotationInput(a,b,c,removebutton=d,?showTextLabels=showTextLabels))
+ oas, (OntologyAnnotation.empty()), label, setter,
+ (fun (a,b,c,d) -> FormComponents.OntologyAnnotationInput(a,c,label=b,removebutton=d,?showTextLabels=showTextLabels, ?parent=parent))
)
[]
static member PersonInput(input: Person, setter: Person -> unit, ?deletebutton: MouseEvent -> unit) =
let isExtended, setIsExtended = React.useState(false)
// Must use `React.useRef` do this. Otherwise simultanios updates will overwrite each other
- let state, setState = React.useState(Helper.PersonMutable.fromPerson input)
- React.useEffect((fun _ -> setState <| Helper.PersonMutable.fromPerson input), [|box input|])
+ let state, setState = React.useState(input)
+ React.useEffect((fun _ -> setState input), [|box input|])
let fn = Option.defaultValue "" state.FirstName
let ln = Option.defaultValue "" state.LastName
let mi = Option.defaultValue "" state.MidInitials
@@ -420,21 +803,21 @@ type FormComponents =
(fun s ->
let s = if s = "" then None else Some s
personSetter s
- state.ToPerson() |> setter),
+ state |> setter),
fullwidth=true
)
- let countFilledFieldsString (person: Helper.PersonMutable) =
+ let countFilledFieldsString (person: Person) =
let fields = [
- state.FirstName
- state.LastName
- state.MidInitials
- state.ORCID
- state.Address
- state.Affiliation
- state.EMail
- state.Phone
- state.Fax
- state.Roles |> Option.map (fun _ -> "")
+ person.FirstName
+ person.LastName
+ person.MidInitials
+ person.ORCID
+ person.Address
+ person.Affiliation
+ person.EMail
+ person.Phone
+ person.Fax
+ if person.Roles.Count > 0 then Some "roles" else None // just for count. Value does not matter
]
let all = fields.Length
let filled = fields |> List.choose id |> _.Length
@@ -470,7 +853,15 @@ type FormComponents =
]
Helper.cardFormGroup [
createPersonFieldTextInput(state.MidInitials, "Mid Initials", fun s -> state.MidInitials <- s)
- createPersonFieldTextInput(state.ORCID, "ORCID", fun s -> state.ORCID <- s)
+ FormComponents.PersonRequestInput(
+ state.ORCID,
+ (fun s ->
+ let s = if s = "" then None else Some s
+ state.ORCID <- s
+ state |> setter),
+ (fun s -> setter s),
+ "ORCID"
+ )
]
Helper.cardFormGroup [
createPersonFieldTextInput(state.Affiliation, "Affiliation", fun s -> state.Affiliation <- s)
@@ -482,14 +873,14 @@ type FormComponents =
createPersonFieldTextInput(state.Fax, "Fax", fun s -> state.Fax <- s)
]
FormComponents.OntologyAnnotationsInput(
- Option.defaultValue [||] state.Roles,
+ Array.ofSeq state.Roles,
"Roles",
(fun oas ->
- let oas = if oas = [||] then None else Some oas
- state.Roles <- oas
- state.ToPerson() |> setter
+ state.Roles <- ResizeArray(oas)
+ state |> setter
),
showTextLabels = false
+ //parent=Shared.TermCollection.PersonRoleWithinExperiment
)
if deletebutton.IsSome then
Helper.deleteButton deletebutton.Value
@@ -527,13 +918,17 @@ type FormComponents =
FormComponents.TextInput(
comment.Name |> Option.defaultValue "",
(if showTextLabels then $"Term Name" else ""),
- (fun s -> {comment with Name = if s = "" then None else Some s} |> setter),
+ (fun s ->
+ comment.Name <- if s = "" then None else Some s
+ comment |> setter),
fullwidth = true
)
FormComponents.TextInput(
comment.Value |> Option.defaultValue "",
(if showTextLabels then $"TSR" else ""),
- (fun s -> {comment with Value = if s = "" then None else Some s} |> setter),
+ (fun s ->
+ comment.Value <- if s = "" then None else Some s
+ comment |> setter),
fullwidth = true
)
if removebutton.IsSome then
@@ -570,27 +965,26 @@ type FormComponents =
static member PublicationInput(input: Publication, setter: Publication -> unit, ?deletebutton: MouseEvent -> unit) =
let isExtended, setIsExtended = React.useState(false)
// Must use `React.useRef` do this. Otherwise simultanios updates will overwrite each other
- let state, setState = React.useState(Helper.PublicationMutable.fromPublication input)
- React.useEffect((fun _ -> setState <| Helper.PublicationMutable.fromPublication input), [|box input|])
+ let state, setState = React.useState(input)
+ React.useEffect((fun _ -> setState input), [|box input|])
let title = Option.defaultValue "" state.Title
- let doi = Option.defaultValue "" state.Doi
- let createPersonFieldTextInput(field: string option, label, personSetter: string option -> unit) =
+ let doi = Option.defaultValue "" state.DOI
+ let createPersonFieldTextInput(field: string option, label, publicationSetter: string option -> unit) =
FormComponents.TextInput(
field |> Option.defaultValue "",
label,
(fun s ->
let s = if s = "" then None else Some s
- personSetter s
- state.ToPublication() |> setter),
+ publicationSetter s
+ state |> setter),
fullwidth=true
)
let countFilledFieldsString () =
let fields = [
- state.PubmedId
- state.Doi
+ state.PubMedID
+ state.DOI
state.Title
state.Authors
- state.Comments |> Option.map (fun _ -> "")
state.Status |> Option.map (fun _ -> "")
]
let all = fields.Length
@@ -623,24 +1017,41 @@ type FormComponents =
prop.children [
createPersonFieldTextInput(state.Title, "Title", fun s -> state.Title <- s)
Helper.cardFormGroup [
- createPersonFieldTextInput(state.PubmedId, "PubMed Id", fun s -> state.PubmedId <- s)
- createPersonFieldTextInput(state.Doi, "DOI", fun s -> state.Doi <- s)
+ FormComponents.PubMedIDInput(
+ state.PubMedID,
+ (fun s ->
+ let s = if s = "" then None else Some s
+ state.PubMedID <- s
+ state |> setter),
+ (fun pub -> setter pub),
+ "PubMed Id"
+ )
+ FormComponents.DOIInput(
+ state.DOI,
+ (fun s ->
+ let s = if s = "" then None else Some s
+ state.DOI <- s
+ state |> setter),
+ (fun pub -> setter pub),
+ "DOI"
+ )
]
createPersonFieldTextInput(state.Authors, "Authors", fun s -> state.Authors <- s)
FormComponents.OntologyAnnotationInput(
- Option.defaultValue OntologyAnnotation.empty state.Status,
- "Status",
+ Option.defaultValue (OntologyAnnotation.empty()) state.Status,
(fun s ->
- state.Status <- if s = OntologyAnnotation.empty then None else Some s
- state.ToPublication() |> setter
- )
+ state.Status <- if s = (OntologyAnnotation.empty()) then None else Some s
+ state |> setter
+ ),
+ "Status",
+ parent=Shared.TermCollection.PublicationStatus
)
FormComponents.CommentsInput(
- Option.defaultValue [||] state.Comments,
+ Array.ofSeq state.Comments,
"Comments",
(fun c ->
- state.Comments <- if c = [||] then None else Some c
- state.ToPublication() |> setter
+ state.Comments <- ResizeArray(c)
+ state |> setter
)
)
if deletebutton.IsSome then
@@ -658,96 +1069,12 @@ type FormComponents =
(fun (a,b,c,d) -> FormComponents.PublicationInput(a,c,deletebutton=d))
)
- []
- static member FactorInput(input: Factor, setter: Factor -> unit, ?deletebutton: MouseEvent -> unit) =
- let isExtended, setIsExtended = React.useState(false)
- // Must use `React.useRef` do this. Otherwise simultanios updates will overwrite each other
- let state, setState = React.useState(Helper.FactorMutable.fromFactor input)
- React.useEffect((fun _ -> setState <| Helper.FactorMutable.fromFactor input), [|box input|])
- let name = Option.defaultValue "" state.Name
- let type' = Option.defaultValue "" (state.FactorType |> Option.map (fun x -> x.NameText))
- let createFieldTextInput(field: string option, label, personSetter: string option -> unit) =
- FormComponents.TextInput(
- field |> Option.defaultValue "",
- label,
- (fun s ->
- let s = if s = "" then None else Some s
- personSetter s
- state.ToFactor() |> setter),
- fullwidth=true
- )
- let countFilledFieldsString () =
- let fields = [
- state.Name
- state.FactorType |> Option.map (fun _ -> "")
- state.Comments |> Option.map (fun _ -> "")
- ]
- let all = fields.Length
- let filled = fields |> List.choose id |> _.Length
- $"{filled}/{all}"
- Bulma.card [
- Bulma.cardHeader [
- Bulma.cardHeaderTitle.div [
- //prop.classes ["is-align-items-flex-start"]
- prop.children [
- Html.div [
- Bulma.title.h5 name
- Bulma.subtitle.h6 type'
- ]
- Html.div [
- prop.style [style.custom("marginLeft", "auto")]
- prop.text (countFilledFieldsString ())
- ]
- ]
- ]
- Bulma.cardHeaderIcon.a [
- prop.onClick (fun _ -> not isExtended |> setIsExtended)
- prop.children [
- Bulma.icon [Html.i [prop.classes ["fas"; "fa-angle-down"]]]
- ]
- ]
- ]
- Bulma.cardContent [
- prop.classes [if not isExtended then "is-hidden"]
- prop.children [
- createFieldTextInput(state.Name, "Name", fun s -> state.Name <- s)
- FormComponents.OntologyAnnotationInput(
- Option.defaultValue OntologyAnnotation.empty state.FactorType,
- "Status",
- (fun s ->
- state.FactorType <- if s = OntologyAnnotation.empty then None else Some s
- state.ToFactor() |> setter
- )
- )
- FormComponents.CommentsInput(
- Option.defaultValue [||] state.Comments,
- "Comments",
- (fun c ->
- state.Comments <- if c = [||] then None else Some c
- state.ToFactor() |> setter
- )
- )
- if deletebutton.IsSome then
- Helper.deleteButton deletebutton.Value
- ]
- ]
- ]
-
- static member FactorsInput(input: Factor [], label: string, setter: Factor [] -> unit) =
- FormComponents.InputSequence(
- input,
- Factor.create(),
- label,
- setter,
- (fun (a,b,c,d) -> FormComponents.FactorInput(a,c,deletebutton=d))
- )
-
[]
static member OntologySourceReferenceInput(input: OntologySourceReference, setter: OntologySourceReference -> unit, ?deletebutton: MouseEvent -> unit) =
let isExtended, setIsExtended = React.useState(false)
// Must use `React.useRef` do this. Otherwise simultanios updates will overwrite each other
- let state, setState = React.useState(Helper.OntologySourceReferenceMutable.fromOntologySourceReference input)
- React.useEffect((fun _ -> setState <| Helper.OntologySourceReferenceMutable.fromOntologySourceReference input), [|box input|])
+ let state, setState = React.useState(input)
+ React.useEffect((fun _ -> setState input), [|box input|])
let name = Option.defaultValue "" state.Name
let version = Option.defaultValue "" state.Version
let createFieldTextInput(field: string option, label, personSetter: string option -> unit) =
@@ -757,7 +1084,7 @@ type FormComponents =
(fun s ->
let s = if s = "" then None else Some s
personSetter s
- state.ToOntologySourceReference() |> setter),
+ state |> setter),
fullwidth=true
)
let countFilledFieldsString () =
@@ -766,7 +1093,7 @@ type FormComponents =
state.File
state.Version
state.Description
- state.Comments |> Option.map (fun _ -> "")
+ if state.Comments.Count > 0 then Some "comments" else None // just for count. Value does not matter
]
let all = fields.Length
let filled = fields |> List.choose id |> _.Length
@@ -807,16 +1134,16 @@ type FormComponents =
(fun s ->
let s = if s = "" then None else Some s
state.Description <- s
- state.ToOntologySourceReference() |> setter),
+ state |> setter),
fullwidth=true,
isarea=true
)
FormComponents.CommentsInput(
- Option.defaultValue [||] state.Comments,
+ Array.ofSeq state.Comments,
"Comments",
(fun c ->
- state.Comments <- if c = [||] then None else Some c
- state.ToOntologySourceReference() |> setter
+ state.Comments <- ResizeArray(c)
+ state |> setter
)
)
if deletebutton.IsSome then
diff --git a/src/Client/MainComponents/Metadata/Investigation.fs b/src/Client/MainComponents/Metadata/Investigation.fs
index 6823884d..671c8265 100644
--- a/src/Client/MainComponents/Metadata/Investigation.fs
+++ b/src/Client/MainComponents/Metadata/Investigation.fs
@@ -1,4 +1,4 @@
-module MainComponents.Metadata.Investigation
+module MainComponents.Metadata.Investigation
open Feliz
open Feliz.Bulma
@@ -7,7 +7,7 @@ open Spreadsheet
open Messages
open Browser.Types
open Fable.Core.JsInterop
-open ARCtrl.ISA
+open ARCtrl
open Shared
let Main(inv: ArcInvestigation, model: Messages.Model, dispatch: Msg -> unit) =
@@ -37,6 +37,20 @@ let Main(inv: ArcInvestigation, model: Messages.Model, dispatch: Msg -> unit) =
fullwidth=true,
isarea=true
)
+ FormComponents.PersonsInput(
+ Array.ofSeq inv.Contacts,
+ "Contacts",
+ (fun i ->
+ inv.Contacts <- ResizeArray i
+ inv |> Investigation |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch)
+ )
+ FormComponents.PublicationsInput(
+ Array.ofSeq inv.Publications,
+ "Publications",
+ (fun i ->
+ inv.Publications <- ResizeArray i
+ inv |> Investigation |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch)
+ )
FormComponents.DateTimeInput (
Option.defaultValue "" inv.SubmissionDate,
"Submission Date",
@@ -52,38 +66,24 @@ let Main(inv: ArcInvestigation, model: Messages.Model, dispatch: Msg -> unit) =
inv |> Investigation |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch)
)
FormComponents.OntologySourceReferencesInput(
- inv.OntologySourceReferences,
+ Array.ofSeq inv.OntologySourceReferences,
"Ontology Source References",
(fun oas ->
- inv.OntologySourceReferences <- oas
- inv |> Investigation |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch)
- )
- FormComponents.PublicationsInput(
- inv.Publications,
- "Publications",
- (fun i ->
- inv.Publications <- i
- inv |> Investigation |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch)
- )
- FormComponents.PersonsInput(
- inv.Contacts,
- "Contacts",
- (fun i ->
- inv.Contacts <- i
- inv |> Investigation |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch)
- )
- FormComponents.TextInputs(
- Array.ofSeq inv.RegisteredStudyIdentifiers,
- "RegisteredStudyIdentifiers",
- (fun i ->
- inv.RegisteredStudyIdentifiers <- ResizeArray i
+ inv.OntologySourceReferences <- ResizeArray oas
inv |> Investigation |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch)
)
+ //FormComponents.TextInputs(
+ // Array.ofSeq inv.RegisteredStudyIdentifiers,
+ // "RegisteredStudyIdentifiers",
+ // (fun i ->
+ // inv.RegisteredStudyIdentifiers <- ResizeArray i
+ // inv |> Investigation |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch)
+ //)
FormComponents.CommentsInput(
- inv.Comments,
+ Array.ofSeq inv.Comments,
"Comments",
(fun i ->
- inv.Comments <- i
+ inv.Comments <- ResizeArray i
inv |> Investigation |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch)
)
]
\ No newline at end of file
diff --git a/src/Client/MainComponents/Metadata/Study.fs b/src/Client/MainComponents/Metadata/Study.fs
index 4cce4c12..cec36820 100644
--- a/src/Client/MainComponents/Metadata/Study.fs
+++ b/src/Client/MainComponents/Metadata/Study.fs
@@ -1,9 +1,9 @@
-module MainComponents.Metadata.Study
+module MainComponents.Metadata.Study
open Feliz
open Feliz.Bulma
open Messages
-open ARCtrl.ISA
+open ARCtrl
open Shared
let Main(study: ArcStudy, assignedAssays: ArcAssay list, model: Messages.Model, dispatch: Msg -> unit) =
@@ -26,6 +26,20 @@ let Main(study: ArcStudy, assignedAssays: ArcAssay list, model: Messages.Model,
fullwidth=true,
isarea=true
)
+ FormComponents.PersonsInput(
+ Array.ofSeq study.Contacts,
+ "Contacts",
+ fun persons ->
+ study.Contacts <- ResizeArray(persons)
+ (study, assignedAssays) |> Study |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
+ )
+ FormComponents.PublicationsInput (
+ Array.ofSeq study.Publications,
+ "Publications",
+ fun pubs ->
+ study.Publications <- ResizeArray(pubs)
+ (study, assignedAssays) |> Study |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
+ )
FormComponents.DateTimeInput(
Option.defaultValue "" study.SubmissionDate,
"Submission Date",
@@ -42,46 +56,25 @@ let Main(study: ArcStudy, assignedAssays: ArcAssay list, model: Messages.Model,
study.PublicReleaseDate <- s
(study, assignedAssays) |> Study |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
)
- FormComponents.PublicationsInput (
- study.Publications,
- "Publications",
- fun pubs ->
- study.Publications <- pubs
- (study, assignedAssays) |> Study |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
- )
- FormComponents.PersonsInput(
- study.Contacts,
- "Contacts",
- fun persons ->
- study.Contacts <- persons
- (study, assignedAssays) |> Study |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
- )
FormComponents.OntologyAnnotationsInput(
- study.StudyDesignDescriptors,
+ Array.ofSeq study.StudyDesignDescriptors,
"Study Design Descriptors",
fun oas ->
- study.StudyDesignDescriptors <- oas
- (study, assignedAssays) |> Study |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
- )
- FormComponents.TextInputs(
- Array.ofSeq study.RegisteredAssayIdentifiers,
- "Registered Assay Identifiers",
- fun rais ->
- study.RegisteredAssayIdentifiers <- ResizeArray(rais)
- (study, assignedAssays) |> Study |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
- )
- FormComponents.FactorsInput(
- study.Factors,
- "Factors",
- fun factors ->
- study.Factors <- factors
+ study.StudyDesignDescriptors <- ResizeArray(oas)
(study, assignedAssays) |> Study |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
)
+ //FormComponents.TextInputs(
+ // Array.ofSeq study.RegisteredAssayIdentifiers,
+ // "Registered Assay Identifiers",
+ // fun rais ->
+ // study.RegisteredAssayIdentifiers <- ResizeArray(rais)
+ // (study, assignedAssays) |> Study |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
+ //)
FormComponents.CommentsInput(
- study.Comments,
+ Array.ofSeq study.Comments,
"Comments",
fun comments ->
- study.Comments <- comments
+ study.Comments <- ResizeArray(comments)
(study, assignedAssays) |> Study |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch
)
]
\ No newline at end of file
diff --git a/src/Client/MainComponents/Metadata/Template.fs b/src/Client/MainComponents/Metadata/Template.fs
index 28ec2d58..cbecc012 100644
--- a/src/Client/MainComponents/Metadata/Template.fs
+++ b/src/Client/MainComponents/Metadata/Template.fs
@@ -7,7 +7,7 @@ open Spreadsheet
open Messages
open Browser.Types
open Fable.Core.JsInterop
-open ARCtrl.ISA
+open ARCtrl
open Shared
open ARCtrl.Template
@@ -63,24 +63,24 @@ let Main(template: Template, model: Messages.Model, dispatch: Msg -> unit) =
template |> ArcFiles.Template |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch)
)
FormComponents.OntologyAnnotationsInput(
- template.Tags,
+ Array.ofSeq template.Tags,
"Tags",
(fun (s) ->
- template.Tags <- s
+ template.Tags <- ResizeArray s
template |> ArcFiles.Template |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch)
)
FormComponents.OntologyAnnotationsInput(
- template.EndpointRepositories,
+ Array.ofSeq template.EndpointRepositories,
"Endpoint Repositories",
(fun (s) ->
- template.EndpointRepositories <- s
+ template.EndpointRepositories <- ResizeArray s
template |> ArcFiles.Template |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch)
)
FormComponents.PersonsInput(
- template.Authors,
+ Array.ofSeq template.Authors,
"Authors",
(fun (s) ->
- template.Authors <- s
+ template.Authors <-ResizeArray s
template |> ArcFiles.Template |> Spreadsheet.UpdateArcFile |> SpreadsheetMsg |> dispatch)
)
]
\ No newline at end of file
diff --git a/src/Client/MainComponents/Navbar.fs b/src/Client/MainComponents/Navbar.fs
index 1f897290..419f0e0b 100644
--- a/src/Client/MainComponents/Navbar.fs
+++ b/src/Client/MainComponents/Navbar.fs
@@ -7,9 +7,9 @@ open Feliz.Bulma
open LocalHistory
open Messages
open Components.QuickAccessButton
+open MainComponents
-
-let quickAccessButtonListStart (state: LocalHistory.Model) dispatch =
+let private quickAccessButtonListStart (state: LocalHistory.Model) dispatch =
Html.div [
prop.style [
style.display.flex; style.flexDirection.row
@@ -47,14 +47,14 @@ let quickAccessButtonListStart (state: LocalHistory.Model) dispatch =
]
]
-let quickAccessButtonListEnd (model: Model) dispatch =
+let private quickAccessButtonListEnd (model: Model) dispatch =
Html.div [
prop.style [
style.display.flex; style.flexDirection.row
]
prop.children [
QuickAccessButton.create(
- "Save as xlsx",
+ "Save",
[
Bulma.icon [Html.i [prop.className "fa-solid fa-floppy-disk";]]
],
@@ -66,14 +66,60 @@ let quickAccessButtonListEnd (model: Model) dispatch =
Bulma.icon [Html.i [prop.className "fa-sharp fa-solid fa-trash";]]
],
(fun _ -> Modals.Controller.renderModal("ResetTableWarning", Modals.ResetTable.Main dispatch)),
- buttonProps = [Bulma.color.isDanger; Bulma.button.isInverted; Bulma.button.isOutlined]
+ buttonProps = [Bulma.color.isDanger]
+ ).toReactElement()
+ ]
+ ]
+
+let private WidgetNavbarList (model, dispatch, addWidget: Widget -> unit) =
+ Html.div [
+ prop.style [
+ style.display.flex; style.flexDirection.row
+ ]
+ prop.children [
+ QuickAccessButton.create(
+ "Add Building Block",
+ [
+ Bulma.icon [
+ Html.i [prop.className "fa-solid fa-circle-plus" ]
+ Html.i [prop.className "fa-solid fa-table-columns" ]
+ ]
+ ],
+ (fun _ -> addWidget Widget._BuildingBlock)
+ ).toReactElement()
+ QuickAccessButton.create(
+ "Add Template",
+ [
+ Bulma.icon [
+ Html.i [prop.className "fa-solid fa-circle-plus" ]
+ Html.i [prop.className "fa-solid fa-table" ]
+ ]
+ ],
+ (fun _ -> addWidget Widget._Template)
+ ).toReactElement()
+ QuickAccessButton.create(
+ "File Picker",
+ [
+ Bulma.icon [
+ Html.i [prop.className "fa-solid fa-file-signature" ]
+ ]
+ ],
+ (fun _ -> addWidget Widget._FilePicker)
).toReactElement()
]
]
+
[]
-let Main (model: Messages.Model) dispatch =
+let Main(model: Messages.Model, dispatch, widgets, setWidgets) =
+ let addWidget (widget: Widget) =
+ let add (widget) widgets = widget::widgets |> List.rev |> setWidgets
+ if widgets |> List.contains widget then
+ List.filter (fun w -> w <> widget) widgets
+ |> fun filteredWidgets -> add widget filteredWidgets
+ else
+ add widget widgets
Bulma.navbar [
prop.className "myNavbarSticky"
prop.id "swate-mainNavbar"
@@ -95,32 +141,12 @@ let Main (model: Messages.Model) dispatch =
prop.ariaLabel "menu"
prop.children [
match model.PersistentStorageState.Host with
- | Some Swatehost.ARCitect ->
+ | Some (Swatehost.ARCitect) ->
Bulma.navbarStart.div [
prop.style [style.display.flex; style.alignItems.stretch; style.justifyContent.flexStart; style.custom("marginRight", "auto")]
prop.children [
- Html.div [
- prop.style [
- style.display.flex; style.flexDirection.row
- ]
- prop.children [
- QuickAccessButton.create(
- "Return to ARCitect",
- [
- Bulma.icon [Html.i [prop.className "fa-solid fa-circle-left";]]
- ],
- (fun _ -> ARCitect.ARCitect.send Model.ARCitect.TriggerSwateClose)
- ).toReactElement()
- QuickAccessButton.create(
- "Alpha State",
- [
- Html.span "ALPHA STATE"
- ],
- (fun e -> ()),
- false
- ).toReactElement()
- ]
- ]
+ quickAccessButtonListStart model.History dispatch
+ if model.SpreadsheetModel.TableViewIsActive() then WidgetNavbarList(model, dispatch, addWidget)
]
]
| Some _ ->
@@ -128,6 +154,7 @@ let Main (model: Messages.Model) dispatch =
prop.style [style.display.flex; style.alignItems.stretch; style.justifyContent.flexStart; style.custom("marginRight", "auto")]
prop.children [
quickAccessButtonListStart model.History dispatch
+ if model.SpreadsheetModel.TableViewIsActive() then WidgetNavbarList(model, dispatch, addWidget)
]
]
Bulma.navbarEnd.div [
diff --git a/src/Client/MainComponents/NoTablesElement.fs b/src/Client/MainComponents/NoTablesElement.fs
index 4cd4c1de..4a1e82e1 100644
--- a/src/Client/MainComponents/NoTablesElement.fs
+++ b/src/Client/MainComponents/NoTablesElement.fs
@@ -3,11 +3,11 @@ module MainComponents.NoTablesElement
open Feliz
open Feliz.Bulma
-open Spreadsheet
+open SpreadsheetInterface
open Messages
open Browser.Types
open Fable.Core.JsInterop
-open ARCtrl.ISA
+open ARCtrl
open Shared
open Elmish
@@ -22,7 +22,7 @@ module private UploadHandler =
[]
let id = "droparea"
- let updateMsg = fun r -> r |> SetArcFileFromBytes |> SpreadsheetMsg
+ let updateMsg = fun r -> r |> ImportXlsx |> InterfaceMsg
let setActive_DropArea() =
styleCounter <- styleCounter + 1
@@ -69,7 +69,7 @@ let private uploadNewTable dispatch =
reader.onload <- fun evt ->
let (r: byte []) = evt.target?result
- r |> SetArcFileFromBytes |> SpreadsheetMsg |> dispatch
+ r |> ImportXlsx |> InterfaceMsg |> dispatch
reader.onerror <- fun evt ->
curry GenericLog Cmd.none ("Error", evt?Value) |> DevMsg |> dispatch
@@ -127,7 +127,7 @@ let private createNewTable isActive toggle (dispatch: Messages.Msg -> unit) =
let i = ArcInvestigation.init("New Investigation")
ArcFiles.Investigation i
|> UpdateArcFile
- |> Messages.SpreadsheetMsg
+ |> InterfaceMsg
|> dispatch
)
prop.text "Investigation"
@@ -135,10 +135,10 @@ let private createNewTable isActive toggle (dispatch: Messages.Msg -> unit) =
Bulma.dropdownItem.a [
prop.onClick(fun _ ->
let s = ArcStudy.init("New Study")
- let newTable = s.InitTable("New Study Table")
+ let _ = s.InitTable("New Study Table")
ArcFiles.Study (s, [])
|> UpdateArcFile
- |> Messages.SpreadsheetMsg
+ |> InterfaceMsg
|> dispatch
)
prop.text "Study"
@@ -149,7 +149,7 @@ let private createNewTable isActive toggle (dispatch: Messages.Msg -> unit) =
let newTable = a.InitTable("New Assay Table")
ArcFiles.Assay a
|> UpdateArcFile
- |> Messages.SpreadsheetMsg
+ |> InterfaceMsg
|> dispatch
)
prop.text "Assay"
@@ -165,7 +165,7 @@ let private createNewTable isActive toggle (dispatch: Messages.Msg -> unit) =
template.LastUpdated <- System.DateTime.Now
ArcFiles.Template template
|> UpdateArcFile
- |> Messages.SpreadsheetMsg
+ |> InterfaceMsg
|> dispatch
)
prop.text "Template"
diff --git a/src/Client/MainComponents/SpreadsheetView.fs b/src/Client/MainComponents/SpreadsheetView.fs
index 0831d964..1c98ad91 100644
--- a/src/Client/MainComponents/SpreadsheetView.fs
+++ b/src/Client/MainComponents/SpreadsheetView.fs
@@ -6,22 +6,10 @@ open Feliz.Bulma
open Spreadsheet
open Messages
open Spreadsheet.Cells
-open ARCtrl.ISA
+open ARCtrl
open Shared
-
-//let private referenceColumns (state:Set, header:SwateCell, (columnIndex: int, rowIndex:int), model, dispatch) =
-// if header.Header.isTermColumn then
-// [
-// let isExtended = state.Contains(columnIndex)
-// if isExtended then
-// if header.Header.HasUnit then
-// yield UnitCell((columnIndex,rowIndex), model, dispatch)
-// yield TANCell((columnIndex,rowIndex), model, dispatch)
-// ]
-// else []
-
-let cellPlaceholder (c_opt: CompositeCell option) =
+let private cellPlaceholder (c_opt: CompositeCell option) =
let tableCell (children: ReactElement list) = Html.td [
Html.div [
prop.style [style.minHeight (length.px 30); style.minWidth (length.px 100)]
@@ -40,38 +28,92 @@ let cellPlaceholder (c_opt: CompositeCell option) =
]
]
-let private bodyRow (rowIndex: int) (state:Set) setState (model:Model) (dispatch: Msg -> unit) =
+///
+/// rowIndex < 0 equals header
+///
+///
+let private RowLabel (rowIndex: int) =
+ let t : IReactProperty list -> ReactElement = if rowIndex < 0 then Html.th else Html.td
+ t [
+ //prop.style [style.resize.none; style.border(length.px 1, borderStyle.solid, "darkgrey")]
+ //prop.children [
+ // Bulma.button.button [
+ // prop.className "px-2 py-1"
+ // prop.style [style.custom ("border", "unset"); style.borderRadius 0]
+ // Bulma.button.isFullWidth
+ // Bulma.button.isStatic
+ // prop.tabIndex -1
+ // prop.text (if rowIndex < 0 then "" else $"{rowIndex+1}")
+ // ]
+ //]
+ prop.style [style.resize.none; style.border(length.px 1, borderStyle.solid, "darkgrey"); style.height(length.perc 100)]
+ prop.children [
+ Html.div [
+ prop.style [style.height(length.perc 100);]
+ prop.className "is-flex is-justify-content-center is-align-items-center px-2 is-unselectable my-grey-out"
+ prop.disabled true
+ prop.children [
+ Html.b (if rowIndex < 0 then "" else $"{rowIndex+1}")
+ ]
+ ]
+ ]
+ ]
+
+let private bodyRow (rowIndex: int) (state:Set) (model:Model) (dispatch: Msg -> unit) =
let table = model.SpreadsheetModel.ActiveTable
Html.tr [
+ RowLabel rowIndex
for columnIndex in 0 .. (table.ColumnCount-1) do
let index = columnIndex, rowIndex
- Cells.BodyCell(index, state, setState, model, dispatch)
- //Cell((columnIndex,rowIndex), state, setState, model, dispatch)
- //yield! referenceColumns(state, header, (column,row), model, dispatch)
+ let cell = model.SpreadsheetModel.ActiveTable.Values.[index]
+ Cells.Cell.Body (index, cell, model, dispatch)
+ let isExtended = state.Contains columnIndex
+ if isExtended && (cell.isTerm || cell.isUnitized) then
+ if cell.isUnitized then
+ Cell.BodyUnit(index, cell, model, dispatch)
+ else
+ Cell.Empty()
+ Cell.BodyTSR(index, cell, model, dispatch)
+ Cell.BodyTAN(index, cell, model, dispatch)
]
-let private bodyRows (state:Set) setState (model:Model) (dispatch: Msg -> unit) =
+let private bodyRows (state:Set) (model:Model) (dispatch: Msg -> unit) =
Html.tbody [
for rowInd in 0 .. model.SpreadsheetModel.ActiveTable.RowCount-1 do
- yield bodyRow rowInd state setState model dispatch
+ yield bodyRow rowInd state model dispatch
]
let private headerRow (state:Set) setState (model:Model) (dispatch: Msg -> unit) =
let table = model.SpreadsheetModel.ActiveTable
Html.tr [
+ if table.ColumnCount > 0 then RowLabel -1
for columnIndex in 0 .. (table.ColumnCount-1) do
- yield
- Cells.HeaderCell(columnIndex, state, setState, model, dispatch)
- //yield! referenceColumns(state, cell, (column,row), model, dispatch)
+ let header = table.Headers.[columnIndex]
+ Cells.Cell.Header(columnIndex, header, state, setState, model, dispatch)
+ let isExtended = state.Contains columnIndex
+ if isExtended then
+ Cell.HeaderUnit(columnIndex, header, state, setState, model, dispatch)
+ Cell.HeaderTSR(columnIndex, header, state, setState, model, dispatch)
+ Cell.HeaderTAN(columnIndex, header, state, setState, model, dispatch)
]
+open Fable.Core.JsInterop
+
[]
let Main (model:Model) (dispatch: Msg -> unit) =
+ //React.useListener.on("keydown", (Spreadsheet.KeyboardShortcuts.onKeydownEvent dispatch))
+ let ref = React.useElementRef()
+ //React.useElementListener.on(ref, "keydown", (Spreadsheet.KeyboardShortcuts.onKeydownEvent dispatch))
/// This state is used to track which columns are expanded
let state, setState : Set * (Set -> unit) = React.useState(Set.empty)
+ React.useEffect((fun _ -> setState Set.empty), [|box model.SpreadsheetModel.ActiveView|])
Html.div [
+ prop.id "SPREADSHEET_MAIN_VIEW"
+ prop.tabIndex 0
prop.style [style.border(1, borderStyle.solid, "grey"); style.width.minContent; style.marginRight(length.vw 10)]
+ prop.ref ref
+ prop.onKeyDown(fun e -> Spreadsheet.KeyboardShortcuts.onKeydownEvent dispatch e)
prop.children [
Html.table [
prop.className "fixed_headers"
@@ -79,7 +121,7 @@ let Main (model:Model) (dispatch: Msg -> unit) =
Html.thead [
headerRow state setState model dispatch
]
- bodyRows state setState model dispatch
+ bodyRows state model dispatch
]
]
]
diff --git a/src/Client/MainComponents/Widgets.fs b/src/Client/MainComponents/Widgets.fs
new file mode 100644
index 00000000..21a5b542
--- /dev/null
+++ b/src/Client/MainComponents/Widgets.fs
@@ -0,0 +1,255 @@
+namespace MainComponents
+
+open Feliz
+open Feliz.Bulma
+open Browser.Types
+
+open LocalStorage.Widgets
+
+module private InitExtensions =
+
+ type Rect with
+
+ static member initSizeFromPrefix(prefix: string) =
+ match Size.load prefix with
+ | Some p -> Some p
+ | None -> None
+
+ static member initPositionFromPrefix(prefix: string) =
+ match Position.load prefix with
+ | Some p -> Some p
+ | None -> None
+
+
+open InitExtensions
+
+open Fable.Core
+open Fable.Core.JsInterop
+open Protocol
+
+module private MoveEventListener =
+
+ open Fable.Core.JsInterop
+
+ let ensurePositionInsideWindow (element:IRefValue) (position: Rect) =
+ let maxX = Browser.Dom.window.innerWidth - element.current.Value.offsetWidth;
+ let tempX = position.X
+ let newX = System.Math.Min(System.Math.Max(tempX,0),int maxX)
+ let maxY = Browser.Dom.window.innerHeight - element.current.Value.offsetHeight;
+ let tempY = position.Y
+ let newY = System.Math.Min(System.Math.Max(tempY,0),int maxY)
+ {X = newX; Y = newY}
+
+ let calculatePosition (element:IRefValue) (startPosition: Rect) = fun (e: Event) ->
+ let e : MouseEvent = !!e
+ let tempX = int e.clientX - startPosition.X
+ let tempY = int e.clientY - startPosition.Y
+ let tempPosition = {X = tempX; Y = tempY}
+ ensurePositionInsideWindow element tempPosition
+
+ let onmousemove (element:IRefValue) (startPosition: Rect) setPosition = fun (e: Event) ->
+ let nextPosition = calculatePosition element startPosition e
+ setPosition (Some nextPosition)
+
+ let onmouseup (prefix,element:IRefValue) onmousemove =
+ Browser.Dom.document.removeEventListener("mousemove", onmousemove)
+ if element.current.IsSome then
+ let rect = element.current.Value.getBoundingClientRect()
+ let position = {X = int rect.left; Y = int rect.top}
+ Position.write(prefix,position)
+
+module private ResizeEventListener =
+
+ open Fable.Core.JsInterop
+
+ let onmousemove (startPosition: Rect) (startSize: Rect) setSize = fun (e: Event) ->
+ let e : MouseEvent = !!e
+ let width = int e.clientX - startPosition.X + startSize.X
+ // I did not enable this, as it creates issues with overlays such as the term search dropdown.
+ // The widget card itself has overflow: visible, which makes a set height impossible,
+ // but wihout the visible overflow term search results might require scrolling.
+ // // let height = int e.clientY - startPosition.Y + startSize.Y
+ setSize (Some {X = width; Y = startSize.Y})
+
+ let onmouseup (prefix, element: IRefValue) onmousemove =
+ Browser.Dom.document.removeEventListener("mousemove", onmousemove)
+ if element.current.IsSome then
+ Size.write(prefix,{X = int element.current.Value.offsetWidth; Y = int element.current.Value.offsetHeight})
+
+module private Elements =
+
+ let helpExtendButton (extendToggle: unit -> unit) =
+ Bulma.help [
+ prop.className "is-flex"
+ prop.children [
+ Html.a [
+ prop.text "Help";
+ prop.style [style.marginLeft length.auto; style.userSelect.none]
+ prop.onClick (fun e -> e.preventDefault(); e.stopPropagation(); extendToggle())
+ ]
+ ]
+ ]
+
+[]
+type Widget =
+ | _BuildingBlock
+ | _Template
+ | _FilePicker
+
+ []
+ static member Base(content: ReactElement, prefix: string, rmv: MouseEvent -> unit, ?help: ReactElement) =
+ let position, setPosition = React.useState(fun _ -> Rect.initPositionFromPrefix prefix)
+ let size, setSize = React.useState(fun _ -> Rect.initSizeFromPrefix prefix)
+ let helpIsActive, setHelpIsActive = React.useState(false)
+ let element = React.useElementRef()
+ React.useLayoutEffectOnce(fun _ -> position |> Option.iter (fun position -> MoveEventListener.ensurePositionInsideWindow element position |> Some |> setPosition)) // Reposition widget inside window
+ let resizeElement (content: ReactElement) =
+ Bulma.card [
+ prop.ref element
+ prop.onMouseDown(fun e -> // resize
+ e.preventDefault()
+ e.stopPropagation()
+ let startPosition = {X = int e.clientX; Y = int e.clientY}
+ let startSize = {X = int element.current.Value.offsetWidth; Y = int element.current.Value.offsetHeight}
+ let onmousemove = ResizeEventListener.onmousemove startPosition startSize setSize
+ let onmouseup = fun e -> ResizeEventListener.onmouseup (prefix, element) onmousemove
+ Browser.Dom.document.addEventListener("mousemove", onmousemove)
+ let config = createEmpty
+ config.once <- true
+ Browser.Dom.document.addEventListener("mouseup", onmouseup, config)
+ )
+ prop.style [
+ style.zIndex 40
+ style.cursor.eastWestResize//style.cursor.northWestSouthEastResize ;
+ style.display.flex
+ style.paddingRight(2);
+ style.overflow.visible
+ style.position.fixedRelativeToWindow
+ style.minWidth.minContent
+ if size.IsSome then
+ style.width size.Value.X
+ //style.height size.Value.Y
+ if position.IsNone then
+ //style.transform.translate (length.perc -50,length.perc -50)
+ style.top (length.perc 20); style.left (length.perc 20);
+ else
+ style.top position.Value.Y; style.left position.Value.X;
+ ]
+ prop.children content
+ ]
+ resizeElement <| Html.div [
+ prop.onMouseDown(fun e -> e.stopPropagation())
+ prop.style [style.cursor.defaultCursor; style.display.flex; style.flexDirection.column; style.flexGrow 1]
+ prop.children [
+ Bulma.cardHeader [
+ prop.onMouseDown(fun e -> // move
+ e.preventDefault()
+ e.stopPropagation()
+ let x = e.clientX - element.current.Value.offsetLeft
+ let y = e.clientY - element.current.Value.offsetTop;
+ let startPosition = {X = int x; Y = int y}
+ let onmousemove = MoveEventListener.onmousemove element startPosition setPosition
+ let onmouseup = fun e -> MoveEventListener.onmouseup (prefix, element) onmousemove
+ Browser.Dom.document.addEventListener("mousemove", onmousemove)
+ let config = createEmpty
+ config.once <- true
+ Browser.Dom.document.addEventListener("mouseup", onmouseup, config)
+ )
+ prop.style [style.cursor.move]
+ prop.children [
+ Bulma.cardHeaderTitle.p Html.none
+ Bulma.cardHeaderIcon.a [
+ Bulma.delete [
+ prop.onClick (fun e -> e.stopPropagation(); rmv e)
+ ]
+ ]
+ ]
+ ]
+ Bulma.cardContent [
+ prop.style [style.overflow.inheritFromParent]
+ prop.children [
+ content
+ if help.IsSome then Elements.helpExtendButton (fun _ -> setHelpIsActive (not helpIsActive))
+ ]
+ ]
+ Bulma.cardFooter [
+ prop.style [style.padding 5]
+ if help.IsSome then
+ prop.children [
+ Bulma.content [
+ prop.className "widget-help-container"
+ prop.style [style.overflow.hidden; if not helpIsActive then style.display.none; ]
+ prop.children [
+ help.Value
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+
+ static member BuildingBlock (model, dispatch, rmv: MouseEvent -> unit) =
+ let content = BuildingBlock.SearchComponent.Main model dispatch
+ let help = Html.div [
+ Html.p "Add a new Building Block."
+ Html.ul [
+ Html.li "If a cell is selected, a new Building Block is added to the right of the selected cell."
+ Html.li "If no cell is selected, a new Building Block is appended at the right end of the table."
+ ]
+ ]
+ let prefix = BuildingBlockWidgets
+ Widget.Base(content, prefix, rmv, help)
+
+
+ []
+ static member Templates (model: Messages.Model, dispatch, rmv: MouseEvent -> unit) =
+ let templates, setTemplates = React.useState(model.ProtocolState.Templates)
+ let config, setConfig = React.useState(TemplateFilterConfig.init)
+ let filteredTemplates = Protocol.Search.filterTemplates (templates, config)
+ React.useEffectOnce(fun _ -> Messages.Protocol.GetAllProtocolsRequest |> Messages.ProtocolMsg |> dispatch)
+ React.useEffect((fun _ -> setTemplates model.ProtocolState.Templates), [|box model.ProtocolState.Templates|])
+ let selectContent() =
+ [
+ Protocol.Search.FileSortElement(model, config, setConfig)
+ Protocol.Search.Component (filteredTemplates, model, dispatch, length.px 350)
+ ]
+ let insertContent() =
+ [
+ Bulma.field.div [
+ Protocol.TemplateFromDB.addFromDBToTableButton model dispatch
+ ]
+ Bulma.field.div [
+ prop.style [style.maxHeight (length.px 350); style.overflow.auto]
+ prop.children [
+ Protocol.TemplateFromDB.displaySelectedProtocolEle model dispatch
+ ]
+ ]
+ ]
+ let content =
+ let switchContent = if model.ProtocolState.TemplateSelected.IsNone then selectContent() else insertContent()
+ Html.div [
+ prop.children switchContent
+ ]
+
+ let help = Protocol.Search.InfoField()
+ let prefix = TemplatesWidgets
+ Widget.Base(content, prefix, rmv, help)
+
+ static member FilePicker (model, dispatch, rmv) =
+ let content = Html.div [
+ FilePicker.uploadButton model dispatch
+ if model.FilePickerState.FileNames <> [] then
+ FilePicker.fileSortElements model dispatch
+
+ Bulma.field.div [
+ prop.style [style.maxHeight (length.px 350); style.overflow.auto]
+ prop.children [
+ FilePicker.FileNameTable.table model dispatch
+ ]
+ ]
+ //fileNameElements model dispatch
+ FilePicker.insertButton model dispatch
+ ]
+ let prefix = FilePickerWidgets
+ let help = Html.div []
+ Widget.Base(content, prefix, rmv, help)
\ No newline at end of file
diff --git a/src/Client/Messages.fs b/src/Client/Messages.fs
index e20df903..aed5f8a1 100644
--- a/src/Client/Messages.fs
+++ b/src/Client/Messages.fs
@@ -8,13 +8,12 @@ open Fable.Remoting.Client
open Fable.SimpleJson
open TermTypes
-open TemplateTypes
open ExcelColors
open OfficeInterop
open OfficeInteropTypes
open Model
open Routing
-open ARCtrl.ISA
+open ARCtrl
open Fable.Core
type System.Exception with
@@ -43,40 +42,26 @@ module AdvancedSearch =
type Msg =
| GetSearchResults of {| config:AdvancedSearchTypes.AdvancedSearchOptions; responseSetter: Term [] -> unit |}
+module Ontologies =
+
+ type Msg =
+ | GetOntologies
+
type DevMsg =
| LogTableMetadata
| GenericLog of Cmd * (string*string)
| GenericInteropLogs of Cmd * InteropLogging.Msg list
| GenericError of Cmd * exn
| UpdateDisplayLogList of LogItem list
-
-type ApiRequestMsg =
- | GetNewUnitTermSuggestions of string
- | FetchAllOntologies
- /// TermSearchable [] is created by officeInterop and passed to server for db search.
- | SearchForInsertTermsRequest of TermSearchable []
- //
- | GetAppVersion
-
-type ApiResponseMsg =
- | UnitTermSuggestionResponse of Term []
- | FetchAllOntologiesResponse of Ontology []
- | SearchForInsertTermsResponse of TermSearchable []
- //
- | GetAppVersionResponse of string
-
-type ApiMsg =
- | Request of ApiRequestMsg
- | Response of ApiResponseMsg
- | ApiError of exn
- | ApiSuccess of (string*string)
type StyleChangeMsg =
| UpdateColorMode of ColorMode
-type PersistentStorageMsg =
+module PersistentStorage =
+ type Msg =
| NewSearchableOntologies of Ontology []
| UpdateAppVersion of string
+ | UpdateShowSidebar of bool
module FilePicker =
type Msg =
@@ -88,32 +73,25 @@ module BuildingBlock =
open TermSearch
type Msg =
+ | UpdateHeaderWithIO of BuildingBlock.HeaderCellType * IOType
| UpdateHeaderCellType of BuildingBlock.HeaderCellType
| UpdateHeaderArg of U2 option
| UpdateBodyCellType of BuildingBlock.BodyCellType
| UpdateBodyArg of U2 option
- // Below everything is more or less deprecated
- // Is still used for unit update in office
- | SearchUnitTermTextChange of searchString:string
- | UnitTermSuggestionUsed of unitTerm:Term
- | NewUnitTermSuggestions of Term []
module Protocol =
type Msg =
- // // ------ Process from file ------
- | ParseUploadedFileRequest of raw: byte []
- | ParseUploadedFileResponse of (string * InsertBuildingBlock []) []
// Client
- | RemoveUploadedFileParsed
+ | UpdateTemplates of Template []
+ | UpdateLoading of bool
+ | RemoveSelectedProtocol
// // ------ Protocol from Database ------
+ | GetAllProtocolsForceRequest
| GetAllProtocolsRequest
- | GetAllProtocolsResponse of string []
- | SelectProtocol of ARCtrl.Template.Template
+ | GetAllProtocolsResponse of string
+ | SelectProtocol of Template
| ProtocolIncreaseTimesUsed of protocolName:string
- // Client
- | RemoveSelectedProtocol
- | UpdateLoading of bool
type BuildingBlockDetailsMsg =
| GetSelectedBuildingBlockTermsRequest of TermSearchable []
@@ -133,31 +111,19 @@ type Model = {
PageState : PageState
///Data that needs to be persistent once loaded
PersistentStorageState : PersistentStorageState
- ///Debouncing
- DebouncerState : Debouncer.State
///Error handling, Logging, etc.
DevState : DevState
///States regarding term search
TermSearchState : TermSearch.Model
///Use this in the future to model excel stuff like table data
ExcelState : OfficeInterop.Model
- /// This should be removed. Overhead making maintainance more difficult
- /// "Use this to log Api calls and maybe handle them better"
- ApiState : ApiState
///States regarding File picker functionality
FilePickerState : FilePicker.Model
ProtocolState : Protocol.Model
///Insert annotation columns
AddBuildingBlockState : BuildingBlock.Model
- ///Create Validation scheme for Table
- ValidationState : Validation.Model
///Used to show selected building block information
BuildingBlockDetailsState : BuildingBlockDetailsState
- ///Used to manage all custom xml settings
- SettingsXmlState : SettingsXml.Model
- JsonExporterModel : JsonExporter.Model
- TemplateMetadataModel : TemplateMetadata.Model
- DagModel : Dag.Model
CytoscapeModel : Cytoscape.Model
/// Contains all information about spreadsheet view
SpreadsheetModel : Spreadsheet.Model
@@ -165,35 +131,23 @@ type Model = {
} with
member this.updateByExcelState (s:OfficeInterop.Model) =
{ this with ExcelState = s}
- member this.updateByJsonExporterModel (m:JsonExporter.Model) =
- { this with JsonExporterModel = m}
- member this.updateByTemplateMetadataModel (m:TemplateMetadata.Model) =
- { this with TemplateMetadataModel = m}
- member this.updateByDagModel (m:Dag.Model) =
- { this with DagModel = m}
type Msg =
-| Bounce of (System.TimeSpan*string*Msg)
-| DebouncerSelfMsg of Debouncer.SelfMessage
-| Api of ApiMsg
| DevMsg of DevMsg
+| OntologyMsg of Ontologies.Msg
| TermSearchMsg of TermSearch.Msg
| AdvancedSearchMsg of AdvancedSearch.Msg
| OfficeInteropMsg of OfficeInterop.Msg
-| PersistentStorage of PersistentStorageMsg
+| PersistentStorageMsg of PersistentStorage.Msg
| FilePickerMsg of FilePicker.Msg
| BuildingBlockMsg of BuildingBlock.Msg
| ProtocolMsg of Protocol.Msg
-| JsonExporterMsg of JsonExporter.Msg
-| TemplateMetadataMsg of TemplateMetadata.Msg
| BuildingBlockDetails of BuildingBlockDetailsMsg
| CytoscapeMsg of Cytoscape.Msg
| SpreadsheetMsg of Spreadsheet.Msg
-| DagMsg of Dag.Msg
/// This is used to forward Msg to SpreadsheetMsg/OfficeInterop
| InterfaceMsg of SpreadsheetInterface.Msg
//| SettingsProtocolMsg of SettingsProtocolMsg
-| TopLevelMsg of TopLevelMsg
| UpdatePageState of Routing.Route option
| UpdateIsExpert of bool
| Batch of seq
diff --git a/src/Client/Modals/MoveColumn.fs b/src/Client/Modals/MoveColumn.fs
new file mode 100644
index 00000000..41c0262f
--- /dev/null
+++ b/src/Client/Modals/MoveColumn.fs
@@ -0,0 +1,115 @@
+namespace Modals
+
+open Feliz
+open Feliz.Bulma
+open Model
+open Messages
+open Shared
+open OfficeInteropTypes
+
+open ARCtrl
+
+type MoveColumn =
+
+ []
+ static member InputField(index: int, set, max: int, submit) =
+ let input, setInput = React.useState(index)
+ Bulma.field.div [
+ prop.className "is-grouped is-justify-content-space-between"
+ prop.style [style.gap (length.rem 1)]
+ prop.children [
+ Bulma.field.div [
+ Bulma.label "Preview"
+ Bulma.field.div [
+ Bulma.field.hasAddons
+ prop.children [
+ Bulma.control.div [
+ Bulma.control.isExpanded
+ prop.children [
+ Bulma.input.number [
+ prop.onChange(fun i -> setInput i)
+ prop.defaultValue input
+ prop.min 0
+ prop.max max
+ ]
+ ]
+ ]
+ Bulma.control.div [
+ Bulma.button.button [
+ prop.onClick(fun _ -> set (index,input))
+ prop.text "Apply"
+ ]
+ ]
+ ]
+ ]
+ ]
+ Bulma.field.div [
+ Bulma.label "Update Table"
+ Bulma.button.a [
+ Bulma.color.isInfo
+ prop.onClick (submit input)
+ prop.text "Submit"
+ ]
+ ]
+ ]
+ ]
+
+ []
+ static member Main (columnIndex: int, model: Messages.Model, dispatch) (rmv: _ -> unit) =
+ let table = model.SpreadsheetModel.ActiveTable
+ let state, setState = React.useState(Array.ofSeq table.Headers)
+ let index, setIndex = React.useState(columnIndex)
+ let updateIndex(current, next) =
+ setIndex next
+ let nextState = ResizeArray(state)
+ Helper.arrayMoveColumn current next nextState
+ setState (Array.ofSeq nextState)
+ let submit = fun i e ->
+ Spreadsheet.MoveColumn(columnIndex, i) |> SpreadsheetMsg |> dispatch
+ rmv e
+ Bulma.modal [
+ Bulma.modal.isActive
+ prop.children [
+ Bulma.modalBackground [ prop.onClick rmv ]
+ Bulma.modalCard [
+ prop.style [style.maxHeight(length.percent 70); style.overflowY.hidden]
+ prop.children [
+ Bulma.modalCardHead [
+ Bulma.modalCardTitle "Move Column"
+ Bulma.delete [ prop.onClick rmv ]
+ ]
+ Bulma.modalCardBody [
+ MoveColumn.InputField(index, updateIndex, state.Length-1, submit)
+ Bulma.tableContainer [
+ prop.style [style.maxHeight 400; style.overflowY.auto]
+ prop.children [
+ Bulma.table [
+ Bulma.table.isFullWidth
+ prop.children [
+ Html.thead [
+ Html.tr [
+ Html.th "Index"
+ Html.th "Column"
+ ]
+ ]
+ Html.tbody [
+ for i in 0 .. state.Length-1 do
+ Html.tr [
+ if i = index then
+ Bulma.color.hasBackgroundDanger;
+ prop.className "has-background-danger"
+ prop.children [
+ Html.td i
+ Html.td (state.[i].ToString())
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
\ No newline at end of file
diff --git a/src/Client/Modals/SelectiveImportModal.fs b/src/Client/Modals/SelectiveImportModal.fs
new file mode 100644
index 00000000..40a2d01b
--- /dev/null
+++ b/src/Client/Modals/SelectiveImportModal.fs
@@ -0,0 +1,311 @@
+namespace Modals
+
+open Feliz
+open Feliz.Bulma
+open Model
+open Messages
+open Shared
+
+open ARCtrl
+
+[]
+type private ImportTable = {
+ Index: int
+ /// If FullImport is true, the table will be imported in full, otherwise it will be appended to active table.
+ FullImport: bool
+}
+
+type private SelectiveImportModalState = {
+ ImportType: ARCtrl.TableJoinOptions
+ ImportMetadata: bool
+ ImportTables: ImportTable list
+} with
+ static member init() =
+ {
+ ImportType = ARCtrl.TableJoinOptions.Headers
+ ImportMetadata = false
+ ImportTables = []
+ }
+
+module private Helper =
+
+ let submitWithMetadata (uploadedFile: ArcFiles) (state: SelectiveImportModalState) (dispatch: Messages.Msg -> unit) =
+ if not state.ImportMetadata then failwith "Metadata must be imported"
+ let createUpdatedTables (arcTables: ResizeArray) =
+ [
+ for it in state.ImportTables do
+ let sourceTable = arcTables.[it.Index]
+ let appliedTable = ArcTable.init(sourceTable.Name)
+ appliedTable.Join(sourceTable, joinOptions=state.ImportType)
+ appliedTable
+ ]
+ |> ResizeArray
+ let arcFile =
+ match uploadedFile with
+ | Assay a as arcFile->
+ let tables = createUpdatedTables a.Tables
+ a.Tables <- tables
+ arcFile
+ | Study (s,_) as arcFile ->
+ let tables = createUpdatedTables s.Tables
+ s.Tables <- tables
+ arcFile
+ | Template t as arcFile ->
+ let table = createUpdatedTables (ResizeArray[t.Table])
+ t.Table <- table.[0]
+ arcFile
+ | Investigation _ as arcFile ->
+ arcFile
+ SpreadsheetInterface.UpdateArcFile arcFile |> InterfaceMsg |> dispatch
+
+ let submitTables (tables: ResizeArray) (importState: SelectiveImportModalState) (activeTable: ArcTable) (dispatch: Messages.Msg -> unit) =
+ if importState.ImportTables.Length = 0 then
+ ()
+ else
+ let addMsgs =
+ importState.ImportTables
+ |> Seq.filter (fun x -> x.FullImport)
+ |> Seq.map (fun x -> tables.[x.Index])
+ |> Seq.map (fun table ->
+ let nTable = ArcTable.init(table.Name)
+ nTable.Join(table, joinOptions=importState.ImportType)
+ nTable
+ )
+ |> Seq.map (fun table -> SpreadsheetInterface.AddTable table |> InterfaceMsg)
+ let appendMsg =
+ let tables = importState.ImportTables |> Seq.filter (fun x -> not x.FullImport) |> Seq.map (fun x -> tables.[x.Index])
+ /// Everything will be appended against this table, which in the end will be appended to the main table
+ let tempTable = ArcTable.init("ThisIsAPlaceholder")
+ for table in tables do
+ let preparedTemplate = Table.distinctByHeader tempTable table
+ tempTable.Join(preparedTemplate, joinOptions=importState.ImportType)
+ let appendTable = Table.distinctByHeader activeTable tempTable
+ SpreadsheetInterface.JoinTable (appendTable, None, Some importState.ImportType) |> InterfaceMsg
+ appendMsg |> dispatch
+ if Seq.length addMsgs = 0 then () else addMsgs |> Seq.iter dispatch
+
+open Helper
+
+type SelectiveImportModal =
+
+ static member private ImportTypeRadio(importType: TableJoinOptions, setImportType: TableJoinOptions -> unit) =
+ let myradio(target: TableJoinOptions, txt: string) =
+ let isChecked = importType = target
+ Html.label [
+ prop.className "radio is-unselectable"
+ prop.children [
+ Html.input [
+ prop.type'.radio
+ prop.name "importType"
+ prop.isChecked isChecked
+ prop.onChange (fun (b:bool) -> if b then setImportType target)
+ ]
+ Html.text txt
+ ]
+ ]
+
+ Bulma.box [
+ Bulma.field.div [
+ Bulma.label [
+ Html.i [prop.className "fa-solid fa-cog"]
+ Html.text (" Import Type")
+ ]
+ Bulma.control.div [
+ prop.className "is-flex is-justify-content-space-between"
+ prop.children [
+ myradio(ARCtrl.TableJoinOptions.Headers, " Column Headers")
+ myradio(ARCtrl.TableJoinOptions.WithUnit, " ..With Units")
+ myradio(ARCtrl.TableJoinOptions.WithValues, " ..With Values")
+ ]
+ ]
+ ]
+ ]
+
+ static member private MetadataImport(isActive: bool, setActive: bool -> unit, disArcFile: ArcFilesDiscriminate) =
+ let name = string disArcFile
+ Bulma.box [
+ if isActive then color.hasBackgroundInfo
+ prop.children [
+ Bulma.field.div [
+ Bulma.label [
+ Html.i [prop.className "fa-solid fa-lightbulb"]
+ Html.textf " %s Metadata" name
+ ]
+ Bulma.control.div [
+ Html.label [
+ prop.className "checkbox is-unselectable"
+ prop.children [
+ Html.input [
+ prop.type'.checkbox
+ prop.onChange (fun (b:bool) -> setActive b)
+ ]
+ Html.text " Import"
+ ]
+ ]
+ ]
+ Html.span [
+ color.hasTextWarning
+ prop.text "Importing metadata will overwrite the current file."
+ ]
+ ]
+ ]
+ ]
+
+ []
+ static member private TableImport(index: int, table: ArcTable, state: SelectiveImportModalState, addTableImport: int -> bool -> unit, rmvTableImport: int -> unit) =
+ let showData, setShowData = React.useState(false)
+ let name = table.Name
+ let radioGroup = "radioGroup_" + name
+ let import = state.ImportTables |> List.tryFind (fun it -> it.Index = index)
+ let isActive = import.IsSome
+ let disableAppend = state.ImportMetadata
+ Bulma.box [
+ if isActive then color.hasBackgroundSuccess
+ prop.children [
+ Bulma.field.div [
+ Bulma.label [
+ Html.i [prop.className "fa-solid fa-table"]
+ Html.span (" " + name)
+ Bulma.button.span [
+ if showData then button.isActive
+ button.isSmall
+ prop.onClick (fun _ -> setShowData (not showData))
+ prop.style [style.float'.right; style.cursor.pointer]
+ prop.children [
+ Bulma.icon [
+ icon.isSmall
+ prop.children [
+ Html.i [
+ prop.style [style.transitionProperty "transform"; style.transitionDuration (System.TimeSpan.FromSeconds 0.35)]
+ prop.className ["fa-solid"; "fa-angle-down"; if showData then "fa-rotate-180"]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ Bulma.control.div [
+ Html.label [
+ let isInnerActive = isActive && import.Value.FullImport
+ prop.className "radio is-unselectable"
+ prop.children [
+ Html.input [
+ prop.type'.radio
+ prop.name radioGroup
+ prop.isChecked isInnerActive
+ prop.onChange (fun (b:bool) -> addTableImport index true)
+ ]
+ Html.text " Import"
+ ]
+ ]
+ Html.label [
+ let isInnerActive = isActive && not import.Value.FullImport
+ prop.className "radio is-unselectable"
+ prop.children [
+ Html.input [
+ prop.type'.radio
+ prop.name radioGroup
+ if disableAppend then prop.disabled true
+ prop.isChecked isInnerActive
+ prop.onChange (fun (b:bool) -> addTableImport index false)
+ ]
+ Html.text " Append to active table"
+ ]
+ ]
+ Html.label [
+ let isInnerActive = not isActive
+ prop.className "radio is-unselectable"
+ prop.children [
+ Html.input [
+ prop.type'.radio
+ prop.name radioGroup
+ prop.isChecked isInnerActive
+ prop.onChange (fun (b:bool) -> rmvTableImport index)
+ ]
+ Html.text " No Import"
+ ]
+ ]
+ ]
+ ]
+ if showData then
+ Bulma.field.div [
+ Bulma.tableContainer [
+ Bulma.table [
+ Bulma.table.isBordered
+ prop.children [
+ Html.thead [
+ Html.tr [
+ for c in table.Headers do
+ Html.th (c.ToString())
+ ]
+ ]
+ Html.tbody [
+ for ri in 0 .. (table.RowCount-1) do
+ let row = table.GetRow(ri, true)
+ Html.tr [
+ for c in row do
+ Html.td (c.ToString())
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+
+ []
+ static member Main (import: ArcFiles) (model: Spreadsheet.Model) dispatch (rmv: _ -> unit) =
+ let state, setState = React.useState(SelectiveImportModalState.init)
+ let tables, disArcfile =
+ match import with
+ | Assay a -> a.Tables, ArcFilesDiscriminate.Assay
+ | Study (s,_) -> s.Tables, ArcFilesDiscriminate.Study
+ | Template t -> ResizeArray([t.Table]), ArcFilesDiscriminate.Template
+ | Investigation _ -> ResizeArray(), ArcFilesDiscriminate.Investigation
+ let setMetadataImport = fun b ->
+ {state with ImportMetadata = b; ImportTables = state.ImportTables |> List.map (fun t -> {t with FullImport = true})} |> setState
+ let addTableImport = fun (i:int) (fullImport: bool) ->
+ let newImportTable: ImportTable = {Index = i; FullImport = fullImport}
+ let newImportTables = newImportTable::state.ImportTables |> List.distinct
+ {state with ImportTables = newImportTables} |> setState
+ let rmvTableImport = fun i ->
+ {state with ImportTables = state.ImportTables |> List.filter (fun it -> it.Index <> i)} |> setState
+ Bulma.modal [
+ Bulma.modal.isActive
+ prop.children [
+ Bulma.modalBackground [ prop.onClick rmv ]
+ Bulma.modalCard [
+ prop.style [style.maxHeight(length.percent 70); style.overflowY.hidden]
+ prop.children [
+ Bulma.modalCardHead [
+ Bulma.modalCardTitle "Import"
+ Bulma.delete [ prop.onClick rmv ]
+ ]
+ Bulma.modalCardBody [
+ prop.className "p-5"
+ prop.children [
+ SelectiveImportModal.ImportTypeRadio(state.ImportType, fun it -> {state with ImportType = it} |> setState)
+ SelectiveImportModal.MetadataImport(state.ImportMetadata, setMetadataImport, disArcfile)
+ for ti in 0 .. (tables.Count-1) do
+ let t = tables.[ti]
+ SelectiveImportModal.TableImport(ti, t, state, addTableImport, rmvTableImport)
+ ]
+ ]
+ Bulma.modalCardFoot [
+ Bulma.button.button [
+ color.isInfo
+ prop.style [style.marginLeft length.auto]
+ prop.text "Submit"
+ prop.onClick(fun e ->
+ match state.ImportMetadata with
+ | true -> submitWithMetadata import state dispatch
+ | false -> submitTables tables state model.ActiveTable dispatch
+ rmv e
+ )
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
diff --git a/src/Client/Modals/UpdateColumn.fs b/src/Client/Modals/UpdateColumn.fs
new file mode 100644
index 00000000..709be490
--- /dev/null
+++ b/src/Client/Modals/UpdateColumn.fs
@@ -0,0 +1,257 @@
+namespace Modals
+
+open Feliz
+open Feliz.Bulma
+open Model
+open Messages
+open Shared
+
+open ARCtrl
+open System.Text.RegularExpressions
+
+[]
+type private FunctionPage =
+| Create
+| Update
+
+module private Components =
+
+ open System
+
+ let calculateRegex (regex:string) (input: string) =
+ try
+ let regex = Regex(regex)
+ let m = regex.Match(input)
+ match m.Success with
+ | true -> m.Index, m.Length
+ | false -> 0,0
+ with
+ | _ -> 0,0
+
+
+ let split (start: int) (length: int) (str: string) =
+ let s0, s1 =
+ str |> Seq.toList |> List.splitAt (start)
+ let s1, s2 =
+ s1 |> Seq.toList |> List.splitAt (length)
+ String.Join("", s0), String.Join("", s1), String.Join("", s2)
+
+ let Tab(targetPage: FunctionPage, currentPage, setPage) =
+ Bulma.tab [
+ if targetPage = currentPage then tab.isActive
+ prop.onClick (fun _ -> setPage targetPage)
+ prop.children [
+ Html.a [
+ prop.text (targetPage.ToString())
+ ]
+ ]
+ ]
+
+ let TabNavigation(currentPage, setPage) =
+ Bulma.tabs [
+ prop.style [style.flexGrow 1]
+ tabs.isCentered
+ tabs.isFullWidth
+ prop.children [
+ Html.ul [
+ Tab(FunctionPage.Create, currentPage, setPage)
+ Tab(FunctionPage.Update, currentPage, setPage)
+ ]
+ ]
+ ]
+
+ let PreviewRow(index:int,cell0: string, cell: string, markedIndices: int*int) =
+ Html.tr [
+ Html.td index
+ Html.td [
+ let s0,marked,s2 = split (fst markedIndices) (snd markedIndices) cell0
+ Html.span s0
+ Html.span [
+ prop.className "has-background-info"
+ prop.text marked
+ ]
+ Html.span s2
+ ]
+ Html.td (cell)
+ ]
+
+ let PreviewTable(column: CompositeColumn, cellValues: string [], regex) =
+ Bulma.field.div [
+ Bulma.label "Preview"
+ Bulma.tableContainer [
+ Bulma.table [
+ Html.thead [
+ Html.tr [Html.th "";Html.th "Before"; Html.th "After"]
+ ]
+ Html.tbody [
+ let previewCount = 5
+ let preview = takeFromArray previewCount cellValues
+ for i in 0 .. (preview.Length-1) do
+ let cell0 = column.Cells.[i].ToString()
+ let cell = preview.[i]
+ let regexMarkedIndex = calculateRegex regex cell0
+ PreviewRow(i,cell0,cell,regexMarkedIndex)
+ ]
+ ]
+ ]
+ ]
+
+type UpdateColumn =
+
+ []
+ static member private CreateForm(cellValues: string [], setPreview) =
+ let baseStr, setBaseStr = React.useState("")
+ let suffix, setSuffix = React.useState(false)
+ let updateCells (baseStr: string) (suffix:bool) =
+ cellValues
+ |> Array.mapi (fun i c ->
+ match suffix with
+ | true -> baseStr + string (i+1)
+ | false -> baseStr
+ )
+ |> setPreview
+ Bulma.field.div [
+ Bulma.field.div [
+ Bulma.label "Base"
+ Bulma.input.text [
+ prop.autoFocus true
+ prop.valueOrDefault baseStr
+ prop.onChange(fun s ->
+ setBaseStr s
+ updateCells s suffix
+ )
+ ]
+ ]
+ Bulma.field.div [
+ Bulma.control.div [
+ Html.label [
+ prop.className "is-flex is-align-items-center checkbox"
+ prop.style [style.gap (length.rem 0.5)]
+ prop.children [
+ Html.input [
+ prop.type' "checkbox"
+ prop.isChecked suffix
+ prop.onChange(fun e ->
+ setSuffix e
+ updateCells baseStr e
+ )
+ ]
+ Bulma.help "Add number suffix"
+ ]
+ ]
+ ]
+ ]
+ ]
+
+ []
+ static member private UpdateForm(cellValues: string [], setPreview, regex: string, setRegex: string -> unit) =
+ let replacement, setReplacement = React.useState("")
+ let updateCells (replacement: string) (regex: string) =
+ if regex <> "" then
+ try
+ let regex = Regex(regex)
+ cellValues
+ |> Array.mapi (fun i c ->
+ let m = regex.Match(c)
+ match m.Success with
+ | true ->
+ let replaced = c.Replace(m.Value, replacement)
+ replaced
+ | false ->
+ c
+ )
+ |> setPreview
+ with
+ | _ -> ()
+ else
+ ()
+ Bulma.field.div [
+ Bulma.field.div [
+ Html.div [
+ prop.className "is-flex is-flex-direction-row"
+ prop.style [style.gap (length.rem 1)]
+ prop.children [
+ Bulma.control.div [
+ prop.style [style.flexGrow 1]
+ prop.children [
+ Bulma.label "Regex"
+ Bulma.input.text [
+ prop.autoFocus true
+ prop.valueOrDefault regex
+ prop.onChange (fun s ->
+ setRegex s;
+ updateCells replacement s
+ )
+ ]
+ ]
+ ]
+ Bulma.control.div [
+ prop.style [style.flexGrow 1]
+ prop.children [
+ Bulma.label "Replacement"
+ Bulma.input.text [
+ prop.valueOrDefault replacement
+ prop.onChange (fun s ->
+ setReplacement s;
+ updateCells s regex
+ )
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+
+ []
+ static member Main(index: int, column: CompositeColumn, dispatch) (rmv: _ -> unit) =
+ let getCellStrings() = column.Cells |> Array.map (fun c -> c.ToString())
+ let preview, setPreview = React.useState(getCellStrings)
+ let initPage = if preview.Length = 0 || preview |> String.concat "" = "" then FunctionPage.Create else FunctionPage.Update
+ let currentPage, setPage = React.useState(initPage)
+ /// This state is only used for update logic
+ let regex, setRegex = React.useState("")
+ let setPage = fun p ->
+ if p <> FunctionPage.Update then
+ setRegex ""
+ setPage p
+ let submit = fun () ->
+ preview
+ |> Array.map (fun x -> CompositeCell.FreeText x)
+ |> fun x -> CompositeColumn.create(column.Header, x)
+ |> fun x -> Spreadsheet.SetColumn (index, x)
+ |> SpreadsheetMsg
+ |> dispatch
+ Bulma.modal [
+ Bulma.modal.isActive
+ prop.children [
+ Bulma.modalBackground [ prop.onClick rmv ]
+ Bulma.modalCard [
+ prop.style [style.maxHeight(length.percent 70); style.overflowY.hidden]
+ prop.children [
+ Bulma.modalCardHead [
+ Bulma.modalCardTitle "Update Column"
+ Bulma.delete [ prop.onClick rmv ]
+ ]
+ Bulma.modalCardBody [
+ Components.TabNavigation(currentPage, setPage)
+ match currentPage with
+ | FunctionPage.Create -> UpdateColumn.CreateForm(getCellStrings(), setPreview)
+ | FunctionPage.Update -> UpdateColumn.UpdateForm(getCellStrings(), setPreview, regex, setRegex)
+ Components.PreviewTable(column, preview, regex)
+ ]
+ Bulma.modalCardFoot [
+ Bulma.button.button [
+ color.isInfo
+ prop.style [style.marginLeft length.auto]
+ prop.text "Submit"
+ prop.onClick(fun e ->
+ submit()
+ rmv e
+ )
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
diff --git a/src/Client/Model.fs b/src/Client/Model.fs
index 12273014..4d8c6327 100644
--- a/src/Client/Model.fs
+++ b/src/Client/Model.fs
@@ -4,7 +4,6 @@ open Fable.React
open Fable.React.Props
open Shared
open TermTypes
-open TemplateTypes
open Thoth.Elmish
open Routing
@@ -89,7 +88,7 @@ type LogItem =
module TermSearch =
- open ARCtrl.ISA
+ open ARCtrl
type Model = {
SelectedTerm : OntologyAnnotation option
@@ -143,39 +142,17 @@ type PersistentStorageState = {
SearchableOntologies : (Set*Ontology) []
AppVersion : string
Host : Swatehost option
+ ShowSideBar : bool
HasOntologiesLoaded : bool
} with
static member init () = {
SearchableOntologies = [||]
Host = None
AppVersion = ""
+ ShowSideBar = false
HasOntologiesLoaded = false
}
-type ApiCallStatus =
- | IsNone
- | Pending
- | Successfull
- | Failed of string
-
-type ApiCallHistoryItem = {
- FunctionName : string
- Status : ApiCallStatus
-}
-
-type ApiState = {
- currentCall : ApiCallHistoryItem
- callHistory : ApiCallHistoryItem list
-} with
- static member init() = {
- currentCall = ApiState.noCall
- callHistory = []
- }
- static member noCall = {
- FunctionName = "None"
- Status = IsNone
- }
-
type PageState = {
CurrentPage : Routing.Route
IsExpert : bool
@@ -199,7 +176,7 @@ open Fable.Core
module BuildingBlock =
- open ARCtrl.ISA
+ open ARCtrl
type [] HeaderCellType =
| Component
@@ -280,14 +257,6 @@ module BuildingBlock =
BodyCellType : BodyCellType
BodyArg : U2 option
- // Below everything is more or less deprecated
- // This section is used to add a unit directly to an already existing building block
- Unit2TermSearchText : string
- Unit2SelectedTerm : Term option
- Unit2TermSuggestions : Term []
- HasUnit2TermSuggestionsLoading : bool
- ShowUnit2TermSuggestions : bool
-
} with
static member init () = {
@@ -295,14 +264,6 @@ module BuildingBlock =
HeaderArg = None
BodyCellType = BodyCellType.Term
BodyArg = None
-
- // Below everything is more or less deprecated
- // This section is used to add a unit directly to an already existing building block
- Unit2TermSearchText = ""
- Unit2SelectedTerm = None
- Unit2TermSuggestions = [||]
- ShowUnit2TermSuggestions = false
- HasUnit2TermSuggestionsLoading = false
}
member this.TryHeaderOA() =
@@ -325,46 +286,47 @@ module BuildingBlock =
| Some (U2.Case1 s) -> Some s
| _ -> None
-/// Validation scheme for Table
-module Validation =
- type Model = {
- ActiveTableBuildingBlocks : BuildingBlock []
- TableValidationScheme : OfficeInterop.CustomXmlTypes.Validation.TableValidation
- // Client view related
- DisplayedOptionsId : int option
- } with
- static member init () = {
- ActiveTableBuildingBlocks = [||]
- TableValidationScheme = OfficeInterop.CustomXmlTypes.Validation.TableValidation.init()
- DisplayedOptionsId = None
- }
-
module Protocol =
[]
- type CuratedCommunityFilter =
- | Both
+ type CommunityFilter =
+ | All
| OnlyCurated
- | OnlyCommunity
+ | Community of string
+
+ member this.ToStringRdb() =
+ match this with
+ | All -> "All"
+ | OnlyCurated -> "DataPLANT official"
+ | Community name -> name
+
+ static member fromString(str:string) =
+ match str with
+ | "All" -> All
+ | "DataPLANT official" -> OnlyCurated
+ | anyElse -> Community anyElse
+
+ static member CommunityFromOrganisation(org: ARCtrl.Organisation) =
+ match org with
+ | ARCtrl.Organisation.DataPLANT -> None
+ | ARCtrl.Organisation.Other name -> Some <| Community name
/// This model is used for both protocol insert and protocol search
type Model = {
// Client
Loading : bool
- // // ------ Process from file ------
- UploadedFileParsed : (string*InsertBuildingBlock []) []
+ LastUpdated : System.DateTime option
// ------ Protocol from Database ------
- ProtocolSelected : ARCtrl.Template.Template option
- ProtocolsAll : ARCtrl.Template.Template []
+ TemplateSelected : ARCtrl.Template option
+ Templates : ARCtrl.Template []
} with
static member init () = {
// Client
Loading = false
- ProtocolSelected = None
- // // ------ Process from file ------
- UploadedFileParsed = [||]
+ LastUpdated = None
+ TemplateSelected = None
// ------ Protocol from Database ------
- ProtocolsAll = [||]
+ Templates = [||]
}
type RequestBuildingBlockInfoStates =
@@ -386,38 +348,4 @@ type BuildingBlockDetailsState = {
BuildingBlockValues = [||]
}
-module SettingsXml =
- type Model = {
- // // Client // //
- // Validation xml
- ActiveSwateValidation : obj option //OfficeInterop.Types.Xml.ValidationTypes.TableValidation option
- NextAnnotationTableForActiveValidation : string option
- // Protocol group xml
- ActiveProtocolGroup : obj option //OfficeInterop.Types.Xml.GroupTypes.ProtocolGroup option
- NextAnnotationTableForActiveProtGroup : string option
- // Protocol
- ActiveProtocol : obj option //OfficeInterop.Types.Xml.GroupTypes.Protocol option
- NextAnnotationTableForActiveProtocol : string option
- //
- RawXml : string option
- NextRawXml : string option
- FoundTables : string []
- ValidationXmls : obj [] //OfficeInterop.Types.Xml.ValidationTypes.TableValidation []
- } with
- static member init () = {
- // Client
- ActiveSwateValidation = None
- NextAnnotationTableForActiveValidation = None
- ActiveProtocolGroup = None
- NextAnnotationTableForActiveProtGroup = None
- ActiveProtocol = None
- // Unused
- NextAnnotationTableForActiveProtocol = None
- //
- RawXml = None
- NextRawXml = None
- FoundTables = [||]
- ValidationXmls = [||]
- }
-
// The main MODEL was shifted to 'Messages.fs' to allow saving 'Msg'
diff --git a/src/Client/OfficeInterop/Functions/TemplateMetadataFunctions.fs b/src/Client/OfficeInterop/Functions/TemplateMetadataFunctions.fs
deleted file mode 100644
index b1f0c431..00000000
--- a/src/Client/OfficeInterop/Functions/TemplateMetadataFunctions.fs
+++ /dev/null
@@ -1,130 +0,0 @@
-module OfficeInterop.TemplateMetadataFunctions
-
-open System
-
-open Fable.Core
-open ExcelJS.Fable
-open Excel
-open GlobalBindings
-
-open Shared.OfficeInteropTypes
-open Shared.TemplateTypes.Metadata
-
-let private colorOuterBordersWhite (borderSeq:seq) =
- borderSeq
- |> Seq.iter (fun border ->
- if border.sideIndex = U2.Case1 BorderIndex.EdgeBottom || border.sideIndex = U2.Case1 BorderIndex.EdgeLeft || border.sideIndex = U2.Case1 BorderIndex.EdgeRight || border.sideIndex = U2.Case1 BorderIndex.EdgeTop then
- border.color <- NFDIColors.white
- )
-
-let private colorTopBottomBordersWhite (borderSeq:seq) =
- borderSeq
- |> Seq.iter (fun border ->
- if border.sideIndex = U2.Case1 BorderIndex.EdgeBottom || border.sideIndex = U2.Case1 BorderIndex.EdgeTop then
- border.color <- NFDIColors.white
- )
-
-let rec extendMetadataFields (metadatafields:MetadataField) =
- let children = metadatafields.Children |> List.collect extendMetadataFields
- if metadatafields.Key <> "" && metadatafields.Children.IsEmpty |> not && metadatafields.List then
- let metadatafields' = {metadatafields with ExtendedNameKey = $"#{metadatafields.ExtendedNameKey.ToUpper()} list"}
- metadatafields'::children
- elif metadatafields.Key <> "" && metadatafields.Children.IsEmpty |> not then
- let metadatafields' = {metadatafields with ExtendedNameKey = "#" + metadatafields.ExtendedNameKey.ToUpper()}
- metadatafields'::children
- elif metadatafields.Key <> "" then
- metadatafields::children
- else
- children
-
-let createTemplateMetadataWorksheet (metadatafields:MetadataField) =
- Excel.run (fun context ->
- promise {
-
- let extended = extendMetadataFields metadatafields |> Array.ofList
-
- let rowLength = float extended.Length
-
- let! newWorksheet = context.sync().``then``(fun e->
- context.workbook.worksheets.add TemplateMetadataWorksheetName
- )
-
- let! firstColumn, fstColumnCells, sndColumn, sndColumnCells = context.sync().``then``(fun e ->
- let fst = newWorksheet.getRangeByIndexes(0.,0.,rowLength,1.)
- let _ = fst.format.borders.load(propertyNames=U2.Case1 "items")
- let fstCells = [|
- for i in 0. .. rowLength-1. do
- let cell = fst.getCell (i,0.)
- let _ = cell.format.borders.load(propertyNames=U2.Case1 "items")
- yield cell
- |]
- let sndCells = [|
- for i in 0. .. rowLength-1. do
- let cell = fst.getCell (i,1.)
- let _ = cell.format.borders.load(propertyNames=U2.Case1 "items")
- yield cell
- |]
- let snd = newWorksheet.getRangeByIndexes(0.,1.,rowLength,1.)
- fst, fstCells, snd, sndCells
- )
-
- let newIdent = System.Guid.NewGuid()
- let idValueIndex = extended |> Array.findIndex (fun x -> x.Key = RowKeys.TemplateIdKey )
- let descriptionValueIndex = extended |> Array.findIndex (fun x -> x.Key = RowKeys.DescriptionKey )
- let columnValues =
- ResizeArray [|
- for i in 0 .. int rowLength - 1 do
- yield ResizeArray [|Some <| box (extended.[i].ExtendedNameKey)|]
- |]
- let! update = context.sync().``then``(fun e ->
- firstColumn.values <- columnValues
- sndColumnCells.[idValueIndex].values <- ResizeArray [|ResizeArray [| newIdent |> box |> Some|]|]
- firstColumn.format.autofitColumns()
- firstColumn.format.autofitRows()
- firstColumn.format.font.bold <- true
- firstColumn.format.font.color <- "whitesmoke"
- firstColumn.format.borders.items |> colorOuterBordersWhite
- firstColumn.format.borders.items |> Seq.iter (fun border -> if border.sideIndex = U2.Case1 BorderIndex.EdgeRight then border.weight <- U2.Case1 BorderWeight.Thick)
- sndColumnCells |> Array.iter (fun cell -> cell.format.borders.items |> colorOuterBordersWhite )
- firstColumn.format.verticalAlignment <- U2.Case1 VerticalAlignment.Top
- sndColumn.format.verticalAlignment <- U2.Case1 VerticalAlignment.Top
- let sndColStyling =
- extended
- |> Array.iteri (fun i info ->
- if info.Children.IsEmpty then
- fstColumnCells.[i].format.fill.color <- ExcelColors.Excel.Primary
- sndColumnCells.[i].format.fill.color <- ExcelColors.Excel.Tint40
- else
- fstColumnCells.[i].format.fill.color <- ExcelColors.Excel.Shade10
- sndColumnCells.[i].format.borders.items |> colorTopBottomBordersWhite
- sndColumnCells.[i].format.fill.color <- ExcelColors.Excel.Shade10
- )
- //sndColumn.format.fill.color <- ExcelColors.Excel.Tint40
- sndColumnCells.[idValueIndex].format.fill.color <- NFDIColors.Red.Base
- let newComments =
- extended
- |> Array.iteri (fun i info ->
- if info.Description.IsSome && info.Description.Value <> "" then
- let targetCellRange : U2 = U2.Case1 fstColumnCells.[i]
- let content : U2 = U2.Case2 info.Description.Value
- // WARNING!
- // If you use "let comment = ..." outside of this if-else case ONLY the comment with reply will be added
- if i = idValueIndex then
- let comment = context.workbook.comments.add(targetCellRange, content, contentType = ContentType.Plain)
- let reply : U2 = U2.Case2 $"id={newIdent.ToString()}"
- let _ = comment.replies.add(reply, contentType = ContentType.Plain)
- ()
- else
- let comment = context.workbook.comments.add(targetCellRange, content, contentType = ContentType.Plain)
- ()
- else
- ()
- )
- sndColumn.format.columnWidth <- 300.
- sndColumnCells.[descriptionValueIndex].format.rowHeight <- 50.
- sndColumn.format.wrapText <- true
- newWorksheet.activate()
- )
- return "Info", "Created new template metadata sheet!"
- }
- )
\ No newline at end of file
diff --git a/src/Client/Pages/BuildingBlock/BuildingBlockView.fs b/src/Client/Pages/BuildingBlock/BuildingBlockView.fs
index 2b35fe26..2f47098f 100644
--- a/src/Client/Pages/BuildingBlock/BuildingBlockView.fs
+++ b/src/Client/Pages/BuildingBlock/BuildingBlockView.fs
@@ -38,301 +38,53 @@ let update (addBuildingBlockMsg:BuildingBlock.Msg) (state: BuildingBlock.Model)
BodyArg = None
}
nextState, Cmd.none
- | UpdateBodyCellType next ->
- let nextState = { state with BodyCellType = next }
- nextState, Cmd.none
-
- | SearchUnitTermTextChange (newTerm) ->
-
- let triggerNewSearch =
- newTerm.Length > 2
-
- let (delay, bounceId, msgToBounce) =
- (System.TimeSpan.FromSeconds 0.5),
- "GetNewUnitTermSuggestions",
- (
- if triggerNewSearch then
- (newTerm) |> (GetNewUnitTermSuggestions >> Request >> Api)
- else
- DoNothing
- )
-
- let nextState = {
- state with
- Unit2TermSearchText = newTerm
- Unit2SelectedTerm = None
- ShowUnit2TermSuggestions = triggerNewSearch
- HasUnit2TermSuggestionsLoading = true
- }
-
- nextState, ((delay, bounceId, msgToBounce) |> Bounce |> Cmd.ofMsg)
-
- | NewUnitTermSuggestions suggestions ->
-
+ | UpdateHeaderWithIO (hct, iotype) ->
let nextState = {
- state with
- Unit2TermSuggestions = suggestions
- ShowUnit2TermSuggestions = true
- HasUnit2TermSuggestionsLoading = false
- }
-
- nextState,Cmd.none
-
- | UnitTermSuggestionUsed suggestion ->
- let nextState ={
- state with
- Unit2TermSearchText = suggestion.Name
- Unit2SelectedTerm = Some suggestion
- ShowUnit2TermSuggestions = false
- HasUnit2TermSuggestionsLoading = false
+ state with
+ HeaderCellType = hct
+ HeaderArg = Some (Fable.Core.U2.Case2 iotype)
+ BodyArg = None
+ BodyCellType = BuildingBlock.BodyCellType.Text
}
nextState, Cmd.none
-
-//let addBuildingBlockFooterComponent (model:Model) (dispatch:Messages.Msg -> unit) =
-// Content.content [ ] [
-// Label.label [Label.Props [Style [Color model.SiteStyleState.ColorMode.Accent]]] [
-// str (sprintf "More about %s:" (model.AddBuildingBlockState.CurrentBuildingBlock.Type.toString ))
-// ]
-// Text.p [Props [Style [TextAlign TextAlignOptions.Justify]]] [
-// span [] [model.AddBuildingBlockState.CurrentBuildingBlock.Type.toLongExplanation |> str]
-// span [] [str " You can find more information on our "]
-// a [Href Shared.URLs.AnnotationPrinciplesUrl; Target "_blank"] [str "website"]
-// span [] [str "."]
-// ]
-// ]
+ | UpdateBodyCellType next ->
+ let nextState = { state with BodyCellType = next }
+ nextState, Cmd.none
open SidebarComponents
+open Feliz
+open Feliz.Bulma
-//let addBuildingBlockElements (model:Model) (dispatch:Messages.Msg -> unit) =
-// let autocompleteParamsTerm = AutocompleteSearch.AutocompleteParameters.ofAddBuildingBlockState model.AddBuildingBlockState
-// let autocompleteParamsUnit = AutocompleteSearch.AutocompleteParameters.ofAddBuildingBlockUnitState model.AddBuildingBlockState
-
+//let addUnitToExistingBlockElements (model:Model) (dispatch:Messages.Msg -> unit) =
// mainFunctionContainer [
-// AdvancedSearch.advancedSearchModal model autocompleteParamsTerm.ModalId autocompleteParamsTerm.InputId dispatch autocompleteParamsTerm.OnAdvancedSearch
-// AdvancedSearch.advancedSearchModal model autocompleteParamsUnit.ModalId autocompleteParamsUnit.InputId dispatch autocompleteParamsUnit.OnAdvancedSearch
-// Field.div [] [
-// let autocompleteParams = (AutocompleteSearch.AutocompleteParameters.ofAddBuildingBlockState model.AddBuildingBlockState)
-// Field.div [Field.HasAddons] [
-// Choose building block type dropdown element
-// Control.p [] [
-// Dropdown.dropdown [
-// Dropdown.IsActive model.AddBuildingBlockState.ShowBuildingBlockSelection
-// ] [
-// Dropdown.trigger [] [
-// Button.a [Button.OnClick (fun e -> e.stopPropagation(); ToggleSelectionDropdown |> BuildingBlockMsg |> dispatch)] [
-// span [Style [MarginRight "5px"]] [str model.AddBuildingBlockState.CurrentBuildingBlock.Type.toString]
-// Fa.i [Fa.Solid.AngleDown] []
-// ]
-// ]
-// Dropdown.menu [ ] [
-// match model.AddBuildingBlockState.DropdownPage with
-// | Model.BuildingBlock.DropdownPage.Main ->
-// Helper.DropdownElements.dropdownContentMain model dispatch
-// | Model.BuildingBlock.DropdownPage.ProtocolTypes ->
-// Helper.DropdownElements.dropdownContentProtocolTypeColumns model dispatch
-// | Model.BuildingBlock.DropdownPage.Output ->
-// Helper.DropdownElements.dropdownContentOutputColumns model dispatch
-// |> fun content -> Dropdown.content [Props [Style [yield! colorControlInArray model.SiteStyleState.ColorMode]] ] content
-// ]
-// ]
-// ]
-// Ontology Term search field
-// if model.AddBuildingBlockState.CurrentBuildingBlock.Type.isTermColumn && model.AddBuildingBlockState.CurrentBuildingBlock.Type.isFeaturedColumn |> not then
-// AutocompleteSearch.autocompleteTermSearchComponentInputComponent
-// dispatch
-// false // isDisabled
-// "Start typing to search"
-// None // No input size specified
-// autocompleteParams
-
-// ]
-// Ontology Term search preview
-// AutocompleteSearch.autocompleteDropdownComponent
-// dispatch
-// model.SiteStyleState.ColorMode
-// autocompleteParams.DropDownIsVisible
-// autocompleteParams.DropDownIsLoading
-// (AutocompleteSearch.createAutocompleteSuggestions dispatch autocompleteParams model)
-// ]
-// Ontology Unit Term search field
-// if model.AddBuildingBlockState.CurrentBuildingBlock.Type.isTermColumn && model.AddBuildingBlockState.CurrentBuildingBlock.Type.isFeaturedColumn |> not then
-// let unitAutoCompleteParams = AutocompleteSearch.AutocompleteParameters.ofAddBuildingBlockUnitState model.AddBuildingBlockState
-// Field.div [] [
-// Field.div [Field.HasAddons] [
-// Control.p [] [
-// Button.a [
-// Button.Props [Style [
-// if model.AddBuildingBlockState.BuildingBlockHasUnit then Color NFDIColors.Mint.Base else Color NFDIColors.Red.Base
-// ]]
-// Button.OnClick (fun _ ->
-// let inputId = (AutocompleteSearch.AutocompleteParameters.ofAddBuildingBlockUnitState model.AddBuildingBlockState).InputId
-// if model.AddBuildingBlockState.BuildingBlockHasUnit = true then
-// let e = Browser.Dom.document.getElementById inputId
-// e?value <- null
-// ToggleBuildingBlockHasUnit |> BuildingBlockMsg |> dispatch
-// )
-// ] [
-// Fa.i [
-// Fa.Size Fa.FaLarge;
-// Fa.Props [Style [AlignSelf AlignSelfOptions.Center; Transform "translateY(1px)"]]
-// if model.AddBuildingBlockState.BuildingBlockHasUnit then
-// Fa.Solid.Check
-// else
-// Fa.Solid.Ban
-// ] [ ]
-// ]
-// ]
-// Control.p [] [
-// Button.button [Button.IsStatic true; Button.Props [Style [BackgroundColor ExcelColors.Colorfull.white]]] [
-// str (sprintf "This %s has a unit:" (model.AddBuildingBlockState.CurrentBuildingBlock.Type.toString))
-// ]
-// ]
-// AutocompleteSearch.autocompleteTermSearchComponentInputComponent
-// dispatch
-// if BuildingBlockHasUnit = false then disabled = true
-// (model.AddBuildingBlockState.BuildingBlockHasUnit |> not)
-// "Start typing to search"
-// None // No input size specified
-// unitAutoCompleteParams
-// ]
-// Ontology Unit Term search preview
-// AutocompleteSearch.autocompleteDropdownComponent
-// dispatch
-// model.SiteStyleState.ColorMode
-// unitAutoCompleteParams.DropDownIsVisible
-// unitAutoCompleteParams.DropDownIsLoading
-// (AutocompleteSearch.createAutocompleteSuggestions dispatch unitAutoCompleteParams model)
-// ]
-
-// div [] [
-// Help.help [Help.Props [Style [Display DisplayOptions.Inline]]] [
-// a [OnClick (fun _ -> AdvancedSearch.ToggleModal (AutocompleteSearch.AutocompleteParameters.ofAddBuildingBlockState model.AddBuildingBlockState).ModalId |> AdvancedSearchMsg |> dispatch)] [
-// str "Use advanced search building block"
-// ]
-// ]
-// if model.AddBuildingBlockState.CurrentBuildingBlock.Type.isTermColumn then
-// Help.help [Help.Props [Style [Display DisplayOptions.Inline; Float FloatOptions.Right]]] [
-// a [OnClick (fun _ -> AdvancedSearch.ToggleModal (AutocompleteSearch.AutocompleteParameters.ofAddBuildingBlockUnitState model.AddBuildingBlockState).ModalId |> AdvancedSearchMsg |> dispatch)] [
-// str "Use advanced search unit"
-// ]
-// ]
-// ]
-
-// Field.div [] [
-// Button.button [
-// let isValid = model.AddBuildingBlockState.CurrentBuildingBlock |> Helper.isValidBuildingBlock
+// Bulma.field.div [
+// Bulma.button.button [
+
+// let isValid = model.AddBuildingBlockState.Unit2TermSearchText <> ""
+// Bulma.color.isSuccess
// if isValid then
-// Button.Color Color.IsSuccess
-// Button.IsActive true
+// Bulma.button.isActive
// else
-// Button.Color Color.IsDanger
-// Button.Props [Disabled true]
-// Button.IsFullWidth
-// Button.OnClick (fun e ->
-// let colName = model.AddBuildingBlockState.CurrentBuildingBlock
-// let colTerm =
-// if colName.isFeaturedColumn then
-// TermMinimal.create colName.Type.toString colName.Type.getFeaturedColumnAccession |> Some
-// elif model.AddBuildingBlockState.BuildingBlockSelectedTerm.IsSome && not colName.isSingleColumn then
-// TermMinimal.ofTerm model.AddBuildingBlockState.BuildingBlockSelectedTerm.Value |> Some
-// else
-// None
-// let unitTerm = if model.AddBuildingBlockState.UnitSelectedTerm.IsSome && colName.isTermColumn && not colName.isFeaturedColumn then TermMinimal.ofTerm model.AddBuildingBlockState.UnitSelectedTerm.Value |> Some else None
-// let newBuildingBlock = InsertBuildingBlock.create colName colTerm unitTerm Array.empty
-// SpreadsheetInterface.AddAnnotationBlock newBuildingBlock |> InterfaceMsg |> dispatch
+// Bulma.color.isDanger
+// prop.disabled true
+// Bulma.button.isFullWidth
+// prop.onClick (fun _ ->
+// let unitTerm =
+// if model.AddBuildingBlockState.Unit2SelectedTerm.IsSome then Some <| TermMinimal.ofTerm model.AddBuildingBlockState.Unit2SelectedTerm.Value else None
+// match model.AddBuildingBlockState.Unit2TermSearchText with
+// | "" ->
+// curry GenericLog Cmd.none ("Error", "Cannot execute function with empty unit input") |> DevMsg |> dispatch
+// | hasUnitTerm when model.AddBuildingBlockState.Unit2SelectedTerm.IsSome ->
+// OfficeInterop.UpdateUnitForCells unitTerm.Value |> OfficeInteropMsg |> dispatch
+// | freeText ->
+// OfficeInterop.UpdateUnitForCells (TermMinimal.create model.AddBuildingBlockState.Unit2TermSearchText "") |> OfficeInteropMsg |> dispatch
// )
-// ] [
-// str "Add building block"
+// prop.text "Update unit for cells"
// ]
// ]
// ]
-open Feliz
-open Feliz.Bulma
-
-let addUnitToExistingBlockElements (model:Model) (dispatch:Messages.Msg -> unit) =
- // /// advanced unit term search 2
- //let autocompleteParamsUnit2 = AutocompleteSearch.AutocompleteParameters.ofAddBuildingBlockUnit2State model.AddBuildingBlockState
- mainFunctionContainer [
- // advanced unit term search 2
- //AdvancedSearch.advancedSearchModal model autocompleteParamsUnit2.ModalId autocompleteParamsUnit2.InputId dispatch autocompleteParamsUnit2.OnAdvancedSearch
- //Bulma.field.div [
- // Bulma.help [
- // b [] [str "Adds a unit to a complete building block." ]
- // str " If the building block already has a unit assigned, the new unit is only applied to selected rows of the selected column."
- // ]
- //]
- //Bulma.field.div [
- // let changeUnitAutoCompleteParams = AutocompleteSearch.AutocompleteParameters.ofAddBuildingBlockUnit2State model.AddBuildingBlockState
- // Bulma.field.div [
- // Bulma.field.hasAddons
- // prop.children [
- // Bulma.control.p [
- // Bulma.button.button [
- // Bulma.button.isStatic
- // Bulma.color.hasBackgroundWhite
- // prop.text "Add unit"
- // ]
- // ]
- // // Add/Update unit ontology term search field
- // AutocompleteSearch.autocompleteTermSearchComponentInputComponent
- // dispatch
- // false // isDisabled
- // "Start typing to search"
- // None // No input size specified
- // changeUnitAutoCompleteParams
- // ]
- // ]
- // // Add/Update Ontology Unit Term search preview
- // AutocompleteSearch.autocompleteDropdownComponent
- // dispatch
- // changeUnitAutoCompleteParams.DropDownIsVisible
- // changeUnitAutoCompleteParams.DropDownIsLoading
- // (AutocompleteSearch.createAutocompleteSuggestions dispatch changeUnitAutoCompleteParams model)
-
- //]
- //Bulma.help [
- // prop.style [style.display.inlineElement]
- // prop.children [
- // Html.a [
- // prop.onClick(fun e ->
- // e.preventDefault()
- // AdvancedSearch.ToggleModal (
- // AutocompleteSearch.AutocompleteParameters.ofAddBuildingBlockUnit2State model.AddBuildingBlockState).ModalId
- // |> AdvancedSearchMsg
- // |> dispatch
- // )
- // prop.text "Use advanced search"
- // ]
- // ]
- //]
- Bulma.field.div [
- Bulma.button.button [
-
- let isValid = model.AddBuildingBlockState.Unit2TermSearchText <> ""
- Bulma.color.isSuccess
- if isValid then
- Bulma.button.isActive
- else
- Bulma.color.isDanger
- prop.disabled true
- Bulma.button.isFullWidth
- prop.onClick (fun _ ->
- let unitTerm =
- if model.AddBuildingBlockState.Unit2SelectedTerm.IsSome then Some <| TermMinimal.ofTerm model.AddBuildingBlockState.Unit2SelectedTerm.Value else None
- match model.AddBuildingBlockState.Unit2TermSearchText with
- | "" ->
- curry GenericLog Cmd.none ("Error", "Cannot execute function with empty unit input") |> DevMsg |> dispatch
- | hasUnitTerm when model.AddBuildingBlockState.Unit2SelectedTerm.IsSome ->
- OfficeInterop.UpdateUnitForCells unitTerm.Value |> OfficeInteropMsg |> dispatch
- | freeText ->
- OfficeInterop.UpdateUnitForCells (TermMinimal.create model.AddBuildingBlockState.Unit2TermSearchText "") |> OfficeInteropMsg |> dispatch
- )
- prop.text "Update unit for cells"
- ]
- ]
- ]
let addBuildingBlockComponent (model:Model) (dispatch:Messages.Msg -> unit) =
div [
@@ -344,17 +96,14 @@ let addBuildingBlockComponent (model:Model) (dispatch:Messages.Msg -> unit) =
// Input forms, etc related to add building block.
Bulma.label "Add annotation building blocks (columns) to the annotation table."
- //match model.PersistentStorageState.Host with
- //| Swatehost.Excel _ ->
- // addBuildingBlockElements model dispatch
- //| _ ->
- // ()
- SearchComponent.Main model dispatch
+ mainFunctionContainer [
+ SearchComponent.Main model dispatch
+ ]
- match model.PersistentStorageState.Host with
- | Some Swatehost.Excel ->
- Bulma.label "Add/Update unit reference to existing building block."
- // Input forms, etc related to add unit to existing building block.
- addUnitToExistingBlockElements model dispatch
- | _ -> Html.none
+ //match model.PersistentStorageState.Host with
+ //| Some Swatehost.Excel ->
+ // Bulma.label "Add/Update unit reference to existing building block."
+ // // Input forms, etc related to add unit to existing building block.
+ // addUnitToExistingBlockElements model dispatch
+ //| _ -> Html.none
]
\ No newline at end of file
diff --git a/src/Client/Pages/BuildingBlock/Dropdown.fs b/src/Client/Pages/BuildingBlock/Dropdown.fs
index f2bd479c..7810d325 100644
--- a/src/Client/Pages/BuildingBlock/Dropdown.fs
+++ b/src/Client/Pages/BuildingBlock/Dropdown.fs
@@ -11,7 +11,7 @@ open Model.BuildingBlock
open Model.TermSearch
open Model
open Messages
-open ARCtrl.ISA
+open ARCtrl
open BuildingBlock.Helper
open Fable.Core
@@ -97,23 +97,21 @@ module private DropdownElements =
let createIOTypeDropdownItem (model: Model) dispatch setUiState (headerType: BuildingBlock.HeaderCellType) (iotype: IOType) =
let setIO (ioType) =
- Helper.selectHeaderCellType headerType setUiState dispatch
- U2.Case2 ioType |> Some |> BuildingBlock.UpdateHeaderArg |> BuildingBlockMsg |> dispatch
+ { DropdownPage = DropdownPage.Main; DropdownIsActive = false } |> setUiState
+ (headerType,ioType) |> BuildingBlock.UpdateHeaderWithIO |> BuildingBlockMsg |> dispatch
Bulma.dropdownItem.a [
- prop.children [
- match iotype with
- | IOType.FreeText s ->
- let onSubmit = fun (v: string) ->
- let header = IOType.FreeText v
- setIO header
- FreeTextInputElement onSubmit
- | _ ->
- Html.div [
- prop.onClick (fun e -> e.stopPropagation(); setIO iotype)
- prop.onKeyDown(fun k -> if (int k.which) = 13 then setIO iotype)
- prop.text (iotype.ToString())
- ]
- ]
+ match iotype with
+ | IOType.FreeText s ->
+ let onSubmit = fun (v: string) ->
+ let header = IOType.FreeText v
+ setIO header
+ prop.children [FreeTextInputElement onSubmit]
+ | _ ->
+ prop.onClick (fun e -> e.stopPropagation(); setIO iotype)
+ prop.onKeyDown(fun k -> if (int k.which) = 13 then setIO iotype)
+ prop.children [
+ Html.div [prop.text (iotype.ToString())]
+ ]
]
/// Main column types subpage for dropdown
@@ -151,24 +149,10 @@ module private DropdownElements =
/// Output columns subpage for dropdown
let dropdownContentIOTypeColumns header state setState (model:Model) dispatch =
[
- // Heading
- //Bulma.dropdownItem.div [
- // prop.style [style.textAlign.center]
- // prop.children [
- // Html.h6 [
- // prop.className "subtitle"
- // prop.style [style.fontWeight.bold]
- // prop.text name
- // ]
- // ]
- //]
- //Bulma.dropdownDivider []
IOType.Source |> createIOTypeDropdownItem model dispatch setState header
IOType.Sample |> createIOTypeDropdownItem model dispatch setState header
IOType.Material |> createIOTypeDropdownItem model dispatch setState header
- IOType.RawDataFile |> createIOTypeDropdownItem model dispatch setState header
- IOType.DerivedDataFile |> createIOTypeDropdownItem model dispatch setState header
- IOType.ImageFile |> createIOTypeDropdownItem model dispatch setState header
+ IOType.Data |> createIOTypeDropdownItem model dispatch setState header
IOType.FreeText "" |> createIOTypeDropdownItem model dispatch setState header
// Navigation element back to main page
backToMainDropdownButton setState
diff --git a/src/Client/Pages/BuildingBlock/Helper.fs b/src/Client/Pages/BuildingBlock/Helper.fs
index b23d4f83..b56c058d 100644
--- a/src/Client/Pages/BuildingBlock/Helper.fs
+++ b/src/Client/Pages/BuildingBlock/Helper.fs
@@ -4,7 +4,7 @@ open Shared
open OfficeInteropTypes
open Model
open Messages
-open ARCtrl.ISA
+open ARCtrl
open Model.BuildingBlock
let isSameMajorHeaderCellType (hct1: BuildingBlock.HeaderCellType) (hct2: BuildingBlock.HeaderCellType) =
@@ -18,7 +18,7 @@ let selectHeaderCellType (hct: BuildingBlock.HeaderCellType) setUiState dispatch
open Fable.Core
let createCompositeHeaderFromState (state: BuildingBlock.Model) =
- let getOA() = state.TryHeaderOA() |> Option.defaultValue OntologyAnnotation.empty
+ let getOA() = state.TryHeaderOA() |> Option.defaultValue (OntologyAnnotation.empty())
let getIOType() = state.TryHeaderIO() |> Option.defaultValue (IOType.FreeText "")
match state.HeaderCellType with
| HeaderCellType.Component -> CompositeHeader.Component <| getOA()
diff --git a/src/Client/Pages/BuildingBlock/SearchComponent.fs b/src/Client/Pages/BuildingBlock/SearchComponent.fs
index d4f47d3f..7e1a193e 100644
--- a/src/Client/Pages/BuildingBlock/SearchComponent.fs
+++ b/src/Client/Pages/BuildingBlock/SearchComponent.fs
@@ -1,4 +1,4 @@
-module BuildingBlock.SearchComponent
+module BuildingBlock.SearchComponent
open Feliz
open Feliz.Bulma
@@ -11,7 +11,7 @@ open Model.BuildingBlock
open Model.TermSearch
open Model
open Messages
-open ARCtrl.ISA
+open ARCtrl
open BuildingBlock.Helper
let private termOrUnitizedSwitch (model:Messages.Model) dispatch =
@@ -38,17 +38,15 @@ let private termOrUnitizedSwitch (model:Messages.Model) dispatch =
]
]
-
open Fable.Core
-let private SearchBuildingBlockBodyElement (model: Messages.Model) dispatch =
- let id = "SearchBuildingBlockBodyElementID"
+[]
+let private SearchBuildingBlockBodyElement (model: Messages.Model, dispatch) =
let element = React.useElementRef()
- React.useEffectOnce(fun _ -> element.current <- Some <| Browser.Dom.document.getElementById(id))
- let width = element.current |> Option.map (fun ele -> length.px ele.clientWidth)
+
Bulma.field.div [
- prop.id id
- prop.style [ style.display.flex; style.justifyContent.spaceBetween ]
+ prop.ref element
+ prop.style [ style.display.flex; style.justifyContent.spaceBetween; style.position.relative ]
prop.children [
termOrUnitizedSwitch model dispatch
let setter (oaOpt: OntologyAnnotation option) =
@@ -56,18 +54,16 @@ let private SearchBuildingBlockBodyElement (model: Messages.Model) dispatch =
BuildingBlock.UpdateBodyArg case |> BuildingBlockMsg |> dispatch
let parent = model.AddBuildingBlockState.TryHeaderOA()
let input = model.AddBuildingBlockState.TryBodyOA()
- Components.TermSearch.Input(setter, dispatch, fullwidth=true, ?input=input, ?parent'=parent, displayParent=false, ?dropdownWidth=width, alignRight=true)
+ Components.TermSearch.Input(setter, fullwidth=true, ?input=input, ?parent=parent, displayParent=false, ?portalTermSelectArea=element.current, debounceSetter=1000)
]
]
-let private SearchBuildingBlockHeaderElement (ui: BuildingBlockUIState) setUi (model: Model) dispatch =
+[]
+let private SearchBuildingBlockHeaderElement (ui: BuildingBlockUIState, setUi, model: Model, dispatch) =
let state = model.AddBuildingBlockState
- let id = "SearchBuildingBlockHeaderElementID"
let element = React.useElementRef()
- React.useEffectOnce(fun _ -> element.current <- Some <| Browser.Dom.document.getElementById(id))
- let width = element.current |> Option.map (fun ele -> length.px ele.clientWidth)
Bulma.field.div [
- prop.id id
+ prop.ref element
Bulma.field.hasAddons
prop.style [style.position.relative]
// Choose building block type dropdown element
@@ -81,7 +77,7 @@ let private SearchBuildingBlockHeaderElement (ui: BuildingBlockUIState) setUi (m
BuildingBlock.UpdateHeaderArg case |> BuildingBlockMsg |> dispatch
//selectHeader ui setUi h |> dispatch
let input = model.AddBuildingBlockState.TryHeaderOA()
- Components.TermSearch.Input(setter, dispatch, ?input=input, isExpanded=true, fullwidth=true, ?dropdownWidth=width, alignRight=true)
+ Components.TermSearch.Input(setter, fullwidth=true, ?input=input, isExpanded=true, ?portalTermSelectArea=element.current, debounceSetter=1000)
elif state.HeaderCellType.HasIOType() then
Bulma.control.div [
Bulma.control.isExpanded
@@ -102,6 +98,28 @@ let private SearchBuildingBlockHeaderElement (ui: BuildingBlockUIState) setUi (m
]
]
+let private scrollIntoViewRetry (id: string) =
+ let rec loop (iteration: int) =
+ let headerelement = Browser.Dom.document.getElementById(id)
+ if isNull headerelement then
+ if iteration < 5 then
+ Fable.Core.JS.setTimeout (fun _ -> loop (iteration+1)) 100 |> ignore
+ else
+ ()
+ else
+ let rect = headerelement.getBoundingClientRect()
+ if rect.left >= 0 && ((rect.right <= Browser.Dom.window.innerWidth) || (rect.right <= Browser.Dom.document.documentElement.clientWidth)) then
+ ()
+ else
+ let config = createEmpty
+ config.behavior <- Browser.Types.ScrollBehavior.Smooth
+ config.block <- Browser.Types.ScrollAlignment.End
+ config.``inline`` <- Browser.Types.ScrollAlignment.End
+ //log headerelement
+ headerelement.scrollIntoView(config)
+ loop 0
+
+
let private addBuildingBlockButton (model: Model) dispatch =
let state = model.AddBuildingBlockState
Bulma.field.div [
@@ -111,14 +129,21 @@ let private addBuildingBlockButton (model: Model) dispatch =
let isValid = Helper.isValidColumn header
if isValid then
Bulma.color.isSuccess
- Bulma.button.isActive
else
Bulma.color.isDanger
prop.disabled true
Bulma.button.isFullWidth
prop.onClick (fun _ ->
- let column = CompositeColumn.create(header, [|if body.IsSome then body.Value|])
+ let bodyCells =
+ if body.IsSome then // create as many body cells as there are rows in the active table
+ Array.init (model.SpreadsheetModel.ActiveTable.RowCount) (fun _ -> body.Value)
+ else
+ Array.empty
+ let column = CompositeColumn.create(header, bodyCells)
+ let index = Spreadsheet.BuildingBlocks.Controller.SidebarControllerAux.getNextColumnIndex model.SpreadsheetModel
SpreadsheetInterface.AddAnnotationBlock column |> InterfaceMsg |> dispatch
+ let id = $"Header_{index}_Main"
+ scrollIntoViewRetry id
)
prop.text "Add Column"
]
@@ -129,11 +154,9 @@ let Main (model: Model) dispatch =
let state_bb, setState_bb = React.useState(BuildingBlockUIState.init)
//let state_searchHeader, setState_searchHeader = React.useState(TermSearchUIState.init)
//let state_searchBody, setState_searchBody = React.useState(TermSearchUIState.init)
- mainFunctionContainer [
- SearchBuildingBlockHeaderElement state_bb setState_bb model dispatch
+ Html.div [
+ SearchBuildingBlockHeaderElement (state_bb, setState_bb, model, dispatch)
if model.AddBuildingBlockState.HeaderCellType.IsTermColumn() then
- SearchBuildingBlockBodyElement model dispatch
- //AdvancedSearch.modal_container state_bb setState_bb model dispatch
- //AdvancedSearch.links_container model.AddBuildingBlockState.Header dispatch
+ SearchBuildingBlockBodyElement (model, dispatch)
addBuildingBlockButton model dispatch
]
diff --git a/src/Client/Pages/Dag/Dag.fs b/src/Client/Pages/Dag/Dag.fs
deleted file mode 100644
index 9ec7e525..00000000
--- a/src/Client/Pages/Dag/Dag.fs
+++ /dev/null
@@ -1,101 +0,0 @@
-[]
-module Dag.Core
-
-open Fable.React
-open Fable.React.Props
-open Fable.Core.JsInterop
-open Elmish
-
-open Shared
-
-open ExcelColors
-open Model
-open Messages
-
-open Dag
-
-let update (msg:Msg) (currentModel: Messages.Model) : Messages.Model * Cmd =
- match msg with
- | UpdateLoading loading ->
- let nextModel = {
- currentModel.DagModel with
- Loading = loading
- }
- currentModel.updateByDagModel nextModel, Cmd.none
- | ParseTablesOfficeInteropRequest ->
- let nextModel = {
- currentModel.DagModel with
- Loading = true
- }
- let cmd =
- Cmd.OfPromise.either
- OfficeInterop.Core.getBuildingBlocksAndSheets
- ()
- (ParseTablesDagServerRequest >> DagMsg)
- (curry GenericError (Dag.UpdateLoading false |> DagMsg |> Cmd.ofMsg) >> DevMsg)
- currentModel.updateByDagModel nextModel, cmd
- | ParseTablesDagServerRequest (worksheetBuildingBlocksTuple) ->
- let cmd =
- Cmd.OfAsync.either
- Api.dagApi.parseAnnotationTablesToDagHtml
- worksheetBuildingBlocksTuple
- (ParseTablesDagServerResponse >> DagMsg)
- (curry GenericError (Dag.UpdateLoading false |> DagMsg |> Cmd.ofMsg) >> DevMsg)
- currentModel, cmd
- //
- | ParseTablesDagServerResponse dagHtml ->
- let nextModel = {
- currentModel.DagModel with
- Loading = false
- DagHtml = Some dagHtml
- }
- currentModel.updateByDagModel nextModel, Cmd.none
-
-open Messages
-open Feliz
-open Feliz.Bulma
-
-let defaultMessageEle (model:Model) dispatch =
- mainFunctionContainer [
- Bulma.field.div [
- Bulma.help [
- str "A "
- b [] [str "D"]
- str "irected "
- b [] [str "A"]
- str "cyclic "
- b [] [str "G"]
- str "raph represents the chain of applied protocols to samples. Within are all intermediate products as well as protocols displayed."
- ]
- Bulma.help [
- str "This only works if your input and output columns have values."
- ]
- ]
-
- Bulma.field.div [
- Bulma.button.a [
- Bulma.button.isFullWidth
- Bulma.color.isInfo
- prop.onClick(fun _ -> SpreadsheetInterface.ParseTablesToDag |> InterfaceMsg |> dispatch)
- prop.text "Display dag"
- ]
- ]
-
- if model.DagModel.DagHtml.IsSome then
- Bulma.field.div [
- iframe [SrcDoc model.DagModel.DagHtml.Value; Style [Width "100%"; Height "400px"] ] []
- ]
- ]
-
-let mainElement (model:Messages.Model) dispatch =
- Bulma.content [
- prop.onSubmit (fun e -> e.preventDefault())
- prop.onKeyDown (fun k -> if (int k.which) = 13 then k.preventDefault())
- prop.children [
- pageHeader "Visualize Protocol Flow"
-
- Bulma.label "Display directed acyclic graph"
-
- defaultMessageEle model dispatch
- ]
- ]
\ No newline at end of file
diff --git a/src/Client/Pages/FilePicker/FilePickerView.fs b/src/Client/Pages/FilePicker/FilePickerView.fs
index e5b69535..9253afeb 100644
--- a/src/Client/Pages/FilePicker/FilePickerView.fs
+++ b/src/Client/Pages/FilePicker/FilePickerView.fs
@@ -30,42 +30,8 @@ let update (filePickerMsg:FilePicker.Msg) (currentState: FilePicker.Model) : Fil
}
nextState, Cmd.none
-/// This logic only works as soon as we can access electron. Will not work in Browser.
-module PathRerooting =
-
- open Fable.Core
- open Fable.Core.JsInterop
-
- let private normalizePath (path:string) =
- path.Replace('\\','/')
-
- let listOfSupportedDirectories = ["studies"; "assays"; "workflows"; "runs"]
-
- let private matchesSupportedDirectory (str:string) =
- listOfSupportedDirectories |> List.contains str
-
- /// Normalizes path and searches for 'listOfSupportedDirectories' (["studies"; "assays"; "workflows"; "runs"]) in path. reroots path to parent of supported directory if found
- /// else returns only file name.
- let rerootPath (path:string) =
- let sep = '/'
- let path = normalizePath path // shadow path variable to normalized
- let splitPath = path.Split(sep)
- let tryFindLevel = Array.tryFindIndexBack (fun x -> matchesSupportedDirectory x) splitPath
- match tryFindLevel with
- // if we cannot find any of `listOfSupportedDirectories` we just return the file name
- | None ->
- splitPath |> Array.last
- | Some levelIndex ->
- // If we find one of `listOfSupportedDirectories` we want to reroot relative to the folder containing the investigation file.
- // It is located one level higher than any of `listOfSupportedDirectories`
- let rootPath =
- Array.take levelIndex splitPath // one level higher so `levelIndex` instead of `(levelIndex + 1)`
- |> String.concat (string sep)
- let relativePath =
- path.Replace(rootPath + string sep, "")
- relativePath
-
-let uploadButton (model:Messages.Model) dispatch (inputId: string) =
+let uploadButton (model:Messages.Model) dispatch =
+ let inputId = "filePicker_OnFilePickerMainFunc"
Bulma.field.div [
Html.input [
prop.style [style.display.none]
@@ -87,15 +53,40 @@ let uploadButton (model:Messages.Model) dispatch (inputId: string) =
//picker?value <- null
)
]
- Bulma.button.button [
- Bulma.color.isInfo
- Bulma.button.isFullWidth
- prop.onClick(fun e ->
- let getUploadElement = Browser.Dom.document.getElementById inputId
- getUploadElement.click()
- )
- prop.text "Pick file names"
- ]
+ match model.PersistentStorageState.Host with
+ | Some (Swatehost.ARCitect) ->
+ Html.div [
+ prop.className "is-flex is-flex-direction-row"
+ prop.style [style.gap (length.rem 1)]
+ prop.children [
+ Bulma.button.button [
+ Bulma.color.isInfo
+ Bulma.button.isFullWidth
+ prop.onClick(fun e ->
+ ARCitect.RequestPaths false |> ARCitect.ARCitect.send
+ )
+ prop.text "Pick Files"
+ ]
+ Bulma.button.button [
+ Bulma.color.isInfo
+ Bulma.button.isFullWidth
+ prop.onClick(fun e ->
+ ARCitect.RequestPaths true |> ARCitect.ARCitect.send
+ )
+ prop.text "Pick Directories"
+ ]
+ ]
+ ]
+ | _ ->
+ Bulma.button.button [
+ Bulma.color.isInfo
+ Bulma.button.isFullWidth
+ prop.onClick(fun e ->
+ let getUploadElement = Browser.Dom.document.getElementById inputId
+ getUploadElement.click()
+ )
+ prop.text "Pick file names"
+ ]
]
let insertButton (model:Messages.Model) dispatch =
@@ -122,31 +113,31 @@ let sortButton icon msg =
let fileSortElements (model:Messages.Model) dispatch =
Bulma.field.div [
Bulma.buttons [
- Bulma.button.a [
- prop.title "Copy to Clipboard"
- prop.onClick(fun e ->
- CustomComponents.ResponsiveFA.triggerResponsiveReturnEle "clipboard_filepicker"
- let txt = model.FilePickerState.FileNames |> List.map snd |> String.concat System.Environment.NewLine
- let textArea = Browser.Dom.document.createElement "textarea"
- textArea?value <- txt
- textArea?style?top <- "0"
- textArea?style?left <- "0"
- textArea?style?position <- "fixed"
-
- Browser.Dom.document.body.appendChild textArea |> ignore
-
- textArea.focus()
- // Can't belive this actually worked
- textArea?select()
-
- let t = Browser.Dom.document.execCommand("copy")
- Browser.Dom.document.body.removeChild(textArea) |> ignore
- ()
- )
- prop.children [
- CustomComponents.ResponsiveFA.responsiveReturnEle "clipboard_filepicker" "fa-regular fa-clipboard" "fa-solid fa-check"
- ]
- ]
+ //Bulma.button.a [
+ // prop.title "Copy to Clipboard"
+ // prop.onClick(fun e ->
+ // CustomComponents.ResponsiveFA.triggerResponsiveReturnEle "clipboard_filepicker"
+ // let txt = model.FilePickerState.FileNames |> List.map snd |> String.concat System.Environment.NewLine
+ // let textArea = Browser.Dom.document.createElement "textarea"
+ // textArea?value <- txt
+ // textArea?style?top <- "0"
+ // textArea?style?left <- "0"
+ // textArea?style?position <- "fixed"
+
+ // Browser.Dom.document.body.appendChild textArea |> ignore
+
+ // textArea.focus()
+ // // Can't belive this actually worked
+ // textArea?select()
+
+ // let t = Browser.Dom.document.execCommand("copy")
+ // Browser.Dom.document.body.removeChild(textArea) |> ignore
+ // ()
+ // )
+ // prop.children [
+ // CustomComponents.ResponsiveFA.responsiveReturnEle "clipboard_filepicker" "fa-solid fa-copy" "fa-solid fa-check"
+ // ]
+ //]
Bulma.buttons [
Bulma.buttons.hasAddons
@@ -255,12 +246,10 @@ module FileNameTable =
]
-let fileContainer (model:Messages.Model) dispatch inputId=
+let fileContainer (model:Messages.Model) dispatch =
mainFunctionContainer [
- Bulma.help "Choose one or multiple files, rearrange them and add their names to the Excel sheet."
-
- uploadButton model dispatch inputId
+ uploadButton model dispatch
if model.FilePickerState.FileNames <> [] then
fileSortElements model dispatch
@@ -271,12 +260,11 @@ let fileContainer (model:Messages.Model) dispatch inputId=
]
let filePickerComponent (model:Messages.Model) (dispatch:Messages.Msg -> unit) =
- let inputId = "filePicker_OnFilePickerMainFunc"
Bulma.content [
pageHeader "File Picker"
Bulma.label "Select files from your computer and insert their names into Excel"
// Colored container element for all uploaded file names and sort elements
- fileContainer model dispatch inputId
+ fileContainer model dispatch
]
\ No newline at end of file
diff --git a/src/Client/Pages/JsonExporter/JsonExporter.fs b/src/Client/Pages/JsonExporter/JsonExporter.fs
index e0f2d8fc..4a73cf99 100644
--- a/src/Client/Pages/JsonExporter/JsonExporter.fs
+++ b/src/Client/Pages/JsonExporter/JsonExporter.fs
@@ -11,7 +11,6 @@ open ExcelColors
open Model
open Messages
-open JsonExporter
open Browser.Dom
@@ -25,422 +24,371 @@ let download(filename, text) =
element.click();
- document.body.removeChild(element);
-
-let update (msg:JsonExporter.Msg) (currentModel: Messages.Model) : Messages.Model * Cmd =
- match msg with
- // Style
- | UpdateLoading isLoading ->
- let nextModel = { currentModel with Messages.Model.JsonExporterModel.Loading = isLoading }
- nextModel, Cmd.none
- | UpdateShowTableExportTypeDropdown nextVal ->
- let nextModel = {
- currentModel.JsonExporterModel with
- ShowTableExportTypeDropdown = nextVal
- ShowWorkbookExportTypeDropdown = false
- ShowXLSXExportTypeDropdown = false
- }
- currentModel.updateByJsonExporterModel nextModel, Cmd.none
- | UpdateShowWorkbookExportTypeDropdown nextVal ->
- let nextModel = {
- currentModel.JsonExporterModel with
- ShowTableExportTypeDropdown = false
- ShowWorkbookExportTypeDropdown = nextVal
- ShowXLSXExportTypeDropdown = false
- }
- currentModel.updateByJsonExporterModel nextModel, Cmd.none
- | UpdateShowXLSXExportTypeDropdown nextVal ->
- let nextModel = {
- currentModel.JsonExporterModel with
- ShowTableExportTypeDropdown = false
- ShowWorkbookExportTypeDropdown = false
- ShowXLSXExportTypeDropdown = nextVal
- }
- currentModel.updateByJsonExporterModel nextModel, Cmd.none
- | UpdateTableJsonExportType nextType ->
- let nextModel = {
- currentModel.JsonExporterModel with
- TableJsonExportType = nextType
- ShowTableExportTypeDropdown = false
- }
- currentModel.updateByJsonExporterModel nextModel, Cmd.none
- | UpdateWorkbookJsonExportType nextType ->
- let nextModel = {
- currentModel.JsonExporterModel with
- WorkbookJsonExportType = nextType
- ShowWorkbookExportTypeDropdown = false
- }
- currentModel.updateByJsonExporterModel nextModel, Cmd.none
- | UpdateXLSXParsingExportType nextType ->
- let nextModel = {
- currentModel.JsonExporterModel with
- XLSXParsingExportType = nextType
- ShowXLSXExportTypeDropdown = false
- }
- currentModel.updateByJsonExporterModel nextModel, Cmd.none
- | CloseAllDropdowns ->
- let nextModel = {
- currentModel.JsonExporterModel with
- ShowTableExportTypeDropdown = false
- ShowWorkbookExportTypeDropdown = false
- ShowXLSXExportTypeDropdown = false
- }
- currentModel.updateByJsonExporterModel nextModel, Cmd.none
- //
- | ParseTableOfficeInteropRequest ->
- let nextModel = {
- currentModel.JsonExporterModel with
- Loading = true
- }
- let cmd =
- Cmd.OfPromise.either
- OfficeInterop.Core.getBuildingBlocksAndSheet
- ()
- (ParseTableServerRequest >> JsonExporterMsg)
- (curry GenericError (UpdateLoading false |> JsonExporterMsg |> Cmd.ofMsg) >> DevMsg)
- currentModel.updateByJsonExporterModel nextModel, cmd
- | ParseTableServerRequest (worksheetName, buildingBlocks) ->
- let nextModel = {
- currentModel.JsonExporterModel with
- CurrentExportType = Some currentModel.JsonExporterModel.TableJsonExportType
- Loading = true
- }
- let api =
- match currentModel.JsonExporterModel.TableJsonExportType with
- | JsonExportType.Assay ->
- Api.swateJsonAPIv1.parseAnnotationTableToAssayJson
- | JsonExportType.ProcessSeq ->
- Api.swateJsonAPIv1.parseAnnotationTableToProcessSeqJson
- | anythingElse -> failwith $"Cannot parse \"{anythingElse.ToString()}\" with this endpoint."
- let cmd =
- Cmd.OfAsync.either
- api
- (worksheetName, buildingBlocks)
- (ParseTableServerResponse >> JsonExporterMsg)
- (curry GenericError (UpdateLoading false |> JsonExporterMsg |> Cmd.ofMsg) >> DevMsg)
- currentModel.updateByJsonExporterModel nextModel, cmd
- //
- | ParseTablesOfficeInteropRequest ->
- let cmd =
- Cmd.OfPromise.either
- OfficeInterop.Core.getBuildingBlocksAndSheets
- ()
- (ParseTablesServerRequest >> JsonExporterMsg)
- (curry GenericError (UpdateLoading false |> JsonExporterMsg |> Cmd.ofMsg) >> DevMsg)
- currentModel, cmd
- | ParseTablesServerRequest (worksheetBuildingBlocksTuple) ->
- let nextModel = {
- currentModel.JsonExporterModel with
- CurrentExportType = Some currentModel.JsonExporterModel.WorkbookJsonExportType
- Loading = true
- }
- let api =
- match currentModel.JsonExporterModel.WorkbookJsonExportType with
- | JsonExportType.ProcessSeq ->
- Api.swateJsonAPIv1.parseAnnotationTablesToProcessSeqJson
- | JsonExportType.Assay ->
- Api.swateJsonAPIv1.parseAnnotationTablesToAssayJson
- | anythingElse -> failwith $"Cannot parse \"{anythingElse.ToString()}\" with this endpoint."
- let cmd =
- Cmd.OfAsync.either
- api
- worksheetBuildingBlocksTuple
- (ParseTableServerResponse >> JsonExporterMsg)
- (curry GenericError (UpdateLoading false |> JsonExporterMsg |> Cmd.ofMsg) >> DevMsg)
-
- currentModel.updateByJsonExporterModel nextModel, cmd
- //
- | ParseTableServerResponse parsedJson ->
- let n = System.DateTime.Now.ToUniversalTime().ToString("yyyyMMdd_hhmmss")
- let jsonName = Option.bind (fun x -> Some <| "_" + x.ToString()) currentModel.JsonExporterModel.CurrentExportType |> Option.defaultValue ""
- let _ = download ($"{n}{jsonName}.json",parsedJson)
- let nextModel = {
- currentModel.JsonExporterModel with
- Loading = false
- CurrentExportType = None
- }
- currentModel.updateByJsonExporterModel nextModel, Cmd.none
- //
- | StoreXLSXByteArray byteArr ->
- let nextModel = {
- currentModel.JsonExporterModel with
- XLSXByteArray = byteArr
- }
- currentModel.updateByJsonExporterModel nextModel , Cmd.none
- | ParseXLSXToJsonRequest byteArr ->
- let nextModel = {
- currentModel.JsonExporterModel with
- CurrentExportType = Some currentModel.JsonExporterModel.XLSXParsingExportType
- Loading = true
- }
- let apif =
- match currentModel.JsonExporterModel.XLSXParsingExportType with
- | JsonExportType.ProcessSeq -> Api.isaDotNetCommonApi.toProcessSeqJsonStr
- | JsonExportType.Assay -> Api.isaDotNetCommonApi.toAssayJsonStr
- | JsonExportType.ProtocolTemplate -> Api.isaDotNetCommonApi.toSwateTemplateJsonStr
- let cmd =
- Cmd.OfAsync.either
- apif
- byteArr
- (ParseXLSXToJsonResponse >> JsonExporterMsg)
- (curry GenericError (UpdateLoading false |> JsonExporterMsg |> Cmd.ofMsg) >> DevMsg)
- currentModel.updateByJsonExporterModel nextModel, cmd
- | ParseXLSXToJsonResponse jsonStr ->
- let n = System.DateTime.Now.ToUniversalTime().ToString("yyyyMMdd_hhmmss")
- let jsonName = Option.bind (fun x -> Some <| "_" + x.ToString()) currentModel.JsonExporterModel.CurrentExportType |> Option.defaultValue ""
- let _ = download ($"{n}{jsonName}.json",jsonStr)
- let nextModel = {
- currentModel.JsonExporterModel with
- Loading = false
- }
-
- currentModel.updateByJsonExporterModel nextModel, Cmd.none
+ document.body.removeChild(element) |> ignore
+ ()
+
+//open Messages
+//open Feliz
+//open Feliz.Bulma
+
+//let dropdownItem (exportType:JsonExportType) (model:Model) msg (isActive:bool) =
+// Bulma.dropdownItem.a [
+// prop.tabIndex 0
+// prop.onClick (fun e ->
+// e.stopPropagation()
+// exportType |> msg
+// )
+// prop.onKeyDown (fun k -> if (int k.which) = 13 then exportType |> msg)
+// prop.children [
+// Html.span [
+// prop.className "has-tooltip-right has-tooltip-multiline"
+// prop.custom ("data-tooltip", exportType.toExplanation)
+// prop.style [style.fontSize(length.rem 1.1); style.paddingRight 10; style.textAlign.center; style.color NFDIColors.Yellow.Darker20]
+// Html.i [prop.className "fa-solid fa-circle-info"] |> prop.children
+// ]
+
+// Html.span (exportType.ToString())
+// ]
+// ]
+
+//let parseTableToISAJsonEle (model:Model) (dispatch:Messages.Msg -> unit) =
+// mainFunctionContainer [
+// Bulma.field.div [
+// Bulma.field.hasAddons
+// prop.children [
+// Bulma.control.div [
+// Bulma.dropdown [
+// if model.JsonExporterModel.ShowTableExportTypeDropdown then Bulma.dropdown.isActive
+// prop.children [
+// Bulma.dropdownTrigger [
+// Bulma.button.a [
+// prop.onClick(fun e -> e.stopPropagation(); UpdateShowTableExportTypeDropdown (not model.JsonExporterModel.ShowTableExportTypeDropdown) |> JsonExporterMsg |> dispatch )
+// prop.children [
+// span [Style [MarginRight "5px"]] [str (model.JsonExporterModel.TableJsonExportType.ToString())]
+// Html.i [prop.className "fa-solid fa-angle-down"]
+// ]
+// ]
+// ]
+// Bulma.dropdownMenu [
+// Bulma.dropdownContent [
+// let msg = (UpdateTableJsonExportType >> JsonExporterMsg >> dispatch)
+// dropdownItem JsonExportType.Assay model msg (model.JsonExporterModel.TableJsonExportType = JsonExportType.Assay)
+// dropdownItem JsonExportType.ProcessSeq model msg (model.JsonExporterModel.TableJsonExportType = JsonExportType.ProcessSeq)
+// ]
+// ]
+// ]
+// ]
+// ]
+// Bulma.control.div [
+// Bulma.control.isExpanded
+// Bulma.button.a [
+// Bulma.color.isInfo
+// Bulma.button.isFullWidth
+// prop.onClick(fun _ ->
+// InterfaceMsg SpreadsheetInterface.ExportJsonTable |> dispatch
+// )
+// prop.text "Download as isa json"
+// ] |> prop.children
+// ]
+// ]
+// ]
+// ]
+
+//let parseTablesToISAJsonEle (model:Model) (dispatch:Messages.Msg -> unit) =
+// mainFunctionContainer [
+// Bulma.field.div [
+// Bulma.field.hasAddons
+// prop.children [
+// Bulma.control.div [
+// Bulma.dropdown [
+// if model.JsonExporterModel.ShowWorkbookExportTypeDropdown then Bulma.dropdown.isActive
+// prop.children [
+// Bulma.dropdownTrigger [
+// Bulma.button.a [
+// prop.onClick (fun e -> e.stopPropagation(); UpdateShowWorkbookExportTypeDropdown (not model.JsonExporterModel.ShowWorkbookExportTypeDropdown) |> JsonExporterMsg |> dispatch )
+// prop.children [
+// span [Style [MarginRight "5px"]] [str (model.JsonExporterModel.WorkbookJsonExportType.ToString())]
+// Html.i [prop.className "fa-solid fa-angle-down"]
+// ]
+// ]
+// ]
+// Bulma.dropdownMenu [
+// Bulma.dropdownContent [
+// let msg = (UpdateWorkbookJsonExportType >> JsonExporterMsg >> dispatch)
+// dropdownItem JsonExportType.Assay model msg (model.JsonExporterModel.WorkbookJsonExportType = JsonExportType.Assay)
+// dropdownItem JsonExportType.ProcessSeq model msg (model.JsonExporterModel.WorkbookJsonExportType = JsonExportType.ProcessSeq)
+// ]
+// ]
+// ]
+// ]
+// ]
+// Bulma.control.div [
+// Bulma.control.isExpanded
+// Bulma.button.a [
+// Bulma.color.isInfo
+// Bulma.button.isFullWidth
+// prop.onClick(fun _ ->
+// InterfaceMsg SpreadsheetInterface.ExportJsonTables |> dispatch
+// )
+// prop.text "Download as isa json"
+// ]
+// |> prop.children
+// ]
+// ]
+// ]
+// ]
+
+//// SND ELEMENT
+//open Browser.Types
+
+//let fileUploadButton (model:Model) dispatch (id: string) =
+// Bulma.label [
+// prop.className "mb-2 has-text-weight-normal"
+// prop.children [
+// Bulma.fileInput [
+// prop.id id
+// prop.type' "file";
+// prop.style [style.display.none]
+// prop.onChange (fun (ev: File list) ->
+// let files = ev//: Browser.Types.FileList = ev.target?files
+
+// let blobs =
+// files
+// |> List.map (fun f -> f.slice() )
+
+// let reader = Browser.Dom.FileReader.Create()
+
+// reader.onload <- fun evt ->
+// let byteArr =
+// let arraybuffer : Fable.Core.JS.ArrayBuffer = evt.target?result
+// let uintArr = Fable.Core.JS.Constructors.Uint8Array.Create arraybuffer
+// uintArr.ToString().Split([|","|], System.StringSplitOptions.RemoveEmptyEntries)
+// |> Array.map (fun byteStr -> byte byteStr)
+
+// StoreXLSXByteArray byteArr |> JsonExporterMsg |> dispatch
+
+// reader.onerror <- fun evt ->
+// curry GenericLog Cmd.none ("Error", evt.Value) |> DevMsg |> dispatch
+
+// reader.readAsArrayBuffer(blobs |> List.head)
+
+// let picker = Browser.Dom.document.getElementById(id)
+// // https://stackoverflow.com/questions/3528359/html-input-type-file-file-selection-event/3528376
+// picker?value <- null
+// )
+// ]
+// Bulma.button.a [
+// Bulma.color.isInfo;
+// Bulma.button.isFullWidth
+// prop.text "Upload Excel file"
+// ]
+// ]
+// ]
+
+
+//let xlsxUploadAndParsingMainElement (model:Model) (dispatch: Msg -> unit) =
+// let inputId = "xlsxConverter_uploadButton"
+// mainFunctionContainer [
+// // Upload xlsx file to byte []
+// fileUploadButton model dispatch inputId
+// // Request parsing
+// Bulma.field.div [
+// Bulma.field.hasAddons
+// prop.children [
+// Bulma.control.div [
+// Bulma.dropdown [
+// if model.JsonExporterModel.ShowXLSXExportTypeDropdown then Bulma.dropdown.isActive
+// prop.children [
+// Bulma.dropdownTrigger [
+// Bulma.button.a [
+// prop.onClick (fun e -> e.stopPropagation(); UpdateShowXLSXExportTypeDropdown (not model.JsonExporterModel.ShowXLSXExportTypeDropdown) |> JsonExporterMsg |> dispatch )
+// prop.children [
+// span [Style [MarginRight "5px"]] [str (model.JsonExporterModel.XLSXParsingExportType.ToString())]
+// Html.i [prop.className "fa-solid fa-angle-down"]
+// ]
+// ]
+// ]
+// Bulma.dropdownMenu [
+// Bulma.dropdownContent [
+// let msg = (UpdateXLSXParsingExportType >> JsonExporterMsg >> dispatch)
+// dropdownItem JsonExportType.Assay model msg (model.JsonExporterModel.XLSXParsingExportType = JsonExportType.Assay)
+// dropdownItem JsonExportType.ProcessSeq model msg (model.JsonExporterModel.XLSXParsingExportType = JsonExportType.ProcessSeq)
+// dropdownItem JsonExportType.ProtocolTemplate model msg (model.JsonExporterModel.XLSXParsingExportType = JsonExportType.ProtocolTemplate)
+// ]
+// ]
+// ]
+// ]
+// ]
+// Bulma.control.div [
+// Bulma.control.isExpanded
+// Bulma.button.a [
+// let hasContent = model.JsonExporterModel.XLSXByteArray <> Array.empty
+// Bulma.color.isInfo
+// if hasContent then
+// Bulma.button.isActive
+// else
+// Bulma.color.isDanger
+// prop.disabled true
+// Bulma.button.isFullWidth
+// prop.onClick(fun _ ->
+// if hasContent then
+// ParseXLSXToJsonRequest model.JsonExporterModel.XLSXByteArray |> JsonExporterMsg |> dispatch
+// )
+// prop.text "Download as isa json"
+// ]
+// |> prop.children
+// ]
+// ]
+// ]
+// ]
+
+//let jsonExporterMainElement (model:Messages.Model) (dispatch: Messages.Msg -> unit) =
+
+ //Bulma.content [
+
+ // prop.onSubmit (fun e -> e.preventDefault())
+ // prop.onKeyDown (fun k -> if (int k.which) = 13 then k.preventDefault())
+ // prop.onClick (fun e -> CloseAllDropdowns |> JsonExporterMsg |> dispatch)
+ // prop.style [style.minHeight(length.vh 100)]
+ // prop.children [
+ // Bulma.label "Json Exporter"
+
+ // Bulma.help [
+ // str "Export swate annotation tables to "
+ // a [Href @"https://en.wikipedia.org/wiki/JSON"] [str "JSON"]
+ // str " format. Official ISA-JSON types can be found "
+ // a [Href @"https://isa-specs.readthedocs.io/en/latest/isajson.html#"] [str "here"]
+ // str "."
+ // ]
+
+ // Bulma.label "Export active table"
+
+ // parseTableToISAJsonEle model dispatch
+
+ // Bulma.label "Export workbook"
+
+ // parseTablesToISAJsonEle model dispatch
+
+ // Bulma.label "Export Swate conform xlsx file."
+
+ // xlsxUploadAndParsingMainElement model dispatch
+ // ]
+ //]
-open Messages
open Feliz
open Feliz.Bulma
-let dropdownItem (exportType:JsonExportType) (model:Model) msg (isActive:bool) =
- Bulma.dropdownItem.a [
- prop.tabIndex 0
- prop.onClick (fun e ->
- e.stopPropagation()
- exportType |> msg
- )
- prop.onKeyDown (fun k -> if (int k.which) = 13 then exportType |> msg)
- prop.children [
- Html.span [
- prop.className "has-tooltip-right has-tooltip-multiline"
- prop.custom ("data-tooltip", exportType.toExplanation)
- prop.style [style.fontSize(length.rem 1.1); style.paddingRight 10; style.textAlign.center; style.color NFDIColors.Yellow.Darker20]
- Html.i [prop.className "fa-solid fa-circle-info"] |> prop.children
- ]
+type private JsonExportState = {
+ ExportFormat: JsonExportFormat
+} with
+ static member init() = {
+ ExportFormat = JsonExportFormat.ROCrate
+ }
+
+type FileExporter =
- Html.span (exportType.ToString())
+
+ static member private FileFormat(efm: JsonExportFormat, state: JsonExportState, setState) =
+ let isSelected = efm = state.ExportFormat
+ Html.option [
+ prop.selected isSelected
+ prop.text (string efm)
]
- ]
-
-let parseTableToISAJsonEle (model:Model) (dispatch:Messages.Msg -> unit) =
- mainFunctionContainer [
- Bulma.field.div [
- Bulma.field.hasAddons
- prop.children [
- Bulma.control.div [
- Bulma.dropdown [
- if model.JsonExporterModel.ShowTableExportTypeDropdown then Bulma.dropdown.isActive
- prop.children [
- Bulma.dropdownTrigger [
- Bulma.button.a [
- prop.onClick(fun e -> e.stopPropagation(); UpdateShowTableExportTypeDropdown (not model.JsonExporterModel.ShowTableExportTypeDropdown) |> JsonExporterMsg |> dispatch )
+
+ []
+ static member JsonExport(model: Messages.Model, dispatch) =
+ let state, setState = React.useState JsonExportState.init
+ Html.div [
+ Bulma.field.div [
+ Bulma.field.hasAddons
+ prop.children [
+ Bulma.control.p [
+ Html.span [
+ prop.className "select"
+ prop.children [
+ Html.select [
+ prop.onChange (fun (e:Browser.Types.Event) ->
+ let jef: JsonExportFormat = JsonExportFormat.fromString (e.target?value)
+ { state with
+ ExportFormat = jef }
+ |> setState
+ )
prop.children [
- span [Style [MarginRight "5px"]] [str (model.JsonExporterModel.TableJsonExportType.ToString())]
- Html.i [prop.className "fa-solid fa-angle-down"]
+ FileExporter.FileFormat(JsonExportFormat.ROCrate, state, setState)
+ FileExporter.FileFormat(JsonExportFormat.ISA, state, setState)
+ FileExporter.FileFormat(JsonExportFormat.ARCtrl, state, setState)
+ FileExporter.FileFormat(JsonExportFormat.ARCtrlCompressed, state, setState)
]
]
]
- Bulma.dropdownMenu [
- Bulma.dropdownContent [
- let msg = (UpdateTableJsonExportType >> JsonExporterMsg >> dispatch)
- dropdownItem JsonExportType.Assay model msg (model.JsonExporterModel.TableJsonExportType = JsonExportType.Assay)
- dropdownItem JsonExportType.ProcessSeq model msg (model.JsonExporterModel.TableJsonExportType = JsonExportType.ProcessSeq)
- ]
- ]
]
]
- ]
- Bulma.control.div [
- Bulma.control.isExpanded
- Bulma.button.a [
- Bulma.color.isInfo
- Bulma.button.isFullWidth
- prop.onClick(fun _ ->
- InterfaceMsg SpreadsheetInterface.ExportJsonTable |> dispatch
- )
- prop.text "Download as isa json"
- ] |> prop.children
- ]
- ]
- ]
- ]
-
-let parseTablesToISAJsonEle (model:Model) (dispatch:Messages.Msg -> unit) =
- mainFunctionContainer [
- Bulma.field.div [
- Bulma.field.hasAddons
- prop.children [
- Bulma.control.div [
- Bulma.dropdown [
- if model.JsonExporterModel.ShowWorkbookExportTypeDropdown then Bulma.dropdown.isActive
+ Bulma.control.p [
+ Bulma.control.isExpanded
prop.children [
- Bulma.dropdownTrigger [
- Bulma.button.a [
- prop.onClick (fun e -> e.stopPropagation(); UpdateShowWorkbookExportTypeDropdown (not model.JsonExporterModel.ShowWorkbookExportTypeDropdown) |> JsonExporterMsg |> dispatch )
- prop.children [
- span [Style [MarginRight "5px"]] [str (model.JsonExporterModel.WorkbookJsonExportType.ToString())]
- Html.i [prop.className "fa-solid fa-angle-down"]
- ]
- ]
- ]
- Bulma.dropdownMenu [
- Bulma.dropdownContent [
- let msg = (UpdateWorkbookJsonExportType >> JsonExporterMsg >> dispatch)
- dropdownItem JsonExportType.Assay model msg (model.JsonExporterModel.WorkbookJsonExportType = JsonExportType.Assay)
- dropdownItem JsonExportType.ProcessSeq model msg (model.JsonExporterModel.WorkbookJsonExportType = JsonExportType.ProcessSeq)
- ]
+ Bulma.button.button [
+ Bulma.button.isFullWidth
+ prop.text "Download"
+ prop.onClick (fun _ ->
+ if model.SpreadsheetModel.ArcFile.IsSome then
+ SpreadsheetInterface.ExportJson (model.SpreadsheetModel.ArcFile.Value, state.ExportFormat) |> InterfaceMsg |> dispatch
+ )
]
]
]
]
- Bulma.control.div [
- Bulma.control.isExpanded
- Bulma.button.a [
- Bulma.color.isInfo
- Bulma.button.isFullWidth
- prop.onClick(fun _ ->
- InterfaceMsg SpreadsheetInterface.ExportJsonTables |> dispatch
- )
- prop.text "Download as isa json"
- ]
- |> prop.children
- ]
]
]
- ]
-
-// SND ELEMENT
-open Browser.Types
-
-let fileUploadButton (model:Model) dispatch (id: string) =
- Bulma.label [
- prop.className "mb-2 has-text-weight-normal"
- prop.children [
- Bulma.fileInput [
- prop.id id
- prop.type' "file";
- prop.style [style.display.none]
- prop.onChange (fun (ev: File list) ->
- let files = ev//: Browser.Types.FileList = ev.target?files
-
- let blobs =
- files
- |> List.map (fun f -> f.slice() )
-
- let reader = Browser.Dom.FileReader.Create()
-
- reader.onload <- fun evt ->
- let byteArr =
- let arraybuffer : Fable.Core.JS.ArrayBuffer = evt.target?result
- let uintArr = Fable.Core.JS.Constructors.Uint8Array.Create arraybuffer
- uintArr.ToString().Split([|","|], System.StringSplitOptions.RemoveEmptyEntries)
- |> Array.map (fun byteStr -> byte byteStr)
-
- StoreXLSXByteArray byteArr |> JsonExporterMsg |> dispatch
-
- reader.onerror <- fun evt ->
- curry GenericLog Cmd.none ("Error", evt.Value) |> DevMsg |> dispatch
-
- reader.readAsArrayBuffer(blobs |> List.head)
- let picker = Browser.Dom.document.getElementById(id)
- // https://stackoverflow.com/questions/3528359/html-input-type-file-file-selection-event/3528376
- picker?value <- null
- )
- ]
- Bulma.button.a [
- Bulma.color.isInfo;
- Bulma.button.isFullWidth
- prop.text "Upload Excel file"
- ]
- ]
- ]
-
-
-let xlsxUploadAndParsingMainElement (model:Model) (dispatch: Msg -> unit) =
- let inputId = "xlsxConverter_uploadButton"
- mainFunctionContainer [
- // Upload xlsx file to byte []
- fileUploadButton model dispatch inputId
- // Request parsing
- Bulma.field.div [
- Bulma.field.hasAddons
- prop.children [
- Bulma.control.div [
- Bulma.dropdown [
- if model.JsonExporterModel.ShowXLSXExportTypeDropdown then Bulma.dropdown.isActive
- prop.children [
- Bulma.dropdownTrigger [
- Bulma.button.a [
- prop.onClick (fun e -> e.stopPropagation(); UpdateShowXLSXExportTypeDropdown (not model.JsonExporterModel.ShowXLSXExportTypeDropdown) |> JsonExporterMsg |> dispatch )
- prop.children [
- span [Style [MarginRight "5px"]] [str (model.JsonExporterModel.XLSXParsingExportType.ToString())]
- Html.i [prop.className "fa-solid fa-angle-down"]
- ]
+ static member Main(model:Messages.Model, dispatch: Messages.Msg -> unit) =
+ Html.div [
+ pageHeader "File Export"
+
+ Bulma.label "Export to Json"
+ mainFunctionContainer [
+ Bulma.field.div [
+ Bulma.content [
+ Bulma.help "Export Swate annotation tables to official JSON."
+ Html.ul [
+ Html.li [
+ Html.b "ARCtrl"
+ str ": A simple ARCtrl specific format."
+ ]
+ Html.li [
+ Html.b "ARCtrlCompressed"
+ str ": A compressed ARCtrl specific format."
+ ]
+ Html.li [
+ Html.b "ISA"
+ str ": ISA-JSON format ("
+ Html.a [
+ prop.target.blank
+ prop.href "https://isa-specs.readthedocs.io/en/latest/isajson.html#"
+ prop.text "ISA-JSON"
]
+ str ")."
]
- Bulma.dropdownMenu [
- Bulma.dropdownContent [
- let msg = (UpdateXLSXParsingExportType >> JsonExporterMsg >> dispatch)
- dropdownItem JsonExportType.Assay model msg (model.JsonExporterModel.XLSXParsingExportType = JsonExportType.Assay)
- dropdownItem JsonExportType.ProcessSeq model msg (model.JsonExporterModel.XLSXParsingExportType = JsonExportType.ProcessSeq)
- dropdownItem JsonExportType.ProtocolTemplate model msg (model.JsonExporterModel.XLSXParsingExportType = JsonExportType.ProtocolTemplate)
+ Html.li [
+ Html.b "ROCrate"
+ str ": ROCrate format ("
+ Html.a [
+ prop.target.blank
+ prop.href "https://www.researchobject.org/ro-crate/"
+ prop.text "ROCrate"
+ ]
+ str ", "
+ Html.a [
+ prop.target.blank
+ prop.href "https://github.com/nfdi4plants/isa-ro-crate-profile/blob/main/profile/isa_ro_crate.md"
+ prop.text "ISA-Profile"
]
+ str ")."
]
]
]
]
- Bulma.control.div [
- Bulma.control.isExpanded
- Bulma.button.a [
- let hasContent = model.JsonExporterModel.XLSXByteArray <> Array.empty
- Bulma.color.isInfo
- if hasContent then
- Bulma.button.isActive
- else
- Bulma.color.isDanger
- prop.disabled true
- Bulma.button.isFullWidth
- prop.onClick(fun _ ->
- if hasContent then
- ParseXLSXToJsonRequest model.JsonExporterModel.XLSXByteArray |> JsonExporterMsg |> dispatch
- )
- prop.text "Download as isa json"
- ]
- |> prop.children
- ]
+ FileExporter.JsonExport(model, dispatch)
]
]
- ]
-
-
-let jsonExporterMainElement (model:Messages.Model) (dispatch: Messages.Msg -> unit) =
-
- Bulma.content [
-
- prop.onSubmit (fun e -> e.preventDefault())
- prop.onKeyDown (fun k -> if (int k.which) = 13 then k.preventDefault())
- prop.onClick (fun e -> CloseAllDropdowns |> JsonExporterMsg |> dispatch)
- prop.style [style.minHeight(length.vh 100)]
- prop.children [
- Bulma.label "Json Exporter"
-
- Bulma.help [
- str "Export swate annotation tables to "
- a [Href @"https://en.wikipedia.org/wiki/JSON"] [str "JSON"]
- str " format. Official ISA-JSON types can be found "
- a [Href @"https://isa-specs.readthedocs.io/en/latest/isajson.html#"] [str "here"]
- str "."
- ]
-
- Bulma.label "Export active table"
-
- parseTableToISAJsonEle model dispatch
- Bulma.label "Export workbook"
-
- parseTablesToISAJsonEle model dispatch
-
- Bulma.label "Export Swate conform xlsx file."
- xlsxUploadAndParsingMainElement model dispatch
- ]
- ]
\ No newline at end of file
diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearchView.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs
similarity index 61%
rename from src/Client/Pages/ProtocolTemplates/ProtocolSearchView.fs
rename to src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs
index b958e884..d4d5478b 100644
--- a/src/Client/Pages/ProtocolTemplates/ProtocolSearchView.fs
+++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearch.fs
@@ -7,7 +7,7 @@ open Messages
open Feliz
open Feliz.Bulma
-let breadcrumbEle (model:Model) dispatch =
+let private breadcrumbEle (model:Model) dispatch =
Bulma.breadcrumb [
Bulma.breadcrumb.hasArrowSeparator
prop.children [
@@ -30,11 +30,13 @@ let breadcrumbEle (model:Model) dispatch =
open Fable.Core
[]
-let ProtocolSearchView (model:Model) dispatch =
- React.useEffectOnce(fun () ->
- Messages.Protocol.GetAllProtocolsRequest |> ProtocolMsg |> dispatch
- )
- let isEmpty = model.ProtocolState.ProtocolsAll |> isNull || model.ProtocolState.ProtocolsAll |> Array.isEmpty
+let Main (model:Model) dispatch =
+ let templates, setTemplates = React.useState(model.ProtocolState.Templates)
+ let config, setConfig = React.useState(TemplateFilterConfig.init)
+ let filteredTemplates = Protocol.Search.filterTemplates (templates, config)
+ React.useEffectOnce(fun _ -> Messages.Protocol.GetAllProtocolsRequest |> Messages.ProtocolMsg |> dispatch)
+ React.useEffect((fun _ -> setTemplates model.ProtocolState.Templates), [|box model.ProtocolState.Templates|])
+ let isEmpty = model.ProtocolState.Templates |> isNull || model.ProtocolState.Templates |> Array.isEmpty
let isLoading = model.ProtocolState.Loading
div [
OnSubmit (fun e -> e.preventDefault())
@@ -46,11 +48,11 @@ let ProtocolSearchView (model:Model) dispatch =
if isEmpty && not isLoading then
Bulma.help [Bulma.color.isDanger; prop.text "No templates were found. This can happen if connection to the server was lost. You can try reload this site or contact a developer."]
- if isLoading then
- Modals.Loading.loadingModal
-
Bulma.label "Search the database for protocol templates."
- if not isEmpty then
- Protocol.Component.ProtocolContainer model dispatch
+ mainFunctionContainer [
+ Protocol.Search.InfoField()
+ Protocol.Search.FileSortElement(model, config, setConfig)
+ Protocol.Search.Component (filteredTemplates, model, dispatch)
+ ]
]
\ No newline at end of file
diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs b/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs
index 9cb14133..4eb25885 100644
--- a/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs
+++ b/src/Client/Pages/ProtocolTemplates/ProtocolSearchViewComponent.fs
@@ -1,7 +1,6 @@
-module Protocol.Component
+namespace Protocol
open Shared
-open TemplateTypes
open Model
open Messages.Protocol
open Messages
@@ -9,14 +8,9 @@ open Messages
open Feliz
open Feliz.Bulma
-let private curatedOrganisationNames = [
- "dataplant"
- "nfdi4plants"
-]
-
/// Fields of Template that can be searched
[]
-type private SearchFields =
+type SearchFields =
| Name
| Organisation
| Authors
@@ -44,15 +38,14 @@ type private SearchFields =
static member GetOfQuery(query:string) =
SearchFields.ofFieldString query
-open ARCtrl.ISA
+open ARCtrl
-type private ProtocolViewState = {
- DisplayedProtDetailsId : int option
+type TemplateFilterConfig = {
ProtocolSearchQuery : string
ProtocolTagSearchQuery : string
ProtocolFilterTags : OntologyAnnotation list
ProtocolFilterErTags : OntologyAnnotation list
- CuratedCommunityFilter : Model.Protocol.CuratedCommunityFilter
+ CommunityFilter : Model.Protocol.CommunityFilter
TagFilterIsAnd : bool
Searchfield : SearchFields
} with
@@ -61,389 +54,387 @@ type private ProtocolViewState = {
ProtocolTagSearchQuery = ""
ProtocolFilterTags = []
ProtocolFilterErTags = []
- CuratedCommunityFilter = Model.Protocol.CuratedCommunityFilter.Both
+ CommunityFilter = Model.Protocol.CommunityFilter.OnlyCurated
TagFilterIsAnd = true
- DisplayedProtDetailsId = None
Searchfield = SearchFields.Name
}
-[]
-let private SearchFieldId = "template_searchfield_main"
+module ComponentAux =
-let private queryField (model:Model) (state: ProtocolViewState) (setState: ProtocolViewState -> unit) =
- Bulma.column [
- Bulma.label $"Search by {state.Searchfield.toNameRdb}"
- let hasSearchAddon = state.Searchfield <> SearchFields.Name
- Bulma.field.div [
- if hasSearchAddon then Bulma.field.hasAddons
- prop.children [
- if hasSearchAddon then
+ let curatedOrganisationNames = [
+ "dataplant"
+ "nfdi4plants"
+ ]
+
+ []
+ let SearchFieldId = "template_searchfield_main"
+
+ let queryField (model:Model) (state: TemplateFilterConfig) (setState: TemplateFilterConfig -> unit) =
+ Html.div [
+ Bulma.label $"Search by {state.Searchfield.toNameRdb}"
+ let hasSearchAddon = state.Searchfield <> SearchFields.Name
+ Bulma.field.div [
+ if hasSearchAddon then Bulma.field.hasAddons
+ prop.children [
+ if hasSearchAddon then
+ Bulma.control.div [
+ Bulma.button.a [ Bulma.button.isStatic; prop.text state.Searchfield.toStr]
+ ]
Bulma.control.div [
- Bulma.button.a [ Bulma.button.isStatic; prop.text state.Searchfield.toStr]
- ]
- Bulma.control.div [
- Bulma.control.hasIconsRight
- prop.children [
- Bulma.input.text [
- prop.placeholder $".. {state.Searchfield.toNameRdb}"
- prop.id SearchFieldId
- Bulma.color.isPrimary
- prop.valueOrDefault state.ProtocolSearchQuery
- prop.onChange (fun (e: string) ->
- let query = e
- // if query starts with "/" expect intend to search by different field
- if query.StartsWith "/" then
- let searchField = SearchFields.GetOfQuery query
- if searchField.IsSome then
- {state with Searchfield = searchField.Value; ProtocolSearchQuery = ""} |> setState
- //let inp = Browser.Dom.document.getElementById SearchFieldId
- // if query starts NOT with "/" update query
- else
- {
- state with
- ProtocolSearchQuery = query
- DisplayedProtDetailsId = None
- }
- |> setState
- )
+ Bulma.control.hasIconsRight
+ prop.children [
+ Bulma.input.text [
+ prop.style [style.minWidth 200]
+ prop.placeholder $".. {state.Searchfield.toNameRdb}"
+ prop.id SearchFieldId
+ Bulma.color.isPrimary
+ prop.valueOrDefault state.ProtocolSearchQuery
+ prop.onChange (fun (e: string) ->
+ let query = e
+ // if query starts with "/" expect intend to search by different field
+ if query.StartsWith "/" then
+ let searchField = SearchFields.GetOfQuery query
+ if searchField.IsSome then
+ {state with Searchfield = searchField.Value; ProtocolSearchQuery = ""} |> setState
+ //let inp = Browser.Dom.document.getElementById SearchFieldId
+ // if query starts NOT with "/" update query
+ else
+ {
+ state with
+ ProtocolSearchQuery = query
+ }
+ |> setState
+ )
+ ]
+ Bulma.icon [Bulma.icon.isSmall; Bulma.icon.isRight; prop.children (Html.i [prop.className "fa-solid fa-search"])]
]
- Bulma.icon [Bulma.icon.isSmall; Bulma.icon.isRight; prop.children (Html.i [prop.className "fa-solid fa-search"])]
]
]
]
]
- ]
-let private tagQueryField (model:Model) (state: ProtocolViewState) (setState: ProtocolViewState -> unit) =
- let allTags = model.ProtocolState.ProtocolsAll |> Array.collect (fun x -> x.Tags) |> Array.distinct |> Array.filter (fun x -> state.ProtocolFilterTags |> List.contains x |> not )
- let allErTags = model.ProtocolState.ProtocolsAll |> Array.collect (fun x -> x.EndpointRepositories) |> Array.distinct |> Array.filter (fun x -> state.ProtocolFilterErTags |> List.contains x |> not )
- let hitTagList, hitErTagList =
- if state.ProtocolTagSearchQuery <> ""
- then
- let queryBigram = state.ProtocolTagSearchQuery |> Shared.SorensenDice.createBigrams
- let getMatchingTags (allTags: OntologyAnnotation []) =
- allTags
- |> Array.map (fun x ->
- x.NameText
- |> Shared.SorensenDice.createBigrams
- |> Shared.SorensenDice.calculateDistance queryBigram
- , x
- )
- |> Array.filter (fun x -> fst x >= 0.3 || (snd x).TermAccessionShort = state.ProtocolTagSearchQuery)
- |> Array.sortByDescending fst
- |> Array.map snd
- let sortedTags = getMatchingTags allTags
- let sortedErTags = getMatchingTags allErTags
- sortedTags, sortedErTags
- else
- [||], [||]
- Bulma.column [
- Bulma.label "Search for tags"
- Bulma.control.div [
- Bulma.control.hasIconsRight
- prop.children [
- Bulma.input.text [
- prop.placeholder ".. protocol tag"
- Bulma.color.isPrimary
- prop.valueOrDefault state.ProtocolTagSearchQuery
- prop.onChange (fun (e:string) ->
- {state with ProtocolTagSearchQuery = e} |> setState
+ let tagQueryField (model:Model) (state: TemplateFilterConfig) (setState: TemplateFilterConfig -> unit) =
+ let allTags = model.ProtocolState.Templates |> Seq.collect (fun x -> x.Tags) |> Seq.distinct |> Seq.filter (fun x -> state.ProtocolFilterTags |> List.contains x |> not ) |> Array.ofSeq
+ let allErTags = model.ProtocolState.Templates |> Seq.collect (fun x -> x.EndpointRepositories) |> Seq.distinct |> Seq.filter (fun x -> state.ProtocolFilterErTags |> List.contains x |> not ) |> Array.ofSeq
+ let hitTagList, hitErTagList =
+ if state.ProtocolTagSearchQuery <> ""
+ then
+ let queryBigram = state.ProtocolTagSearchQuery |> Shared.SorensenDice.createBigrams
+ let getMatchingTags (allTags: OntologyAnnotation []) =
+ allTags
+ |> Array.map (fun x ->
+ x.NameText
+ |> Shared.SorensenDice.createBigrams
+ |> Shared.SorensenDice.calculateDistance queryBigram
+ , x
)
- ]
- Bulma.icon [
- Bulma.icon.isSmall; Bulma.icon.isRight
- Html.i [prop.className "fa-solid fa-search"] |> prop.children
- ]
- // Pseudo dropdown
- Bulma.box [
- prop.style [
- style.position.absolute
- style.width(length.perc 100)
- style.zIndex 10
- if hitTagList |> Array.isEmpty && hitErTagList |> Array.isEmpty then style.display.none
+ |> Array.filter (fun x -> fst x >= 0.3 || (snd x).TermAccessionShort = state.ProtocolTagSearchQuery)
+ |> Array.sortByDescending fst
+ |> Array.map snd
+ let sortedTags = getMatchingTags allTags
+ let sortedErTags = getMatchingTags allErTags
+ sortedTags, sortedErTags
+ else
+ [||], [||]
+ Html.div [
+ Bulma.label "Search for tags"
+ Bulma.control.div [
+ Bulma.control.hasIconsRight
+ prop.children [
+ Bulma.input.text [
+ prop.style [style.minWidth 150]
+ prop.placeholder ".. protocol tag"
+ Bulma.color.isPrimary
+ prop.valueOrDefault state.ProtocolTagSearchQuery
+ prop.onChange (fun (e:string) ->
+ {state with ProtocolTagSearchQuery = e} |> setState
+ )
]
- prop.children [
- if hitErTagList <> [||] then
- Bulma.label "Endpoint Repositories"
- Bulma.tags [
- for tagSuggestion in hitErTagList do
- yield
- Bulma.tag [
- prop.className "clickableTag"
- Bulma.color.isLink
- prop.onClick (fun _ ->
- let nextState = {
- state with
- ProtocolFilterErTags = tagSuggestion::state.ProtocolFilterErTags
- ProtocolTagSearchQuery = ""
- DisplayedProtDetailsId = None
- }
- setState nextState
- )
- prop.title tagSuggestion.TermAccessionShort
- prop.text tagSuggestion.NameText
- ]
- ]
- if hitTagList <> [||] then
- Bulma.label "Tags"
- Bulma.tags [
- for tagSuggestion in hitTagList do
- yield
- Bulma.tag [
- prop.className "clickableTag"
- Bulma.color.isInfo
- prop.onClick (fun _ ->
- let nextState = {
+ Bulma.icon [
+ Bulma.icon.isSmall; Bulma.icon.isRight
+ Html.i [prop.className "fa-solid fa-search"] |> prop.children
+ ]
+ // Pseudo dropdown
+ Bulma.box [
+ prop.style [
+ style.position.absolute
+ style.width(length.perc 100)
+ style.zIndex 10
+ if hitTagList |> Array.isEmpty && hitErTagList |> Array.isEmpty then style.display.none
+ ]
+ prop.children [
+ if hitErTagList <> [||] then
+ Bulma.label "Endpoint Repositories"
+ Bulma.tags [
+ for tagSuggestion in hitErTagList do
+ yield
+ Bulma.tag [
+ prop.className "clickableTag"
+ Bulma.color.isLink
+ prop.onClick (fun _ ->
+ let nextState = {
state with
- ProtocolFilterTags = tagSuggestion::state.ProtocolFilterTags
+ ProtocolFilterErTags = tagSuggestion::state.ProtocolFilterErTags
ProtocolTagSearchQuery = ""
- DisplayedProtDetailsId = None
}
- setState nextState
- //AddProtocolTag tagSuggestion |> ProtocolMsg |> dispatch
- )
- prop.title tagSuggestion.TermAccessionShort
- prop.text tagSuggestion.NameText
- ]
- ]
+ setState nextState
+ )
+ prop.title tagSuggestion.TermAccessionShort
+ prop.text tagSuggestion.NameText
+ ]
+ ]
+ if hitTagList <> [||] then
+ Bulma.label "Tags"
+ Bulma.tags [
+ for tagSuggestion in hitTagList do
+ yield
+ Bulma.tag [
+ prop.className "clickableTag"
+ Bulma.color.isInfo
+ prop.onClick (fun _ ->
+ let nextState = {
+ state with
+ ProtocolFilterTags = tagSuggestion::state.ProtocolFilterTags
+ ProtocolTagSearchQuery = ""
+ }
+ setState nextState
+ //AddProtocolTag tagSuggestion |> ProtocolMsg |> dispatch
+ )
+ prop.title tagSuggestion.TermAccessionShort
+ prop.text tagSuggestion.NameText
+ ]
+ ]
+ ]
]
]
]
]
- ]
-let private tagDisplayField (model:Model) (state: ProtocolViewState) (setState: ProtocolViewState -> unit) =
- Bulma.columns [
- Bulma.columns.isMobile
- prop.children [
- Bulma.column [
+ open Fable.Core.JsInterop
+
+ let communitySelectField (model: Messages.Model) (state: TemplateFilterConfig) setState =
+ let communityNames =
+ model.ProtocolState.Templates
+ |> Array.choose (fun t -> Model.Protocol.CommunityFilter.CommunityFromOrganisation t.Organisation)
+ |> Array.distinct |> List.ofArray
+ let options =
+ [
+ Model.Protocol.CommunityFilter.All
+ Model.Protocol.CommunityFilter.OnlyCurated
+ ]@communityNames
+ Html.div [
+ Bulma.label "Select community"
+ Bulma.control.div [
+ Bulma.control.isExpanded
+ prop.children [
+ Bulma.select [
+ prop.value (state.CommunityFilter.ToStringRdb())
+ prop.onChange(fun (e: Browser.Types.Event) ->
+ let filter = Model.Protocol.CommunityFilter.fromString e.target?value
+ if state.CommunityFilter <> filter then
+ {state with CommunityFilter = filter} |> setState
+ )
+ prop.children [
+ for option in options do
+ Html.option [
+ //prop.selected (state.CommunityFilter = option)
+ prop.value (option.ToStringRdb())
+ prop.text (option.ToStringRdb())
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+
+ let TagRemovableElement (tag:OntologyAnnotation) (color: IReactProperty) (rmv: unit -> unit) =
+ Bulma.control.div [
+ Bulma.tags [
+ prop.style [style.flexWrap.nowrap]
+ Bulma.tags.hasAddons
+ prop.children [
+ Bulma.tag [color; prop.style [style.borderWidth 0]; prop.text tag.NameText; prop.title tag.TermAccessionShort]
+ Bulma.tag [
+ Bulma.tag.isDelete
+ prop.onClick (fun _ -> rmv())
+ ]
+ ]
+ ]
+ ]
+
+ let SwitchElement (tagIsFilterAnd: bool) (setFilter: bool -> unit) =
+ Html.div [
+ prop.style [style.marginLeft length.auto]
+ prop.children [
+ Bulma.button.button [
+ Bulma.button.isSmall
+ prop.onClick (fun _ -> setFilter (not tagIsFilterAnd))
+ prop.title (if tagIsFilterAnd then "Templates contain all tags." else "Templates contain at least one tag.")
+ prop.text (if tagIsFilterAnd then "And" else "Or")
+ ]
+ ]
+ ]
+
+ let TagDisplayField (model:Model) (state: TemplateFilterConfig) (setState: TemplateFilterConfig -> unit) =
+ Html.div [
+ prop.className "is-flex"
+ prop.children [
Bulma.field.div [
Bulma.field.isGroupedMultiline
+ prop.style [style.display.flex; style.flexGrow 1; style.gap (length.rem 0.5); style.flexWrap.wrap; style.flexDirection.row]
prop.children [
for selectedTag in state.ProtocolFilterErTags do
- yield Bulma.control.div [
- Bulma.tags [
- Bulma.tags.hasAddons
- prop.children [
- Bulma.tag [Bulma.color.isLink; prop.style [style.borderWidth 0]; prop.text selectedTag.NameText]
- Bulma.delete [
- prop.className "clickableTagDelete"
- prop.onClick (fun _ ->
- {state with ProtocolFilterErTags = state.ProtocolFilterErTags |> List.except [selectedTag]} |> setState
- //RemoveProtocolErTag selectedTag |> ProtocolMsg |> dispatch
- )
- ]
- ]
- ]
- ]
+ let rmv = fun () -> {state with ProtocolFilterErTags = state.ProtocolFilterErTags |> List.except [selectedTag]} |> setState
+ TagRemovableElement selectedTag Bulma.color.isLink rmv
for selectedTag in state.ProtocolFilterTags do
- yield Bulma.control.div [
- Bulma.tags [
- Bulma.tags.hasAddons
- prop.children [
- Bulma.tag [Bulma.color.isInfo; prop.style [style.borderWidth 0]; prop.text selectedTag.NameText]
- Bulma.delete [
- prop.className "clickableTagDelete"
- //Tag.Color IsWarning;
- prop.onClick (fun _ ->
- {state with ProtocolFilterTags = state.ProtocolFilterTags |> List.except [selectedTag]} |> setState
- //RemoveProtocolTag selectedTag |> ProtocolMsg |> dispatch
- )
- ]
- ]
- ]
- ]
+ let rmv = fun () -> {state with ProtocolFilterTags = state.ProtocolFilterTags |> List.except [selectedTag]} |> setState
+ TagRemovableElement selectedTag Bulma.color.isInfo rmv
]
]
- ]
- // tag filter (AND or OR)
- Bulma.column [
- Bulma.column.isNarrow
- prop.title (if state.TagFilterIsAnd then "Templates contain all tags." else "Templates contain at least one tag.")
- Switch.checkbox [
- Bulma.color.isDark
- prop.style [style.userSelect.none]
- switch.isOutlined
- switch.isSmall
- prop.id "switch-2"
- prop.isChecked state.TagFilterIsAnd
- prop.onChange (fun (e:bool) ->
- {state with TagFilterIsAnd = not state.TagFilterIsAnd} |> setState
- //UpdateTagFilterIsAnd (not state.TagFilterIsAnd) |> ProtocolMsg |> dispatch
- )
- prop.children (if state.TagFilterIsAnd then Html.b "And" else Html.b "Or")
- ] |> prop.children
+ // tag filter (AND or OR)
+ let filtersetter = fun b -> setState {state with TagFilterIsAnd = b}
+ SwitchElement state.TagFilterIsAnd filtersetter
]
]
- ]
-let private fileSortElements (model:Model) (state: ProtocolViewState) (setState: ProtocolViewState -> unit) =
+ let fileSortElements (model:Model) (state: TemplateFilterConfig) (setState: TemplateFilterConfig -> unit) =
- Html.div [
- prop.style [style.marginBottom(length.rem 0.75)]
- prop.children [
- Bulma.columns [
- Bulma.columns.isMobile; prop.style [style.marginBottom 0]
- prop.children [
- queryField model state setState
- tagQueryField model state setState
+ Html.div [
+ prop.style [style.marginBottom(length.rem 0.75); style.display.flex; style.flexDirection.column]
+ prop.children [
+ Bulma.field.div [
+ prop.className "template-filter-container"
+ prop.children [
+ queryField model state setState
+ tagQueryField model state setState
+ communitySelectField model state setState
+ ]
]
+ // Only show the tag list and tag filter (AND or OR) if any tag exists
+ if state.ProtocolFilterErTags <> [] || state.ProtocolFilterTags <> [] then
+ Bulma.field.div [
+ TagDisplayField model state setState
+ ]
]
- // Only show the tag list and tag filter (AND or OR) if any tag exists
- if state.ProtocolFilterErTags <> [] || state.ProtocolFilterTags <> [] then
- tagDisplayField model state setState
]
- ]
-let private curatedTag = Bulma.tag [prop.text "curated"; Bulma.color.isSuccess]
-let private communitytag = Bulma.tag [prop.text "community"; Bulma.color.isWarning]
-let private curatedCommunityTag =
- Bulma.tag [
- prop.style [style.custom("background", "linear-gradient(90deg, rgba(31,194,167,1) 50%, rgba(255,192,0,1) 50%)")]
- Bulma.color.isSuccess
- prop.children [
- Html.span [prop.style [style.marginRight (length.em 0.75)]; prop.text "cur"]
- Html.span [prop.style [style.marginLeft (length.em 0.75); style.color "rgba(0, 0, 0, 0.7)"]; prop.text "com"]
- ]
- ]
-
-let createAuthorStringHelper (author: Person) =
- let mi = if author.MidInitials.IsSome then author.MidInitials.Value else ""
- $"{author.FirstName} {mi} {author.LastName}"
-let createAuthorsStringHelper (authors: Person []) = authors |> Array.map createAuthorStringHelper |> String.concat ", "
-
-let private protocolElement i (template:ARCtrl.Template.Template) (model:Model) (state:ProtocolViewState) dispatch (setState: ProtocolViewState -> unit) =
- let isActive =
- match state.DisplayedProtDetailsId with
- | Some id when id = i ->
- true
- | _ ->
- false
- [
- Html.tr [
- prop.key $"{i}_{template.Id}"
- prop.classes [ "nonSelectText"; if isActive then "hoverTableEle"]
- prop.style [
- style.cursor.pointer; style.userSelect.none;
- ]
- prop.onClick (fun e ->
- e.preventDefault()
- { state with
- DisplayedProtDetailsId = if isActive then None else Some i }
- |> setState
- //if isActive then
- // UpdateDisplayedProtDetailsId None |> ProtocolMsg |> dispatch
- //else
- // UpdateDisplayedProtDetailsId (Some i) |> ProtocolMsg |> dispatch
- )
+ let curatedTag = Bulma.tag [prop.text "curated"; Bulma.color.isSuccess]
+ let communitytag = Bulma.tag [prop.text "community"; Bulma.color.isWarning]
+ let curatedCommunityTag =
+ Bulma.tag [
+ prop.style [style.custom("background", "linear-gradient(90deg, rgba(31,194,167,1) 50%, rgba(255,192,0,1) 50%)")]
+ Bulma.color.isSuccess
prop.children [
- Html.td template.Name
- Html.td (
- if curatedOrganisationNames |> List.contains (template.Organisation.ToString().ToLower()) then
- curatedTag
- else
- communitytag
- )
- //td [ Style [TextAlign TextAlignOptions.Center; VerticalAlign "middle"] ] [ a [ OnClick (fun e -> e.stopPropagation()); Href prot.DocsLink; Target "_Blank"; Title "docs" ] [Fa.i [Fa.Size Fa.Fa2x ; Fa.Regular.FileAlt] []] ]
- Html.td [ prop.style [style.textAlign.center; style.verticalAlign.middle]; prop.text template.Version ]
- //td [ Style [TextAlign TextAlignOptions.Center; VerticalAlign "middle"] ] [ str (string template.Used) ]
- Html.td (
- Bulma.icon [Html.i [prop.className "fa-solid fa-chevron-down"]]
- )
+ Html.span [prop.style [style.marginRight (length.em 0.75)]; prop.text "cur"]
+ Html.span [prop.style [style.marginLeft (length.em 0.75); style.color "rgba(0, 0, 0, 0.7)"]; prop.text "com"]
]
]
- Html.tr [
- Html.td [
+
+ let createAuthorStringHelper (author: Person) =
+ let mi = if author.MidInitials.IsSome then author.MidInitials.Value else ""
+ $"{author.FirstName} {mi} {author.LastName}"
+ let createAuthorsStringHelper (authors: ResizeArray) = authors |> Seq.map createAuthorStringHelper |> String.concat ", "
+
+ let protocolElement i (template:ARCtrl.Template) (isShown:bool) (setIsShown: bool -> unit) (model:Model) dispatch =
+ [
+ Html.tr [
+ prop.key $"{i}_{template.Id}"
+ prop.classes [ "nonSelectText"; if isShown then "hoverTableEle"]
prop.style [
- style.padding 0
- if isActive then
- style.borderBottom (2, borderStyle.solid, "black")
- else
- style.display.none
+ style.cursor.pointer; style.userSelect.none;
]
- prop.colSpan 4
+ prop.onClick (fun e ->
+ e.preventDefault()
+ setIsShown (not isShown)
+ //if isActive then
+ // UpdateDisplayedProtDetailsId None |> ProtocolMsg |> dispatch
+ //else
+ // UpdateDisplayedProtDetailsId (Some i) |> ProtocolMsg |> dispatch
+ )
prop.children [
- Bulma.box [
- prop.style [style.borderRadius 0]
+ Html.td [
+ prop.text template.Name
+ prop.key $"{i}_{template.Id}_name"
+ ]
+ Html.td [
+ prop.key $"{i}_{template.Id}_tag"
prop.children [
- Html.div [
- Html.div template.Description
+ if curatedOrganisationNames |> List.contains (template.Organisation.ToString().ToLower()) then
+ curatedTag
+ else
+ communitytag
+ ]
+ ]
+ //td [ Style [TextAlign TextAlignOptions.Center; VerticalAlign "middle"] ] [ a [ OnClick (fun e -> e.stopPropagation()); Href prot.DocsLink; Target "_Blank"; Title "docs" ] [Fa.i [Fa.Size Fa.Fa2x ; Fa.Regular.FileAlt] []] ]
+ Html.td [ prop.key $"{i}_{template.Id}_version"; prop.style [style.textAlign.center; style.verticalAlign.middle]; prop.text template.Version ]
+ //td [ Style [TextAlign TextAlignOptions.Center; VerticalAlign "middle"] ] [ str (string template.Used) ]
+ Html.td [
+ prop.key $"{i}_{template.Id}_button"
+ prop.children [Bulma.icon [Html.i [prop.className "fa-solid fa-chevron-down"]] ]
+ ]
+ ]
+ ]
+ Html.tr [
+ Html.td [
+ prop.key $"{i}_{template.Id}_description"
+ prop.style [
+ style.padding 0
+ if isShown then
+ style.borderBottom (2, borderStyle.solid, "black")
+ else
+ style.display.none
+ ]
+ prop.colSpan 4
+ prop.children [
+ Bulma.box [
+ prop.style [style.borderRadius 0]
+ prop.children [
Html.div [
- Html.div [ Html.b "Author: "; Html.span (createAuthorsStringHelper template.Authors) ]
- Html.div [ Html.b "Created: "; Html.span (template.LastUpdated.ToString("yyyy/MM/dd")) ]
+ Html.div template.Description
+ Html.div [
+ Html.div [ Html.b "Author: "; Html.span (createAuthorsStringHelper template.Authors) ]
+ Html.div [ Html.b "Created: "; Html.span (template.LastUpdated.ToString("yyyy/MM/dd")) ]
+ ]
+ Html.div [
+ Html.div [ Html.b "Organisation: "; Html.span (template.Organisation.ToString()) ]
+ ]
]
- Html.div [
- Html.div [ Html.b "Organisation: "; Html.span (template.Organisation.ToString()) ]
+ Bulma.tags [
+ for tag in template.EndpointRepositories do
+ yield
+ Bulma.tag [Bulma.color.isLink; prop.text tag.NameText; prop.title tag.TermAccessionShort]
+ ]
+ Bulma.tags [
+ for tag in template.Tags do
+ yield
+ Bulma.tag [Bulma.color.isInfo; prop.text tag.NameText; prop.title tag.TermAccessionShort]
+ ]
+ Bulma.button.a [
+ prop.onClick (fun _ -> SelectProtocol template |> ProtocolMsg |> dispatch)
+ Bulma.button.isFullWidth; Bulma.color.isSuccess
+ prop.text "select"
]
- ]
- Bulma.tags [
- for tag in template.EndpointRepositories do
- yield
- Bulma.tag [Bulma.color.isLink; prop.text tag.NameText; prop.title tag.TermAccessionShort]
- ]
- Bulma.tags [
- for tag in template.Tags do
- yield
- Bulma.tag [Bulma.color.isInfo; prop.text tag.NameText; prop.title tag.TermAccessionShort]
- ]
- Bulma.button.a [
- prop.onClick (fun _ -> SelectProtocol template |> ProtocolMsg |> dispatch)
- Bulma.button.isFullWidth; Bulma.color.isSuccess
- prop.text "select"
]
]
]
]
]
]
- ]
-let private curatedCommunityFilterDropdownItem (filter:Protocol.CuratedCommunityFilter) (child: ReactElement) (state:ProtocolViewState) (setState: ProtocolViewState -> unit) =
- Bulma.dropdownItem.a [
- prop.onClick(fun e ->
- e.preventDefault();
- {state with CuratedCommunityFilter = filter} |> setState
- //UpdateCuratedCommunityFilter filter |> ProtocolMsg |> dispatch
- )
- prop.children child
- ]
-
-let private curatedCommunityFilterElement (state:ProtocolViewState) (setState: ProtocolViewState -> unit) =
- Bulma.dropdown [
- Bulma.dropdown.isHoverable
- prop.children [
- Bulma.dropdownTrigger [
- Bulma.button.button [
- Bulma.button.isSmall; Bulma.button.isOutlined; Bulma.color.isWhite;
- prop.style [style.padding 0]
- match state.CuratedCommunityFilter with
- | Protocol.CuratedCommunityFilter.Both -> curatedCommunityTag
- | Protocol.CuratedCommunityFilter.OnlyCommunity -> communitytag
- | Protocol.CuratedCommunityFilter.OnlyCurated -> curatedTag
- |> prop.children
- ]
- ]
- Bulma.dropdownMenu [
- prop.style [style.minWidth.unset; style.fontWeight.normal]
- Bulma.dropdownContent [
- curatedCommunityFilterDropdownItem Protocol.CuratedCommunityFilter.Both curatedCommunityTag state setState
- curatedCommunityFilterDropdownItem Protocol.CuratedCommunityFilter.OnlyCurated curatedTag state setState
- curatedCommunityFilterDropdownItem Protocol.CuratedCommunityFilter.OnlyCommunity communitytag state setState
- ] |> prop.children
+ let RefreshButton (model:Messages.Model) dispatch =
+ Bulma.button.button [
+ Bulma.button.isSmall
+ prop.onClick (fun _ -> Messages.Protocol.GetAllProtocolsForceRequest |> ProtocolMsg |> dispatch)
+ prop.children [
+ Bulma.icon [Html.i [prop.className "fa-solid fa-arrows-rotate"]]
]
]
- ]
-
-open Feliz
-open System
-[]
-let ProtocolContainer (model:Model) dispatch =
+module FilterHelper =
+ open ComponentAux
- let state, setState = React.useState(ProtocolViewState.init)
-
- let sortTableBySearchQuery (protocol: ARCtrl.Template.Template []) =
- let query = state.ProtocolSearchQuery.Trim()
+ let sortTableBySearchQuery (searchfield: SearchFields) (searchQuery: string) (protocol: ARCtrl.Template []) =
+ let query = searchQuery.Trim()
// Only search if field is not empty and does not start with "/".
// If it starts with "/" and does not match SearchFields then it will never trigger search
// As soon as it matches SearchFields it will be removed and can become 'query <> ""'
@@ -458,58 +449,53 @@ let ProtocolContainer (model:Model) dispatch =
protocol
|> Array.map (fun template ->
let score =
- match state.Searchfield with
+ match searchfield with
| SearchFields.Name ->
createScore template.Name
| SearchFields.Organisation ->
createScore (template.Organisation.ToString())
| SearchFields.Authors ->
let query' = query.ToLower()
- let scores = template.Authors |> Array.filter (fun author ->
+ let scores = template.Authors |> Seq.filter (fun author ->
(createAuthorStringHelper author).ToLower().Contains query'
|| (author.ORCID.IsSome && author.ORCID.Value = query)
)
- if Array.isEmpty scores then 0.0 else 1.0
+ if Seq.isEmpty scores then 0.0 else 1.0
score, template
)
- |> Array.filter (fun (score,_) -> score > 0.1)
+ |> Array.filter (fun (score,_) -> score > 0.3)
|> Array.sortByDescending fst
+ |> fun y ->
+ for score, x in y do
+ log (score, x.Name)
+ y
|> Array.map snd
scoredTemplate
else
protocol
- let filterTableByTags (protocol:ARCtrl.Template.Template []) =
- if state.ProtocolFilterTags <> [] || state.ProtocolFilterErTags <> [] then
- protocol |> Array.filter (fun x ->
- let tags = Array.append x.Tags x.EndpointRepositories |> Array.distinct
- let filterTags = state.ProtocolFilterTags@state.ProtocolFilterErTags |> List.distinct
- Seq.except filterTags tags
- |> fun filteredTags ->
- // if we want to filter by tag with AND, all tags must match
- if state.TagFilterIsAnd then
- Seq.length filteredTags = tags.Length - filterTags.Length
- // if we want to filter by tag with OR, at least one tag must match
- else
- Seq.length filteredTags < tags.Length
- )
+ let filterTableByTags tags ertags tagfilter (templates: ARCtrl.Template []) =
+ if tags <> [] || ertags <> [] then
+ let tagArray = tags@ertags |> ResizeArray
+ let filteredTemplates = ResizeArray templates |> ARCtrl.Templates.filterByOntologyAnnotation(tagArray, tagfilter)
+ Array.ofSeq filteredTemplates
else
- protocol
- let filterTableByCuratedCommunityFilter (protocol:ARCtrl.Template.Template []) =
- match state.CuratedCommunityFilter with
- | Protocol.CuratedCommunityFilter.Both -> protocol
- | Protocol.CuratedCommunityFilter.OnlyCurated -> protocol |> Array.filter (fun x -> List.contains (x.Organisation.ToString().ToLower()) curatedOrganisationNames)
- | Protocol.CuratedCommunityFilter.OnlyCommunity -> protocol |> Array.filter (fun x -> List.contains (x.Organisation.ToString().ToLower()) curatedOrganisationNames |> not)
-
- let sortedTable =
- model.ProtocolState.ProtocolsAll
- |> filterTableByTags
- |> filterTableByCuratedCommunityFilter
- |> sortTableBySearchQuery
- |> Array.sortBy (fun template -> template.Name, template.Organisation)
-
- mainFunctionContainer [
+ templates
+ let filterTableByCommunityFilter communityfilter (protocol:ARCtrl.Template []) =
+ match communityfilter with
+ | Protocol.CommunityFilter.All -> protocol
+ | Protocol.CommunityFilter.OnlyCurated -> protocol |> Array.filter (fun x -> x.Organisation.IsOfficial())
+ | Protocol.CommunityFilter.Community name -> protocol |> Array.filter (fun x -> x.Organisation.ToString() = name)
+
+open Feliz
+open System
+open ComponentAux
+
+
+type Search =
+
+ static member InfoField() =
Bulma.field.div [
- Bulma.help [
+ Bulma.content [
Html.b "Search for templates."
Html.span " For more information you can look "
Html.a [ prop.href Shared.URLs.SwateWiki; prop.target "_Blank"; prop.text "here"]
@@ -517,7 +503,7 @@ let ProtocolContainer (model:Model) dispatch =
Html.a [ prop.href URLs.Helpdesk.UrlTemplateTopic; prop.target "_Blank"; prop.text "here"]
Html.span "."
]
- Bulma.help [
+ Bulma.content [
Html.span "You can search by template name, organisation and authors. Just type:"
Bulma.content [
Html.ul [
@@ -528,26 +514,74 @@ let ProtocolContainer (model:Model) dispatch =
]
]
]
- fileSortElements model state setState
- Bulma.table [
- Bulma.table.isFullWidth
- Bulma.table.isStriped
+
+ static member filterTemplates(templates: ARCtrl.Template [], config: TemplateFilterConfig) =
+ if templates.Length = 0 then [||] else
+ templates
+ |> Array.ofSeq
+ |> Array.sortBy (fun template -> template.Name, template.Organisation)
+ |> FilterHelper.filterTableByTags config.ProtocolFilterTags config.ProtocolFilterErTags config.TagFilterIsAnd
+ |> FilterHelper.filterTableByCommunityFilter config.CommunityFilter
+ |> FilterHelper.sortTableBySearchQuery config.Searchfield config.ProtocolSearchQuery
+
+ []
+ static member FileSortElement(model, config, configSetter: TemplateFilterConfig -> unit) =
+ fileSortElements model config configSetter
+
+ []
+ static member Component (templates, model:Model, dispatch, ?maxheight: Styles.ICssUnit) =
+ let maxheight = defaultArg maxheight (length.px 600)
+ let showIds, setShowIds = React.useState(fun _ -> [])
+ Html.div [
+ prop.style [style.overflow.auto; style.maxHeight maxheight]
prop.children [
- Html.thead [
- Html.tr [
- Html.th "Template Name"
- //th [ Style [ Color model.SiteStyleState.ColorMode.Text; TextAlign TextAlignOptions.Center] ] [ str "Documentation" ]
- Html.th [curatedCommunityFilterElement state setState]
- Html.th "Template Version"
- //th [ Style [ Color model.SiteStyleState.ColorMode.Text; TextAlign TextAlignOptions.Center] ] [ str "Uses" ]
- Html.th Html.none
+ Bulma.table [
+ Bulma.table.isFullWidth
+ Bulma.table.isStriped
+ prop.className "tableFixHead"
+ prop.children [
+ Html.thead [
+ Html.tr [
+ Html.th "Template Name"
+ //th [ Style [ Color model.SiteStyleState.ColorMode.Text; TextAlign TextAlignOptions.Center] ] [ str "Documentation" ]
+ Html.th "Community"//[CommunityFilterElement state setState]
+ Html.th "Template Version"
+ //th [ Style [ Color model.SiteStyleState.ColorMode.Text; TextAlign TextAlignOptions.Center] ] [ str "Uses" ]
+ Html.th [
+ RefreshButton model dispatch
+ ]
+ ]
+ ]
+ Html.tbody [
+ match model.ProtocolState.Loading with
+ | true ->
+ Html.tr [
+ Html.td [
+ prop.colSpan 4
+ prop.style [style.textAlign.center]
+ prop.children [
+ Bulma.icon [
+ Bulma.icon.isMedium
+ prop.children [
+ Html.i [prop.className "fa-solid fa-spinner fa-spin fa-lg"]
+ ]
+ ]
+ ]
+ ]
+ ]
+ | false ->
+ match templates with
+ | [||] ->
+ Html.tr [ Html.td "Empty" ]
+ | _ ->
+ for i in 0 .. templates.Length-1 do
+ let isShown = showIds |> List.contains i
+ let setIsShown (show: bool) =
+ if show then i::showIds |> setShowIds else showIds |> List.filter (fun x -> x <> i) |> setShowIds
+ yield!
+ protocolElement i templates.[i] isShown setIsShown model dispatch
+ ]
]
]
- Html.tbody [
- for i in 0 .. sortedTable.Length-1 do
- yield!
- protocolElement i sortedTable.[i] model state dispatch setState
- ]
]
- ]
- ]
\ No newline at end of file
+ ]
\ No newline at end of file
diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs
index 5f4dc290..9182297f 100644
--- a/src/Client/Pages/ProtocolTemplates/ProtocolState.fs
+++ b/src/Client/Pages/ProtocolTemplates/ProtocolState.fs
@@ -10,45 +10,53 @@ module Protocol =
open Shared
open Fable.Core
- let update (fujMsg:Protocol.Msg) (currentState: Protocol.Model) : Protocol.Model * Cmd =
+ let update (fujMsg:Protocol.Msg) (state: Protocol.Model) : Protocol.Model * Cmd =
match fujMsg with
- // // ------ Process from file ------
- | ParseUploadedFileRequest bytes ->
- let nextModel = { currentState with Loading = true }
- failwith "ParseUploadedFileRequest IS NOT IMPLEMENTED YET"
- //let cmd =
- // Cmd.OfAsync.either
- // Api.templateApi.tryParseToBuildingBlocks
- // bytes
- // (ParseUploadedFileResponse >> ProtocolMsg)
- // (curry GenericError (UpdateLoading false |> ProtocolMsg |> Cmd.ofMsg) >> DevMsg)
- nextModel, Cmd.none
- | ParseUploadedFileResponse buildingBlockTables ->
- let nextState = { currentState with UploadedFileParsed = buildingBlockTables; Loading = false }
- nextState, Cmd.none
+ | UpdateLoading next ->
+ {state with Loading = next}, Cmd.none
// ------ Protocol from Database ------
| GetAllProtocolsRequest ->
- let nextState = {currentState with Loading = true}
+ let now = System.DateTime.UtcNow
+ let olderThanOneHour = state.LastUpdated |> Option.map (fun last -> (now - last) > System.TimeSpan(1,0,0))
+ let cmd =
+ if olderThanOneHour.IsNone || olderThanOneHour.Value then GetAllProtocolsForceRequest |> ProtocolMsg |> Cmd.ofMsg else Cmd.none
+ state, cmd
+ | GetAllProtocolsForceRequest ->
+ let nextState = {state with Loading = true}
let cmd =
+ let updateRequestStateOnErrorCmd = UpdateLoading false |> ProtocolMsg |> Cmd.ofMsg
Cmd.OfAsync.either
Api.templateApi.getTemplates
()
(GetAllProtocolsResponse >> ProtocolMsg)
- (curry GenericError Cmd.none >> DevMsg)
+ (curry GenericError updateRequestStateOnErrorCmd >> DevMsg)
nextState, cmd
| GetAllProtocolsResponse protocolsJson ->
- let protocols = protocolsJson |> Array.map (ARCtrl.Template.Json.Template.fromJsonString)
+ let state = {state with Loading = false}
+ let templates =
+ try
+ protocolsJson |> ARCtrl.Json.Templates.fromJsonString |> Ok
+ with
+ | e -> Result.Error e
+ let nextState, cmd =
+ match templates with
+ | Ok t0 ->
+ let t = Array.ofSeq t0
+ let nextState = { state with LastUpdated = Some System.DateTime.UtcNow }
+ nextState, UpdateTemplates t |> ProtocolMsg |> Cmd.ofMsg
+ | Result.Error e -> state, GenericError (Cmd.none,e) |> DevMsg |> Cmd.ofMsg
+ nextState, cmd
+ | UpdateTemplates templates ->
let nextState = {
- currentState with
- ProtocolsAll = protocols
- Loading = false
+ state with
+ Templates = templates
}
nextState, Cmd.none
| SelectProtocol prot ->
let nextState = {
- currentState with
- ProtocolSelected = Some prot
+ state with
+ TemplateSelected = Some prot
}
nextState, Cmd.ofMsg (UpdatePageState <| Some Routing.Route.Protocol)
| ProtocolIncreaseTimesUsed templateId ->
@@ -58,20 +66,12 @@ module Protocol =
// Api.templateApi.increaseTimesUsedById
// templateId
// (curry GenericError Cmd.none >> DevMsg)
- currentState, Cmd.none
+ state, Cmd.none
// Client
- | UpdateLoading nextLoadingState ->
- let nextState = {
- currentState with Loading = nextLoadingState
- }
- nextState, Cmd.none
| RemoveSelectedProtocol ->
let nextState = {
- currentState with
- ProtocolSelected = None
+ state with
+ TemplateSelected = None
}
nextState, Cmd.none
- | RemoveUploadedFileParsed ->
- let nextState = {currentState with UploadedFileParsed = Array.empty}
- nextState, Cmd.none
diff --git a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs
index 59925ed7..3a298d5b 100644
--- a/src/Client/Pages/ProtocolTemplates/ProtocolView.fs
+++ b/src/Client/Pages/ProtocolTemplates/ProtocolView.fs
@@ -1,4 +1,4 @@
-module Protocol.Core
+namespace Protocol
open System
@@ -13,250 +13,31 @@ open Fable.Core.JsInterop
open Model
open Messages
open Browser.Types
-
-open Shared
-
-open OfficeInterop
-open Protocol
-
+open SpreadsheetInterface
open Messages
open Elmish
open Feliz
open Feliz.Bulma
-module TemplateFromJsonFile =
-
- let fileUploadButton (model:Model) dispatch =
- let uploadId = "UploadFiles_ElementId"
- Bulma.label [
- Bulma.fileInput [
- prop.id uploadId
- prop.type' "file";
- prop.style [style.display.none]
- prop.onChange (fun (ev: File list) ->
- let fileList = ev //: FileList = ev.target?files
-
- if fileList.Length > 0 then
- let file = fileList.Item 0 |> fun f -> f.slice()
-
- let reader = Browser.Dom.FileReader.Create()
-
- reader.onload <- fun evt ->
- let (r: byte []) = evt.target?result
- r |> ParseUploadedFileRequest |> ProtocolMsg |> dispatch
-
- reader.onerror <- fun evt ->
- curry GenericLog Cmd.none ("Error", evt.Value) |> DevMsg |> dispatch
-
- reader.readAsArrayBuffer(file)
- else
- ()
- let picker = Browser.Dom.document.getElementById(uploadId)
- // https://stackoverflow.com/questions/3528359/html-input-type-file-file-selection-event/3528376
- picker?value <- null
- )
- ]
- Bulma.button.a [
- Bulma.color.isInfo;
- Bulma.button.isFullWidth
- prop.onClick(fun e ->
- e.preventDefault()
- let getUploadElement = Browser.Dom.document.getElementById uploadId
- getUploadElement.click()
- ()
- )
- prop.text "Upload protocol"
- ]
- ]
-
- let fileUploadEle (model:Model) dispatch =
- let hasData = model.ProtocolState.UploadedFileParsed <> Array.empty
- Bulma.columns [
- Bulma.columns.isMobile
- prop.children [
- Bulma.column [
- fileUploadButton model dispatch
- ]
- if hasData then
- Bulma.column [
- Bulma.column.isNarrow
- Bulma.button.a [
- prop.onClick (fun e -> RemoveUploadedFileParsed |> ProtocolMsg |> dispatch)
- Bulma.color.isDanger
- prop.children (Html.i [prop.className "fa-solid fa-times"])
- ] |> prop.children
- ]
- ]
- ]
-
- let importToTableEle (model:Model) (dispatch:Messages.Msg -> unit) =
- let hasData = model.ProtocolState.UploadedFileParsed <> Array.empty
- Bulma.field.div [
- Bulma.field.hasAddons
- Bulma.control.div [
- Bulma.control.isExpanded
- Bulma.button.a [
- Bulma.color.isInfo
- if hasData then
- Bulma.button.isActive
- else
- Bulma.color.isDanger
- prop.disabled true
- Bulma.button.isFullWidth
- prop.onClick(fun _ ->
- Browser.Dom.window.alert("'SpreadsheetInterface.ImportFile' is not implemented")
- //SpreadsheetInterface.ImportFile model.ProtocolState.UploadedFileParsed |> InterfaceMsg |> dispatch
- )
- prop.text "Insert json"
- ] |> prop.children
- ] |> prop.children
- ]
-
- let protocolInsertElement (model:Model) dispatch =
- mainFunctionContainer [
- Bulma.field.div [
- Bulma.help [
- b [] [str "Insert tables via ISA-JSON files."]
- str " You can use Swate.Experts to create these files from existing Swate tables. "
- span [Style [Color NFDIColors.Red.Base]] [str "Only missing building blocks will be added."]
- ]
- ]
-
- Bulma.field.div [
- fileUploadEle model dispatch
- ]
-
- importToTableEle model dispatch
- ]
-
-module TemplateFromDB =
-
- let toProtocolSearchElement (model:Model) dispatch =
- Bulma.button.span [
- prop.onClick(fun _ -> UpdatePageState (Some Routing.Route.ProtocolSearch) |> dispatch)
- Bulma.color.isInfo
- Bulma.button.isFullWidth
- prop.style [style.margin (length.rem 1, length.px 0)]
- prop.text "Browse database" ]
-
- let addFromDBToTableButton (model:Messages.Model) dispatch =
- Bulma.columns [
- Bulma.columns.isMobile
- prop.children [
- Bulma.column [
- prop.children [
- Bulma.button.a [
- Bulma.color.isSuccess
- if model.ProtocolState.ProtocolSelected.IsSome then
- Bulma.button.isActive
- else
- Bulma.color.isDanger
- prop.disabled true
- Bulma.button.isFullWidth
- prop.onClick (fun _ ->
- if model.ProtocolState.ProtocolSelected.IsNone then
- failwith "No template selected!"
- // Remove existing columns
- let mutable columnsToRemove = []
- // find duplicate columns
- let tablecopy = model.ProtocolState.ProtocolSelected.Value.Table.Copy()
- for header in tablecopy.Headers do
- let containsAtIndex = model.SpreadsheetModel.ActiveTable.Headers.FindIndex(fun h -> h = header)
- if containsAtIndex >= 0 then
- columnsToRemove <- containsAtIndex::columnsToRemove
- tablecopy.RemoveColumns (Array.ofList columnsToRemove)
- let index = Spreadsheet.Sidebar.Controller.SidebarControllerAux.getNextColumnIndex model.SpreadsheetModel
- SpreadsheetInterface.JoinTable (tablecopy, Some index, Some ARCtrl.ISA.TableJoinOptions.WithUnit ) |> InterfaceMsg |> dispatch
- )
- prop.text "Add template"
- ]
- ]
- ]
- if model.ProtocolState.ProtocolSelected.IsSome then
- Bulma.column [
- Bulma.column.isNarrow
- Bulma.button.a [
- prop.onClick (fun e -> RemoveSelectedProtocol |> ProtocolMsg |> dispatch)
- Bulma.color.isDanger
- Html.i [prop.className "fa-solid fa-times"] |> prop.children
- ] |> prop.children
- ]
- ]
- ]
-
- let displaySelectedProtocolEle (model:Model) dispatch =
- [
- div [Style [OverflowX OverflowOptions.Auto; MarginBottom "1rem"]] [
- Bulma.table [
- Bulma.table.isFullWidth;
- Bulma.table.isBordered
- prop.children [
- thead [] [
- Html.tr [
- Html.th "Column"
- Html.th "Column TAN"
- //Html.th "Unit"
- //Html.th "Unit TAN"
- ]
- ]
- tbody [] [
- for column in model.ProtocolState.ProtocolSelected.Value.Table.Columns do
- //let unitOption = column.TryGetColumnUnits()
- yield
- Html.tr [
- td [] [str (column.Header.ToString())]
- td [] [str (if column.Header.IsTermColumn then column.Header.ToTerm().TermAccessionShort else "-")]
- //td [] [str (if unitOption.IsSome then insertBB.UnitTerm.Value.Name else "-")]
- //td [] [str (if insertBB.HasUnit then insertBB.UnitTerm.Value.TermAccession else "-")]
- ]
- ]
- ]
- ]
- ]
- addFromDBToTableButton model dispatch
- ]
-
-
- let showDatabaseProtocolTemplate (model:Messages.Model) dispatch =
- mainFunctionContainer [
- Bulma.field.div [
- Bulma.help [
- b [] [str "Search the database for templates."]
- str " The building blocks from these templates can be inserted into the Swate table. "
- span [Style [Color NFDIColors.Red.Base]] [str "Only missing building blocks will be added."]
- ]
- ]
- Bulma.field.div [
- toProtocolSearchElement model dispatch
- ]
-
- Bulma.field.div [
- addFromDBToTableButton model dispatch
- ]
- if model.ProtocolState.ProtocolSelected.IsSome then
- Bulma.field.div [
- yield! displaySelectedProtocolEle model dispatch
- ]
- ]
-
+type Templates =
-let fileUploadViewComponent (model:Messages.Model) dispatch =
- div [
- OnSubmit (fun e -> e.preventDefault())
- // https://keycode.info/
- OnKeyDown (fun k -> if k.key = "Enter" then k.preventDefault())
- ] [
+ static member Main (model:Messages.Model, dispatch) =
+ div [
+ OnSubmit (fun e -> e.preventDefault())
+ // https://keycode.info/
+ OnKeyDown (fun k -> if k.key = "Enter" then k.preventDefault())
+ ] [
- pageHeader "Templates"
+ pageHeader "Templates"
- // Box 1
- Bulma.label "Add template from database."
+ // Box 1
+ Bulma.label "Add template from database."
- TemplateFromDB.showDatabaseProtocolTemplate model dispatch
+ TemplateFromDB.Main(model, dispatch)
- //// Box 2
- //Bulma.label "Add template(s) from file."
+ // Box 2
+ Bulma.label "Add template(s) from file."
- //TemplateFromJsonFile.protocolInsertElement model dispatch
- ]
\ No newline at end of file
+ TemplateFromFile.Main(model, dispatch)
+ ]
\ No newline at end of file
diff --git a/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs b/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs
new file mode 100644
index 00000000..b31c2299
--- /dev/null
+++ b/src/Client/Pages/ProtocolTemplates/TemplateFromDB.fs
@@ -0,0 +1,115 @@
+namespace Protocol
+
+open Feliz
+open Feliz.Bulma
+open Messages
+open Shared
+
+type TemplateFromDB =
+
+ static member toProtocolSearchElement (model:Model) dispatch =
+ Bulma.button.span [
+ prop.onClick(fun _ -> UpdatePageState (Some Routing.Route.ProtocolSearch) |> dispatch)
+ Bulma.color.isInfo
+ Bulma.button.isFullWidth
+ prop.style [style.margin (length.rem 1, length.px 0)]
+ prop.text "Browse database" ]
+
+ static member addFromDBToTableButton (model:Messages.Model) dispatch =
+ Bulma.columns [
+ Bulma.columns.isMobile
+ prop.children [
+ Bulma.column [
+ prop.children [
+ Bulma.button.a [
+ Bulma.color.isSuccess
+ if model.ProtocolState.TemplateSelected.IsSome then
+ Bulma.button.isActive
+ else
+ Bulma.color.isDanger
+ prop.disabled true
+ Bulma.button.isFullWidth
+ prop.onClick (fun _ ->
+ if model.ProtocolState.TemplateSelected.IsNone then
+ failwith "No template selected!"
+ /// Filter out existing building blocks and keep input/output values.
+ let joinConfig = ARCtrl.TableJoinOptions.WithValues // If changed to anything else we need different logic to keep input/output values
+ let preparedTemplate = Table.selectiveTablePrepare model.SpreadsheetModel.ActiveTable model.ProtocolState.TemplateSelected.Value.Table
+ let index = Spreadsheet.BuildingBlocks.Controller.SidebarControllerAux.getNextColumnIndex model.SpreadsheetModel
+ SpreadsheetInterface.JoinTable (preparedTemplate, Some index, Some joinConfig) |> InterfaceMsg |> dispatch
+ )
+ prop.text "Add template"
+ ]
+ ]
+ ]
+ if model.ProtocolState.TemplateSelected.IsSome then
+ Bulma.column [
+ Bulma.column.isNarrow
+ Bulma.button.a [
+ prop.onClick (fun e -> Protocol.RemoveSelectedProtocol |> ProtocolMsg |> dispatch)
+ Bulma.color.isDanger
+ Html.i [prop.className "fa-solid fa-times"] |> prop.children
+ ] |> prop.children
+ ]
+ ]
+ ]
+
+ static member displaySelectedProtocolEle (model:Model) dispatch =
+ Html.div [
+ prop.style [style.overflowX.auto; style.marginBottom (length.rem 1)]
+ prop.children [
+ Bulma.table [
+ Bulma.table.isFullWidth;
+ Bulma.table.isBordered
+ prop.children [
+ Html.thead [
+ Html.tr [
+ Html.th "Column"
+ Html.th "Column TAN"
+ //Html.th "Unit"
+ //Html.th "Unit TAN"
+ ]
+ ]
+ Html.tbody [
+ for column in model.ProtocolState.TemplateSelected.Value.Table.Columns do
+ //let unitOption = column.TryGetColumnUnits()
+ yield
+ Html.tr [
+ Html.td (column.Header.ToString())
+ Html.td (if column.Header.IsTermColumn then column.Header.ToTerm().TermAccessionShort else "-")
+ //td [] [str (if unitOption.IsSome then insertBB.UnitTerm.Value.Name else "-")]
+ //td [] [str (if insertBB.HasUnit then insertBB.UnitTerm.Value.TermAccession else "-")]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+
+ static member Main(model:Messages.Model, dispatch) =
+ mainFunctionContainer [
+ Bulma.field.div [
+ Bulma.help [
+ Html.b "Search the database for templates."
+ Html.text " The building blocks from these templates can be inserted into the Swate table. "
+ Html.span [
+ color.hasTextDanger
+ prop.text "Only missing building blocks will be added."
+ ]
+ ]
+ ]
+ Bulma.field.div [
+ TemplateFromDB.toProtocolSearchElement model dispatch
+ ]
+
+ Bulma.field.div [
+ TemplateFromDB.addFromDBToTableButton model dispatch
+ ]
+ if model.ProtocolState.TemplateSelected.IsSome then
+ Bulma.field.div [
+ TemplateFromDB.displaySelectedProtocolEle model dispatch
+ ]
+ Bulma.field.div [
+ TemplateFromDB.addFromDBToTableButton model dispatch
+ ]
+ ]
diff --git a/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs
new file mode 100644
index 00000000..1a36ea71
--- /dev/null
+++ b/src/Client/Pages/ProtocolTemplates/TemplateFromFile.fs
@@ -0,0 +1,178 @@
+namespace Protocol
+
+open Fable.Core
+open Fable.React
+open Fable.React.Props
+//open Fable.Core.JS
+open Fable.Core.JsInterop
+
+open Model
+open Messages
+open Browser.Types
+open SpreadsheetInterface
+open Messages
+open Elmish
+
+open Feliz
+open Feliz.Bulma
+open Shared
+open ARCtrl
+
+type private TemplateFromFileState = {
+ /// User select type to upload
+ FileType: ArcFilesDiscriminate
+ /// User selects json type to upload
+ JsonFormat: JsonExportFormat
+ UploadedFile: ArcFiles option
+ Loading: bool
+} with
+ static member init () =
+ {
+ FileType = ArcFilesDiscriminate.Assay
+ JsonFormat = JsonExportFormat.ROCrate
+ UploadedFile = None
+ Loading = false
+ }
+
+module private Helper =
+
+ let upload (uploadId: string) (state: TemplateFromFileState) setState dispatch (ev: File list) =
+ let fileList = ev //: FileList = ev.target?files
+
+ if fileList.Length > 0 then
+ let file = fileList.Item 0 |> fun f -> f.slice()
+
+ let reader = Browser.Dom.FileReader.Create()
+
+ reader.onload <- fun evt ->
+ let (r: string) = evt.target?result
+ async {
+ setState {state with Loading = true}
+ let! af = Spreadsheet.IO.Json.readFromJson state.FileType state.JsonFormat r |> Async.AwaitPromise
+ setState {state with UploadedFile = Some af; Loading = false}
+ } |> Async.StartImmediate
+
+ reader.onerror <- fun evt ->
+ curry GenericLog Cmd.none ("Error", evt.Value) |> DevMsg |> dispatch
+
+ reader.readAsText(file)
+ else
+ ()
+ let picker = Browser.Dom.document.getElementById(uploadId)
+ // https://stackoverflow.com/questions/3528359/html-input-type-file-file-selection-event/3528376
+ picker?value <- null
+
+type TemplateFromFile =
+
+ static member private fileUploadButton (state:TemplateFromFileState, setState: TemplateFromFileState -> unit, dispatch) =
+ let uploadId = "UploadFiles_ElementId"
+ Bulma.label [
+ Bulma.fileInput [
+ prop.id uploadId
+ prop.type' "file";
+ prop.style [style.display.none]
+ prop.onChange (fun (ev: File list) ->
+ Helper.upload uploadId state setState dispatch ev
+ )
+ ]
+ Bulma.button.a [
+ Bulma.color.isInfo;
+ Bulma.button.isFullWidth
+ prop.onClick(fun e ->
+ e.preventDefault()
+ let getUploadElement = Browser.Dom.document.getElementById uploadId
+ getUploadElement.click()
+ )
+ prop.text "Upload protocol"
+ ]
+ ]
+
+ static member private SelectorButton<'a when 'a : equality> (targetselector: 'a, selector: 'a, setSelector: 'a -> unit, ?isDisabled) =
+ Bulma.button.button [
+ if isDisabled.IsSome then
+ prop.disabled isDisabled.Value
+ prop.style [style.flexGrow 1]
+ if (targetselector = selector) then
+ color.isSuccess
+ button.isSelected
+ prop.onClick (fun _ -> setSelector targetselector)
+ prop.text (string targetselector)
+ ]
+
+ []
+ static member Main(model: Messages.Model, dispatch) =
+ let state, setState = React.useState(TemplateFromFileState.init)
+ let af = React.useRef (
+ let a = ArcAssay.init("My Assay")
+ let t1 = a.InitTable("Template Table 1")
+ t1.AddColumns([|
+ CompositeColumn.create(CompositeHeader.Input IOType.Source, [| for i in 1 .. 5 do sprintf "Source _ %i" i |> CompositeCell.FreeText |])
+ CompositeColumn.create(CompositeHeader.Output IOType.Sample, [| for i in 1 .. 5 do sprintf "Sample _ %i" i |> CompositeCell.FreeText |])
+ CompositeColumn.create(CompositeHeader.Component (OntologyAnnotation("instrument model", "MS", "MS:19283")), [| for i in 1 .. 5 do OntologyAnnotation("SCIEX instrument model", "MS", "MS:21387189237") |> CompositeCell.Term |])
+ CompositeColumn.create(CompositeHeader.Factor (OntologyAnnotation("temperature", "UO", "UO:21387")), [| for i in 1 .. 5 do CompositeCell.createUnitized("", OntologyAnnotation("degree celcius", "UO", "UO:21387189237")) |])
+ |])
+ let t2 = a.InitTable("Template Table 2")
+ t2.AddColumns([|
+ CompositeColumn.create(CompositeHeader.Input IOType.Source, [| for i in 1 .. 5 do sprintf "Source2 _ %i" i |> CompositeCell.FreeText |])
+ CompositeColumn.create(CompositeHeader.Output IOType.Sample, [| for i in 1 .. 5 do sprintf "Sample2 _ %i" i |> CompositeCell.FreeText |])
+ CompositeColumn.create(CompositeHeader.Component (OntologyAnnotation("instrument", "MS", "MS:19283")), [| for i in 1 .. 5 do OntologyAnnotation("SCIEX instrument model", "MS", "MS:21387189237") |> CompositeCell.Term |])
+ CompositeColumn.create(CompositeHeader.Factor (OntologyAnnotation("temperature", "UO", "UO:21387")), [| for i in 1 .. 5 do CompositeCell.createUnitized("", OntologyAnnotation("degree celcius", "UO", "UO:21387189237")) |])
+ |])
+ let af = ArcFiles.Assay a
+ af
+ )
+ let setJsonFormat = fun x -> setState { state with JsonFormat = x }
+ let setFileType = fun x -> setState { state with FileType = x }
+ let fileTypeDisabled (ft: ArcFilesDiscriminate) =
+ match state.JsonFormat, ft with
+ // isa and rocrate do not support template
+ | JsonExportFormat.ROCrate, ArcFilesDiscriminate.Template
+ | JsonExportFormat.ISA, ArcFilesDiscriminate.Template -> true
+ | _ -> false
+ let jsonFormatDisabled (jf: JsonExportFormat) =
+ match state.FileType ,jf with
+ // template does not support isa and rocrate
+ | ArcFilesDiscriminate.Template, JsonExportFormat.ROCrate
+ | ArcFilesDiscriminate.Template, JsonExportFormat.ISA -> true
+ | _ -> false
+ mainFunctionContainer [
+ // modal!
+ match state.UploadedFile with
+ | Some af ->
+ Modals.SelectiveImportModal.Main af model.SpreadsheetModel dispatch (fun _ -> TemplateFromFileState.init() |> setState)
+ | None -> Html.none
+ //Modals.SelectiveImportModal.Main af.current model.SpreadsheetModel dispatch (fun _ -> TemplateFromFileState.init() |> setState)
+ Bulma.field.div [
+ Bulma.help [
+ b [] [str "Import JSON files."]
+ str " You can use \"Json Export\" to create these files from existing Swate tables. "
+ ]
+ ]
+ Bulma.field.div [
+ Bulma.buttons [
+ buttons.hasAddons
+ prop.children [
+ JsonExportFormat.ROCrate |> fun jef -> TemplateFromFile.SelectorButton (jef, state.JsonFormat, setJsonFormat, jsonFormatDisabled jef)
+ JsonExportFormat.ISA |> fun jef -> TemplateFromFile.SelectorButton (jef, state.JsonFormat, setJsonFormat, jsonFormatDisabled jef)
+ JsonExportFormat.ARCtrl |> fun jef -> TemplateFromFile.SelectorButton (jef, state.JsonFormat, setJsonFormat, jsonFormatDisabled jef)
+ JsonExportFormat.ARCtrlCompressed |> fun jef -> TemplateFromFile.SelectorButton (jef, state.JsonFormat, setJsonFormat, jsonFormatDisabled jef)
+ ]
+ ]
+ ]
+
+ Bulma.field.div [
+ Bulma.buttons [
+ buttons.hasAddons
+ prop.children [
+ ArcFilesDiscriminate.Assay |> fun ft -> TemplateFromFile.SelectorButton (ft, state.FileType, setFileType, fileTypeDisabled ft)
+ ArcFilesDiscriminate.Study |> fun ft -> TemplateFromFile.SelectorButton (ft, state.FileType, setFileType, fileTypeDisabled ft)
+ ArcFilesDiscriminate.Investigation |> fun ft -> TemplateFromFile.SelectorButton (ft, state.FileType, setFileType, fileTypeDisabled ft)
+ ArcFilesDiscriminate.Template |> fun ft -> TemplateFromFile.SelectorButton (ft, state.FileType, setFileType, fileTypeDisabled ft)
+ ]
+ ]
+ ]
+
+ Bulma.field.div [
+ TemplateFromFile.fileUploadButton(state, setState, dispatch)
+ ]
+ ]
\ No newline at end of file
diff --git a/src/Client/Pages/Settings/SettingsView.fs b/src/Client/Pages/Settings/SettingsView.fs
index d60095f4..bea788a8 100644
--- a/src/Client/Pages/Settings/SettingsView.fs
+++ b/src/Client/Pages/Settings/SettingsView.fs
@@ -119,9 +119,9 @@ let settingsViewComponent (model:Model) dispatch =
//Label.label [Label.Props [Style [Color model.SiteStyleState.ColorMode.Accent]]] [str "Advanced Settings"]
//customXmlSettings model dispatch
- Bulma.label "Advanced Settings"
- if model.PageState.IsExpert then
- swateCore model dispatch
- else
- swateExperts model dispatch
+ //Bulma.label "Advanced Settings"
+ //if model.PageState.IsExpert then
+ // swateCore model dispatch
+ //else
+ // swateExperts model dispatch
]
\ No newline at end of file
diff --git a/src/Client/Pages/TemplateMetadata/TemplateMetadata.fs b/src/Client/Pages/TemplateMetadata/TemplateMetadata.fs
deleted file mode 100644
index 2e68bab2..00000000
--- a/src/Client/Pages/TemplateMetadata/TemplateMetadata.fs
+++ /dev/null
@@ -1,59 +0,0 @@
-module TemplateMetadata.Core
-
-open Fable.React
-open Fable.React.Props
-open Fable.Core.JsInterop
-open Elmish
-
-open Shared
-
-open ExcelColors
-open Model
-open Messages
-
-open TemplateMetadata
-
-open TemplateTypes
-
-let update (msg:Msg) (currentModel: Messages.Model) : Messages.Model * Cmd =
- match msg with
- | CreateTemplateMetadataWorksheet metadataFieldsOpt ->
- let cmd =
- Cmd.OfPromise.either
- OfficeInterop.TemplateMetadataFunctions.createTemplateMetadataWorksheet
- (metadataFieldsOpt)
- (curry GenericLog Cmd.none >> DevMsg)
- (curry GenericError Cmd.none >> DevMsg)
- currentModel, cmd
-
-open Messages
-open Feliz
-open Feliz.Bulma
-
-let defaultMessageEle (model:Model) dispatch =
-
- mainFunctionContainer [
- Bulma.field.div [
- Bulma.help [
- str "Use this function to create a prewritten template metadata worksheet."
- ]
- ]
- Bulma.field.div [
- Bulma.button.a [
- prop.onClick(fun e -> CreateTemplateMetadataWorksheet Metadata.root |> TemplateMetadataMsg |> dispatch)
- Bulma.button.isFullWidth
- Bulma.color.isInfo
- prop.text "Create metadata"
- ]
- ]
- ]
-
-let newNameMainElement (model:Messages.Model) dispatch =
- Bulma.content [
-
- Bulma.label "Template Metadata"
-
- Bulma.label "Create template metadata worksheet"
-
- defaultMessageEle model dispatch
- ]
\ No newline at end of file
diff --git a/src/Client/Pages/TermSearch/TermSearchView.fs b/src/Client/Pages/TermSearch/TermSearchView.fs
index c8cf860c..e6aeac63 100644
--- a/src/Client/Pages/TermSearch/TermSearchView.fs
+++ b/src/Client/Pages/TermSearch/TermSearchView.fs
@@ -22,7 +22,7 @@ let update (termSearchMsg: TermSearch.Msg) (currentState:TermSearch.Model) : Ter
open Feliz
open Feliz.Bulma
-open ARCtrl.ISA
+open ARCtrl
open Fable.Core.JsInterop
/// "Fill selected cells with this term" - button //
@@ -109,7 +109,7 @@ let Main (model:Messages.Model, dispatch) =
mainFunctionContainer [
Bulma.field.div [
- Components.TermSearch.Input(setTerm, dispatch, fullwidth=true, size=Bulma.input.isLarge, ?parent'=model.TermSearchState.ParentTerm, showAdvancedSearch=true)
+ Components.TermSearch.Input(setTerm, fullwidth=true, size=Bulma.input.isLarge, ?parent=model.TermSearchState.ParentTerm, advancedSearchDispatch=dispatch)
]
addButton(model, dispatch)
]
diff --git a/src/Client/React.useListener.fs b/src/Client/React.useListener.fs
new file mode 100644
index 00000000..5919f490
--- /dev/null
+++ b/src/Client/React.useListener.fs
@@ -0,0 +1,107 @@
+[]
+module ReactHelper
+
+// https://github.com/Shmew/Feliz.UseListener/blob/master/src/Feliz.UseListener/Listener.fs
+
+open Browser.Types
+open Browser.Dom
+open Fable.Core
+open Fable.Core.JsInterop
+open Fable.Core
+open Feliz
+open System.ComponentModel
+
+[]
+module Impl =
+ []
+ let isWindowDefined () : bool = jsNative
+
+ []
+ let isWindowListenerFunction () : bool = jsNative
+
+ []
+ let definePassive (updater: unit -> unit) : JS.PropertyDescriptor = jsNative
+
+ let allowsPassiveEvents =
+ let mutable passive = false
+
+ try
+ if isWindowDefined() && isWindowListenerFunction() then
+ let options =
+ jsOptions(fun o ->
+ o.passive <- true
+ )
+
+ window.addEventListener("testPassiveEventSupport", ignore, options)
+ window.removeEventListener("testPassiveEventSupport", ignore)
+ with _ -> ()
+
+ passive
+
+ let defaultPassive = jsOptions(fun o -> o.passive <- true)
+
+ let adjustPassive (maybeOptions: AddEventListenerOptions option) =
+ maybeOptions
+ |> Option.map (fun options ->
+ if options.passive && not allowsPassiveEvents then
+ jsOptions(fun o ->
+ o.capture <- options.capture
+ o.once <- options.once
+ o.passive <- false
+ )
+ else options)
+
+ let createRemoveOptions (maybeOptions: AddEventListenerOptions option) =
+ maybeOptions
+ |> Option.bind (fun options ->
+ if options.capture then
+ Some (jsOptions(fun o -> o.capture <- true))
+ else None)
+
+[]
+module React =
+ []
+ type useListener =
+ static member inline on (eventType: string, action: #Event -> unit, ?options: AddEventListenerOptions) =
+ let addOptions = React.useMemo((fun () -> Impl.adjustPassive options), [| options |])
+ let removeOptions = React.useMemo((fun () -> Impl.createRemoveOptions options), [| options |])
+ let fn = React.useMemo((fun () -> unbox<#Event> >> action), [| action |])
+
+ let listener = React.useCallbackRef(fun () ->
+ match addOptions with
+ | Some options ->
+ document.addEventListener(eventType, fn, options)
+ | None -> document.addEventListener(eventType, fn)
+
+ React.createDisposable(fun () ->
+ match removeOptions with
+ | Some options -> document.removeEventListener(eventType, fn, options)
+ | None -> document.removeEventListener(eventType, fn)
+ )
+ )
+
+ React.useEffect(listener)
+
+ []
+ type useElementListener =
+ static member inline on (elemRef: IRefValue<#HTMLElement option>, eventType: string, action: #Event -> unit, ?options: AddEventListenerOptions) =
+ let addOptions = React.useMemo((fun () -> Impl.adjustPassive options), [| options |])
+ let removeOptions = React.useMemo((fun () -> Impl.createRemoveOptions options), [| options |])
+ let fn = React.useMemo((fun () -> unbox<#Event> >> action), [| action |])
+
+ let listener = React.useCallbackRef(fun () ->
+ elemRef.current |> Option.iter(fun elem ->
+ match addOptions with
+ | Some options -> elem.addEventListener(eventType, fn, options)
+ | None -> elem.addEventListener(eventType, fn)
+ )
+
+ React.createDisposable(fun () ->
+ elemRef.current |> Option.iter(fun elem ->
+ match removeOptions with
+ | Some options -> elem.removeEventListener(eventType, fn, options)
+ | None -> elem.removeEventListener(eventType, fn)
+ ))
+ )
+
+ React.useEffect(listener)
\ No newline at end of file
diff --git a/src/Client/Routing.fs b/src/Client/Routing.fs
index 6985fae3..af5375f1 100644
--- a/src/Client/Routing.fs
+++ b/src/Client/Routing.fs
@@ -14,9 +14,7 @@ type Route =
| Info
| Protocol
| ProtocolSearch
-| Dag /// Directed Acylclic Graph
| JsonExport
-| TemplateMetadata
| ActivityLog
| Settings
| NotFound
@@ -28,9 +26,7 @@ type Route =
| Route.FilePicker -> "File Picker"
| Route.Protocol -> "Templates"
| Route.ProtocolSearch -> "Template Search"
- | Route.Dag -> "Directed Acylclic Graph"
| Route.JsonExport -> "Json Export"
- | Route.TemplateMetadata -> "Template Metadata"
| Route.Info -> "Info"
| Route.ActivityLog -> "Activity Log"
| Route.Settings -> "Settings"
@@ -38,7 +34,7 @@ type Route =
member this.isExpert =
match this with
- | Route.TemplateMetadata | Route.JsonExport -> true
+ | Route.JsonExport -> true
| _ -> false
member this.isActive(currentRoute: Route) =
@@ -64,14 +60,10 @@ type Route =
createElem [ Html.i [prop.className "fa-solid fa-circle-plus" ];Html.i [prop.className "fa-solid fa-table" ]] p.toStringRdbl
| Route.ProtocolSearch ->
createElem [ Html.i [prop.className "fa-solid fa-table" ]; Html.i [prop.className "fa-solid fa-magnifying-glass" ]] p.toStringRdbl
- | Route.Dag ->
- createElem [ Html.i [prop.className "fa-solid fa-diagram-project" ]] p.toStringRdbl
| Route.JsonExport ->
createElem [ Html.i [prop.className "fa-solid fa-file-export" ]] p.toStringRdbl
- | Route.TemplateMetadata ->
- createElem [ Html.i [prop.className "fa-solid fa-circle-plus" ];Html.i [prop.className "fa-solid fa-table" ]] p.toStringRdbl
| Route.FilePicker ->
- createElem [ Html.i [prop.className "fa-solid fa-upload" ]] p.toStringRdbl
+ createElem [ Html.i [prop.className "fa-solid fa-file-signature" ]] p.toStringRdbl
| Route.ActivityLog ->
createElem [ Html.i [prop.className "fa-solid fa-timeline" ]] p.toStringRdbl
| Route.Info ->
@@ -96,9 +88,7 @@ module Routing =
map Route.Info (s "Info")
map Route.Protocol (s "ProtocolInsert")
map Route.ProtocolSearch (s "Protocol" > s "Search")
- map Route.Dag (s "Dag")
map Route.JsonExport (s "Experts" > s "JsonExport")
- map Route.TemplateMetadata (s "Experts" > s "TemplateMetadata")
map Route.ActivityLog (s "ActivityLog")
map Route.Settings (s "Settings")
map Route.NotFound (s "NotFound")
diff --git a/src/Client/SharedComponents/ClickOutsideHandler.fs b/src/Client/SharedComponents/ClickOutsideHandler.fs
index db8b5743..7f635c1f 100644
--- a/src/Client/SharedComponents/ClickOutsideHandler.fs
+++ b/src/Client/SharedComponents/ClickOutsideHandler.fs
@@ -1,5 +1,6 @@
namespace Components
+open Feliz
open Fable.Core
open Browser.Types
open Fable.Core.JsInterop
@@ -8,9 +9,26 @@ type ClickOutsideHandler =
static member AddListener(containerId: string, clickedOutsideEvent: Event -> unit) =
let rec closeEvent = fun (e: Event) ->
+ let rmv = fun () -> Browser.Dom.document.removeEventListener("click", closeEvent)
let dropdown = Browser.Dom.document.getElementById(containerId)
- let isClickedInsideDropdown : bool = dropdown?contains(e.target)
- if not isClickedInsideDropdown then
- clickedOutsideEvent e
- Browser.Dom.document.removeEventListener("click", closeEvent)
- Browser.Dom.document.addEventListener("click", closeEvent)
\ No newline at end of file
+ if isNull dropdown then
+ rmv()
+ else
+ let isClickedInsideDropdown : bool = dropdown?contains(e.target)
+ if not isClickedInsideDropdown then
+ clickedOutsideEvent e
+ rmv()
+ Browser.Dom.document.addEventListener("click", closeEvent)
+
+ static member AddListener(element: IRefValue, clickedOutsideEvent: Event -> unit) =
+ let rec closeEvent = fun (e: Event) ->
+ let rmv = fun () -> Browser.Dom.document.removeEventListener("click", closeEvent)
+ let dropdown = element.current
+ if dropdown.IsNone then
+ rmv()
+ else
+ let isClickedInsideDropdown : bool = dropdown?contains(e.target)
+ if not isClickedInsideDropdown then
+ clickedOutsideEvent e
+ rmv()
+ Browser.Dom.document.addEventListener("click", closeEvent)
diff --git a/src/Client/SharedComponents/QuickAccessButton.fs b/src/Client/SharedComponents/QuickAccessButton.fs
index 3a82add8..471ffe7b 100644
--- a/src/Client/SharedComponents/QuickAccessButton.fs
+++ b/src/Client/SharedComponents/QuickAccessButton.fs
@@ -30,9 +30,9 @@ type QuickAccessButton = {
prop.children [
Bulma.button.a [
prop.tabIndex (if isDisabled then -1 else 0)
+ prop.className "myNavbarButton"
yield! this.ButtonProps
prop.disabled isDisabled
- prop.className "myNavbarButton"
prop.onClick this.Msg
prop.children [
Html.div [
diff --git a/src/Client/SharedComponents/TermSearchInput.fs b/src/Client/SharedComponents/TermSearchInput.fs
index 0df72acb..ddaa4b8b 100644
--- a/src/Client/SharedComponents/TermSearchInput.fs
+++ b/src/Client/SharedComponents/TermSearchInput.fs
@@ -3,7 +3,7 @@
open Feliz
open Feliz.Bulma
open Browser.Types
-open ARCtrl.ISA
+open ARCtrl
open Shared
open Fable.Core.JsInterop
@@ -28,7 +28,7 @@ module TermSearchAux =
let searchByName(query: string, setResults: TermTypes.Term [] -> unit) =
async {
- let! terms = Api.ontology.searchTerms {|limit = 5; ontologies = []; query=query|}
+ let! terms = Api.ontology.searchTerms {|limit = 10; ontologies = []; query=query|}
setResults terms
}
@@ -89,6 +89,12 @@ module TermSearchAux =
setSearchNameState <| SearchState.init()
debouncel debounceStorage "TermSearch" debounceTimer setLoading queryDB ()
+ let dsetter (inp: OntologyAnnotation option, setter, debounceStorage: System.Collections.Generic.Dictionary, setLoading: bool -> unit, debounceSetter: int option) =
+ if debounceSetter.IsSome then
+ debouncel debounceStorage "SetterDebounce" debounceSetter.Value setLoading setter inp
+ else
+ setter inp
+
module Components =
let termSeachNoResults = [
@@ -211,12 +217,34 @@ module TermSearchAux =
]
]
]
-
+
open TermSearchAux
open Fable.Core.JsInterop
type TermSearch =
+ static member ToggleSearchContainer (element: ReactElement, ref: IRefValue, searchable: bool, searchableSetter: bool -> unit) =
+ Bulma.field.div [
+ prop.style [style.flexGrow 1; style.position.relative]
+ prop.ref ref
+ Bulma.field.hasAddons
+ prop.children [
+ element
+ Bulma.control.p [
+ prop.style [style.marginRight 0]
+ prop.children [
+ Bulma.button.a [
+ prop.style [style.borderWidth 0; style.borderRadius 0]
+ if not searchable then Bulma.color.hasTextGreyLight
+ Bulma.button.isInverted
+ prop.onClick(fun _ -> searchableSetter (not searchable))
+ prop.children [Bulma.icon [Html.i [prop.className "fa-solid fa-magnifying-glass"]]]
+ ]
+ ]
+ ]
+ ]
+ ]
+
[]
static member TermSelectItem (term: TermTypes.Term, setTerm, ?isDirectedSearchResult: bool) =
let isDirectedSearchResult = defaultArg isDirectedSearchResult false
@@ -230,7 +258,8 @@ type TermSearch =
]
]
- static member TermSelectArea (id: string, searchNameState: SearchState, searchTreeState: SearchState, setTerm: TermTypes.Term option -> unit, show: bool, width: Styles.ICssUnit, alignRight) =
+ []
+ static member TermSelectArea (id: string, searchNameState: SearchState, searchTreeState: SearchState, setTerm: TermTypes.Term option -> unit, show: bool) =
let searchesAreComplete = searchNameState.SearchIs = SearchIs.Done && searchTreeState.SearchIs = SearchIs.Done
let foundInBoth (term:TermTypes.Term) =
(searchTreeState.Results |> Array.contains term)
@@ -260,7 +289,7 @@ type TermSearch =
Html.div [
prop.id id
prop.classes ["term-select-area"; if not show then "is-hidden";]
- prop.style [style.width width; if alignRight then style.right 0]
+ prop.style [style.width (length.perc 100); style.top (length.perc 100)]
prop.children [
yield! matchSearchState searchNameState false
yield! matchSearchState searchTreeState true
@@ -269,27 +298,32 @@ type TermSearch =
[]
static member Input (
- setter: OntologyAnnotation option -> unit, dispatch,
- ?input: OntologyAnnotation, ?parent': OntologyAnnotation,
- ?showAdvancedSearch: bool,
- ?fullwidth: bool, ?size: IReactProperty, ?isExpanded: bool, ?dropdownWidth: Styles.ICssUnit, ?alignRight: bool, ?displayParent: bool)
+ setter: OntologyAnnotation option -> unit,
+ ?input: OntologyAnnotation, ?parent: OntologyAnnotation,
+ ?debounceSetter: int, ?searchableToggle: bool,
+ ?advancedSearchDispatch: Messages.Msg -> unit,
+ ?portalTermSelectArea: HTMLElement,
+ ?onBlur: Event -> unit, ?onEscape: KeyboardEvent -> unit, ?onEnter: KeyboardEvent -> unit,
+ ?autofocus: bool, ?fullwidth: bool, ?size: IReactProperty, ?isExpanded: bool, ?displayParent: bool, ?borderRadius: int, ?border: string, ?minWidth: Styles.ICssUnit)
=
+ let searchableToggle = defaultArg searchableToggle false
+ let autofocus = defaultArg autofocus false
let displayParent = defaultArg displayParent true
- let alignRight = defaultArg alignRight false
- let dropdownWidth = defaultArg dropdownWidth (length.perc 100)
let isExpanded = defaultArg isExpanded false
- let showAdvancedSearch = defaultArg showAdvancedSearch false
let advancedSearchActive, setAdvancedSearchActive = React.useState(false)
let fullwidth = defaultArg fullwidth false
let loading, setLoading = React.useState(false)
let state, setState = React.useState(input)
+ let searchable, setSearchable = React.useState(true)
let searchNameState, setSearchNameState = React.useState(SearchState.init)
let searchTreeState, setSearchTreeState = React.useState(SearchState.init)
let isSearching, setIsSearching = React.useState(false)
- let debounceStorage, setdebounceStorage = React.useState(newDebounceStorage)
- let parent, setParent = React.useState(parent')
+ let debounceStorage = React.useRef(newDebounceStorage())
+ let ref = React.useElementRef()
+ if onBlur.IsSome then React.useLayoutEffectOnce(fun _ -> ClickOutsideHandler.AddListener (ref, onBlur.Value))
+ React.useEffect((fun () -> setState input), dependencies=[|box input|])
let stopSearch() =
- debounceStorage.Clear()
+ debounceStorage.current.Remove("TermSearch") |> ignore
setLoading false
setIsSearching false
setSearchTreeState {searchTreeState with SearchIs = SearchIs.Idle}
@@ -299,50 +333,83 @@ type TermSearch =
setState oaOpt
setter oaOpt
setIsSearching false
- let startSearch(queryString: string option) =
- let oaOpt = queryString |> Option.map (fun s -> OntologyAnnotation.fromString(s) )
- setter oaOpt
- setState oaOpt
+ let startSearch() =
+ setLoading true
setSearchNameState <| SearchState.init()
setSearchTreeState <| SearchState.init()
setIsSearching true
- React.useEffect((fun () -> setParent parent'), dependencies=[|box parent'|]) // careful, check console. might result in maximum dependency depth error.
+ let registerChange(queryString: string option) =
+ let oaOpt = queryString |> Option.map (fun s -> OntologyAnnotation(s) )
+ dsetter(oaOpt,setter,debounceStorage.current,setLoading,debounceSetter)
+ setState oaOpt
Bulma.control.div [
if isExpanded then Bulma.control.isExpanded
if size.IsSome then size.Value
- Bulma.control.hasIconsLeft
+ if not searchableToggle then Bulma.control.hasIconsLeft
Bulma.control.hasIconsRight
+ if not searchableToggle then prop.ref ref
prop.style [
if fullwidth then style.flexGrow 1;
+ if minWidth.IsSome then style.minWidth minWidth.Value
]
if loading then Bulma.control.isLoading
prop.children [
Bulma.input.text [
+ prop.autoFocus autofocus
+ prop.style [
+ if borderRadius.IsSome then style.borderRadius borderRadius.Value
+ if border.IsSome then style.custom("border", border.Value)
+ ]
if size.IsSome then size.Value
if state.IsSome then prop.valueOrDefault state.Value.NameText
+ prop.onMouseDown(fun e -> e.stopPropagation())
prop.onDoubleClick(fun e ->
let s : string = e.target?value
if s.Trim() = "" && parent.IsSome && parent.Value.TermAccessionShort <> "" then // trigger get all by parent search
- startSearch(None)
- allByParentSearch(parent.Value, setSearchTreeState, setLoading, stopSearch, debounceStorage, 0)
+ if searchable then
+ startSearch()
+ allByParentSearch(parent.Value, setSearchTreeState, setLoading, stopSearch, debounceStorage.current, 0)
elif s.Trim() <> "" then
- startSearch (Some s)
- mainSearch(s, parent, setSearchNameState, setSearchTreeState, setLoading, stopSearch, debounceStorage, 0)
+ if searchable then
+ startSearch ()
+ mainSearch(s, parent, setSearchNameState, setSearchTreeState, setLoading, stopSearch, debounceStorage.current, 0)
else
()
)
prop.onChange(fun (s: string) ->
if s.Trim() = "" then
- startSearch(None)
- stopSearch()
+ registerChange(None)
+ stopSearch() // When deleting text this should stop search from completing
else
- startSearch (Some s)
- mainSearch(s, parent, setSearchNameState, setSearchTreeState, setLoading, stopSearch, debounceStorage, 1000)
+ registerChange(Some s)
+ if searchable then
+ startSearch()
+ mainSearch(s, parent, setSearchNameState, setSearchTreeState, setLoading, stopSearch, debounceStorage.current, 1000)
+ )
+ prop.onKeyDown(fun e ->
+ e.stopPropagation()
+ match e.which with
+ | 27. -> //escape
+ if onEscape.IsSome then onEscape.Value e
+ stopSearch()
+ | 13. -> //enter
+ if onEnter.IsSome then onEnter.Value e
+ | 9. -> //tab
+ if searchableToggle then
+ e.preventDefault()
+ setSearchable (not searchable)
+ | _ -> ()
+
)
- prop.onKeyDown(key.escape, fun _ -> stopSearch())
]
- TermSearch.TermSelectArea (SelectAreaID, searchNameState, searchTreeState, selectTerm, isSearching, dropdownWidth, alignRight)
- Components.searchIcon
+ let TermSelectArea = TermSearch.TermSelectArea (SelectAreaID, searchNameState, searchTreeState, selectTerm, isSearching)
+ if portalTermSelectArea.IsSome then
+ ReactDOM.createPortal(TermSelectArea,portalTermSelectArea.Value)
+ elif ref.current.IsSome then
+ ReactDOM.createPortal(TermSelectArea,ref.current.Value)
+ else
+ TermSelectArea
+ if not searchableToggle then Components.searchIcon
if state.IsSome && state.Value.Name.IsSome && state.Value.TermAccessionNumber.IsSome && not isSearching then Components.verifiedIcon
// Optional elements
Html.div [
@@ -353,11 +420,11 @@ type TermSearch =
Html.span "Parent: "
Html.span $"{parent.Value.NameText}, {parent.Value.TermAccessionShort}"
]
- if showAdvancedSearch then
+ if advancedSearchDispatch.IsSome then
Components.AdvancedSearch.Main(advancedSearchActive, setAdvancedSearchActive, (fun t ->
setAdvancedSearchActive false
Some t |> selectTerm),
- dispatch
+ advancedSearchDispatch.Value
)
Html.a [
prop.onClick(fun e -> e.preventDefault(); e.stopPropagation(); setAdvancedSearchActive true)
@@ -368,3 +435,12 @@ type TermSearch =
]
]
]
+ |> fun main ->
+ if searchableToggle then
+ TermSearch.ToggleSearchContainer(main, ref, searchable, setSearchable)
+ else
+ main
+
+
+ //static member InputWithSearchToggle() =
+
\ No newline at end of file
diff --git a/src/Client/SidebarComponents/Navbar.fs b/src/Client/SidebarComponents/Navbar.fs
index 9ae30c8b..0bc9471f 100644
--- a/src/Client/SidebarComponents/Navbar.fs
+++ b/src/Client/SidebarComponents/Navbar.fs
@@ -195,6 +195,7 @@ let NavbarComponent (model : Model) (dispatch : Msg -> unit) (sidebarsize: Model
span [AriaHidden true] [ ]
span [AriaHidden true] [ ]
span [AriaHidden true] [ ]
+ span [AriaHidden true] [ ]
]
]
]
@@ -214,6 +215,13 @@ let NavbarComponent (model : Model) (dispatch : Msg -> unit) (sidebarsize: Model
Html.i [prop.className "fa-brands fa-twitter"; prop.style [style.color "#1DA1F2"; style.marginLeft 2]]
]
]
+ Bulma.navbarItem.a [
+ prop.onClick (fun e ->
+ setState {state with BurgerActive = not state.BurgerActive}
+ UpdatePageState (Some Routing.Route.Info) |> dispatch
+ )
+ prop.text Routing.Route.Info.toStringRdbl
+ ]
Bulma.navbarItem.a [
prop.href Shared.URLs.SwateWiki ;
prop.target "_Blank";
diff --git a/src/Client/SidebarComponents/ResponsiveFA.fs b/src/Client/SidebarComponents/ResponsiveFA.fs
index 32bd50bb..a14d1faf 100644
--- a/src/Client/SidebarComponents/ResponsiveFA.fs
+++ b/src/Client/SidebarComponents/ResponsiveFA.fs
@@ -65,18 +65,16 @@ let triggerResponsiveReturnEle id =
let responsiveReturnEle id (fa: string) (faToggled: string) =
let notTriggeredId = createNonTriggeredId id
let triggeredId = createTriggeredId id
- div [Style [
- Position PositionOptions.Relative
- ]] [
+ Bulma.icon [
Html.i [
prop.style [
- style.position.absolute
- style.top 0
- style.left 0
- style.display.block
- style.custom("transition", "opacity 0.25s, transform 0.25s")
- style.opacity 1
- ]
+ style.position.absolute
+ //style.top 0
+ //style.left 0
+ style.display.block
+ style.custom("transition", "opacity 0.25s, transform 0.25s")
+ style.opacity 1
+ ]
prop.id notTriggeredId
prop.onTransitionEnd (fun e ->
Fable.Core.JS.setTimeout (fun () ->
@@ -89,8 +87,8 @@ let responsiveReturnEle id (fa: string) (faToggled: string) =
Html.i [
prop.style [
style.position.absolute
- style.top 0
- style.left 0
+ //style.top 0
+ //style.left 0
style.display.block
style.custom("transition", "opacity 0.25s, transform 0.25s")
style.opacity 0
diff --git a/src/Client/Spreadsheet/Sidebar.Controller.fs b/src/Client/Spreadsheet/BuildingBlocks.Controller.fs
similarity index 74%
rename from src/Client/Spreadsheet/Sidebar.Controller.fs
rename to src/Client/Spreadsheet/BuildingBlocks.Controller.fs
index 7d7d9ce2..393ef38d 100644
--- a/src/Client/Spreadsheet/Sidebar.Controller.fs
+++ b/src/Client/Spreadsheet/BuildingBlocks.Controller.fs
@@ -1,26 +1,15 @@
-module Spreadsheet.Sidebar.Controller
+module Spreadsheet.BuildingBlocks.Controller
open System.Collections.Generic
open Shared.TermTypes
open Shared.OfficeInteropTypes
open Spreadsheet
open Types
-open ARCtrl.ISA
+open ARCtrl
open Shared
module SidebarControllerAux =
- let rec createNewTableName (ind: int) names =
- let name = "NewTable" + string ind
- if Seq.contains name names then
- createNewTableName (ind+1) names
- else
- name
-
- /// Uses current `ActiveTableIndex` to return next `ActiveTableIndex` whenever a new table is added and we want to
- /// switch to the new table.
- let getNextActiveTableIndex (state: Spreadsheet.Model) =
- if state.Tables.TableCount = 0 then 0 else state.ActiveView.TableIndex + 1
///
/// Uses the first selected columnIndex from `state.SelectedCells` to determine if new column should be inserted or appended.
@@ -45,38 +34,6 @@ module SanityChecks =
open SidebarControllerAux
-/// This is the basic function to create new Tables from an array of SwateBuildingBlocks
-let addTable (newTable: ArcTable) (state: Spreadsheet.Model) : Spreadsheet.Model =
- let tables = state.Tables
- // calculate next index
- let newIndex = getNextActiveTableIndex state
- tables.AddTable(newTable, newIndex)
- { state with
- ArcFile = state.ArcFile
- ActiveView = ActiveView.Table newIndex }
-
-/// This function is used to create multiple tables at once.
-let addTables (tables: ArcTable []) (state: Spreadsheet.Model) : Spreadsheet.Model =
- let newIndex = getNextActiveTableIndex state
- state.Tables.AddTables(tables, newIndex)
- { state with
- ArcFile = state.ArcFile
- ActiveView = ActiveView.Table (newIndex + tables.Length) }
-
-
-/// Adds the most basic empty Swate table with auto generated name.
-let createTable (usePrevOutput:bool) (state: Spreadsheet.Model) : Spreadsheet.Model =
- let tables = state.ArcFile.Value.Tables()
- let newName = createNewTableName 0 tables.TableNames
- let newTable = ArcTable.init(newName)
- if usePrevOutput && (tables.TableCount-1) >= state.ActiveView.TableIndex then
- let table = tables.GetTableAt(state.ActiveView.TableIndex)
- let output = table.GetOutputColumn()
- let newInput = output.Header.TryOutput().Value |> CompositeHeader.Input
- newTable.AddColumn(newInput,output.Cells,forceReplace=true)
- let nextState = {state with ArcFile = state.ArcFile}
- addTable newTable nextState
-
let addBuildingBlock(newColumn: CompositeColumn) (state: Spreadsheet.Model) : Spreadsheet.Model =
let table = state.ActiveTable
// add one to last column index OR to selected column index to append one to the right.
diff --git a/src/Client/Spreadsheet/Clipboard.Controller.fs b/src/Client/Spreadsheet/Clipboard.Controller.fs
index cb37cbef..bc1f0de3 100644
--- a/src/Client/Spreadsheet/Clipboard.Controller.fs
+++ b/src/Client/Spreadsheet/Clipboard.Controller.fs
@@ -1,56 +1,114 @@
module Spreadsheet.Clipboard.Controller
-open ARCtrl.ISA
+open Fable.Core
+open ARCtrl
open Shared
-module ClipboardAux =
- let setClipboardCell (state: Spreadsheet.Model) (cell: CompositeCell option) =
- let nextState = {state with Clipboard = { state.Clipboard with Cell = cell}}
- nextState
+let copyCell (cell: CompositeCell) : JS.Promise =
+ let tab = cell.ToTabStr()
+ navigator.clipboard.writeText(tab)
-open ClipboardAux
+let copyCells (cells: CompositeCell []) : JS.Promise =
+ let tab = CompositeCell.ToTabTxt cells
+ navigator.clipboard.writeText(tab)
-let copyCell (index: int*int) (state: Spreadsheet.Model) : Spreadsheet.Model =
- let cell = state.ActiveTable.TryGetCellAt(index)
- let nextState = {state with Clipboard = { state.Clipboard with Cell = cell}}
- nextState
+let copyCellByIndex (index: int*int) (state: Spreadsheet.Model) : JS.Promise =
+ let cell = state.ActiveTable.Values.[index]
+ copyCell cell
-let copySelectedCell (state: Spreadsheet.Model) : Spreadsheet.Model =
+let copyCellsByIndex (indices: (int*int) []) (state: Spreadsheet.Model) : JS.Promise =
+ let cells = [|for index in indices do yield state.ActiveTable.Values.[index] |]
+ log cells
+ copyCells cells
+
+let copySelectedCell (state: Spreadsheet.Model) : JS.Promise =
/// Array.head is used until multiple cells are supported, should this ever be intended
let index = state.SelectedCells |> Set.toArray |> Array.min
- copyCell index state
+ copyCellByIndex index state
+
+let copySelectedCells (state: Spreadsheet.Model) : JS.Promise =
+ /// Array.head is used until multiple cells are supported, should this ever be intended
+ let indices = state.SelectedCells |> Set.toArray
+ copyCellsByIndex indices state
-let cutCell (index: int*int) (state: Spreadsheet.Model) : Spreadsheet.Model =
- let cell = state.ActiveTable.TryGetCellAt(index)
+let cutCellByIndex (index: int*int) (state: Spreadsheet.Model) : Spreadsheet.Model =
+ let cell = state.ActiveTable.Values.[index]
// Remove selected cell value
-
- let emptyCell = if cell.IsSome then cell.Value.GetEmptyCell() else state.ActiveTable.GetColumn(fst index).GetDefaultEmptyCell()
- state.ActiveTable.UpdateCellAt(fst index,snd index, emptyCell)
- let nextState = setClipboardCell state cell
- nextState
+ let emptyCell = cell.GetEmptyCell()
+ state.ActiveTable.UpdateCellAt(fst index,snd index, emptyCell)
+ copyCell cell |> Promise.start
+ state
+
+let cutCellsByIndices (indices: (int*int) []) (state: Spreadsheet.Model) : Spreadsheet.Model =
+ log "HIT"
+ let cells = ResizeArray()
+ for index in indices do
+ let cell = state.ActiveTable.Values.[index]
+ // Remove selected cell value
+ let emptyCell = cell.GetEmptyCell()
+ state.ActiveTable.UpdateCellAt(fst index,snd index, emptyCell)
+ cells.Add(cell)
+ copyCells (Array.ofSeq cells) |> Promise.start
+ state
let cutSelectedCell (state: Spreadsheet.Model) : Spreadsheet.Model =
/// Array.min is used until multiple cells are supported, should this ever be intended
let index = state.SelectedCells |> Set.toArray |> Array.min
- cutCell index state
+ cutCellByIndex index state
+
+let cutSelectedCells (state: Spreadsheet.Model) : Spreadsheet.Model =
+ /// Array.min is used until multiple cells are supported, should this ever be intended
+ let indices = state.SelectedCells |> Set.toArray
+ cutCellsByIndices indices state
+
+let pasteCellByIndex (index: int*int) (state: Spreadsheet.Model) : JS.Promise =
+ promise {
+ let! tab = navigator.clipboard.readText()
+ let header = state.ActiveTable.Headers.[fst index]
+ let cell = CompositeCell.fromTabTxt tab |> Array.head |> _.ConvertToValidCell(header)
+ state.ActiveTable.SetCellAt(fst index, snd index, cell)
+ return state
+ }
-let pasteCell (index: int*int) (state: Spreadsheet.Model) : Spreadsheet.Model =
- match state.Clipboard.Cell with
- // Don't update if no cell in saved
- | None -> state
- | Some c ->
- state.ActiveTable.UpdateCellAt(fst index, snd index, c)
- state
+let pasteCellsByIndexExtend (index: int*int) (state: Spreadsheet.Model) : JS.Promise =
+ promise {
+ let! tab = navigator.clipboard.readText()
+ let header = state.ActiveTable.Headers.[fst index]
+ let cells = CompositeCell.fromTabTxt tab |> Array.map _.ConvertToValidCell(header)
+ let columnIndex, rowIndex = fst index, snd index
+ let indexedCells = cells |> Array.indexed |> Array.map (fun (i,c) -> (columnIndex, rowIndex + i), c)
+ state.ActiveTable.SetCellsAt indexedCells
+ return state
+ }
-let pasteSelectedCell (state: Spreadsheet.Model) : Spreadsheet.Model =
+let pasteCellIntoSelected (state: Spreadsheet.Model) : JS.Promise =
if state.SelectedCells.IsEmpty then
- state
+ promise {return state}
else
- // TODO:
- //let arr = state.SelectedCells |> Set.toArray
- //let isOneColumn =
- // let c = fst arr.[0] // can just use head of selected cells as all must be same column
- // arr |> Array.forall (fun x -> fst x = c)
- //if not isOneColumn then failwith "Can only paste cells in one column at a time!"
let minIndex = state.SelectedCells |> Set.toArray |> Array.min
- pasteCell minIndex state
\ No newline at end of file
+ pasteCellByIndex minIndex state
+
+let pasteCellsIntoSelected (state: Spreadsheet.Model) : JS.Promise =
+ if state.SelectedCells.IsEmpty then
+ promise {return state}
+ else
+ log "here"
+ let columnIndex = state.SelectedCells |> Set.toArray |> Array.minBy fst |> fst
+ let selectedSingleColumnCells = state.SelectedCells |> Set.filter (fun index -> fst index = columnIndex)
+ promise {
+ let! tab = navigator.clipboard.readText()
+ let header = state.ActiveTable.Headers.[columnIndex]
+ let cells = CompositeCell.fromTabTxt tab |> Array.map _.ConvertToValidCell(header)
+ if cells.Length = 1 then
+ let cell = cells.[0]
+ let newCells = selectedSingleColumnCells |> Array.ofSeq |> Array.map (fun index -> index, cell)
+ state.ActiveTable.SetCellsAt newCells
+ return state
+ else
+ let rowCount = selectedSingleColumnCells.Count
+ let cellsTrimmed = cells |> takeFromArray rowCount
+ let indicesTrimmed = (Set.toArray selectedSingleColumnCells).[0..cellsTrimmed.Length-1]
+ let indexedCellsTrimmed = Array.zip indicesTrimmed cellsTrimmed
+ state.ActiveTable.SetCellsAt indexedCellsTrimmed
+ return state
+ }
\ No newline at end of file
diff --git a/src/Client/Spreadsheet/IO.fs b/src/Client/Spreadsheet/IO.fs
index c191ec0b..85ba5567 100644
--- a/src/Client/Spreadsheet/IO.fs
+++ b/src/Client/Spreadsheet/IO.fs
@@ -1,50 +1,59 @@
module Spreadsheet.IO
-open ARCtrl.ISA
-open ARCtrl.ISA.Spreadsheet
-open FsSpreadsheet.Exceljs
+open ARCtrl
+open ARCtrl.Spreadsheet
+open FsSpreadsheet.Js
open Shared
open FsSpreadsheet
-let private tryToConvertAssay (fswb: FsWorkbook) =
- try
- ArcAssay.fromFsWorkbook fswb |> Assay |> Some
- with
- | _ -> None
-
-let private tryToConvertStudy (fswb: FsWorkbook) =
- try
- ArcStudy.fromFsWorkbook fswb |> Study |> Some
- with
- | _ -> None
-
-let private tryToConvertInvestigation (fswb: FsWorkbook) =
- try
- ArcInvestigation.fromFsWorkbook fswb |> Investigation |> Some
- with
- | _ -> None
-
-let private tryToConvertTemplate (fswb: FsWorkbook) =
- try
- ARCtrl.Template.Spreadsheet.Template.fromFsWorkbook fswb |> Template |> Some
- with
- | _ -> None
-
-// List of conversion functions
-let private converters = [tryToConvertAssay; tryToConvertStudy; tryToConvertInvestigation; tryToConvertTemplate]
-
-// TODO: Can this be done better? If we want to allow upload of any isa.xlsx file?
-let readFromBytes (bytes: byte []) =
- // Try each conversion function and return the first successful result
- let rec tryConvert (converters: ('a -> 'b option) list) (json: 'a) : 'b =
- match converters with
- | [] -> failwith "Unable to parse json to supported isa file."
- | convert :: rest ->
- match convert json with
- | Some result -> result
- | None -> tryConvert rest json
- promise {
- let! fswb = FsSpreadsheet.Exceljs.Xlsx.fromBytes bytes
- let arcFile = tryConvert converters fswb
- return arcFile
- }
\ No newline at end of file
+module Xlsx =
+
+ let readFromBytes (bytes: byte []) =
+ // Try each conversion function and return the first successful result
+ promise {
+ let! fswb = FsSpreadsheet.Js.Xlsx.fromXlsxBytes bytes
+ let ws = fswb.GetWorksheets()
+ let arcfile =
+ match ws with
+ | _ when ws.Exists (fun ws -> ARCtrl.Spreadsheet.ArcAssay.metaDataSheetName = ws.Name ) ->
+ ArcAssay.fromFsWorkbook fswb |> Assay
+ | _ when ws.Exists (fun ws -> ARCtrl.Spreadsheet.ArcStudy.metaDataSheetName = ws.Name ) ->
+ ArcStudy.fromFsWorkbook fswb |> Study
+ | _ when ws.Exists (fun ws -> ARCtrl.Spreadsheet.ArcInvestigation.metaDataSheetName = ws.Name ) ->
+ ArcInvestigation.fromFsWorkbook fswb |> Investigation
+ | _ when ws.Exists (fun ws -> ARCtrl.Spreadsheet.Template.metaDataSheetName = ws.Name ) ->
+ ARCtrl.Spreadsheet.Template.fromFsWorkbook fswb |> Template
+ | _ -> failwith "Unable to identify given file. Missing metadata sheet with correct name."
+ return arcfile
+ }
+
+module Json =
+
+ open ARCtrlHelper
+ open ARCtrl
+ open ARCtrl.Json
+
+ let readFromJson (fileType: ArcFilesDiscriminate) (jsonType: JsonExportFormat) (json: string) =
+ promise {
+ let arcfile =
+ match fileType, jsonType with
+ | ArcFilesDiscriminate.Investigation, JsonExportFormat.ARCtrl -> ArcInvestigation.fromJsonString json |> ArcFiles.Investigation
+ | ArcFilesDiscriminate.Investigation, JsonExportFormat.ARCtrlCompressed -> ArcInvestigation.fromCompressedJsonString json |> ArcFiles.Investigation
+ | ArcFilesDiscriminate.Investigation, JsonExportFormat.ISA -> ArcInvestigation.fromISAJsonString json |> ArcFiles.Investigation
+ | ArcFilesDiscriminate.Investigation, JsonExportFormat.ROCrate -> ArcInvestigation.fromROCrateJsonString json |> ArcFiles.Investigation
+
+ | ArcFilesDiscriminate.Study, JsonExportFormat.ARCtrl -> ArcStudy.fromJsonString json |> fun x -> ArcFiles.Study(x, [])
+ | ArcFilesDiscriminate.Study, JsonExportFormat.ARCtrlCompressed -> ArcStudy.fromCompressedJsonString json |> fun x -> ArcFiles.Study(x, [])
+ | ArcFilesDiscriminate.Study, JsonExportFormat.ISA -> ArcStudy.fromISAJsonString json |> ArcFiles.Study
+ | ArcFilesDiscriminate.Study, JsonExportFormat.ROCrate -> ArcStudy.fromROCrateJsonString json |> ArcFiles.Study
+
+ | ArcFilesDiscriminate.Assay, JsonExportFormat.ARCtrl -> ArcAssay.fromJsonString json |> ArcFiles.Assay
+ | ArcFilesDiscriminate.Assay, JsonExportFormat.ARCtrlCompressed -> ArcAssay.fromCompressedJsonString json |> ArcFiles.Assay
+ | ArcFilesDiscriminate.Assay, JsonExportFormat.ISA -> ArcAssay.fromISAJsonString json |> ArcFiles.Assay
+ | ArcFilesDiscriminate.Assay, JsonExportFormat.ROCrate -> ArcAssay.fromROCrateJsonString json |> ArcFiles.Assay
+
+ | ArcFilesDiscriminate.Template, JsonExportFormat.ARCtrl -> Template.fromJsonString json |> ArcFiles.Template
+ | ArcFilesDiscriminate.Template, JsonExportFormat.ARCtrlCompressed -> Template.fromCompressedJsonString json |> ArcFiles.Template
+ | ArcFilesDiscriminate.Template, anyElse -> failwithf "Error. It is not intended to parse Template from %s format." (string anyElse)
+ return arcfile
+ }
\ No newline at end of file
diff --git a/src/Client/Spreadsheet/Table.Controller.fs b/src/Client/Spreadsheet/Table.Controller.fs
index 58c4a35d..4fa02a22 100644
--- a/src/Client/Spreadsheet/Table.Controller.fs
+++ b/src/Client/Spreadsheet/Table.Controller.fs
@@ -5,11 +5,18 @@ open Shared.TermTypes
open Shared.OfficeInteropTypes
open Spreadsheet
open Types
-open ARCtrl.ISA
+open ARCtrl
open Shared
module ControllerTableAux =
+ let rec createNewTableName (ind: int) names =
+ let name = "NewTable" + string ind
+ if Seq.contains name names then
+ createNewTableName (ind+1) names
+ else
+ name
+
let findEarlierTable (tableIndex:int) (tables: ArcTables) =
let indices = [ 0 .. tables.TableCount-1 ]
let lower = indices |> Seq.tryFindBack (fun k -> k < tableIndex)
@@ -25,12 +32,46 @@ module ControllerTableAux =
open ControllerTableAux
+let switchTable (nextIndex: int) (state: Spreadsheet.Model) : Spreadsheet.Model =
+ match state.ActiveView with
+ | ActiveView.Table i when i = nextIndex -> state
+ | _ ->
+ { state with
+ ActiveCell = None
+ SelectedCells = Set.empty
+ ActiveView = ActiveView.Table nextIndex }
+
+/// This is the basic function to create new Tables from an array of SwateBuildingBlocks
+let addTable (newTable: ArcTable) (state: Spreadsheet.Model) : Spreadsheet.Model =
+ state.Tables.AddTable(newTable)
+ switchTable (state.Tables.TableCount - 1) state
+
+
+/// This function is used to create multiple tables at once.
+let addTables (tables: ArcTable []) (state: Spreadsheet.Model) : Spreadsheet.Model =
+ state.Tables.AddTables(tables)
+ switchTable (state.Tables.TableCount - 1) state
+
+
+/// Adds the most basic empty Swate table with auto generated name.
+let createTable (usePrevOutput:bool) (state: Spreadsheet.Model) : Spreadsheet.Model =
+ let tables = state.ArcFile.Value.Tables()
+ let newName = createNewTableName 0 tables.TableNames
+ let newTable = ArcTable.init(newName)
+ if usePrevOutput && ((tables.TableCount-1) >= state.ActiveView.TableIndex) then
+ let table = tables.GetTableAt(state.ActiveView.TableIndex)
+ let output = table.GetOutputColumn()
+ let newInput = output.Header.TryOutput().Value |> CompositeHeader.Input
+ newTable.AddColumn(newInput,output.Cells,forceReplace=true)
+ let nextState = {state with ArcFile = state.ArcFile}
+ addTable newTable nextState
+
let updateTableOrder (prevIndex:int, newIndex:int) (state:Spreadsheet.Model) =
state.Tables.MoveTable(prevIndex, newIndex)
{state with ArcFile = state.ArcFile}
-let resetTableState () : LocalHistory.Model * Spreadsheet.Model =
- LocalHistory.Model.init().ResetAll(),
+let resetTableState () : Spreadsheet.Model =
+ LocalHistory.Model.ResetHistoryWebStorage()
Spreadsheet.Model.init()
let renameTable (tableIndex:int) (newName: string) (state: Spreadsheet.Model) : Spreadsheet.Model =
@@ -45,22 +86,18 @@ let removeTable (removeIndex: int) (state: Spreadsheet.Model) : Spreadsheet.Mode
// if active table is removed get the next closest table and set it active
match state.ActiveView with
| ActiveView.Table i when i = removeIndex ->
- let nextView =
- let neighbors = findNeighborTables removeIndex state.Tables
- match neighbors with
- | Some (i, _), _ -> ActiveView.Table i
- | None, Some (i, _) -> ActiveView.Table i
- // This is a fallback option, which should never be hit
- | _ -> ActiveView.Metadata
- { state with
- ArcFile = state.ArcFile
- ActiveView = nextView }
+ let neighbors = findNeighborTables removeIndex state.Tables
+ match neighbors with
+ | Some (i, _), _ ->
+ switchTable i state
+ | None, Some (i, _) ->
+ switchTable i state
+ | _ -> { state with ActiveView = ActiveView.Metadata }
| ActiveView.Table i -> // Tables still exist and an inactive one was removed. Just remove it.
- let nextTable_Index = if i > removeIndex then i - 1 else i
+ let nextTableIndex = if i > removeIndex then i - 1 else i
{ state with
- ArcFile = state.ArcFile
- ActiveView = ActiveView.Table nextTable_Index }
- | _ -> state
+ ActiveView = ActiveView.Table nextTableIndex }
+ | _ -> {state with ActiveView = ActiveView.Metadata }
///Add `n` rows to active table.
let addRows (n: int) (state: Spreadsheet.Model) : Spreadsheet.Model =
@@ -86,6 +123,18 @@ let deleteColumn (index: int) (state: Spreadsheet.Model) : Spreadsheet.Model =
ArcFile = state.ArcFile
SelectedCells = Set.empty}
+let setColumn (index: int) (column: CompositeColumn) (state: Spreadsheet.Model) : Spreadsheet.Model =
+ state.ActiveTable.UpdateColumn (index, column.Header, column.Cells)
+ {state with
+ ArcFile = state.ArcFile
+ SelectedCells = Set.empty}
+
+let moveColumn (current: int) (next: int) (state: Spreadsheet.Model) : Spreadsheet.Model =
+ state.ActiveTable.MoveColumn (current, next)
+ {state with
+ ArcFile = state.ArcFile
+ SelectedCells = Set.empty }
+
let fillColumnWithCell (index: int*int) (state: Spreadsheet.Model) : Spreadsheet.Model =
let cell = state.ActiveTable.TryGetCellAt index
let columnIndex = fst index
@@ -93,10 +142,43 @@ let fillColumnWithCell (index: int*int) (state: Spreadsheet.Model) : Spreadsheet
let cell = cell|> Option.defaultValue (column.GetDefaultEmptyCell())
if i = columnIndex then
for cellRowIndex in 0 .. column.Cells.Length-1 do
+ let cell = cell
state.ActiveTable.UpdateCellAt(columnIndex, cellRowIndex, cell)
)
{state with ArcFile = state.ArcFile}
+///
+/// Transform cells of given indices to their empty equivalents
+///
+///
+///
+let clearCells (indexArr: (int*int) []) (state: Spreadsheet.Model) : Spreadsheet.Model =
+ let table = state.ActiveTable
+ let newCells = [|
+ for index in indexArr do
+ let cell = table.Values.[index]
+ let emptyCell = cell.GetEmptyCell()
+ index, emptyCell
+ |]
+ table.SetCellsAt newCells
+ state
+
+open Fable.Core
+open System
+
+let selectRelativeCell (index: int*int) (move: int*int) (table: ArcTable) =
+ //let index =
+ // match index with
+ // | U2.Case2 index -> index,-1
+ // | U2.Case1 index -> index
+ let columnIndex = Math.Min(Math.Max(fst index + fst move, 0), table.ColumnCount-1)
+ let rowIndex = Math.Min(Math.Max(snd index + snd move, 0), table.RowCount-1)
+ //if rowIndex = -1 then
+ // U2.Case2 columnIndex
+ //else
+ // U2.Case1 (columnIndex, rowIndex)
+ columnIndex, rowIndex
+
// Ui depends on main column name, maybe change this to depends on BuildingBlockType?
// Header main column name must be updated
diff --git a/src/Client/States/ARCitect.fs b/src/Client/States/ARCitect.fs
index 54314cae..759b757b 100644
--- a/src/Client/States/ARCitect.fs
+++ b/src/Client/States/ARCitect.fs
@@ -1,16 +1,19 @@
module Model.ARCitect
-open ARCtrl.ISA
+open ARCtrl
type Msg =
| Init
| Error of exn
+ | RequestPaths of selectDirectories: bool
| AssayToARCitect of ArcAssay
| StudyToARCitect of ArcStudy
- | TriggerSwateClose
+ | InvestigationToARCitect of ArcInvestigation
type IEventHandler = {
Error: exn -> unit
- AssayToSwate: {| ArcAssayJsonString: string |} -> unit
- StudyToSwate: {| ArcStudyJsonString: string |} -> unit
+ AssayToSwate : {| ArcAssayJsonString: string |} -> unit
+ StudyToSwate : {| ArcStudyJsonString: string |} -> unit
+ InvestigationToSwate : {| ArcInvestigationJsonString: string |} -> unit
+ PathsToSwate : {| paths: string [] |} -> unit
}
\ No newline at end of file
diff --git a/src/Client/States/DagState.fs b/src/Client/States/DagState.fs
deleted file mode 100644
index f348d80b..00000000
--- a/src/Client/States/DagState.fs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace Dag
-
-open Shared.OfficeInteropTypes
-
-type HtmlString = string
-
-type Model = {
- Loading : bool
- DagHtml : HtmlString option
-} with
- static member init() = {
- Loading = false
- DagHtml = None
- }
-
-type Msg =
-//Client
-| UpdateLoading of bool
-//
-| ParseTablesOfficeInteropRequest
-| ParseTablesDagServerRequest of (string * BuildingBlock []) []
-| ParseTablesDagServerResponse of dagHtml:string
\ No newline at end of file
diff --git a/src/Client/States/JsonExporterState.fs b/src/Client/States/JsonExporterState.fs
deleted file mode 100644
index 2b474dfa..00000000
--- a/src/Client/States/JsonExporterState.fs
+++ /dev/null
@@ -1,57 +0,0 @@
-module Model.JsonExporter
-
-open Shared
-open Shared.OfficeInteropTypes
-
-type Model = {
- /// Use this value to determine on click which export value to use
- CurrentExportType : JsonExportType option
- //
- TableJsonExportType : JsonExportType
- WorkbookJsonExportType : JsonExportType
- XLSXParsingExportType : JsonExportType
- Loading : bool
- ShowTableExportTypeDropdown : bool
- ShowWorkbookExportTypeDropdown : bool
- ShowXLSXExportTypeDropdown : bool
- // XLSX upload with json parsing
- XLSXByteArray : byte []
-} with
- static member init() = {
-
- CurrentExportType = None
- //
- TableJsonExportType = JsonExportType.Assay
- WorkbookJsonExportType = JsonExportType.Assay
- XLSXParsingExportType = JsonExportType.Assay
- Loading = false
- ShowTableExportTypeDropdown = false
- ShowWorkbookExportTypeDropdown = false
- ShowXLSXExportTypeDropdown = false
-
- // XLSX upload with json parsing
- XLSXByteArray = Array.empty
- }
-
-type Msg =
-// Style
-| UpdateLoading of bool
-| UpdateShowTableExportTypeDropdown of bool
-| UpdateShowWorkbookExportTypeDropdown of bool
-| UpdateShowXLSXExportTypeDropdown of bool
-| CloseAllDropdowns
-| UpdateTableJsonExportType of JsonExportType
-| UpdateWorkbookJsonExportType of JsonExportType
-| UpdateXLSXParsingExportType of JsonExportType
-//
-| ParseTableOfficeInteropRequest
-/// parse active annotation table to building blocks
-| ParseTableServerRequest of worksheetName:string * BuildingBlock []
-| ParseTableServerResponse of string
-/// Parse all annotation tables to buildingblocks
-| ParseTablesOfficeInteropRequest
-| ParseTablesServerRequest of (string * BuildingBlock []) []
-// XLSX upload with json parsing
-| StoreXLSXByteArray of byte []
-| ParseXLSXToJsonRequest of byte []
-| ParseXLSXToJsonResponse of string
\ No newline at end of file
diff --git a/src/Client/States/LocalHistory.fs b/src/Client/States/LocalHistory.fs
index d556a493..9e0ca45e 100644
--- a/src/Client/States/LocalHistory.fs
+++ b/src/Client/States/LocalHistory.fs
@@ -52,9 +52,8 @@ module HistoryOrder =
// member this.toJson() = Json.serialize this
module ConversionTypes =
- open ARCtrl.ISA
- open ARCtrl.ISA.Json
- open ARCtrl.Template.Json
+ open ARCtrl
+ open ARCtrl.Json
open Shared
[]
@@ -73,9 +72,9 @@ module ConversionTypes =
static member fromSpreadsheetModel (model: Spreadsheet.Model) =
let jsonArcFile, jsonString =
match model.ArcFile with
- | Some (ArcFiles.Investigation i) -> JsonArcFiles.Investigation, i.ToArcJsonString()
- | Some (ArcFiles.Study (s,al)) -> JsonArcFiles.Study, s.ToArcJsonString()
- | Some (ArcFiles.Assay a) -> JsonArcFiles.Assay, a.ToArcJsonString()
+ | Some (ArcFiles.Investigation i) -> JsonArcFiles.Investigation, ArcInvestigation.toCompressedJsonString 0 i
+ | Some (ArcFiles.Study (s,al)) -> JsonArcFiles.Study, ArcStudy.toCompressedJsonString 0 s
+ | Some (ArcFiles.Assay a) -> JsonArcFiles.Assay, ArcAssay.toCompressedJsonString 0 a
| Some (ArcFiles.Template t) -> JsonArcFiles.Template, Template.toJsonString 0 t
| None -> JsonArcFiles.None, ""
{
@@ -85,18 +84,21 @@ module ConversionTypes =
}
member this.ToSpreadsheetModel() =
let init = Spreadsheet.Model.init()
- let arcFile =
- match this.JsonArcFiles with
- | JsonArcFiles.Investigation -> ArcInvestigation.fromArcJsonString this.JsonString |> ArcFiles.Investigation |> Some
- | JsonArcFiles.Study -> ArcStudy.fromArcJsonString this.JsonString |> fun s -> ArcFiles.Study(s, []) |> Some
- | JsonArcFiles.Assay -> ArcAssay.fromArcJsonString this.JsonString |> ArcFiles.Assay |> Some
- | JsonArcFiles.Template -> Template.fromJsonString this.JsonString |> ArcFiles.Template |> Some
- | JsonArcFiles.None -> None
- {
- init with
- ActiveView = this.ActiveView
- ArcFile = arcFile
- }
+ try
+ let arcFile =
+ match this.JsonArcFiles with
+ | JsonArcFiles.Investigation -> ArcInvestigation.fromCompressedJsonString this.JsonString |> ArcFiles.Investigation |> Some
+ | JsonArcFiles.Study -> ArcStudy.fromCompressedJsonString this.JsonString |> fun s -> ArcFiles.Study(s, []) |> Some
+ | JsonArcFiles.Assay -> ArcAssay.fromCompressedJsonString this.JsonString |> ArcFiles.Assay |> Some
+ | JsonArcFiles.Template -> Template.fromJsonString this.JsonString |> ArcFiles.Template |> Some
+ | JsonArcFiles.None -> None
+ {
+ init with
+ ActiveView = this.ActiveView
+ ArcFile = arcFile
+ }
+ with
+ | _ -> init
static member toSpreadsheetModel (sessionStorage: SessionStorage) =
sessionStorage.ToSpreadsheetModel()
@@ -197,7 +199,6 @@ type Model =
// if e.g at position 4 and we create new table state from position 4 we want to delete position 0 .. 3 and use 4 as new 0
let rebranchedList, toRemoveList1 =
if this.HistoryCurrentPosition <> 0 then
- printfn "[HISTORY] Rebranch to %i" this.HistoryCurrentPosition
this.HistoryOrder
|> List.splitAt this.HistoryCurrentPosition
|> fun (remove, keep) -> keep, remove
@@ -220,10 +221,12 @@ type Model =
Browser.WebStorage.sessionStorage.setItem(Keys.swate_session_history_key, HistoryOrder.toJson(nextState.HistoryOrder))
// reset new table position to 0
Browser.WebStorage.sessionStorage.setItem(Keys.swate_session_history_position, "0")
- printfn "[HISTORY] length: %i" nextState.HistoryOrder.Length
nextState
- member this.ResetAll() =
- Browser.WebStorage.localStorage.clear()
+ static member ResetHistoryWebStorage() =
+ Browser.WebStorage.localStorage.removeItem(Keys.swate_local_spreadsheet_key)
Browser.WebStorage.sessionStorage.clear()
+
+ member this.ResetAll() =
+ Model.ResetHistoryWebStorage()
Model.init()
\ No newline at end of file
diff --git a/src/Client/OfficeInterop/OfficeInteropState.fs b/src/Client/States/OfficeInteropState.fs
similarity index 98%
rename from src/Client/OfficeInterop/OfficeInteropState.fs
rename to src/Client/States/OfficeInteropState.fs
index c3c94b49..d828ce2d 100644
--- a/src/Client/OfficeInterop/OfficeInteropState.fs
+++ b/src/Client/States/OfficeInteropState.fs
@@ -29,6 +29,7 @@ type Msg =
// create and update table element functions
| CreateAnnotationTable of tryUsePrevOutput:bool
| AnnotationtableCreated
+ | TryFindAnnotationTable
| AnnotationTableExists of TryFindAnnoTableResult
| InsertOntologyTerm of TermMinimal
| AddAnnotationBlock of InsertBuildingBlock
diff --git a/src/Client/States/Spreadsheet.fs b/src/Client/States/Spreadsheet.fs
index 6eabcc64..bb802a62 100644
--- a/src/Client/States/Spreadsheet.fs
+++ b/src/Client/States/Spreadsheet.fs
@@ -2,14 +2,18 @@ namespace Spreadsheet
open Shared
open OfficeInteropTypes
-open ARCtrl.ISA
+open ARCtrl
+open Fable.Core
+
+type ColumnType =
+| Main
+| Unit
+| TSR
+| TAN
+with
+ member this.IsMainColumn = match this with | Main -> true | _ -> false
+ member this.IsRefColumn = not this.IsMainColumn
-type TableClipboard = {
- Cell: CompositeCell option
-} with
- static member init() = {
- Cell = None
- }
[]
type ActiveView =
@@ -29,15 +33,30 @@ with
type Model = {
ActiveView: ActiveView
SelectedCells: Set
+ ActiveCell: (U2 * ColumnType) option
ArcFile: ArcFiles option
- Clipboard: TableClipboard
} with
+ member this.CellIsActive(index: U2, columnType) =
+ match this.ActiveCell, index with
+ | Some (U2.Case1 (headerIndex), ct), U2.Case1 (targetIndex) -> headerIndex = targetIndex && ct = columnType
+ | Some (U2.Case2 (ci, ri), ct), U2.Case2 targetIndex -> (ci,ri) = targetIndex && ct = columnType
+ | _ -> false
+ member this.CellIsIdle(index: U2, columnType) =
+ this.CellIsActive(index, columnType) |> not
static member init() =
{
ActiveView = ActiveView.Metadata
SelectedCells = Set.empty
+ ActiveCell = None
ArcFile = None
- Clipboard = TableClipboard.init()
+ }
+
+ static member init(arcFile: ArcFiles) =
+ {
+ ActiveView = ActiveView.Metadata
+ SelectedCells = Set.empty
+ ActiveCell = None
+ ArcFile = Some arcFile
}
member this.Tables
with get() =
@@ -63,13 +82,36 @@ type Model = {
match this.ArcFile with | Some (Assay a) -> a | _ -> ArcAssay.init("ASSAY_NULL")
member this.headerIsSelected =
not this.SelectedCells.IsEmpty && this.SelectedCells |> Seq.exists (fun (c,r) -> r = 0)
+ member this.CanHaveTables() =
+ match this.ArcFile with
+ | Some (ArcFiles.Assay _) | Some (ArcFiles.Study _) -> true
+ | _ -> false
+ member this.TableViewIsActive() =
+ match this.ActiveView with
+ | ActiveView.Table i -> true
+ | _ -> false
+
+[]
+type Key =
+ | Up
+ | Down
+ | Left
+ | Right
+
type Msg =
// <--> UI <-->
+| UpdateState of Model
| UpdateCell of (int*int) * CompositeCell
+| UpdateCells of ((int*int) * CompositeCell) []
| UpdateHeader of columIndex: int * CompositeHeader
| UpdateActiveView of ActiveView
| UpdateSelectedCells of Set
+| MoveSelectedCell of Key
+| MoveColumn of current:int * next:int
+| UpdateActiveCell of (U2 * ColumnType) option
+| SetActiveCellFromSelected
+| AddTable of ArcTable
| RemoveTable of index:int
| RenameTable of index:int * name:string
| UpdateTableOrder of pre_index:int * new_index:int
@@ -78,18 +120,27 @@ type Msg =
| DeleteRow of int
| DeleteRows of int []
| DeleteColumn of int
+| SetColumn of index:int * column: CompositeColumn
| CopySelectedCell
+| CopySelectedCells
| CutSelectedCell
+| CutSelectedCells
| PasteSelectedCell
+| PasteSelectedCells
| CopyCell of index:(int*int)
+| CopyCells of indices:(int*int) []
| CutCell of index:(int*int)
| PasteCell of index:(int*int)
+/// This Msg will paste all cell from clipboard into column starting from index. It will extend the table if necessary.
+| PasteCellsExtend of index:(int*int)
+| Clear of index:(int*int) []
+| ClearSelected
| FillColumnWithTerm of index:(int*int)
// /// Update column of index to new column type defined by given SwateCell.emptyXXX
// | EditColumn of index: int * newType: SwateCell * b_type: BuildingBlockType option
/// This will reset Spreadsheet.Model to Spreadsheet.Model.init() and clear all webstorage.
| Reset
-| SetArcFileFromBytes of byte []
+| ImportXlsx of byte []
// <--> INTEROP <-->
| CreateAnnotationTable of tryUsePrevOutput:bool
| AddAnnotationBlock of CompositeColumn
@@ -102,14 +153,10 @@ type Msg =
| UpdateTermColumns
| UpdateTermColumnsResponse of TermTypes.TermSearchable []
/// Starts chain to export active table to isa json
-| ExportJsonTable
-/// Starts chain to export all tables to isa json
-| ExportJsonTables
+| ExportJson of ArcFiles * JsonExportFormat
/// Starts chain to export all tables to xlsx swate tables.
| ExportXlsx of ArcFiles
| ExportXlsxDownload of filename: string * byte []
-/// Starts chain to parse all tables to DAG
-| ParseTablesToDag
// <--> Result Messages <-->
///// This message will save `Model` to local storage and to session storage for history
//| Success of Model
diff --git a/src/Client/States/SpreadsheetInterface.fs b/src/Client/States/SpreadsheetInterface.fs
index 8fb498df..bed6b720 100644
--- a/src/Client/States/SpreadsheetInterface.fs
+++ b/src/Client/States/SpreadsheetInterface.fs
@@ -3,27 +3,26 @@ namespace SpreadsheetInterface
open Shared
open OfficeInteropTypes
-open ARCtrl.ISA
+open ARCtrl
///This type is used to interface between standalone, electron and excel logic and will forward the command to the correct logic.
type Msg =
| Initialize of Swatehost
| CreateAnnotationTable of tryUsePrevOutput:bool
| RemoveBuildingBlock
+| AddTable of ArcTable
| AddAnnotationBlock of CompositeColumn
| AddAnnotationBlocks of CompositeColumn []
-| JoinTable of ArcTable * index: int option * options: TableJoinOptions option
-| ImportFile of ArcFiles
+/// This function will do preprocessing on the table to join
+| JoinTable of ArcTable * columnIndex: int option * options: TableJoinOptions option
+| UpdateArcFile of ArcFiles
/// Open modal for selected building block, allows editing on standalone only.
| EditBuildingBlock
/// Inserts TermMinimal to selected fields of one column
| InsertOntologyAnnotation of OntologyAnnotation
| InsertFileNames of string list
+| ImportXlsx of byte []
/// Starts chain to export active table to isa json
-| ExportJsonTable
-/// Starts chain to export all tables to isa json
-| ExportJsonTables
-/// Starts chain to parse all tables to DAG
-| ParseTablesToDag
+| ExportJson of ArcFiles * JsonExportFormat
| UpdateTermColumns
| UpdateTermColumnsResponse of TermTypes.TermSearchable []
\ No newline at end of file
diff --git a/src/Client/States/TemplateMetadataState.fs b/src/Client/States/TemplateMetadataState.fs
deleted file mode 100644
index 78e52cd8..00000000
--- a/src/Client/States/TemplateMetadataState.fs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace TemplateMetadata
-
-open Shared
-open TemplateTypes.Metadata
-
-type Model = {
- Default: obj
- MetadataFields : MetadataField option
-} with
- static member init() = {
- Default = ""
- MetadataFields = None
- }
-
-type Msg =
-| CreateTemplateMetadataWorksheet of MetadataField
-//| GetTemplateMetadataJsonSchemaRequest
-//| GetTemplateMetadataJsonSchemaResponse of string
\ No newline at end of file
diff --git a/src/Client/Update.fs b/src/Client/Update.fs
index 7f614aeb..c5efd4fc 100644
--- a/src/Client/Update.fs
+++ b/src/Client/Update.fs
@@ -104,267 +104,9 @@ module Dev =
(curry GenericError Cmd.none >> DevMsg)
currentState, cmd
-let handleApiRequestMsg (reqMsg: ApiRequestMsg) (currentState: ApiState) : ApiState * Cmd =
-
- let handleTermSuggestionRequest (apiFunctionname:string) (responseHandler: Term [] -> ApiMsg) queryString =
- let currentCall = {
- FunctionName = apiFunctionname
- Status = Pending
- }
-
- let nextState = {
- currentState with
- currentCall = currentCall
- }
- let nextCmd =
- Cmd.OfAsync.either
- Api.api.getTermSuggestions
- {|n= 5; query = queryString; ontology = None|}
- (responseHandler >> Api)
- (ApiError >> Api)
-
- nextState,nextCmd
-
- let handleUnitTermSuggestionRequest (apiFunctionname:string) (responseHandler: (Term []) -> ApiMsg) queryString =
- let currentCall = {
- FunctionName = apiFunctionname
- Status = Pending
- }
-
- let nextState = {
- currentState with
- currentCall = currentCall
- }
- let nextCmd =
- Cmd.OfAsync.either
- Api.api.getUnitTermSuggestions
- {|n= 5; query = queryString|}
- (responseHandler >> Api)
- (ApiError >> Api)
-
- nextState,nextCmd
-
- let handleTermSuggestionByParentTermRequest (apiFunctionname:string) (responseHandler: Term [] -> ApiMsg) queryString (parent:TermMinimal) =
- let currentCall = {
- FunctionName = apiFunctionname
- Status = Pending
- }
-
- let nextState = {
- currentState with
- currentCall = currentCall
- }
- let nextCmd =
- Cmd.OfAsync.either
- Api.api.getTermSuggestionsByParentTerm
- {|n= 5; query = queryString; parent_term = parent|}
- (responseHandler >> Api)
- (ApiError >> Api)
-
- nextState,nextCmd
-
- match reqMsg with
-
- | GetNewUnitTermSuggestions (queryString) ->
- handleUnitTermSuggestionRequest
- "getUnitTermSuggestions"
- (UnitTermSuggestionResponse >> Response)
- queryString
-
- | FetchAllOntologies ->
- let currentCall = {
- FunctionName = "getAllOntologies"
- Status = Pending
- }
-
- let nextState = {
- currentState with
- currentCall = currentCall
- }
-
- nextState,
- Cmd.OfAsync.either
- Api.api.getAllOntologies
- ()
- (FetchAllOntologiesResponse >> Response >> Api)
- (ApiError >> Api)
-
- | SearchForInsertTermsRequest (tableTerms) ->
- let currentCall = {
- FunctionName = "getTermsByNames"
- Status = Pending
- }
- let nextState = {
- currentState with
- currentCall = currentCall
- }
- let cmd =
- Cmd.OfAsync.either
- Api.api.getTermsByNames
- tableTerms
- (SearchForInsertTermsResponse >> Response >> Api)
- (fun e ->
- Msg.Batch [
- OfficeInterop.UpdateFillHiddenColsState OfficeInterop.FillHiddenColsState.Inactive |> OfficeInteropMsg
- ApiError e |> Api
- ] )
- let stateCmd = OfficeInterop.UpdateFillHiddenColsState OfficeInterop.FillHiddenColsState.ServerSearchDatabase |> OfficeInteropMsg |> Cmd.ofMsg
- let cmds = Cmd.batch [cmd; stateCmd]
- nextState, cmds
- //
- | GetAppVersion ->
- let currentCall = {
- FunctionName = "getAppVersion"
- Status = Pending
- }
-
- let nextState = {
- currentState with
- currentCall = currentCall
- }
-
- let cmd =
- Cmd.OfAsync.either
- Api.serviceApi.getAppVersion
- ()
- (GetAppVersionResponse >> Response >> Api)
- (ApiError >> Api)
-
- nextState, cmd
-
-
-let handleApiResponseMsg (resMsg: ApiResponseMsg) (currentState: ApiState) : ApiState * Cmd =
-
- let handleTermSuggestionResponse (responseHandler: Term [] -> Msg) (suggestions: Term[]) =
- let finishedCall = {
- currentState.currentCall with
- Status = Successfull
- }
-
- let nextState = {
- currentCall = ApiState.noCall
- callHistory = finishedCall::currentState.callHistory
- }
-
- let cmds = Cmd.batch [
- ("Debug",sprintf "[ApiSuccess]: Call %s successfull." finishedCall.FunctionName) |> ApiSuccess |> Api |> Cmd.ofMsg
- suggestions |> responseHandler |> Cmd.ofMsg
- ]
-
- nextState, cmds
-
- let handleUnitTermSuggestionResponse (responseHandler: Term [] -> Msg) (suggestions: Term[]) =
- let finishedCall = {
- currentState.currentCall with
- Status = Successfull
- }
-
- let nextState = {
- currentCall = ApiState.noCall
- callHistory = finishedCall::currentState.callHistory
- }
-
- let cmds = Cmd.batch [
- ("Debug",sprintf "[ApiSuccess]: Call %s successfull." finishedCall.FunctionName) |> ApiSuccess |> Api |> Cmd.ofMsg
- (suggestions) |> responseHandler |> Cmd.ofMsg
- ]
-
- nextState, cmds
-
- match resMsg with
- | UnitTermSuggestionResponse (suggestions) ->
-
- handleUnitTermSuggestionResponse
- (BuildingBlock.Msg.NewUnitTermSuggestions >> BuildingBlockMsg)
- suggestions
-
- | FetchAllOntologiesResponse onts ->
- let finishedCall = {
- currentState.currentCall with
- Status = Successfull
- }
-
- let nextState = {
- currentCall = ApiState.noCall
- callHistory = finishedCall::currentState.callHistory
- }
-
- let cmds = Cmd.batch [
- ("Debug",sprintf "[ApiSuccess]: Call %s successfull." finishedCall.FunctionName) |> ApiSuccess |> Api |> Cmd.ofMsg
- onts |> NewSearchableOntologies |> PersistentStorage |> Cmd.ofMsg
- ]
-
- nextState, cmds
-
- | SearchForInsertTermsResponse (termsWithSearchResult) ->
- let finishedCall = {
- currentState.currentCall with
- Status = Successfull
- }
- let nextState = {
- currentCall = ApiState.noCall
- callHistory = finishedCall::currentState.callHistory
- }
- let cmd =
- SpreadsheetInterface.UpdateTermColumnsResponse termsWithSearchResult |> InterfaceMsg |> Cmd.ofMsg
- let loggingCmd =
- ("Debug",sprintf "[ApiSuccess]: Call %s successfull." finishedCall.FunctionName) |> ApiSuccess |> Api |> Cmd.ofMsg
- nextState, Cmd.batch [cmd; loggingCmd]
-
- //
- | GetAppVersionResponse appVersion ->
- let finishedCall = {
- currentState.currentCall with
- Status = Successfull
- }
-
- let nextState = {
- currentCall = ApiState.noCall
- callHistory = finishedCall::currentState.callHistory
- }
-
- let cmds = Cmd.batch [
- ("Debug",sprintf "[ApiSuccess]: Call %s successfull." finishedCall.FunctionName) |> ApiSuccess |> Api |> Cmd.ofMsg
- appVersion |> UpdateAppVersion |> PersistentStorage |> Cmd.ofMsg
- ]
-
- nextState, cmds
-
-open Dev
-open Messages
-
-let handleApiMsg (apiMsg:ApiMsg) (currentState:ApiState) : ApiState * Cmd =
- match apiMsg with
- | ApiError e ->
-
- let failedCall = {
- currentState.currentCall with
- Status = Failed (e.GetPropagatedError())
- }
-
- let nextState = {
- currentCall = ApiState.noCall
- callHistory = failedCall::currentState.callHistory
- }
- let batch = Cmd.batch [
- let modalName = "GenericError"
- Cmd.ofEffect(fun _ -> Modals.Controller.renderModal(modalName, Modals.ErrorModal.errorModal(e)))
- curry GenericLog Cmd.none ("Error",sprintf "[ApiError]: Call %s failed with: %s" failedCall.FunctionName (e.GetPropagatedError())) |> DevMsg |> Cmd.ofMsg
- ]
-
- nextState, batch
-
- | ApiSuccess (level,logMsg) ->
- currentState, curry GenericLog Cmd.none (level,logMsg) |> DevMsg |> Cmd.ofMsg
-
- | Request req ->
- handleApiRequestMsg req currentState
- | Response res ->
- handleApiResponseMsg res currentState
-
-let handlePersistenStorageMsg (persistentStorageMsg: PersistentStorageMsg) (currentState:PersistentStorageState) : PersistentStorageState * Cmd =
+let handlePersistenStorageMsg (persistentStorageMsg: PersistentStorage.Msg) (currentState:PersistentStorageState) : PersistentStorageState * Cmd =
match persistentStorageMsg with
- | NewSearchableOntologies onts ->
+ | PersistentStorage.NewSearchableOntologies onts ->
let nextState = {
currentState with
SearchableOntologies = onts |> Array.map (fun ont -> ont.Name |> SorensenDice.createBigrams, ont)
@@ -372,12 +114,14 @@ let handlePersistenStorageMsg (persistentStorageMsg: PersistentStorageMsg) (curr
}
nextState,Cmd.none
- | UpdateAppVersion appVersion ->
+ | PersistentStorage.UpdateAppVersion appVersion ->
let nextState = {
currentState with
AppVersion = appVersion
}
nextState,Cmd.none
+ | PersistentStorage.UpdateShowSidebar show ->
+ {currentState with ShowSideBar = show}, Cmd.none
let handleBuildingBlockDetailsMsg (topLevelMsg:BuildingBlockDetailsMsg) (currentState: BuildingBlockDetailsState) : BuildingBlockDetailsState * Cmd =
match topLevelMsg with
@@ -421,19 +165,18 @@ let handleBuildingBlockDetailsMsg (topLevelMsg:BuildingBlockDetailsMsg) (current
Modals.Controller.renderModal("BuildingBlockDetails", Modals.BuildingBlockDetailsModal.buildingBlockDetailModal(nextState, dispatch))
)
nextState, cmd
-
-let handleTopLevelMsg (topLevelMsg:TopLevelMsg) (currentModel: Model) : Model * Cmd =
- match topLevelMsg with
- // Client
- | CloseSuggestions ->
- let nextModel = {
- currentModel with
- AddBuildingBlockState = {
- currentModel.AddBuildingBlockState with
- ShowUnit2TermSuggestions = false
- }
- }
- nextModel, Cmd.none
+
+module Ontologies =
+ let update (omsg: Ontologies.Msg) (model: Model) =
+ match omsg with
+ | Ontologies.GetOntologies ->
+ let cmd =
+ Cmd.OfAsync.either
+ Api.api.getAllOntologies
+ ()
+ (PersistentStorage.NewSearchableOntologies >> PersistentStorageMsg)
+ (curry GenericError Cmd.none >> DevMsg)
+ model, cmd
let update (msg : Msg) (model : Model) : Model * Cmd =
let innerUpdate (msg: Msg) (currentModel: Model) =
@@ -496,28 +239,10 @@ let update (msg : Msg) (model : Model) : Model * Cmd =
// https://stackoverflow.com/questions/42642863/office-js-nullifies-browser-history-functions-breaking-history-usage
//| Navigate route ->
// currentModel, Navigation.newUrl (Routing.Route.toRouteUrl route)
- | Bounce (delay, bounceId, msgToBounce) ->
- let (debouncerModel, debouncerCmd) =
- currentModel.DebouncerState
- |> Debouncer.bounce delay bounceId msgToBounce
-
- let nextModel = {
- currentModel with
- DebouncerState = debouncerModel
- }
-
- nextModel,Cmd.map DebouncerSelfMsg debouncerCmd
-
- | DebouncerSelfMsg debouncerMsg ->
- let nextDebouncerState, debouncerCmd =
- Debouncer.update debouncerMsg currentModel.DebouncerState
-
- let nextModel = {
- currentModel with
- DebouncerState = nextDebouncerState
- }
- nextModel, debouncerCmd
+ | OntologyMsg msg ->
+ let nextModel, cmd = Ontologies.update msg model
+ nextModel, cmd
| OfficeInteropMsg excelMsg ->
let nextModel,nextCmd = Update.OfficeInterop.update currentModel excelMsg
@@ -555,16 +280,7 @@ let update (msg : Msg) (model : Model) : Model * Cmd =
}
nextModel,nextCmd
- | Api apiMsg ->
- let nextApiState,nextCmd = currentModel.ApiState |> handleApiMsg apiMsg
-
- let nextModel = {
- currentModel with
- ApiState = nextApiState
- }
- nextModel,nextCmd
-
- | PersistentStorage persistentStorageMsg ->
+ | PersistentStorageMsg persistentStorageMsg ->
let nextPersistentStorageState,nextCmd =
currentModel.PersistentStorageState
|> handlePersistenStorageMsg persistentStorageMsg
@@ -639,29 +355,11 @@ let update (msg : Msg) (model : Model) : Model * Cmd =
CytoscapeModel = nextState}
nextModel, nextCmd
- | JsonExporterMsg msg ->
- let nextModel, nextCmd = currentModel |> JsonExporter.Core.update msg
- nextModel, nextCmd
-
- | TemplateMetadataMsg msg ->
- let nextModel, nextCmd = currentModel |> TemplateMetadata.Core.update msg
- nextModel, nextCmd
-
- | DagMsg msg ->
- let nextModel, nextCmd = currentModel |> Dag.Core.update msg
- nextModel, nextCmd
-
- | TopLevelMsg msg ->
- let nextModel, nextCmd =
- handleTopLevelMsg msg currentModel
-
- nextModel, nextCmd
-
/// This function is used to determine which msg should be logged to activity log.
/// The function is exception based, so msg which should not be logged needs to be added here.
let matchMsgToLog (msg: Msg) =
match msg with
- | Bounce _ | DevMsg _ | UpdatePageState _ -> false
+ | DevMsg _ | UpdatePageState _ -> false
| _ -> true
let logg (msg:Msg) (model: Model) : Model =
diff --git a/src/Client/Update/InterfaceUpdate.fs b/src/Client/Update/InterfaceUpdate.fs
index 7e07c787..3caf4601 100644
--- a/src/Client/Update/InterfaceUpdate.fs
+++ b/src/Client/Update/InterfaceUpdate.fs
@@ -12,11 +12,46 @@ open Elmish
open Model
open Shared
open Fable.Core.JsInterop
+open Shared.ARCtrlHelper
-module private Helper =
+/// This seems like such a hack :(
+module private ExcelHelper =
+
+ open Fable.Core
open ExcelJS.Fable.GlobalBindings
- let initializeAddIn () = Office.onReady()
+ let initializeAddIn () = Office.onReady().``then``(fun _ -> ()) |> Async.AwaitPromise
+
+ /// Office-js will kill iframe loading in ARCitect, therefore we must load it conditionally
+ let addOfficeJsScript(callback: unit -> unit) =
+ let cdn = @"https://appsforoffice.microsoft.com/lib/1/hosted/office.js"
+ let _type = "text/javascript"
+ let s = Browser.Dom.document.createElement("script")
+ s?``type`` <- _type
+ s?src <- cdn
+ Browser.Dom.document.head.appendChild s |> ignore
+ s.onload <- fun _ -> callback()
+ ()
+
+ /// Make a function that loops short sleep sequences until a mutable variable is set to true
+ /// do mutabel dotnet ref for variable
+ let myAwaitLoadedThenInit(loaded: ref) =
+ let rec loop() =
+ async {
+ if loaded.Value then
+ do! initializeAddIn()
+ else
+ do! Async.Sleep 100
+ do! loop()
+ }
+ loop()
+
+ let officeload() =
+ let loaded = ref false
+ async {
+ addOfficeJsScript(fun _ -> loaded.Value <- true)
+ do! myAwaitLoadedThenInit loaded
+ }
//open Fable.Core.JS
@@ -29,24 +64,21 @@ module Interface =
| Initialize host ->
let cmd =
Cmd.batch [
- Cmd.ofMsg (GetAppVersion |> Request |> Api)
- Cmd.ofMsg (FetchAllOntologies |> Request |> Api)
+ Cmd.ofMsg (Ontologies.GetOntologies |> OntologyMsg)
match host with
| Swatehost.Excel ->
- Cmd.OfPromise.either
- OfficeInterop.Core.tryFindActiveAnnotationTable
+ Cmd.OfAsync.either
+ ExcelHelper.officeload
()
- (OfficeInterop.AnnotationTableExists >> OfficeInteropMsg)
+ (fun _ -> TryFindAnnotationTable |> OfficeInteropMsg)
(curry GenericError Cmd.none >> DevMsg)
| Swatehost.Browser ->
- Cmd.batch [
- Cmd.ofEffect (fun dispatch -> Spreadsheet.KeyboardShortcuts.addOnKeydownEvent dispatch)
- ]
+ Cmd.none
| Swatehost.ARCitect ->
- Cmd.batch [
- Cmd.ofEffect (fun dispatch -> Spreadsheet.KeyboardShortcuts.addOnKeydownEvent dispatch)
- Cmd.ofEffect (fun _ -> ARCitect.ARCitect.send ARCitect.Init)
- ]
+ Cmd.ofEffect (fun _ ->
+ LocalHistory.Model.ResetHistoryWebStorage()
+ ARCitect.ARCitect.send ARCitect.Init
+ )
]
model, cmd
| CreateAnnotationTable usePrevOutput ->
@@ -58,6 +90,16 @@ module Interface =
let cmd = Spreadsheet.CreateAnnotationTable usePrevOutput |> SpreadsheetMsg |> Cmd.ofMsg
model, cmd
| _ -> failwith "not implemented"
+ | AddTable table ->
+ match host with
+ | Some Swatehost.Excel ->
+ //let cmd = OfficeInterop.AddTable table |> OfficeInteropMsg |> Cmd.ofMsg
+ failwith "AddTable not implemented for Excel"
+ model, Cmd.none
+ | Some Swatehost.Browser | Some Swatehost.ARCitect ->
+ let cmd = Spreadsheet.AddTable table |> SpreadsheetMsg |> Cmd.ofMsg
+ model, cmd
+ | _ -> failwith "not implemented"
| AddAnnotationBlock minBuildingBlockInfo ->
match host with
//| Swatehost.Excel _ ->
@@ -82,7 +124,7 @@ module Interface =
let cmd = Spreadsheet.JoinTable (table, index, options) |> SpreadsheetMsg |> Cmd.ofMsg
model, cmd
| _ -> failwith "not implemented"
- | ImportFile tables ->
+ | UpdateArcFile tables ->
match host with
| Some Swatehost.Excel ->
//let cmd = OfficeInterop.ImportFile tables |> OfficeInteropMsg |> Cmd.ofMsg
@@ -92,6 +134,15 @@ module Interface =
let cmd = Spreadsheet.UpdateArcFile tables |> SpreadsheetMsg |> Cmd.ofMsg
model, cmd
| _ -> failwith "not implemented"
+ | ImportXlsx bytes ->
+ match host with
+ | Some Swatehost.Excel ->
+ Browser.Dom.window.alert "ImportXlsx Not implemented"
+ model, Cmd.none
+ | Some Swatehost.Browser | Some Swatehost.ARCitect ->
+ let cmd = Spreadsheet.ImportXlsx bytes |> SpreadsheetMsg |> Cmd.ofMsg
+ model, cmd
+ | _ -> failwith "not implemented"
| InsertOntologyAnnotation termMinimal ->
match host with
//| Swatehost.Excel _ ->
@@ -103,13 +154,24 @@ module Interface =
| _ -> failwith "not implemented"
| InsertFileNames fileNames ->
match host with
- | Some Swatehost.Excel | Some Swatehost.ARCitect ->
+ | Some Swatehost.Excel ->
let cmd = OfficeInterop.InsertFileNames fileNames |> OfficeInteropMsg |> Cmd.ofMsg
model, cmd
- //| Swatehost.Browser ->
- // let arr = fileNames |> List.toArray |> Array.map (fun x -> TermTypes.TermMinimal.create x "")
- // let cmd = Spreadsheet.InsertOntologyTerms arr |> SpreadsheetMsg |> Cmd.ofMsg
- // model, cmd
+ | Some Swatehost.Browser | Some Swatehost.ARCitect ->
+ if model.SpreadsheetModel.SelectedCells.IsEmpty then
+ model, Cmd.ofMsg (DevMsg.GenericError (Cmd.none, exn("No cell(s) selected.")) |> DevMsg)
+ else
+ let columnIndex, rowIndex = model.SpreadsheetModel.SelectedCells.MinimumElement
+ let mutable rowIndex = rowIndex
+ let cells = [|
+ for name in fileNames do
+ let c0 = model.SpreadsheetModel.ActiveTable.TryGetCellAt(columnIndex,rowIndex).Value
+ let cell = c0.UpdateMainField name
+ (columnIndex, rowIndex), cell
+ rowIndex <- rowIndex + 1
+ |]
+ let cmd = Spreadsheet.UpdateCells cells |> SpreadsheetMsg |> Cmd.ofMsg
+ model, cmd
| _ -> failwith "not implemented"
| RemoveBuildingBlock ->
match host with
@@ -128,31 +190,13 @@ module Interface =
Spreadsheet.DeleteColumn (distinct.[0]) |> SpreadsheetMsg |> Cmd.ofMsg
model, cmd
| _ -> failwith "not implemented"
- | ExportJsonTable ->
- match host with
- | Some Swatehost.Excel ->
- let cmd = JsonExporterMsg JsonExporter.ParseTableOfficeInteropRequest |> Cmd.ofMsg
- model, cmd
- | Some Swatehost.Browser ->
- let cmd = SpreadsheetMsg Spreadsheet.ExportJsonTable |> Cmd.ofMsg
- model, cmd
- | _ -> failwith "not implemented"
- | ExportJsonTables ->
+ | ExportJson (arcfile, jef) ->
match host with
| Some Swatehost.Excel ->
- let cmd = JsonExporterMsg JsonExporter.ParseTablesOfficeInteropRequest |> Cmd.ofMsg
- model, cmd
+ failwith "ExportJson not implemented for Excel"
+ model, Cmd.none
| Some Swatehost.Browser ->
- let cmd = SpreadsheetMsg Spreadsheet.ExportJsonTables |> Cmd.ofMsg
- model, cmd
- | _ -> failwith "not implemented"
- | ParseTablesToDag ->
- match host with
- | Some Swatehost.Excel ->
- let cmd = DagMsg Dag.ParseTablesOfficeInteropRequest |> Cmd.ofMsg
- model, cmd
- | Some Swatehost.Browser | Some Swatehost.ARCitect ->
- let cmd = SpreadsheetMsg Spreadsheet.ParseTablesToDag |> Cmd.ofMsg
+ let cmd = SpreadsheetMsg (Spreadsheet.ExportJson (arcfile, jef)) |> Cmd.ofMsg
model, cmd
| _ -> failwith "not implemented"
| EditBuildingBlock ->
diff --git a/src/Client/Update/OfficeInteropUpdate.fs b/src/Client/Update/OfficeInteropUpdate.fs
index f9c3e4d0..f6d0f093 100644
--- a/src/Client/Update/OfficeInteropUpdate.fs
+++ b/src/Client/Update/OfficeInteropUpdate.fs
@@ -9,9 +9,9 @@ open Shared
open OfficeInteropTypes
module OfficeInterop =
- let update (currentModel:Messages.Model) (excelInteropMsg: OfficeInterop.Msg) : Messages.Model * Cmd =
+ let update (model:Messages.Model) (msg: OfficeInterop.Msg) : Messages.Model * Cmd =
- match excelInteropMsg with
+ match msg with
| AutoFitTable hidecols ->
let p = fun () -> ExcelJS.Fable.GlobalBindings.Excel.run (OfficeInterop.Core.autoFitTable hidecols)
@@ -21,18 +21,26 @@ module OfficeInterop =
()
(curry GenericInteropLogs Cmd.none >> DevMsg)
(curry GenericError Cmd.none >> DevMsg)
- currentModel, cmd
+ model, cmd
+ | TryFindAnnotationTable ->
+ let cmd =
+ Cmd.OfPromise.either
+ OfficeInterop.Core.tryFindActiveAnnotationTable
+ ()
+ (OfficeInterop.AnnotationTableExists >> OfficeInteropMsg)
+ (curry GenericError Cmd.none >> DevMsg)
+ model, cmd
| AnnotationTableExists annoTableOpt ->
let exists =
match annoTableOpt with
| Success name -> true
| _ -> false
let nextState = {
- currentModel.ExcelState with
+ model.ExcelState with
HasAnnotationTable = exists
}
- currentModel.updateByExcelState nextState,Cmd.none
+ model.updateByExcelState nextState,Cmd.none
| InsertOntologyTerm (term) ->
let cmd =
@@ -41,7 +49,7 @@ module OfficeInterop =
term
(curry GenericLog Cmd.none >> DevMsg)
(curry GenericError Cmd.none >> DevMsg)
- currentModel, cmd
+ model, cmd
| AddAnnotationBlock (minBuildingBlockInfo) ->
let cmd =
@@ -50,7 +58,7 @@ module OfficeInterop =
(minBuildingBlockInfo)
(curry GenericInteropLogs Cmd.none >> DevMsg)
(curry GenericError Cmd.none >> DevMsg)
- currentModel, cmd
+ model, cmd
| AddAnnotationBlocks minBuildingBlockInfos ->
let cmd =
@@ -59,7 +67,7 @@ module OfficeInterop =
minBuildingBlockInfos
(curry GenericInteropLogs Cmd.none >> DevMsg)
(curry GenericError Cmd.none >> DevMsg)
- currentModel, cmd
+ model, cmd
| ImportFile buildingBlockTables ->
let nextCmd =
@@ -68,7 +76,7 @@ module OfficeInterop =
buildingBlockTables
(curry GenericInteropLogs Cmd.none >> DevMsg)
(curry GenericError Cmd.none >> DevMsg)
- currentModel, nextCmd
+ model, nextCmd
| RemoveBuildingBlock ->
let cmd =
@@ -77,7 +85,7 @@ module OfficeInterop =
()
(curry GenericInteropLogs Cmd.none >> DevMsg)
(curry GenericError Cmd.none >> DevMsg)
- currentModel, cmd
+ model, cmd
| UpdateUnitForCells (unitTerm) ->
let cmd =
@@ -86,7 +94,7 @@ module OfficeInterop =
unitTerm
(curry GenericInteropLogs Cmd.none >> DevMsg)
(curry GenericError Cmd.none >> DevMsg)
- currentModel, cmd
+ model, cmd
| CreateAnnotationTable(tryUsePrevOutput) ->
let cmd =
@@ -95,14 +103,14 @@ module OfficeInterop =
(false,tryUsePrevOutput)
(curry GenericInteropLogs (AnnotationtableCreated |> OfficeInteropMsg |> Cmd.ofMsg) >> DevMsg)
(curry GenericError Cmd.none >> DevMsg)
- currentModel,cmd
+ model,cmd
| AnnotationtableCreated ->
let nextState = {
- currentModel.ExcelState with
+ model.ExcelState with
HasAnnotationTable = true
}
- currentModel.updateByExcelState nextState, Cmd.none
+ model.updateByExcelState nextState, Cmd.none
| GetParentTerm ->
@@ -110,33 +118,34 @@ module OfficeInterop =
Cmd.OfPromise.either
OfficeInterop.Core.getParentTerm
()
- (fun tmin -> tmin |> Option.map (fun t -> ARCtrl.ISA.OntologyAnnotation.fromTerm t.toTerm) |> TermSearch.UpdateParentTerm |> TermSearchMsg)
+ (fun tmin -> tmin |> Option.map (fun t -> ARCtrl.OntologyAnnotation.fromTerm t.toTerm) |> TermSearch.UpdateParentTerm |> TermSearchMsg)
(curry GenericError Cmd.none >> DevMsg)
- currentModel, cmd
+ model, cmd
//
| FillHiddenColsRequest ->
- let cmd =
- Cmd.OfPromise.either
- OfficeInterop.Core.getAllAnnotationBlockDetails
- ()
- (fun (searchTerms,deprecationLogs) ->
- // Push possible deprecation messages by piping through "GenericInteropLogs"
- GenericInteropLogs (
- // This will be executed after "deprecationLogs" are handled by "GenericInteropLogs"
- SearchForInsertTermsRequest searchTerms |> Request |> Api |> Cmd.ofMsg,
- // This will be pushed to Activity logs, or as wanring modal to user in case of LogIdentifier.Warning
- deprecationLogs
- )
- |> DevMsg
- )
- (curry GenericError (UpdateFillHiddenColsState FillHiddenColsState.Inactive |> OfficeInteropMsg |> Cmd.ofMsg) >> DevMsg)
- let stateCmd = UpdateFillHiddenColsState FillHiddenColsState.ExcelCheckHiddenCols |> OfficeInteropMsg |> Cmd.ofMsg
- let cmds = Cmd.batch [cmd; stateCmd]
- currentModel, cmds
+ failwith "FillHiddenColsRequest Not implemented yet"
+ //let cmd =
+ // Cmd.OfPromise.either
+ // OfficeInterop.Core.getAllAnnotationBlockDetails
+ // ()
+ // (fun (searchTerms,deprecationLogs) ->
+ // // Push possible deprecation messages by piping through "GenericInteropLogs"
+ // GenericInteropLogs (
+ // // This will be executed after "deprecationLogs" are handled by "GenericInteropLogs"
+ // SearchForInsertTermsRequest searchTerms |> Request |> Api |> Cmd.ofMsg,
+ // // This will be pushed to Activity logs, or as wanring modal to user in case of LogIdentifier.Warning
+ // deprecationLogs
+ // )
+ // |> DevMsg
+ // )
+ // (curry GenericError (UpdateFillHiddenColsState FillHiddenColsState.Inactive |> OfficeInteropMsg |> Cmd.ofMsg) >> DevMsg)
+ //let stateCmd = UpdateFillHiddenColsState FillHiddenColsState.ExcelCheckHiddenCols |> OfficeInteropMsg |> Cmd.ofMsg
+ //let cmds = Cmd.batch [cmd; stateCmd]
+ model, Cmd.none
| FillHiddenColumns (termsWithSearchResult) ->
let nextState = {
- currentModel.ExcelState with
+ model.ExcelState with
FillHiddenColsStateStore = FillHiddenColsState.ExcelWriteFoundTerms
}
let cmd =
@@ -145,15 +154,15 @@ module OfficeInterop =
(termsWithSearchResult)
(curry GenericInteropLogs (UpdateFillHiddenColsState FillHiddenColsState.Inactive |> OfficeInteropMsg |> Cmd.ofMsg) >> DevMsg)
(curry GenericError (UpdateFillHiddenColsState FillHiddenColsState.Inactive |> OfficeInteropMsg |> Cmd.ofMsg) >> DevMsg)
- currentModel.updateByExcelState nextState, cmd
+ model.updateByExcelState nextState, cmd
| UpdateFillHiddenColsState newState ->
let nextState = {
- currentModel.ExcelState with
+ model.ExcelState with
FillHiddenColsStateStore = newState
}
- currentModel.updateByExcelState nextState, Cmd.none
+ model.updateByExcelState nextState, Cmd.none
//
| InsertFileNames (fileNameList) ->
let cmd =
@@ -162,7 +171,7 @@ module OfficeInterop =
(fileNameList)
(curry GenericLog Cmd.none >> DevMsg)
(curry GenericError Cmd.none >> DevMsg)
- currentModel, cmd
+ model, cmd
//
| GetSelectedBuildingBlockTerms ->
@@ -178,7 +187,7 @@ module OfficeInterop =
]
)
(curry GenericError (UpdateCurrentRequestState RequestBuildingBlockInfoStates.Inactive |> BuildingBlockDetails |> Cmd.ofMsg) >> DevMsg)
- currentModel, cmd
+ model, cmd
// DEV
| TryExcel ->
@@ -188,7 +197,7 @@ module OfficeInterop =
()
((fun x -> curry GenericLog Cmd.none ("Debug",x)) >> DevMsg)
(curry GenericError Cmd.none >> DevMsg)
- currentModel, cmd
+ model, cmd
| TryExcel2 ->
let cmd =
Cmd.OfPromise.either
@@ -196,7 +205,7 @@ module OfficeInterop =
()
((fun x -> curry GenericLog Cmd.none ("Debug",x)) >> DevMsg)
(curry GenericError Cmd.none >> DevMsg)
- currentModel, cmd
+ model, cmd
//| _ ->
// printfn "Hit currently non existing message"
// currentState, Cmd.none
\ No newline at end of file
diff --git a/src/Client/Update/SpreadsheetUpdate.fs b/src/Client/Update/SpreadsheetUpdate.fs
index e81fb52c..614bbd8c 100644
--- a/src/Client/Update/SpreadsheetUpdate.fs
+++ b/src/Client/Update/SpreadsheetUpdate.fs
@@ -7,15 +7,14 @@ open LocalHistory
open Model
open Shared
open Spreadsheet.Table
-open Spreadsheet.Sidebar
+open Spreadsheet.BuildingBlocks
open Spreadsheet.Clipboard
open Fable.Remoting.Client
-open Fable.Remoting.Client.InternalUtilities
open FsSpreadsheet
-open FsSpreadsheet.Exceljs
-open ARCtrl.ISA
-open ARCtrl.ISA.Spreadsheet
-open Spreadsheet.Sidebar.Controller
+open FsSpreadsheet.Js
+open ARCtrl
+open ARCtrl.Spreadsheet
+open ARCtrl.Json
module Spreadsheet =
@@ -27,6 +26,10 @@ module Spreadsheet =
let download(filename, bytes:byte []) = bytes.SaveFileAs(filename)
+ let downloadFromString(filename, content:string) =
+ let bytes = System.Text.Encoding.UTF8.GetBytes(content)
+ bytes.SaveFileAs(filename)
+
///
/// This function will store the information correctly.
/// Can return save information to local storage (persistent between browser sessions) and session storage.
@@ -34,18 +37,21 @@ module Spreadsheet =
///
let updateHistoryStorageMsg (msg: Spreadsheet.Msg) (state: Spreadsheet.Model, model: Messages.Model, cmd) =
match msg with
- | UpdateActiveView _ | UpdateHistoryPosition _ | Reset | UpdateSelectedCells _ | CopySelectedCell | CopyCell _ ->
+ | UpdateActiveView _ | UpdateHistoryPosition _ | Reset | UpdateSelectedCells _
+ | UpdateActiveCell _ | CopySelectedCell | CopyCell _ | MoveSelectedCell _ | SetActiveCellFromSelected ->
state.SaveToLocalStorage() // This will cache the most up to date table state to local storage.
state, model, cmd
| _ ->
state.SaveToLocalStorage() // This will cache the most up to date table state to local storage.
let nextHistory = model.History.SaveSessionSnapshot state // this will cache the table state for certain operations in session storage.
if model.PersistentStorageState.Host = Some Swatehost.ARCitect then
- match model.SpreadsheetModel.ArcFile with
+ match state.ArcFile with // model is not yet updated at this position.
| Some (Assay assay) ->
ARCitect.ARCitect.send(ARCitect.AssayToARCitect assay)
| Some (Study (study,_)) ->
ARCitect.ARCitect.send(ARCitect.StudyToARCitect study)
+ | Some (Investigation inv) ->
+ ARCitect.ARCitect.send(ARCitect.InvestigationToARCitect inv)
| _ -> ()
state, {model with History = nextHistory}, cmd
@@ -66,6 +72,11 @@ module Spreadsheet =
let innerUpdate (state: Spreadsheet.Model) (model: Messages.Model) (msg: Spreadsheet.Msg) =
match msg with
+ | UpdateState nextState ->
+ nextState, model, Cmd.none
+ | AddTable table ->
+ let nextState = Controller.addTable table state
+ nextState, model, Cmd.none
| CreateAnnotationTable usePrevOutput ->
let nextState = Controller.createTable usePrevOutput state
nextState, model, Cmd.none
@@ -82,7 +93,7 @@ module Spreadsheet =
let nextState = { state with ArcFile = Some arcFile }
nextState, model, Cmd.none
| InitFromArcFile arcFile ->
- let nextState = { Spreadsheet.Model.init() with ArcFile = Some arcFile }
+ let nextState = Spreadsheet.Model.init(arcFile)
nextState, model, Cmd.none
| InsertOntologyAnnotation oa ->
let nextState = Controller.insertTerm_IntoSelected oa state
@@ -97,13 +108,22 @@ module Spreadsheet =
state.ActiveTable.UpdateCellAt(fst index,snd index, cell)
{state with ArcFile = state.ArcFile}
nextState, model, Cmd.none
+ | UpdateCells arr ->
+ let nextState =
+ state.ActiveTable.SetCellsAt arr
+ {state with ArcFile = state.ArcFile}
+ nextState, model, Cmd.none
| UpdateHeader (index, header) ->
let nextState =
state.ActiveTable.UpdateHeader(index, header)
{state with ArcFile = state.ArcFile}
nextState, model, Cmd.none
| UpdateActiveView nextView ->
- let nextState = { state with ActiveView = nextView }
+ let nextState = {
+ state with
+ ActiveView = nextView
+ SelectedCells = Set.empty
+ }
nextState, model, Cmd.none
| RemoveTable removeIndex ->
let nextState = Controller.removeTable removeIndex state
@@ -131,8 +151,8 @@ module Spreadsheet =
let nextState = Controller.addRows n state
nextState, model, Cmd.none
| Reset ->
- let nextHistory, nextState = Controller.resetTableState()
- let nextModel = {model with History = nextHistory}
+ let nextState = Controller.resetTableState()
+ let nextModel = {model with History = LocalHistory.Model.init()}
nextState, nextModel, Cmd.none
| DeleteRow index ->
let nextState = Controller.deleteRow index state
@@ -143,32 +163,121 @@ module Spreadsheet =
| DeleteColumn index ->
let nextState = Controller.deleteColumn index state
nextState, model, Cmd.none
+ | SetColumn (index, column) ->
+ let nextState = Controller.setColumn index column state
+ nextState, model, Cmd.none
+ | MoveColumn (current, next) ->
+ let nextState = Controller.moveColumn current next state
+ nextState, model, Cmd.none
| UpdateSelectedCells nextSelectedCells ->
let nextState = {state with SelectedCells = nextSelectedCells}
nextState, model, Cmd.none
- | CopyCell index ->
- let nextState = Controller.copyCell index state
+ | MoveSelectedCell keypressed ->
+ let cmd =
+ match state.SelectedCells.IsEmpty with
+ | true -> Cmd.none
+ | false ->
+ let moveBy =
+ match keypressed with
+ | Key.Down -> (0,1)
+ | Key.Up -> (0,-1)
+ | Key.Left -> (-1,0)
+ | Key.Right -> (1,0)
+ let nextIndex = Controller.selectRelativeCell state.SelectedCells.MinimumElement moveBy state.ActiveTable
+ let s = Set([nextIndex])
+ UpdateSelectedCells s |> SpreadsheetMsg |> Cmd.ofMsg
+ state, model, cmd
+ | SetActiveCellFromSelected ->
+ let cmd =
+ if state.SelectedCells.IsEmpty then
+ Cmd.none
+ else
+ let min = state.SelectedCells.MinimumElement
+ let cmd = (Fable.Core.U2.Case2 min, ColumnType.Main) |> Some |> UpdateActiveCell |> SpreadsheetMsg
+ Cmd.ofMsg cmd
+ state, model, cmd
+ | UpdateActiveCell next ->
+ let nextState = { state with ActiveCell = next }
nextState, model, Cmd.none
+ | CopyCell index ->
+ let cmd =
+ Cmd.OfPromise.attempt
+ (Controller.copyCellByIndex index)
+ state
+ (curry GenericError Cmd.none >> DevMsg)
+ state, model, cmd
+ | CopyCells indices ->
+ let cmd =
+ Cmd.OfPromise.attempt
+ (Controller.copyCellsByIndex indices)
+ state
+ (curry GenericError Cmd.none >> DevMsg)
+ state, model, cmd
| CopySelectedCell ->
- let nextState =
- if state.SelectedCells.IsEmpty then state else
- Controller.copySelectedCell state
- nextState, model, Cmd.none
+ let cmd =
+ Cmd.OfPromise.attempt
+ (Controller.copySelectedCell)
+ state
+ (curry GenericError Cmd.none >> DevMsg)
+ state, model, cmd
+ | CopySelectedCells ->
+ let cmd =
+ Cmd.OfPromise.attempt
+ (Controller.copySelectedCells)
+ state
+ (curry GenericError Cmd.none >> DevMsg)
+ state, model, cmd
| CutCell index ->
- let nextState = Controller.cutCell index state
+ let nextState = Controller.cutCellByIndex index state
nextState, model, Cmd.none
| CutSelectedCell ->
let nextState =
if state.SelectedCells.IsEmpty then state else
Controller.cutSelectedCell state
nextState, model, Cmd.none
- | PasteCell index ->
- let nextState = if state.Clipboard.Cell.IsNone then state else Controller.pasteCell index state
+ | CutSelectedCells ->
+ let nextState =
+ if state.SelectedCells.IsEmpty then state else
+ Controller.cutSelectedCells state
nextState, model, Cmd.none
+ | PasteCell index ->
+ let cmd =
+ Cmd.OfPromise.either
+ (Clipboard.Controller.pasteCellByIndex index)
+ state
+ (UpdateState >> SpreadsheetMsg)
+ (curry GenericError Cmd.none >> DevMsg)
+ state, model, cmd
+ | PasteCellsExtend index ->
+ let cmd =
+ Cmd.OfPromise.either
+ (Clipboard.Controller.pasteCellsByIndexExtend index)
+ state
+ (UpdateState >> SpreadsheetMsg)
+ (curry GenericError Cmd.none >> DevMsg)
+ state, model, cmd
| PasteSelectedCell ->
- let nextState =
- if state.SelectedCells.IsEmpty || state.Clipboard.Cell.IsNone then state else
- Controller.pasteSelectedCell state
+ let cmd =
+ Cmd.OfPromise.either
+ (Clipboard.Controller.pasteCellIntoSelected)
+ state
+ (UpdateState >> SpreadsheetMsg)
+ (curry GenericError Cmd.none >> DevMsg)
+ state, model, cmd
+ | PasteSelectedCells ->
+ let cmd =
+ Cmd.OfPromise.either
+ (Clipboard.Controller.pasteCellsIntoSelected)
+ state
+ (UpdateState >> SpreadsheetMsg)
+ (curry GenericError Cmd.none >> DevMsg)
+ state, model, cmd
+ | Clear indices ->
+ let nextState = Controller.clearCells indices state
+ nextState, model, Cmd.none
+ | ClearSelected ->
+ let indices = state.SelectedCells |> Set.toArray
+ let nextState = Controller.clearCells indices state
nextState, model, Cmd.none
| FillColumnWithTerm index ->
let nextState = Controller.fillColumnWithCell index state
@@ -176,63 +285,41 @@ module Spreadsheet =
//| EditColumn (columnIndex, newCellType, b_type) ->
// let cmd = createPromiseCmd <| fun _ -> Controller.editColumn (columnIndex, newCellType, b_type) state
// state, model, cmd
- | SetArcFileFromBytes bytes ->
+ | ImportXlsx bytes ->
let cmd =
Cmd.OfPromise.either
- Spreadsheet.IO.readFromBytes
+ Spreadsheet.IO.Xlsx.readFromBytes
bytes
(UpdateArcFile >> Messages.SpreadsheetMsg)
(Messages.curry Messages.GenericError Cmd.none >> Messages.DevMsg)
state, model, cmd
- | ExportJsonTable ->
- failwith "ExportsJsonTable is not implemented"
- //let exportJsonState = {model.JsonExporterModel with Loading = true}
- //let nextModel = model.updateByJsonExporterModel exportJsonState
- //let func() = promise {
- // return Controller.getTable state
- //}
- //let cmd =
- // Cmd.OfPromise.either
- // func
- // ()
- // (JsonExporter.State.ParseTableServerRequest >> Messages.JsonExporterMsg)
- // (Messages.curry Messages.GenericError (JsonExporter.State.UpdateLoading false |> Messages.JsonExporterMsg |> Cmd.ofMsg) >> Messages.DevMsg)
- //state, nextModel, cmd
- state, model, Cmd.none
- | ExportJsonTables ->
- failwith "ExportJsonTables is not implemented"
- //let exportJsonState = {model.JsonExporterModel with Loading = true}
- //let nextModel = model.updateByJsonExporterModel exportJsonState
- //let func() = promise {
- // return Controller.getTables state
- //}
- //let cmd =
- // Cmd.OfPromise.either
- // func
- // ()
- // (JsonExporter.State.ParseTablesServerRequest >> Messages.JsonExporterMsg)
- // (Messages.curry Messages.GenericError (JsonExporter.State.UpdateLoading false |> Messages.JsonExporterMsg |> Cmd.ofMsg) >> Messages.DevMsg)
- //state, nextModel, cmd
- state, model, Cmd.none
- | ParseTablesToDag ->
- failwith "ParseTablesToDag is not implemented"
- //let dagState = {model.DagModel with Loading = true}
- //let nextModel = model.updateByDagModel dagState
- //let func() = promise {
- // return Controller.getTables state
- //}
- //let cmd =
- // Cmd.OfPromise.either
- // func
- // ()
- // (Dag.ParseTablesDagServerRequest >> Messages.DagMsg)
- // (Messages.curry Messages.GenericError (Dag.UpdateLoading false |> Messages.DagMsg |> Cmd.ofMsg) >> Messages.DevMsg)
- //state, nextModel, cmd
+ | ExportJson (arcfile,jef) ->
+ let name, jsonString =
+ let n = System.DateTime.Now.ToUniversalTime().ToString("yyyyMMdd_hhmmss")
+ let nameFromId (id: string) = (n + "_" + id + ".json")
+ match arcfile, jef with
+ | Investigation ai, JsonExportFormat.ARCtrl -> nameFromId ai.Identifier, ArcInvestigation.toJsonString 0 ai
+ | Investigation ai, JsonExportFormat.ARCtrlCompressed -> nameFromId ai.Identifier, ArcInvestigation.toCompressedJsonString 0 ai
+ | Investigation ai, JsonExportFormat.ISA -> nameFromId ai.Identifier, ArcInvestigation.toISAJsonString 0 ai
+ | Investigation ai, JsonExportFormat.ROCrate -> nameFromId ai.Identifier, ArcInvestigation.toROCrateJsonString 0 ai
+
+ | Study (as',_), JsonExportFormat.ARCtrl -> nameFromId as'.Identifier, ArcStudy.toJsonString 0 (as')
+ | Study (as',_), JsonExportFormat.ARCtrlCompressed -> nameFromId as'.Identifier, ArcStudy.toCompressedJsonString 0 (as')
+ | Study (as',aaList), JsonExportFormat.ISA -> nameFromId as'.Identifier, ArcStudy.toISAJsonString (aaList,0) (as')
+ | Study (as',aaList), JsonExportFormat.ROCrate -> nameFromId as'.Identifier, ArcStudy.toROCrateJsonString (aaList,0) (as')
+
+ | Assay aa, JsonExportFormat.ARCtrl -> nameFromId aa.Identifier, ArcAssay.toJsonString 0 aa
+ | Assay aa, JsonExportFormat.ARCtrlCompressed -> nameFromId aa.Identifier, ArcAssay.toCompressedJsonString 0 aa
+ | Assay aa, JsonExportFormat.ISA -> nameFromId aa.Identifier, ArcAssay.toISAJsonString 0 aa
+ | Assay aa, JsonExportFormat.ROCrate -> nameFromId aa.Identifier, ArcAssay.toROCrateJsonString () aa
+
+ | Template t, JsonExportFormat.ARCtrl -> nameFromId t.FileName, Template.toJsonString 0 t
+ | Template t, JsonExportFormat.ARCtrlCompressed -> nameFromId t.FileName, Template.toCompressedJsonString 0 t
+ | Template _, anyElse -> failwithf "Error. It is not intended to parse Template to %s format." (string anyElse)
+ Helper.downloadFromString (name , jsonString)
+
state, model, Cmd.none
| ExportXlsx arcfile->
- // we highjack this loading function
- let exportJsonState = {model.JsonExporterModel with Loading = true}
- let nextModel = model.updateByJsonExporterModel exportJsonState
let name, fswb =
let n = System.DateTime.Now.ToUniversalTime().ToString("yyyyMMdd_hhmmss")
match arcfile with
@@ -243,22 +330,17 @@ module Spreadsheet =
| Assay aa ->
n + "_" + ArcAssay.FileName, ArcAssay.toFsWorkbook aa
| Template t ->
- n + "_" + t.FileName, ARCtrl.Template.Spreadsheet.Template.toFsWorkbook t
+ n + "_" + t.FileName, Spreadsheet.Template.toFsWorkbook t
let cmd =
Cmd.OfPromise.either
- FsSpreadsheet.Exceljs.Xlsx.toBytes
+ Xlsx.toXlsxBytes
fswb
(fun bytes -> ExportXlsxDownload (name,bytes) |> Messages.SpreadsheetMsg)
- (Messages.curry Messages.GenericError (JsonExporter.UpdateLoading false |> Messages.JsonExporterMsg |> Cmd.ofMsg) >> Messages.DevMsg)
- state, nextModel, cmd
+ (Messages.curry Messages.GenericError Cmd.none >> Messages.DevMsg)
+ state, model, cmd
| ExportXlsxDownload (name,xlsxBytes) ->
let _ = Helper.download (name ,xlsxBytes)
- let nextJsonExporter = {
- model.JsonExporterModel with
- Loading = false
- }
- let nextModel = model.updateByJsonExporterModel nextJsonExporter
- state, nextModel, Cmd.none
+ state, model, Cmd.none
| UpdateTermColumns ->
//let getUpdateTermColumns() = promise {
// return Controller.getUpdateTermColumns state
diff --git a/src/Client/Views/MainWindowView.fs b/src/Client/Views/MainWindowView.fs
index 396c6925..795e8fbb 100644
--- a/src/Client/Views/MainWindowView.fs
+++ b/src/Client/Views/MainWindowView.fs
@@ -4,8 +4,29 @@ open Feliz
open Feliz.Bulma
open Messages
open Shared
+open MainComponents
+open Shared
+open Fable.Core.JsInterop
+
+let private WidgetOrderContainer bringWidgetToFront (widget) =
+ Html.div [
+ prop.onClick bringWidgetToFront
+ prop.children [
+ widget
+ ]
+ ]
+
+let private ModalDisplay (widgets: Widget list, displayWidget: Widget -> ReactElement) =
+
+ match widgets.Length with
+ | 0 ->
+ Html.none
+ | _ ->
+ Html.div [
+ for widget in widgets do displayWidget widget
+ ]
-let private spreadsheetSelectionFooter (model: Messages.Model) dispatch =
+let private SpreadsheetSelectionFooter (model: Messages.Model) dispatch =
Html.div [
prop.style [
style.position.sticky;
@@ -15,23 +36,19 @@ let private spreadsheetSelectionFooter (model: Messages.Model) dispatch =
Html.div [
prop.children [
Bulma.tabs [
- prop.style [style.overflowY.visible]
Bulma.tabs.isBoxed
prop.children [
Html.ul [
- yield Bulma.tab [
+ Bulma.tab [
prop.style [style.width (length.px 20)]
]
- yield
- MainComponents.FooterTabs.MainMetadata {| model=model; dispatch = dispatch |}
+ MainComponents.FooterTabs.MainMetadata (model, dispatch)
for index in 0 .. (model.SpreadsheetModel.Tables.TableCount-1) do
- yield
- MainComponents.FooterTabs.Main {| index = index; tables = model.SpreadsheetModel.Tables; model = model; dispatch = dispatch |}
- match model.SpreadsheetModel.ArcFile with
- | Some (ArcFiles.Template _) | Some (ArcFiles.Investigation _) ->
- yield Html.none
- | _ ->
- yield MainComponents.FooterTabs.MainPlus {| dispatch = dispatch |}
+ MainComponents.FooterTabs.Main (index, model.SpreadsheetModel.Tables, model, dispatch)
+ if model.SpreadsheetModel.CanHaveTables() then
+ MainComponents.FooterTabs.MainPlus (model, dispatch)
+ if model.SpreadsheetModel.TableViewIsActive() then
+ MainComponents.FooterTabs.ToggleSidebar(model, dispatch)
]
]
]
@@ -43,7 +60,22 @@ let private spreadsheetSelectionFooter (model: Messages.Model) dispatch =
open Shared
[]
-let Main (model: Messages.Model) dispatch =
+let Main (model: Messages.Model, dispatch) =
+ let widgets, setWidgets = React.useState([])
+ let rmvWidget (widget: Widget) = widgets |> List.except [widget] |> setWidgets
+ let bringWidgetToFront (widget: Widget) =
+ let newList = widgets |> List.except [widget] |> fun x -> widget::x |> List.rev
+ setWidgets newList
+ let displayWidget (widget: Widget) =
+ let rmv (widget: Widget) = fun _ -> rmvWidget widget
+ let bringWidgetToFront = fun _ -> bringWidgetToFront widget
+ match widget with
+ | Widget._BuildingBlock -> Widget.BuildingBlock (model, dispatch, rmv widget)
+ | Widget._Template -> Widget.Templates (model, dispatch, rmv widget)
+ | Widget._FilePicker -> Widget.FilePicker (model, dispatch, rmv widget)
+ |> WidgetOrderContainer bringWidgetToFront
+ let addWidget (widget: Widget) =
+ widget::widgets |> List.rev |> setWidgets
let state = model.SpreadsheetModel
Html.div [
prop.id "MainWindow"
@@ -54,7 +86,8 @@ let Main (model: Messages.Model) dispatch =
style.height (length.percent 100)
]
prop.children [
- MainComponents.Navbar.Main model dispatch
+ MainComponents.Navbar.Main (model, dispatch, widgets, setWidgets)
+ ModalDisplay (widgets, displayWidget)
Html.div [
prop.id "TableContainer"
prop.style [
@@ -73,12 +106,13 @@ let Main (model: Messages.Model) dispatch =
| Some (ArcFiles.Study _)
| Some (ArcFiles.Investigation _)
| Some (ArcFiles.Template _) ->
- XlsxFileView.Main {|model = model; dispatch = dispatch|}
+ Html.none
+ XlsxFileView.Main (model, dispatch, (fun () -> addWidget Widget._BuildingBlock), (fun () -> addWidget Widget._Template))
if state.Tables.TableCount > 0 && state.ActiveTable.ColumnCount > 0 && state.ActiveView <> Spreadsheet.ActiveView.Metadata then
MainComponents.AddRows.Main dispatch
]
]
if state.ArcFile.IsSome then
- spreadsheetSelectionFooter model dispatch
+ SpreadsheetSelectionFooter model dispatch
]
]
\ No newline at end of file
diff --git a/src/Client/Views/SidebarView.fs b/src/Client/Views/SidebarView.fs
index 37284b30..a8822137 100644
--- a/src/Client/Views/SidebarView.fs
+++ b/src/Client/Views/SidebarView.fs
@@ -30,6 +30,7 @@ let private createNavigationTab (pageLink: Routing.Route) (model:Model) (dispatc
Bulma.tab [
if isActive then Bulma.tab.isActive
Html.a [
+ prop.className "navigation" // this class does not do anything, but disables styling.
prop.onClick (fun e -> e.preventDefault(); UpdatePageState (Some pageLink) |> dispatch)
match sidebarsize with
| Mini | MobileMini ->
@@ -55,30 +56,13 @@ let private tabs (model:Model) dispatch (sidebarsize: Model.WindowSize) =
let isIEBrowser : bool = Browser.Dom.window.document?documentMode
tabRow model [
if not model.PageState.IsExpert then
- createNavigationTab Routing.Route.BuildingBlock model dispatch sidebarsize
- createNavigationTab Routing.Route.TermSearch model dispatch sidebarsize
- createNavigationTab Routing.Route.Protocol model dispatch sidebarsize
- createNavigationTab Routing.Route.FilePicker model dispatch sidebarsize
- //if not isIEBrowser then
- // docsrc attribute not supported in iframe in IE
- //createNavigationTab Routing.Route.Dag model dispatch sidebarsize
- createNavigationTab Routing.Route.Info model dispatch sidebarsize
+ createNavigationTab Routing.Route.BuildingBlock model dispatch sidebarsize
+ createNavigationTab Routing.Route.TermSearch model dispatch sidebarsize
+ createNavigationTab Routing.Route.Protocol model dispatch sidebarsize
+ createNavigationTab Routing.Route.FilePicker model dispatch sidebarsize
+ createNavigationTab Routing.Route.JsonExport model dispatch sidebarsize
else
- createNavigationTab Routing.Route.JsonExport model dispatch sidebarsize
- createNavigationTab Routing.Route.TemplateMetadata model dispatch sidebarsize
- //createNavigationTab Routing.Route.Validation model dispatch sidebarsize
- createNavigationTab Routing.Route.Info model dispatch sidebarsize
- ]
-
-
-let private footer (model:Model) =
- div [Style [Color "grey"; Position PositionOptions.Sticky; Width "inherit"; Bottom "0"; TextAlign TextAlignOptions.Center ]] [
- div [] [
- str "Swate Release Version "
- a [Href "https://github.com/nfdi4plants/Swate/releases"] [str model.PersistentStorageState.AppVersion]
- str " Host "
- Html.span [prop.style [style.color "#4fb3d9"]; prop.text (sprintf "%O" model.PersistentStorageState.Host)]
- ]
+ createNavigationTab Routing.Route.JsonExport model dispatch sidebarsize
]
module private ResizeObserver =
@@ -131,11 +115,6 @@ let private viewContainer (model: Model) (dispatch: Msg -> unit) (state: Sidebar
let ele = Browser.Dom.document.getElementById(Sidebar_Id)
ResizeObserver.observer(state, setState).observe(ele)
)
- OnClick (fun e ->
- if model.AddBuildingBlockState.ShowUnit2TermSuggestions
- then
- TopLevelMsg.CloseSuggestions |> TopLevelMsg |> dispatch
- )
Style [
Display DisplayOptions.Flex
FlexGrow "1"
@@ -146,9 +125,27 @@ let private viewContainer (model: Model) (dispatch: Msg -> unit) (state: Sidebar
]
] children
+type SidebarView =
+
+ []
+ static member private footer (model:Model, dispatch) =
+ React.useEffectOnce(fun () ->
+ async {
+ let! versionResponse = Api.serviceApi.getAppVersion()
+ PersistentStorage.UpdateAppVersion versionResponse |> PersistentStorageMsg |> dispatch
+ }
+ |> Async.StartImmediate
+ )
+ div [Style [Color "grey"; Position PositionOptions.Sticky; Width "inherit"; Bottom "0"; TextAlign TextAlignOptions.Center ]] [
+ div [] [
+ str "Swate Release Version "
+ a [Href "https://github.com/nfdi4plants/Swate/releases"; HTMLAttr.Target "_Blank"] [str model.PersistentStorageState.AppVersion]
+ str " Host "
+ Html.a [prop.style [style.cursor.defaultCursor] ;prop.text (sprintf "%O" model.PersistentStorageState.Host)]
+ ]
+ ]
-module private Content =
- let main (model:Model) (dispatch: Msg -> unit) =
+ static member private content (model:Model) (dispatch: Msg -> unit) =
match model.PageState.CurrentPage with
| Routing.Route.BuildingBlock | Routing.Route.Home _ ->
BuildingBlock.Core.addBuildingBlockComponent model dispatch
@@ -160,16 +157,13 @@ module private Content =
FilePicker.filePickerComponent model dispatch
| Routing.Route.Protocol ->
- Protocol.Core.fileUploadViewComponent model dispatch
+ Protocol.Templates.Main (model, dispatch)
| Routing.Route.JsonExport ->
- JsonExporter.Core.jsonExporterMainElement model dispatch
-
- | Routing.Route.TemplateMetadata ->
- TemplateMetadata.Core.newNameMainElement model dispatch
+ JsonExporter.Core.FileExporter.Main(model, dispatch)
| Routing.Route.ProtocolSearch ->
- Protocol.Search.ProtocolSearchView model dispatch
+ Protocol.Search.Main model dispatch
| Routing.Route.ActivityLog ->
ActivityLog.activityLogComponent model dispatch
@@ -177,49 +171,43 @@ module private Content =
| Routing.Route.Settings ->
SettingsView.settingsViewComponent model dispatch
- //| Routing.Route.SettingsXml ->
- // SettingsXml.settingsXmlViewComponent model dispatch
-
- | Routing.Route.Dag ->
- Dag.Core.mainElement model dispatch
-
| Routing.Route.Info ->
InfoView.infoComponent model dispatch
| Routing.Route.NotFound ->
NotFoundView.notFoundComponent model dispatch
-/// The base react component for the sidebar view in the app. contains the navbar and takes body and footer components to create the full view.
-[]
-let SidebarView (model: Model) (dispatch: Msg -> unit) =
- let state, setState = React.useState(SidebarStyle.init)
- viewContainer model dispatch state setState [
- SidebarComponents.Navbar.NavbarComponent model dispatch state.Size
-
- Bulma.container [
- Bulma.container.isFluid
- prop.className "pl-4 pr-4"
- prop.children [
- tabs model dispatch state.Size
-
- //str <| state.Size.ToString()
-
- //Button.button [
- // Button.OnClick (fun _ ->
- // //Spreadsheet.Controller.deleteRow 2 model.SpreadsheetModel
- // //()
- // //Spreadsheet.DeleteColumn 1 |> SpreadsheetMsg |> dispatch
- // ()
- // )
- //] [ str "Test button" ]
-
- match model.PersistentStorageState.Host, not model.ExcelState.HasAnnotationTable with
- | Some Swatehost.Excel, true ->
- SidebarComponents.AnnotationTableMissingWarning.annotationTableMissingWarningComponent model dispatch
- | _ -> ()
-
- Content.main model dispatch
+ /// The base react component for the sidebar view in the app. contains the navbar and takes body and footer components to create the full view.
+ []
+ static member Main (model: Model, dispatch: Msg -> unit) =
+ let state, setState = React.useState(SidebarStyle.init)
+ viewContainer model dispatch state setState [
+ SidebarComponents.Navbar.NavbarComponent model dispatch state.Size
+
+ Bulma.container [
+ Bulma.container.isFluid
+ prop.className "pl-4 pr-4"
+ prop.children [
+ tabs model dispatch state.Size
+
+ //str <| state.Size.ToString()
+
+ //Button.button [
+ // Button.OnClick (fun _ ->
+ // //Spreadsheet.Controller.deleteRow 2 model.SpreadsheetModel
+ // //()
+ // //Spreadsheet.DeleteColumn 1 |> SpreadsheetMsg |> dispatch
+ // ()
+ // )
+ //] [ str "Test button" ]
+
+ match model.PersistentStorageState.Host, not model.ExcelState.HasAnnotationTable with
+ | Some Swatehost.Excel, true ->
+ SidebarComponents.AnnotationTableMissingWarning.annotationTableMissingWarningComponent model dispatch
+ | _ -> ()
+
+ SidebarView.content model dispatch
+ ]
]
- ]
- footer model
- ]
\ No newline at end of file
+ SidebarView.footer (model, dispatch)
+ ]
\ No newline at end of file
diff --git a/src/Client/Views/SplitWindowView.fs b/src/Client/Views/SplitWindowView.fs
index 379b3d9c..7378388e 100644
--- a/src/Client/Views/SplitWindowView.fs
+++ b/src/Client/Views/SplitWindowView.fs
@@ -1,6 +1,7 @@
module SplitWindowView
open Feliz
+open Feliz.Bulma
open Elmish
open Browser.Types
open LocalStorage.SplitWindow
@@ -20,10 +21,12 @@ let private calculateNewSideBarSize (model:SplitWindow) (pos:float) =
let private onResize_event (model:SplitWindow) (setModel: SplitWindow -> unit) = (fun (e: Event) ->
/// must get width like this, cannot propagate model correctly.
- let sidebarWindow = Browser.Dom.document.getElementById(sidebarId).clientWidth
- let windowWidth = Browser.Dom.window.innerWidth
- let new_sidebarWidth = calculateNewSideBarSize model (windowWidth - sidebarWindow)
- { model with RightWindowWidth = new_sidebarWidth } |> setModel
+ let ele = Browser.Dom.document.getElementById(sidebarId)
+ if isNull ele |> not then
+ let sidebarWindow = ele.clientWidth
+ let windowWidth = Browser.Dom.window.innerWidth
+ let new_sidebarWidth = calculateNewSideBarSize model (windowWidth - sidebarWindow)
+ { model with RightWindowWidth = new_sidebarWidth } |> setModel
)
/// This event changes the size of main window and sidebar
@@ -74,12 +77,31 @@ let exampleTerm =
false
"MS"
+let private sidebarCombinedElement(sidebarId: string, model: SplitWindow, setModel, dispatch, right) =
+ Html.div [
+ prop.id sidebarId
+ prop.style [
+ style.float'.right;
+ style.minWidth(minWidth);
+ style.flexBasis(length.px model.RightWindowWidth); style.flexShrink 0; style.flexGrow 0
+ style.height(length.vh 100)
+ style.width(length.perc 100)
+ style.overflow.hidden
+ style.display.flex
+ ]
+ prop.children [
+ dragbar model setModel dispatch
+ yield! right
+ ]
+ ]
+
// https://jsfiddle.net/gaby/Bek9L/
// https://stackoverflow.com/questions/6219031/how-can-i-resize-a-div-by-dragging-just-one-side-of-it
/// Splits screen into two parts. Left and right, with a dragbar in between to change size of right side.
[]
let Main (left:seq) (right:seq) (mainModel:Messages.Model) (dispatch: Messages.Msg -> unit) =
let (model, setModel) = React.useState(SplitWindow.init)
+ let isNotMetadataSheet = not (mainModel.SpreadsheetModel.ActiveView = Spreadsheet.ActiveView.Metadata)
React.useEffect(model.WriteToLocalStorage, [|box model|])
React.useEffectOnce(fun _ -> Browser.Dom.window.addEventListener("resize", onResize_event model setModel))
Html.div [
@@ -87,32 +109,8 @@ let Main (left:seq) (right:seq]
-let Main(x: {| model: Messages.Model; dispatch: Messages.Msg -> unit |}) =
- let model, dispatch = x.model, x.dispatch
+let Main(model: Messages.Model, dispatch: Messages.Msg -> unit, openBuildingBlockWidget, openTemplateWidget) =
match model.SpreadsheetModel.ActiveView with
| ActiveView.Table _ ->
- MainComponents.SpreadsheetView.Main model dispatch
+ match model.SpreadsheetModel.ActiveTable.ColumnCount with
+ | 0 ->
+ MainComponents.EmptyTableElement.Main(openBuildingBlockWidget, openTemplateWidget)
+ | _ ->
+ MainComponents.SpreadsheetView.Main model dispatch
| ActiveView.Metadata ->
Bulma.section [
Bulma.container [
diff --git a/src/Client/index.html b/src/Client/index.html
index c550747b..db90cd02 100644
--- a/src/Client/index.html
+++ b/src/Client/index.html
@@ -4,7 +4,6 @@
Swate
-
@@ -15,8 +14,8 @@
""") |> ignore
- // //newScript.AppendLine("""""") |> ignore
- // //newScript.AppendLine("""""") |> ignore
- // newScript.AppendLine("""""") |> ignore
- // newScript.AppendLine("") |> ignore
- // newScript.ToString()
-
- ///// Converts a CyGraph to it HTML representation. The div layer has a default size of 600 if not specified otherwise.
- //let toCytoHTML (cy:Cytoscape) =
- // let guid = Guid.NewGuid().ToString()
- // let id = sprintf "e%s" <| Guid.NewGuid().ToString().Replace("-","").Substring(0,10)
- // cy.container <- PlainJsonString id
-
- // let userZoomingEnabled =
- // match cy.TryGetTypedValue "zoom" with
- // | Some z ->
- // match z.TryGetTypedValue "zoomingEnabled" with
- // | Some t -> t
- // | None -> false
- // | None -> false
- // |> string
- // |> fun s -> s.ToLower()
-
- // let strCANVAS = // DynamicObj.DynObj.tryGetValue cy "Dims" //tryGetLayoutSize gChart
- // match cy.TryGetTypedValue
member this.searchByParentStepwise(query: string, parentId: string, ?searchType:FullTextSearch, ?limit: int) =
let limit = defaultArg limit 5
- let searchNameQuery =
- """CALL db.index.fulltext.queryNodes("TermName", $Search, {limit: $Limit})
- YIELD node
- RETURN node.accession"""
+ let searchNameQuery = Queries.NameQueryFullText ("node", limit=limit)
let searchTreeQuery =
"""MATCH (node:Term)
WHERE node.accession IN $AccessionList
@@ -127,7 +125,7 @@ type Term(?credentials:Neo4JCredentials, ?session:IAsyncSession) =
session.RunAsync(
searchNameQuery,
System.Collections.Generic.Dictionary([
- KeyValuePair("Search", box fulltextSearchStr);
+ KeyValuePair("Name", box fulltextSearchStr);
// KeyValuePair("Accession", box parentId);
KeyValuePair("Limit", box limit)
]),
diff --git a/src/Server/Export.fs b/src/Server/Export.fs
deleted file mode 100644
index f893d81c..00000000
--- a/src/Server/Export.fs
+++ /dev/null
@@ -1,68 +0,0 @@
-module Export
-
-open System
-open ISADotNet
-open Shared.OfficeInteropTypes
-
-//type Column with
-// member this.toMatrixElement() =
-// let header = this.Header.SwateColumnHeader
-// this.Cells
-// |> Array.map (fun cell ->
-// (cell.Index, header), Option.defaultValue "" cell.Value
-// )
-// member this.toMatrixElement(rebaseIndex:int) =
-// let header = this.Header.SwateColumnHeader
-// this.Cells
-// |> Array.map (fun cell ->
-// (cell.Index-rebaseIndex, header), Option.defaultValue "" cell.Value
-// )
-
-//let parseBuildingBlockToMatrix (buildingBlocks:BuildingBlock []) =
-// let matrixHeaders =
-// buildingBlocks
-// |> Array.collect (fun bb -> [|
-// bb.MainColumn.Header.SwateColumnHeader
-// if bb.hasUnit then
-// bb.Unit.Value.Header.SwateColumnHeader
-// if bb.hasCompleteTSRTAN then
-// bb.TSR.Value.Header.SwateColumnHeader
-// bb.TAN.Value.Header.SwateColumnHeader
-// |])
-// let rebaseindex =
-// let getCellIndices (cellArr:Cell []) = cellArr |> Array.map (fun c -> c.Index)
-// buildingBlocks
-// |> Array.collect (fun bb -> [|
-// yield! bb.MainColumn.Cells |> getCellIndices
-// if bb.hasUnit then
-// yield! bb.Unit.Value.Cells |> getCellIndices
-// if bb.hasCompleteTSRTAN then
-// yield! bb.TSR.Value.Cells |> getCellIndices
-// yield! bb.TAN.Value.Cells |> getCellIndices
-// |]) |> Array.min
-// let matrixArr =
-// buildingBlocks
-// |> Array.collect (fun bb -> [|
-// yield! bb.MainColumn.toMatrixElement(rebaseindex)
-// if bb.hasUnit then
-// yield! bb.Unit.Value.toMatrixElement(rebaseindex)
-// if bb.hasCompleteTSRTAN then
-// yield! bb.TSR.Value.toMatrixElement(rebaseindex)
-// yield! bb.TAN.Value.toMatrixElement(rebaseindex)
-// |])
-// let matrix = Collections.Generic.Dictionary<(int*string),string>(Map.ofArray matrixArr)
-// matrixHeaders, matrix
-
-//let parseBuildingBlockToAssay (templateName:string) (buildingBlocks:BuildingBlock []) =
-// let matrixHeaders, matrix = parseBuildingBlockToMatrix buildingBlocks
-// //printfn "%A" matrixHeaders // contains "Component [instrument model]"
-// ISADotNet.XLSX.AssayFile.Assay.fromSparseMatrix templateName matrixHeaders matrix
-
-//let parseBuildingBlockSeqsToAssay (worksheetNameBuildingBlocks: (string*BuildingBlock []) []) =
-// let matrices =
-// worksheetNameBuildingBlocks
-// |> Array.map (fun (templateName, buildingBlocks) ->
-// let matrixHeaders, matrix = parseBuildingBlockToMatrix buildingBlocks
-// templateName, Seq.ofArray matrixHeaders, matrix
-// )
-// ISADotNet.XLSX.AssayFile.Assay.fromSparseMatrices matrices
diff --git a/src/Server/ISADotNet.fs b/src/Server/ISADotNet.fs
deleted file mode 100644
index 6a088f6f..00000000
--- a/src/Server/ISADotNet.fs
+++ /dev/null
@@ -1,282 +0,0 @@
-module ISADotNet
-
-//open Shared
-//open OfficeInteropTypes
-//open TermTypes
-//open ISADotNet
-//open ISADotNet.Json
-
-//module Assay =
-
-// open FsSpreadsheet.ExcelIO
-
-// /// Reads an assay from an xlsx spreadsheetdocument
-// ///
-// /// As factors and protocols are used for the investigation file, they are returned individually
-// ///
-// /// The persons from the metadata sheet are returned independently as they are not a part of the assay object
-// let fromTemplateSpreadsheet (doc:DocumentFormat.OpenXml.Packaging.SpreadsheetDocument,tableName:string) =
-
-// let sst = Spreadsheet.tryGetSharedStringTable doc
-
-// // All sheetnames in the spreadsheetDocument
-// let sheetNames =
-// Spreadsheet.getWorkbookPart doc
-// |> Workbook.get
-// |> Sheet.Sheets.get
-// |> Sheet.Sheets.getSheets
-// |> Seq.map Sheet.getName
-
-// let assay =
-// sheetNames
-// |> Seq.tryPick (fun sheetName ->
-// Spreadsheet.tryGetWorksheetPartBySheetName sheetName doc
-// |> Option.bind (fun wsp ->
-// Table.tryGetByNameBy (fun s -> s = tableName) wsp
-// |> Option.map (fun table ->
-// let sheet = Worksheet.getSheetData wsp.Worksheet
-// let headers = Table.getColumnHeaders table
-// let m = Table.toSparseValueMatrix sst sheet table
-// XLSX.AssayFile.Process.fromSparseMatrix sheetName headers m
-// |> fun ps -> Assay.create(ProcessSequence = ps)
-// )
-// )
-// )
-// assay
-
-
-///// Only use this function for protocol templates from db
-//let rowMajorOfTemplateJson jsonString =
-// let assay = Assay.fromString jsonString
-// let qAssay = QueryModel.QAssay.fromAssay assay
-// if qAssay.Sheets.Length <> 1 then
-// failwith "Swate was unable to identify the information from the requested template (). Please open an issue for the developers."
-// let template = qAssay.Sheets.Head
-// template //QAssay
-
-//let private ColumnPositionCommentName = "ValueIndex"
-
-//type OntologyAnnotation with
-// member this.toTermMinimal =
-// match this.Name, Option.bind Regex.parseTermAccession this.TermAccessionNumber with
-// | Some name, Some tan -> TermMinimal.create (AnnotationValue.toString name) tan |> Some
-// | Some name, None -> TermMinimal.create (AnnotationValue.toString name) "" |> Some
-// | _,_ -> None
-
-//type ISADotNet.Value with
-// member this.toTermMinimal =
-// let nameOpt,tanUrlOpt,tsrOpt = ISADotNet.Value.toOptions this
-// let name = nameOpt |> Option.defaultValue ""
-// let tan =
-// if tanUrlOpt.IsSome then
-// Regex.parseTermAccession tanUrlOpt.Value |> Option.map (fun tan -> tan.Replace("_",":")) |> Option.defaultValue ""
-// else
-// ""
-// TermMinimal.create name tan
-
-//let getColumnPosition (oa:OntologyAnnotation) =
-// let c = oa.Comments |> Option.map (List.find (fun c -> c.Name = Some ColumnPositionCommentName))
-// c.Value.Value.Value |> int
-
-//open ISADotNet.QueryModel
-
-//type ISAValue with
-
-// member this.toInsertBuildingBlock : (int * InsertBuildingBlock) =
-// let buildingBlockType =
-// if this.IsFactorValue then
-// BuildingBlockType.Factor
-// elif this.IsParameterValue then
-// BuildingBlockType.Parameter
-// elif this.IsCharacteristicValue then
-// BuildingBlockType.Characteristic
-// elif this.IsComponent then
-// BuildingBlockType.Component
-// else
-// failwithf "This function should only ever be used to parse Factor/Parameter/Characteristic/Component, instead parsed: %A" this
-// let colHeaderTermName =
-// if this.HasCategory |> not then
-// None
-// else
-// this.Category.toTermMinimal
-// let columnPosition = getColumnPosition this.Category
-// let unitTerm = if this.HasUnit then this.Unit.toTermMinimal else None
-// let headerPrePrint = OfficeInteropTypes.BuildingBlockNamePrePrint.create buildingBlockType colHeaderTermName.Value.Name
-// let value = if this.HasValue then Array.singleton this.Value.toTermMinimal else [||]
-// //printfn "%A" (if this.HasValue then box this.Value else box "")
-// columnPosition, InsertBuildingBlock.create headerPrePrint colHeaderTermName unitTerm value
-
-
-//type IOType with
-// member this.toBuildingBlockType =
-// match this with
-// | Source -> BuildingBlockType.Source
-// | Sample -> BuildingBlockType.Sample
-// | Data -> BuildingBlockType.Data
-// | RawData -> BuildingBlockType.RawDataFile
-// | ProcessedData -> BuildingBlockType.DerivedDataFile
-// | anyElse -> failwith $"Cannot parse {anyElse} IsaDotNet IOType to BuildingBlockType."
-
-//let createBuildingBlock_fromProtocolType (protocol: Protocol) =
-// let header = OfficeInteropTypes.BuildingBlockNamePrePrint.create BuildingBlockType.ProtocolType ""
-// let columnTerm = Some BuildingBlockType.ProtocolType.getFeaturedColumnTermMinimal
-// let rows =
-// protocol
-// |> fun protType ->
-// // row range information is saved in comments and can be accessed + parsed by isadotnet function
-// let rowStart, rowEnd = protType.GetRowRange()
-// let tmEmpty = TermMinimal.create "" ""
-// [| for _ in rowStart .. rowEnd do
-// let hasValue = protType.ProtocolType.IsSome
-// yield
-// if hasValue then protType.ProtocolType.Value.toTermMinimal |> Option.defaultValue tmEmpty else tmEmpty
-// |]
-// let columnPosition = protocol.ProtocolType |> Option.map getColumnPosition |> Option.defaultValue 0
-// columnPosition, InsertBuildingBlock.create header columnTerm None rows
-
-///// extend existing ISADotNet.Json.AssayCommonAPI.RowWiseSheet from ISADotNet library with
-///// static member to map it to the Swate InsertBuildingBlock type used as input for addBuildingBlock functions
-//type QueryModel.QSheet with
-
-// /// This function is only used for Swate templates.
-// /// This function looses values, input and output columns as well as Protocol REF
-// member this.headerToInsertBuildingBlockList : InsertBuildingBlock list =
-// let headerRow = this.Rows.Head
-// if this.Protocols.Length <> 1 then failwith "Protocol template must contain exactly one template"
-
-// let protocolType =
-// let protocol = this.Protocols.Head
-// let hasProtocolType = protocol.ProtocolType.IsSome
-// if hasProtocolType then
-// createBuildingBlock_fromProtocolType protocol |> Some
-// else
-// None
-
-// let rawCols = headerRow.Values().Values
-// let mutable cols = rawCols |> List.map (fun fv -> fv.toInsertBuildingBlock)
-// match protocolType with
-// | Some (pbb) ->
-// cols <- pbb::cols
-// | None -> ()
-// cols
-// |> List.sortBy fst
-// |> List.map snd
-
-// /// This function is the basic parser for all json/xlsx input, with values, input, output and all supported column types.
-// member this.toInsertBuildingBlockList : InsertBuildingBlock list =
-// let insertBuildingBlockRowList =
-// this.Rows |> List.collect (fun r ->
-// let cols =
-// let cols = r.Values().Values
-// cols |> List.map (fun fv -> fv.toInsertBuildingBlock)
-// cols |> List.sortBy fst |> List.map snd
-// )
-// // Check if protocolREF column exists in assay. because this column is not represented as other columns in isa we need to infer this.
-// let protocolRef =
-// let sheetName, _ = Process.decomposeName this.SheetName
-// let protocolNames = this.Protocols |> List.map (fun x -> x.Name, x)
-// // if sheetname and any protocol name differ then we need to create the protocol ref column
-// let hasProtocolRef = protocolNames |> List.exists (fun (name, _) -> name.IsSome && name.Value <> sheetName)
-// if hasProtocolRef then
-// // header must be protocol ref
-// let header = OfficeInteropTypes.BuildingBlockNamePrePrint.create BuildingBlockType.ProtocolREF ""
-// let rows =
-// this.Protocols
-// |> Array.ofList
-// |> Array.collect (fun protRef ->
-// // row range information is saved in comments and can be accessed + parsed by isadotnet function
-// let rowStart, rowEnd = protRef.GetRowRange()
-// [| for _ in rowStart .. rowEnd do
-// yield TermMinimal.create protRef.Name.Value "" |]
-// )
-// InsertBuildingBlock.create header None None rows |> Some
-// else
-// None
-
-// let protocolType =
-// let hasProtocolType =
-// let prots = this.Protocols |> List.choose (fun x -> x.ProtocolType)
-// prots.Length > 0
-// if hasProtocolType then
-// // header must be protocol type
-// let header = OfficeInteropTypes.BuildingBlockNamePrePrint.create BuildingBlockType.ProtocolType ""
-// let columnTerm = Some BuildingBlockType.ProtocolType.getFeaturedColumnTermMinimal
-// let rows =
-// this.Protocols
-// |> Array.ofList
-// |> Array.collect (fun protType ->
-// // row range information is saved in comments and can be accessed + parsed by isadotnet function
-// let rowStart, rowEnd = protType.GetRowRange()
-// let tmEmpty = TermMinimal.create "" ""
-// [| for _ in rowStart .. rowEnd do
-// let hasValue = protType.ProtocolType.IsSome
-// yield
-// if hasValue then protType.ProtocolType.Value.toTermMinimal |> Option.defaultValue tmEmpty else tmEmpty
-// |]
-// )
-// InsertBuildingBlock.create header columnTerm None rows |> Some
-// else
-// None
-
-// /// https://github.com/nfdi4plants/ISADotNet/issues/80
-// /// This needs to be fixed! For now we only have one input so we can assume "Source" but should this change we need to adapt.
-// /// As Soon as this is fixed, create one function for both input and output with (string*IOType option) list as input.
-// let input =
-// if List.isEmpty this.Inputs then
-// None
-// else
-// let names, types = this.Inputs |> List.unzip
-// let inputType =
-// //let distinct = (List.choose id >> List.distinct) types
-// //try
-// // distinct |> List.exactlyOne
-// //with
-// // | _ -> failwith $"Cannot have input of multiple types: {distinct}"
-// IOType.Source.toBuildingBlockType
-// let header = OfficeInteropTypes.BuildingBlockNamePrePrint.create inputType ""
-// let rows = names |> List.map (fun x -> TermMinimal.create x "") |> Array.ofList
-// InsertBuildingBlock.create header None None rows |> Some
-
-// /// https://github.com/nfdi4plants/ISADotNet/issues/80
-// let output =
-// if List.isEmpty this.Outputs then
-// None
-// else
-// let names, types = this.Outputs |> List.unzip
-// //printfn "[OUTPUTS]: %A" this.Outputs
-// //printfn "[OUTUT_TYPES]: %A" types
-// let inputType =
-// // let distinct = (List.choose id >> List.distinct) types
-// // try
-// // distinct |> List.exactlyOne
-// // with
-// // | _ -> failwith $"Cannot have input of multiple types: {distinct}"
-// // |> fun d -> d.toBuildingBlockType
-// IOType.Sample.toBuildingBlockType
-// let header = OfficeInteropTypes.BuildingBlockNamePrePrint.create inputType ""
-// let rows = names |> List.map (fun x -> TermMinimal.create x "") |> Array.ofList
-// InsertBuildingBlock.create header None None rows |> Some
-
-// // group building block values by "InsertBuildingBlock" information (column information without values)
-// insertBuildingBlockRowList
-// |> List.groupBy (fun buildingBlock ->
-// buildingBlock.ColumnHeader,buildingBlock.ColumnTerm,buildingBlock.UnitTerm
-// )
-// |> List.map (fun ((header,term,unit),buildingBlocks) ->
-// let rows = buildingBlocks |> Array.ofList |> Array.collect (fun bb -> bb.Rows)
-// InsertBuildingBlock.create header term unit rows
-// )
-// |> fun l -> // add special columns
-// match protocolRef, protocolType with
-// | None, None -> l
-// | Some ref, Some t -> ref::t::l
-// | Some ref, None -> ref::l
-// | None, Some t -> t::l
-// |> fun l -> // add input
-// match input with
-// | Some i -> i::l
-// | None -> l
-// |> fun l -> // add output
-// match output with
-// | Some o -> l@[o]
-// | None -> l
\ No newline at end of file
diff --git a/src/Server/Import.fs b/src/Server/Import.fs
deleted file mode 100644
index c51ea394..00000000
--- a/src/Server/Import.fs
+++ /dev/null
@@ -1,46 +0,0 @@
-module Import
-
-//open ISADotNet
-//open ISADotNet.Json
-
-
-/////Input is json string.
-//module Json =
-
-// // The following functions are used to unify all jsonInput to the table type.
-
-// let fromAssay jsonString =
-// let assay = Assay.fromString jsonString
-// let tables = QueryModel.QAssay.fromAssay assay
-// tables
-
-// let fromProcessSeq jsonString =
-// let processSeq = ProcessSequence.fromString jsonString
-// let assay = Assay.create(ProcessSequence = List.ofSeq processSeq)
-// let tables = QueryModel.QAssay.fromAssay assay
-// tables
-
-/////Input is byte [].
-//module Xlsx =
-
-// let fromAssay (byteArray: byte []) =
-// let ms = new System.IO.MemoryStream(byteArray)
-// let _,assay = ISADotNet.XLSX.AssayFile.Assay.fromStream ms
-// let tables = QueryModel.QAssay.fromAssay assay
-// tables
-
-///// This function tries to parse any possible json input to building blocks.
-//let tryToTable (bytes: byte []) =
-// let jsonString = System.Text.Encoding.ASCII.GetString(bytes)
-// try
-// Json.fromAssay jsonString
-// with
-// | _ ->
-// try
-// Json.fromProcessSeq jsonString
-// with
-// | _ ->
-// try
-// Xlsx.fromAssay bytes
-// with
-// | _ -> failwith "Could not match given file to supported file type."
\ No newline at end of file
diff --git a/src/Server/Properties/launchSettings.json b/src/Server/Properties/launchSettings.json
index bcea70db..c957d04c 100644
--- a/src/Server/Properties/launchSettings.json
+++ b/src/Server/Properties/launchSettings.json
@@ -1,37 +1,12 @@
{
- "iisSettings": {
- "windowsAuthentication": false,
- "anonymousAuthentication": true,
- "iis": {
- "applicationUrl": "https://localhost/Server",
- "sslPort": 0
- },
- "iisExpress": {
- "applicationUrl": "https://localhost/Swate/",
- "sslPort": 0
+ "profiles": {
+ "Server": {
+ "commandName": "Project",
+ "launchBrowser": false,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:5000"
+ }
}
- },
- "profiles": {
- "IIS Express": {
- "commandName": "IISExpress",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- },
- "IIS": {
- "commandName": "IIS",
- "launchUrl": "https://localhost/Swate",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- },
- "Server": {
- "commandName": "Project",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "NEO4J_TEST_ENV": "MYOUTPUT"
- },
- "applicationUrl": "https://localhost:5001;http://localhost:5000"
- }
- }
}
\ No newline at end of file
diff --git a/src/Server/Server.fs b/src/Server/Server.fs
index 4ed48790..d697b1d0 100644
--- a/src/Server/Server.fs
+++ b/src/Server/Server.fs
@@ -13,11 +13,13 @@ open Fable.Remoting.Giraffe
open Microsoft.Extensions.Logging
open Microsoft.Extensions.Configuration
-let serviceApi = {
+open ARCtrl
+open ARCtrl.Json
+
+let serviceApi: IServiceAPIv1 = {
getAppVersion = fun () -> async { return System.AssemblyVersionInformation.AssemblyMetadata_Version }
}
-open ISADotNet
open Microsoft.AspNetCore.Http
let dagApiv1 = {
@@ -169,20 +171,22 @@ let isaDotNetCommonAPIv1 : IISADotNetCommonAPIv1 =
open Database
-let templateApi credentials = {
- getTemplates = fun () -> async {
- let! templates = ARCtrl.Template.Web.getTemplates None
- let templatesJson = templates |> Array.map (ARCtrl.Template.Json.Template.toJsonString 0)
- return templatesJson
- }
+let templateApi credentials =
+ let templateUrl = @"https://github.com/nfdi4plants/Swate-templates/releases/download/latest/templates_v2.0.0.json"
+ {
+ getTemplates = fun () -> async {
+ let! templates = ARCtrl.Template.Web.getTemplates (None)
+ let templatesJson = ARCtrl.Json.Templates.toJsonString 0 (Array.ofSeq templates)
+ return templatesJson
+ }
- getTemplateById = fun id -> async {
- let! templates = ARCtrl.Template.Web.getTemplates None
- let template = templates |> Array.find (fun t -> t.Id = System.Guid(id))
- let templateJson = ARCtrl.Template.Json.Template.toJsonString 0 template
- return templateJson
+ getTemplateById = fun id -> async {
+ let! templates = ARCtrl.Template.Web.getTemplates (None)
+ let template = templates |> Seq.find (fun t -> t.Id = System.Guid(id))
+ let templateJson = Template.toCompressedJsonString 0 template
+ return templateJson
+ }
}
-}
let testApi (ctx: HttpContext): ITestAPI = {
test = fun () -> async {
@@ -368,10 +372,10 @@ let config (app:IApplicationBuilder) =
)
let app = application {
- //url "http://localhost:5000" //"http://localhost:5000/"
- //app_config config
+ url "http://*:5000"
+ app_config config
use_router topLevelRouter
- //use_cors "CORS_CONFIG" cors_config
+ use_cors "CORS_CONFIG" cors_config
memory_cache
use_static "public"
use_gzip
diff --git a/src/Server/Server.fsproj b/src/Server/Server.fsproj
index 13c8bca1..c5564071 100644
--- a/src/Server/Server.fsproj
+++ b/src/Server/Server.fsproj
@@ -8,16 +8,11 @@
-
-
-
-
-
@@ -26,5 +21,8 @@
+
+
+
\ No newline at end of file
diff --git a/src/Server/Server.fsproj.user b/src/Server/Server.fsproj.user
deleted file mode 100644
index f34b3fce..00000000
--- a/src/Server/Server.fsproj.user
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
- IIS
- false
-
-
- ProjectDebugger
-
-
\ No newline at end of file
diff --git a/src/Server/TemplateMetadata.fs b/src/Server/TemplateMetadata.fs
deleted file mode 100644
index 73de4ae3..00000000
--- a/src/Server/TemplateMetadata.fs
+++ /dev/null
@@ -1,149 +0,0 @@
-module TemplateMetadata
-
-//open Newtonsoft.Json.Schema
-
-//let resolver = JSchemaUrlResolver()
-
-//let jsonSchemaPath = @"public/TemplateMetadataSchema.json"
-
-//let writeSettings =
-// let s = JSchemaWriterSettings()
-// s.ReferenceHandling <- JSchemaWriterReferenceHandling.Never
-// s
-
-//let getJsonSchemaAsXml =
-// System.IO.File.ReadAllText jsonSchemaPath
-// |> fun x -> JSchema.Parse(x,resolver).ToString(writeSettings)
-
-//open System
-//open System.IO
-//open FsSpreadsheet.ExcelIO
-
-//open Shared
-//open TemplateTypes
-//open DynamicObj
-//open Newtonsoft.Json
-
-//type TemplateMetadataJsonExport() =
-// inherit DynamicObj()
-
-// static member init(?props) =
-// let t = TemplateMetadataJsonExport()
-// if props.IsSome then
-// props.Value |> List.iter t.setProp
-// t
-// else
-// t.setProp("", None)
-// t
-
-// member this.setProp(key,value) = DynObj.setValueOpt this key value
-
-// member this.print() = DynObj.print this
-
-// member this.toJson() = this |> JsonConvert.SerializeObject
-
-//let private maxColumnByRows (rows:DocumentFormat.OpenXml.Spreadsheet.Row []) =
-// rows
-// |> Array.map (fun row -> (Row.Spans.toBoundaries >> snd) row.Spans)
-// |> (Array.max >> int)
-
-//let private findRowByKey (key:string) (rowValues: string option [][])=
-// rowValues
-// |> Array.find (fun row ->
-// row.[0].IsSome && row.[0].Value = key
-// )
-
-//let private findRowValuesByKey (key:string) (rowValues: string option [][])=
-// findRowByKey key rowValues
-// |> Array.tail
-
-///// Also gets rows from children
-//let private getAllRelatedRowsValues (metadata:Metadata.MetadataField) (rows: string option [][]) =
-// let rec collectRows (crMetadata:Metadata.MetadataField) =
-// if crMetadata.Children.IsEmpty then
-// let row = rows |> findRowValuesByKey crMetadata.ExtendedNameKey
-// [|row|]
-// else
-// let childRows = crMetadata.Children |> Array.ofList |> Array.collect (fun child -> collectRows child)
-// childRows
-// collectRows metadata
-
-//let private convertToDynObject (sheetData:DocumentFormat.OpenXml.Spreadsheet.SheetData) sst (metadata:Metadata.MetadataField) =
-// let rows = SheetData.getRows sheetData |> Array.ofSeq
-// let rowValues =
-// rows
-// |> Array.mapi (fun i row ->
-// let spans = row.Spans
-// let leftB,rightB = Row.Spans.toBoundaries spans
-// [|
-// for i = leftB to rightB do
-// yield
-// Row.tryGetValueAt sst i row
-// |]
-// )
-// let rec convertDynamic (listIndex: int option) (forwardOutput:TemplateMetadataJsonExport option) (metadata:Metadata.MetadataField) =
-// let output =
-// if forwardOutput.IsSome then
-// forwardOutput.Value
-// else
-// TemplateMetadataJsonExport.init()
-// match metadata with
-// /// hit leaves without children
-// | isOutput when metadata.Children = [] ->
-// let isList = listIndex.IsSome
-// let rowValues = rowValues |> findRowValuesByKey metadata.ExtendedNameKey |> Array.map (fun x -> if x.IsSome then x.Value else "") //|> Array.choose id
-// if isList then
-// let v =
-// let tryFromArr = Array.tryItem (listIndex.Value) rowValues
-// Option.defaultValue "" tryFromArr
-// //if v <> "" then
-// output.setProp(metadata.Key, Some v)
-// else
-// let v = if Array.isEmpty >> not <| rowValues then rowValues.[0] else ""
-// //if v <> "" then
-// output.setProp(metadata.Key, Some v)
-// output
-// /// Treat nested lists as object, as nested lists cannot be represented in excel
-// | isNestedObjectList when metadata.Children <> [] && metadata.List && metadata.Key <> "" && listIndex.IsSome ->
-// /// "WARNING: Cannot parse nested list: metadata.Key, metadata.ExtendedNameKey. Treat by default as object."
-// let noList = { metadata with List = false }
-// convertDynamic listIndex forwardOutput noList
-// /// children are represented by columns
-// | isObjectList when metadata.Children <> [] && metadata.List && metadata.Key <> "" ->
-// let childRows = getAllRelatedRowsValues metadata rowValues
-// /// Only calculate max columns if cell still contains a value. Filter out None and ""
-// let notEmptyChildRows = childRows |> Array.map (Array.choose id) |> Array.map (Array.filter (fun x -> x <> ""))
-// let maxColumn = notEmptyChildRows |> Array.map Array.length |> Array.max
-// let childObjects =
-// [| for i = 0 to maxColumn-1 do
-// let childOutput = TemplateMetadataJsonExport.init()
-// let addChildObject = metadata.Children |> List.map (convertDynamic (Some i) (Some childOutput))
-// yield
-// childOutput
-// |]
-// output.setProp(metadata.Key, Some childObjects)
-// output
-// /// hit if json object
-// | isObject when metadata.Children <> [] && metadata.Key <> "" ->
-// let childOutput = TemplateMetadataJsonExport.init()
-// // Add key values from children to childOutput. childOutput will be added to output afterwards.
-// let childObject = metadata.Children |> List.map (convertDynamic listIndex (Some childOutput))
-// output.setProp(metadata.Key, Some childOutput)
-// output
-// /// This hits only root objects without key
-// | isRoot ->
-// let addChildObject =
-// metadata.Children
-// |> List.map (fun childMetadata ->
-// convertDynamic listIndex (Some output) childMetadata
-// )
-// output
-// convertDynamic None None metadata
-
-//let parseDynMetadataFromByteArr (byteArray:byte []) =
-// let ms = new MemoryStream(byteArray)
-// let spreadsheet = Spreadsheet.fromStream ms false
-// let sst = Spreadsheet.tryGetSharedStringTable spreadsheet
-// let sheetOpt = Spreadsheet.tryGetSheetBySheetName TemplateTypes.Metadata.TemplateMetadataWorksheetName spreadsheet
-// if sheetOpt.IsNone then failwith $"Could not find template metadata worksheet: {TemplateTypes.Metadata.TemplateMetadataWorksheetName}"
-// convertToDynObject sheetOpt.Value sst Metadata.root
diff --git a/src/Server/Version.fs b/src/Server/Version.fs
index 80a6293a..4bd2e3df 100644
--- a/src/Server/Version.fs
+++ b/src/Server/Version.fs
@@ -4,12 +4,12 @@ open System.Reflection
[]
[]
-[]
-[]
+[]
+[]
do ()
module internal AssemblyVersionInformation =
let [] AssemblyTitle = "Swate"
let [] AssemblyVersion = "1.0.0"
- let [] AssemblyMetadata_Version = "1.0.0-alpha.02"
- let [] AssemblyMetadata_ReleaseDate = "24.01.2024"
+ let [] AssemblyMetadata_Version = "v1.0.0-beta.04"
+ let [] AssemblyMetadata_ReleaseDate = "28.06.2024"
diff --git a/src/Server/public/TemplateMetadataSchema.json b/src/Server/public/TemplateMetadataSchema.json
deleted file mode 100644
index 0abda3db..00000000
--- a/src/Server/public/TemplateMetadataSchema.json
+++ /dev/null
@@ -1,75 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2019-09/schema",
- "$id": "https://raw.githubusercontent.com/nfdi4plants/SWATE_templates/FormattingDocs/templates/TemplatingSchema.json",
- "title": "Template Metadata Schema",
- "description": "The schema regarding Swate templating-related JSON files.",
- "type": "object",
- "properties": {
- "templateId": {
- "description": "The unique identifier of this template. It will be auto generated.",
- "type": "string"
- },
- "name": {
- "description": "The name of the Swate template.",
- "type": "string"
- },
- "version": {
- "description": "The current version of this template in SemVer notation.",
- "type": "string"
- },
- "description": {
- "description": "The description of this template. Use few sentences for succinctness.",
- "type": "string"
- },
- "docslink": {
- "description": "The URL to the documentation page.",
- "type": "string"
- },
- "organisation": {
- "description": "The name of the template associated organisation.",
- "type": "string"
- },
- "table": {
- "description": "The name of the Swate annotation table in the worksheet of the template's excel file.",
- "type": "string"
- },
- "er": {
- "description": "A list of all ERs (endpoint repositories) targeted with this template. ERs are realized as Terms: ",
- "type": "array",
- "items": {
- "$ref": "https://raw.githubusercontent.com/ISA-tools/isa-api/master/isatools/resources/schemas/isa_model_version_1_0_schemas/core/ontology_annotation_schema.json"
- },
- "minItems": 1,
- "uniqueItems": true
- },
- "tags": {
- "description": "A list of all tags associated with this template. Tags are realized as Terms: ",
- "type": "array",
- "items": {
- "$ref": "https://raw.githubusercontent.com/ISA-tools/isa-api/master/isatools/resources/schemas/isa_model_version_1_0_schemas/core/ontology_annotation_schema.json"
- },
- "minItems": 1,
- "uniqueItems": true
- },
- "authors": {
- "description": "The author(s) of this template.",
- "type": "array",
- "items": {
- "$ref": "https://raw.githubusercontent.com/ISA-tools/isa-api/master/isatools/resources/schemas/isa_model_version_1_0_schemas/core/person_schema.json"
- },
- "minItems": 1,
- "uniqueItems": true
- }
- },
- "required": [
- "name",
- "version",
- "description",
- "docslink",
- "organisation",
- "table",
- "er",
- "tags",
- "authors"
- ]
-}
\ No newline at end of file
diff --git a/src/Shared/ARCtrl.Helper.fs b/src/Shared/ARCtrl.Helper.fs
index b05637a8..d557c0ac 100644
--- a/src/Shared/ARCtrl.Helper.fs
+++ b/src/Shared/ARCtrl.Helper.fs
@@ -1,17 +1,25 @@
namespace Shared
-open ARCtrl.ISA
+open ARCtrl
open TermTypes
+open System.Collections.Generic
/// This module contains helper functions which might be useful for ARCtrl
[]
module ARCtrlHelper =
+ []
+ type ArcFilesDiscriminate =
+ | Assay
+ | Study
+ | Investigation
+ | Template
+
type ArcFiles =
- | Template of ARCtrl.Template.Template
- | Investigation of ArcInvestigation
- | Study of ArcStudy * ArcAssay list
- | Assay of ArcAssay
+ | Template of Template
+ | Investigation of ArcInvestigation
+ | Study of ArcStudy * ArcAssay list
+ | Assay of ArcAssay
with
member this.Tables() : ArcTables =
@@ -21,25 +29,146 @@ module ARCtrlHelper =
| Study (s,_) -> s
| Assay a -> a
+ []
+ type JsonExportFormat =
+ | ARCtrl
+ | ARCtrlCompressed
+ | ISA
+ | ROCrate
+
+ static member fromString (str: string) =
+ match str.ToLower() with
+ | "arctrl" -> ARCtrl
+ | "arctrlcompressed" -> ARCtrlCompressed
+ | "isa" -> ISA
+ | "rocrate" -> ROCrate
+ | _ -> failwithf "Unknown JSON export format: %s" str
+
+module Table =
+
+ ///
+ /// This functions returns a **copy** of `toJoinTable` without any column already in `activeTable`.
+ ///
+ ///
+ ///
+ let distinctByHeader (activeTable: ArcTable) (toJoinTable: ArcTable) : ArcTable =
+ // Remove existing columns
+ let mutable columnsToRemove = []
+ // find duplicate columns
+ let tablecopy = toJoinTable.Copy()
+ for header in activeTable.Headers do
+ let containsAtIndex = tablecopy.Headers |> Seq.tryFindIndex (fun h -> h = header)
+ if containsAtIndex.IsSome then
+ columnsToRemove <- containsAtIndex.Value::columnsToRemove
+ tablecopy.RemoveColumns (Array.ofList columnsToRemove)
+ tablecopy
+
+ ///
+ /// This function is meant to prepare a table for joining with another table.
+ ///
+ /// It removes columns that are already present in the active table.
+ /// It removes all values from the new table.
+ /// It also fills new Input/Output columns with the input/output values of the active table.
+ ///
+ /// The output of this function can be used with the SpreadsheetInterface.JoinTable Message.
+ ///
+ /// The active/current table
+ /// The new table, which will be added to the existing one.
+ let selectiveTablePrepare (activeTable: ArcTable) (toJoinTable: ArcTable) : ArcTable =
+ // Remove existing columns
+ let mutable columnsToRemove = []
+ // find duplicate columns
+ let tablecopy = toJoinTable.Copy()
+ for header in activeTable.Headers do
+ let containsAtIndex = tablecopy.Headers |> Seq.tryFindIndex (fun h -> h = header)
+ if containsAtIndex.IsSome then
+ columnsToRemove <- containsAtIndex.Value::columnsToRemove
+ tablecopy.RemoveColumns (Array.ofList columnsToRemove)
+ tablecopy.IteriColumns(fun i c0 ->
+ let c1 = {c0 with Cells = [||]}
+ let c2 =
+ if c1.Header.isInput then
+ match activeTable.TryGetInputColumn() with
+ | Some ic ->
+ {c1 with Cells = ic.Cells}
+ | _ -> c1
+ elif c1.Header.isOutput then
+ match activeTable.TryGetOutputColumn() with
+ | Some oc ->
+ {c1 with Cells = oc.Cells}
+ | _ -> c1
+ else
+ c1
+ tablecopy.UpdateColumn(i, c2.Header, c2.Cells)
+ )
+ tablecopy
+
+module Helper =
+
+ let arrayMoveColumn (currentColumnIndex: int) (newColumnIndex: int) (arr: ResizeArray<'A>) =
+ let ele = arr.[currentColumnIndex]
+ arr.RemoveAt(currentColumnIndex)
+ arr.Insert(newColumnIndex, ele)
+
+ let dictMoveColumn (currentColumnIndex: int) (newColumnIndex: int) (table: Dictionary) =
+ /// This is necessary to always access the correct value for an index.
+ /// It is possible to only copy the specific target column at "currentColumnIndex" and sort the keys in the for loop depending on "currentColumnIndex" and "newColumnIndex".
+ /// this means. If currentColumnIndex < newColumnIndex then Seq.sortByDescending keys else Seq.sortBy keys.
+ /// this implementation would result in performance increase, but readability would decrease a lot.
+ let backupTable = Dictionary(table)
+ let range = [System.Math.Min(currentColumnIndex, newColumnIndex) .. System.Math.Max(currentColumnIndex,newColumnIndex)]
+ for columnIndex, rowIndex in backupTable.Keys do
+ let value = backupTable.[(columnIndex,rowIndex)]
+ let newColumnIndex =
+ if columnIndex = currentColumnIndex then
+ newColumnIndex
+ elif List.contains columnIndex range then
+ let modifier = if currentColumnIndex < newColumnIndex then -1 else +1
+ let moveTo = modifier + columnIndex
+ moveTo
+ else
+ 0 + columnIndex
+ let updatedKey = (newColumnIndex, rowIndex)
+ table.[updatedKey] <- value
+
[]
module Extensions =
open ARCtrl.Template
+ open ArcTableAux
+
+ type OntologyAnnotation with
+ static member empty() = OntologyAnnotation.create()
+ static member fromTerm (term:Term) = OntologyAnnotation(term.Name, term.FK_Ontology, term.Accession)
+ member this.ToTermMinimal() = TermMinimal.create this.NameText this.TermAccessionShort
+
+ type ArcTable with
+ member this.SetCellAt(columnIndex: int, rowIndex: int, cell: CompositeCell) =
+ SanityChecks.validateColumn <| CompositeColumn.create(this.Headers.[columnIndex],[|cell|])
+ Unchecked.setCellAt(columnIndex, rowIndex,cell) this.Values
+ Unchecked.fillMissingCells this.Headers this.Values
+
+ member this.SetCellsAt (cells: ((int*int)*CompositeCell) []) =
+ let columns = cells |> Array.groupBy (fun (index, cell) -> fst index)
+ for columnIndex, items in columns do
+ SanityChecks.validateColumn <| CompositeColumn.create(this.Headers.[columnIndex], items |> Array.map snd)
+ for index, cell in cells do
+ Unchecked.setCellAt(fst index, snd index, cell) this.Values
+ Unchecked.fillMissingCells this.Headers this.Values
+
+ member this.MoveColumn(currentIndex: int, nextIndex: int) =
+ let updateHeaders =
+ Helper.arrayMoveColumn currentIndex nextIndex this.Headers
+ let updateBody =
+ Helper.dictMoveColumn currentIndex nextIndex this.Values
+ ()
+
type Template with
member this.FileName
with get() = this.Name.Replace(" ","_") + ".xlsx"
type CompositeHeader with
- member this.AsButtonName =
- match this with
- | CompositeHeader.Parameter _ -> "Parameter"
- | CompositeHeader.Characteristic _ -> "Characteristic"
- | CompositeHeader.Component _ -> "Component"
- | CompositeHeader.Factor _ -> "Factor"
- | CompositeHeader.Input _ -> "Input"
- | CompositeHeader.Output _ -> "Output"
- | anyElse -> anyElse.ToString()
member this.UpdateWithOA (oa: OntologyAnnotation) =
match this with
@@ -49,10 +178,10 @@ module Extensions =
| CompositeHeader.Factor _ -> CompositeHeader.Factor oa
| _ -> failwithf "Cannot update OntologyAnnotation on CompositeHeader without OntologyAnnotation: '%A'" this
- static member ParameterEmpty = CompositeHeader.Parameter OntologyAnnotation.empty
- static member CharacteristicEmpty = CompositeHeader.Characteristic OntologyAnnotation.empty
- static member ComponentEmpty = CompositeHeader.Component OntologyAnnotation.empty
- static member FactorEmpty = CompositeHeader.Factor OntologyAnnotation.empty
+ static member ParameterEmpty = CompositeHeader.Parameter <| OntologyAnnotation.empty()
+ static member CharacteristicEmpty = CompositeHeader.Characteristic <| OntologyAnnotation.empty()
+ static member ComponentEmpty = CompositeHeader.Component <| OntologyAnnotation.empty()
+ static member FactorEmpty = CompositeHeader.Factor <| OntologyAnnotation.empty()
static member InputEmpty = CompositeHeader.Input <| IOType.FreeText ""
static member OutputEmpty = CompositeHeader.Output <| IOType.FreeText ""
@@ -83,7 +212,48 @@ module Extensions =
| CompositeHeader.Factor oa -> Some oa
| _ -> None
+ let internal tryFromContent' (content: string []) =
+ match content with
+ | [|freetext|] -> CompositeCell.createFreeText freetext |> Ok
+ | [|name; tsr; tan|] -> CompositeCell.createTermFromString(name, tsr, tan) |> Ok
+ | [|value; name; tsr; tan|] -> CompositeCell.createUnitizedFromString(value, name, tsr, tan) |> Ok
+ | anyElse -> sprintf "Unable to convert \"%A\" to CompositeCell." anyElse |> Error
+
type CompositeCell with
+
+ static member tryFromContent (content: string []) =
+ match tryFromContent' content with
+ | Ok r -> Some r
+ | Error _ -> None
+
+ static member fromContent (content: string []) =
+ match tryFromContent' content with
+ | Ok r -> r
+ | Error msg -> raise (exn msg)
+
+ member this.ToTabStr() = this.GetContent() |> String.concat "\t"
+
+ static member fromTabStr (str:string) =
+ let content = str.Split('\t', System.StringSplitOptions.TrimEntries)
+ CompositeCell.fromContent content
+
+ static member ToTabTxt (cells: CompositeCell []) =
+ cells
+ |> Array.map (fun c -> c.ToTabStr())
+ |> String.concat (System.Environment.NewLine)
+
+ static member fromTabTxt (tabTxt: string) =
+ let lines = tabTxt.Split(System.Environment.NewLine, System.StringSplitOptions.None)
+ let cells = lines |> Array.map (fun line -> CompositeCell.fromTabStr line)
+ cells
+
+ member this.ConvertToValidCell (header: CompositeHeader) =
+ match header.IsTermColumn, this with
+ | true, CompositeCell.Term _ | true, CompositeCell.Unitized _ -> this
+ | true, CompositeCell.FreeText txt -> this.ToTermCell()
+ | false, CompositeCell.Term _ | false, CompositeCell.Unitized _ -> this.ToFreeTextCell()
+ | false, CompositeCell.FreeText _ -> this
+
member this.UpdateWithOA(oa:OntologyAnnotation) =
match this with
| CompositeCell.Term _ -> CompositeCell.createTerm oa
@@ -94,11 +264,13 @@ module Extensions =
match this with
| CompositeCell.Term oa -> oa
| CompositeCell.Unitized (v, oa) -> oa
- | CompositeCell.FreeText t -> OntologyAnnotation.fromString t
+ | CompositeCell.FreeText t -> OntologyAnnotation.create t
member this.UpdateMainField(s: string) =
match this with
- | CompositeCell.Term oa -> CompositeCell.Term ({oa with Name = Some s})
+ | CompositeCell.Term oa ->
+ oa.Name <- Some s
+ CompositeCell.Term oa
| CompositeCell.Unitized (_, oa) -> CompositeCell.Unitized (s, oa)
| CompositeCell.FreeText _ -> CompositeCell.FreeText s
@@ -107,7 +279,7 @@ module Extensions =
///
///
member this.UpdateTSR(tsr: string) =
- let updateTSR (oa: OntologyAnnotation) = {oa with TermSourceREF = tsr |> Some}
+ let updateTSR (oa: OntologyAnnotation) = oa.TermSourceREF <- Some tsr ;oa
match this with
| CompositeCell.Term oa -> CompositeCell.Term (updateTSR oa)
| CompositeCell.Unitized (v, oa) -> CompositeCell.Unitized (v, updateTSR oa)
@@ -118,12 +290,8 @@ module Extensions =
///
///
member this.UpdateTAN(tan: string) =
- let updateTAN (oa: OntologyAnnotation) = {oa with TermAccessionNumber = tan |> Some}
+ let updateTAN (oa: OntologyAnnotation) = oa.TermSourceREF <- Some tan ;oa
match this with
| CompositeCell.Term oa -> CompositeCell.Term (updateTAN oa)
| CompositeCell.Unitized (v, oa) -> CompositeCell.Unitized (v, updateTAN oa)
| _ -> this
-
- type OntologyAnnotation with
- static member fromTerm (term:Term) = OntologyAnnotation.fromString(term.Name, term.FK_Ontology, term.Accession)
- member this.ToTermMinimal() = TermMinimal.create this.NameText this.TermAccessionShort
diff --git a/src/Shared/OfficeInteropTypes.fs b/src/Shared/OfficeInteropTypes.fs
index 82b0f100..de433843 100644
--- a/src/Shared/OfficeInteropTypes.fs
+++ b/src/Shared/OfficeInteropTypes.fs
@@ -1,7 +1,7 @@
namespace Shared
open System
-open ARCtrl.ISA
+open ARCtrl
module OfficeInteropTypes =
diff --git a/src/Shared/Regex.fs b/src/Shared/Regex.fs
index a1e4bd88..7fbaf42d 100644
--- a/src/Shared/Regex.fs
+++ b/src/Shared/Regex.fs
@@ -1,5 +1,6 @@
namespace Shared
+[]
module Regex =
module Pattern =
diff --git a/src/Shared/Shared.fs b/src/Shared/Shared.fs
index 141e7894..e1832539 100644
--- a/src/Shared/Shared.fs
+++ b/src/Shared/Shared.fs
@@ -3,7 +3,6 @@ namespace Shared
open System
open Shared
open TermTypes
-open TemplateTypes
module Route =
@@ -33,18 +32,6 @@ module SorensenDice =
calculateDistance resultSet searchSet
)
-///This type is still used for JsonExporter page.
-[]
-type JsonExportType =
-| ProcessSeq
-| Assay
-| ProtocolTemplate
- member this.toExplanation =
- match this with
- | ProcessSeq -> "Sequence of ISA process.json."
- | Assay -> "ISA assay.json"
- | ProtocolTemplate -> "Schema for Swate protocol template, with template metadata and table json."
-
/// Development api
type ITestAPI = {
test : unit -> Async
@@ -152,7 +139,7 @@ type IOntologyAPIv3 = {
type ITemplateAPIv1 = {
// must return template as string, fable remoting cannot do conversion automatically
- getTemplates : unit -> Async
+ getTemplates : unit -> Async
getTemplateById : string -> Async
}
diff --git a/src/Shared/Shared.fsproj b/src/Shared/Shared.fsproj
index fe91584b..25d70a89 100644
--- a/src/Shared/Shared.fsproj
+++ b/src/Shared/Shared.fsproj
@@ -4,6 +4,7 @@
net8.0
+
@@ -11,7 +12,6 @@
-
diff --git a/src/Shared/StaticTermCollection.fs b/src/Shared/StaticTermCollection.fs
new file mode 100644
index 00000000..eed1a05b
--- /dev/null
+++ b/src/Shared/StaticTermCollection.fs
@@ -0,0 +1,18 @@
+module Shared.TermCollection
+
+open ARCtrl
+
+///
+/// https://github.com/nfdi4plants/nfdi4plants_ontology/issues/85
+///
+let Published = OntologyAnnotation("published","EFO","EFO:0001796")
+
+///
+/// https://github.com/nfdi4plants/Swate/issues/409#issuecomment-2176134201
+///
+let PublicationStatus = OntologyAnnotation("publication status","EFO","EFO:0001742")
+
+///
+/// https://github.com/nfdi4plants/Swate/issues/409#issuecomment-2176134201
+///
+let PersonRoleWithinExperiment = OntologyAnnotation("person role within the experiment","AGRO","AGRO:00000378")
\ No newline at end of file
diff --git a/src/Shared/TemplateTypes.fs b/src/Shared/TemplateTypes.fs
deleted file mode 100644
index f0829b94..00000000
--- a/src/Shared/TemplateTypes.fs
+++ /dev/null
@@ -1,153 +0,0 @@
-namespace Shared
-
-module TemplateTypes =
-
- open System
-
- module Metadata =
-
- []
- let TemplateMetadataWorksheetName = "SwateTemplateMetadata"
-
- type MetadataField = {
- /// Will be used to create rowKey
- Key : string
- ExtendedNameKey : string
- Description : string option
- List : bool
- Children : MetadataField list
- } with
- static member create(key,?extKey,?desc,?islist,?children) = {
- Key = key
- ExtendedNameKey = if extKey.IsSome then extKey.Value else ""
- Description = desc
- List = if islist.IsSome then islist.Value else false
- Children = if children.IsSome then children.Value else []
- }
-
- static member createParentKey parentKey (newKey:string) =
- let nk = newKey.Replace("#","")
- $"{parentKey} {nk}".Trim()
-
- /// Loop through all children to create ExtendedNameKey for all MetaDataField types
- member this.extendedNameKeys =
- let rec extendName (parentKey:string) (metadata:MetadataField) =
- let nextParentKey = MetadataField.createParentKey parentKey metadata.Key
- { metadata with
- ExtendedNameKey = nextParentKey
- Children = if metadata.Children.IsEmpty |> not then metadata.Children |> List.map (extendName nextParentKey) else metadata.Children
- }
- extendName "" this
-
- static member createWithExtendedKeys(key,?extKey,?desc,?islist,?children) =
- {
- Key = key
- ExtendedNameKey = if extKey.IsSome then extKey.Value else ""
- Description = desc
- List = if islist.IsSome then islist.Value else false
- Children = if children.IsSome then children.Value else []
- }.extendedNameKeys
-
-
- module RowKeys =
- []
- let DescriptionKey = "Description"
- []
- let TemplateIdKey = "Id"
-
- open RowKeys
-
- // annotation value
- let private tsr = MetadataField.create("Term Source REF")
- let private tan = MetadataField.create("Term Accession Number")
- let private annotationValue = MetadataField.create("#")
- //
- let private id = MetadataField.create(TemplateIdKey, desc ="The unique identifier of this template. It will be auto generated.")
- let private name = MetadataField.create("Name", desc="The name of the Swate template.")
- let private version = MetadataField.create("Version", desc="The current version of this template in SemVer notation.")
- let private description = MetadataField.create(DescriptionKey, desc ="The description of this template. Use few sentences for succinctness.")
- //let private docslink = MetadataField.create("Docslink", desc="The URL to the documentation page.")
- let private organisation = MetadataField.create("Organisation", desc="""The name of the template associated organisation. "DataPLANT" will trigger the "DataPLANT" batch of honor for the template.""")
- let private table = MetadataField.create("Table", desc="The name of the Swate annotation table in the workbook of the template's excel file.")
- // er
- let private er = MetadataField.create("ER",desc="A list of all ERs (endpoint repositories) targeted with this template. ERs are realized as Terms.",islist=true, children = [annotationValue; tan; tsr])
- // tags
- let private tags = MetadataField.create("Tags",desc="A list of all tags associated with this template. Tags are realized as Terms.", islist=true, children = [annotationValue; tan; tsr] )
- // person
- let private lastName = MetadataField.create("Last Name")
- let private firstName = MetadataField.create("First Name")
- let private midIntiials = MetadataField.create("Mid Initials")
- let private email = MetadataField.create("Email")
- let private phone = MetadataField.create("Phone")
- let private fax = MetadataField.create("Fax")
- let private address = MetadataField.create("Address")
- let private affiliation = MetadataField.create("Affiliation")
- let private orcid = MetadataField.create("ORCID")
- //let private roles = MetadataField.create("Role", children = [annotationValue; tan; tsr])
- let private roleAnnotationValue = MetadataField.create("Role")
- let private roleTAN = MetadataField.create("Role Term Accession Number")
- let private roleTSR = MetadataField.create("Role Term Source REF")
- let private authors = MetadataField.create("Authors",desc="The author(s) of this template.", islist = true, children = [lastName; firstName; midIntiials; email; phone; fax; address; affiliation; orcid; roleAnnotationValue; roleTAN; roleTSR])
- // entry
- let root = MetadataField.createWithExtendedKeys("",children=[id;name;version;description;(*docslink;*)organisation;table;er;tags;authors])
-
- type Template = {
- Id : string
- Name : string
- Description : string
- Organisation : string
- Version : string
- Authors : string
- /// endpoint repository tags
- Er_Tags : string []
- Tags : string []
- TemplateBuildingBlocks : OfficeInteropTypes.InsertBuildingBlock list
- LastUpdated : System.DateTime
- // WIP
- Used : int
- Rating : int
- } with
- static member create id name describtion organisation version lastUpdated author ertags tags templateBuildingBlocks used rating = {
- Id = id
- Name = name
- Description = describtion
- Organisation = organisation
- Version = version
- Authors = author
- Er_Tags = ertags
- Tags = tags
- TemplateBuildingBlocks = templateBuildingBlocks
- LastUpdated = lastUpdated
- Used = used
- // WIP
- Rating = rating
- }
-
- type Organisation =
- | DataPLANT
- | Other of string
-
- type Author = {
- LastName: string
- FirstName: string
- MidInitials: string
- Email: string
- Phone: string
- Fax: string
- Adress: string
- Affiliation: string
- ORCID: string
- Role: TermTypes.TermMinimal
- }
-
- type TemplateForm = {
- Id : System.Guid
- Name : string
- Version : string
- Description : string
- Organisation : Organisation
- Table : string
- ER_List : TermTypes.TermMinimal []
- Tags : TermTypes.TermMinimal []
- Authors : Author []
- }
\ No newline at end of file
diff --git a/src/Shared/TermTypes.fs b/src/Shared/TermTypes.fs
index e94b7d98..27b5e130 100644
--- a/src/Shared/TermTypes.fs
+++ b/src/Shared/TermTypes.fs
@@ -1,10 +1,9 @@
namespace Shared
-open ARCtrl.ISA
+open ARCtrl
module TermTypes =
- open Shared.Regex
open System
type Ontology = {
diff --git a/tests/Server/JsonExport.Tests.fs b/tests/Server/JsonExport.Tests.fs
index 704743e3..2795d75f 100644
--- a/tests/Server/JsonExport.Tests.fs
+++ b/tests/Server/JsonExport.Tests.fs
@@ -4,7 +4,6 @@ open Expecto
open Shared
open Server
-open ISADotNet
open JsonImport
open System.IO
diff --git a/tests/Server/JsonImport.Tests.fs b/tests/Server/JsonImport.Tests.fs
index 4deb61bc..c5b057ab 100644
--- a/tests/Server/JsonImport.Tests.fs
+++ b/tests/Server/JsonImport.Tests.fs
@@ -4,7 +4,6 @@ open Expecto
open Shared
open Server
-open ISADotNet
let x = 2
//open JsonImport