diff --git a/bun.lock b/bun.lock index 579a047cd..a2a16dfd0 100644 --- a/bun.lock +++ b/bun.lock @@ -9,8 +9,8 @@ "xxhash-wasm": "^1.1.0", }, "devDependencies": { - "@biomejs/biome": "^2.6.5", - "@celldl/editor": "^0.20261105.0", + "@biomejs/biome": "^2.6.6", + "@celldl/editor": "^0.20261109.0", "@celldl/rdf": "^1.20260822.0", "@electron-toolkit/tsconfig": "^2.0.0", "@electron-toolkit/utils": "^4.0.0", @@ -18,8 +18,8 @@ "@physiomelinks/bg2cellml": "^0.6.2", "@primeuix/themes": "2.0.3", "@primevue/auto-import-resolver": "4.5.5", - "@tailwindcss/postcss": "^4.3.4", - "@tailwindcss/vite": "^4.3.4", + "@tailwindcss/postcss": "^4.3.5", + "@tailwindcss/vite": "^4.3.5", "@types/bun": "^1.4.4", "@types/node": "^26.6.6", "@vitejs/plugin-vue": "^6.0.10", @@ -39,10 +39,10 @@ "postcss": "^8.5.32", "primeicons": "7.0.0", "primevue": "4.5.5", - "rollup-plugin-visualizer": "^7.1.2", + "rollup-plugin-visualizer": "^7.1.3", "stylelint": "^18.0.0", "stylelint-config-standard": "^41.0.0", - "tailwindcss": "^4.3.4", + "tailwindcss": "^4.3.5", "tailwindcss-primeui": "^0.6.1", "tar": "^7.5.22", "typescript": "^7.0.2", @@ -96,29 +96,29 @@ "@babel/types": ["@babel/types@7.29.11", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.11", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ=="], - "@biomejs/biome": ["@biomejs/biome@2.6.5", "", { "optionalDependencies": { "@biomejs/cli-android-arm64": "2.6.5", "@biomejs/cli-android-x64": "2.6.5", "@biomejs/cli-darwin-arm64": "2.6.5", "@biomejs/cli-darwin-x64": "2.6.5", "@biomejs/cli-linux-arm64": "2.6.5", "@biomejs/cli-linux-arm64-musl": "2.6.5", "@biomejs/cli-linux-x64": "2.6.5", "@biomejs/cli-linux-x64-musl": "2.6.5", "@biomejs/cli-win32-arm64": "2.6.5", "@biomejs/cli-win32-x64": "2.6.5" }, "bin": { "biome": "bin/biome" } }, "sha512-n0sp74QbSVEkWXlH659TKv4oJNylwAvDeZ8K8vt/PbcF1xneuNWe57z/G0Bxfh00cufhWXF6DiStLM8+jcLUpg=="], + "@biomejs/biome": ["@biomejs/biome@2.6.6", "", { "optionalDependencies": { "@biomejs/cli-android-arm64": "2.6.6", "@biomejs/cli-android-x64": "2.6.6", "@biomejs/cli-darwin-arm64": "2.6.6", "@biomejs/cli-darwin-x64": "2.6.6", "@biomejs/cli-linux-arm64": "2.6.6", "@biomejs/cli-linux-arm64-musl": "2.6.6", "@biomejs/cli-linux-x64": "2.6.6", "@biomejs/cli-linux-x64-musl": "2.6.6", "@biomejs/cli-win32-arm64": "2.6.6", "@biomejs/cli-win32-x64": "2.6.6" }, "bin": { "biome": "bin/biome" } }, "sha512-NCgVJRyrU1+hsW1ILNaRqNAb9fRL4n49bwyef+wjVJQ488/bXbp0BfnNqTTExkiNQT3ugkgYKR1vK8NxsjlNSQ=="], - "@biomejs/cli-android-arm64": ["@biomejs/cli-android-arm64@2.6.5", "", { "os": "android", "cpu": "arm64" }, "sha512-fkLzc1WRU7wRIUNmAm7cxILb4dd6eESz/VycWvVFKVn6tXx0gFQKdUbPnwT42xys8AEvIJQpFGcK00kURwqorg=="], + "@biomejs/cli-android-arm64": ["@biomejs/cli-android-arm64@2.6.6", "", { "os": "android", "cpu": "arm64" }, "sha512-bicnqNNxEX7pjkDQaptYvPRIWDfgXJLl/Ok7iZjC+F1AhfReor3FSS7SgPYnOvUy/jf++5U/KYN4T8OKH5rrNA=="], - "@biomejs/cli-android-x64": ["@biomejs/cli-android-x64@2.6.5", "", { "os": "android", "cpu": "x64" }, "sha512-fkZ9dPnDAzAcaPtPCiwqtkub5C9FQK/oGCGaU9sq+DysTaq13pE9FxBIMcEZ3ydn8/FfcMdXi/DDzKDP+qyn2Q=="], + "@biomejs/cli-android-x64": ["@biomejs/cli-android-x64@2.6.6", "", { "os": "android", "cpu": "x64" }, "sha512-N443kDSElF4kDMKjarsL+ymj/ewLA5R9VAdawA7fJ7pwgN3JVDZp0K+xhKBglH8EwsKi2+K8xC6WxXSkgziFfw=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.6.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bKd0EOREgnVjeZm4s9knQ+WdPOkWS3JHB+eWGHlx+Pq66bqQA1/KybwU0O+zuyAyXiphSCBcrLruZt3YtnJoaQ=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.6.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/vSAqWx3by3p8w5ew+HVJDgguuaTK3U6Fb5gk/oDUH21iWOopm3gOlhT6tZfy5mTaM5R4CVc3uFWkxBtEDrKyw=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.6.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-UkT9BECNOXSiW6SGddjyaRACy362Sc9n3vDaTFkccL0bC2gW7JzM16i4+JKPbEaDn1Sa+oETHV8KungnI/y40Q=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.6.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-lLdOAJKbrAoDs+3GvTyVmMW345D9IhrIE3GgoL2eeHwO1boN4VhrSlALtFYsP0p+Jy4udUp4bTguij0qjKrH8w=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.6.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-H9cVMWBqW/lkFK9NHJdt0bpM2q7PacUDEjQoff9+m0dCV09H/jmBykfTCkbDthHf8+jCuWDJWiQFJ7bDkiGOcA=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.6.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-UzJdUCg9dbEGpZOuZRyehEPrnwKjx9z4Um7sWLG5x6txu7UjeWdJZuBXVGlyAdD39w2Hx0nZeK1jKC4NFUTCNQ=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.6.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-kfc3AePW1a0/d/gUpcIz95nf3Ce8YY2/xL0Fk3fLqAhKsotqUi6PN6BaVWss4p6ijWbiuacZ9NQ4NiQtpjbYpw=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.6.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-rw2Vsoi/WZGDyRjMXx9KZH9zsHe79LIzkdWHkLqsIkeKZm6qiGxtiIApxn7cmJSoEPaSvHaH5pRj1FhTcyL/XA=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.6.5", "", { "os": "linux", "cpu": "x64" }, "sha512-e6/WMSN/+FhH+ZOoBevps/euxFQmv6BXqYNuTn1+ITR6O4XYFjHF5PeVSNZnjkgSYmvhcDkfepSX5lXczMijGA=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.6.6", "", { "os": "linux", "cpu": "x64" }, "sha512-l8/aPXc1cz8VpUyd4hb5Pm5zx+X3voQBks4aLYbp5pC8KSchuHyvqDfY5xax39qCHomI/2xn6NKEot+zebG38g=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.6.5", "", { "os": "linux", "cpu": "x64" }, "sha512-0mHVpLNt7/FugfZ2VPugUIEIiooWIyHGGJUqU9StxamBln9hz+maXRFIECU50jaf5/ZB93veGIViN5VKgw2AEA=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.6.6", "", { "os": "linux", "cpu": "x64" }, "sha512-DiKejs+GnFbOVQsBnukUpiB+T13tqUDFGwwjsTq51vatAsaflzFzVD0BOgjVUGRrqKgE0K0Xf+wNtkcmdN2chw=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.6.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-uBbfpdR/cH5up1iRJFGAPP97xKMzrgMs685CSrY7NM+Q4Unw7chY0o3JulEvJdmU6c6hGQxwqotMq7DDH0DzxA=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.6.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-DjVXwQAQeHrO1JEZAEus54k40Tnh4glyU3VFLcDBKwoz0xnxCEnqw6TCMXoujcQMQynWd3AY8bLrbR1o0XNqow=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.6.5", "", { "os": "win32", "cpu": "x64" }, "sha512-07LaCeN9vvmbAAMOMsUjMfL5UbsCDkvVeyMJIgwNRc0xYqhizBGkjVzx05LYEwCXl2AiHcdzqVPR4IOYAsI1QQ=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.6.6", "", { "os": "win32", "cpu": "x64" }, "sha512-B7KMG26A4I9lAuxwBGMmiVcjMGAeJRoz+pzFt9brRvOAsIrgOu8ptJld5rUWui6tM1uiJwxeJhhDPuAiZtxXdw=="], - "@celldl/editor": ["@celldl/editor@0.20261105.0", "", { "dependencies": { "@atomic-ehr/ucum": "^0.2.5", "@celldl/metadata": "^1.20260822.0", "@celldl/rdf": "^1.20260822.0", "@ctrl/tinycolor": "^4.2.0", "@lucide/vue": "^1.39.0", "@mathjax/src": "^4.1.3", "@vueuse/core": "14.3.0", "buffer": "^6.0.3", "flatbush": "^4.5.0", "graphology": "^0.26.0", "isomorphic-fetch": "^3.0.0", "jsonschema": "^1.5.0", "mini-svg-data-uri": "^1.4.4", "primeicons": "^7.0.0", "primevue": "^4.5.5", "rbush": "^4.0.1", "svg-intersections": "github:dbrnz/svg-intersections#fix-numbers", "svg-path-commander": "^2.1.11", "systeminformation": "^5.27.11", "transformation-matrix": "^3.1.0", "tslog": "^5.4.0", "ua-parser-js": "^2.0.8", "vue": "^3.5.44", "vue-tippy": "^6.7.1" } }, "sha512-xTUm7r56Q9uNmJtRc/iyCzim1S0fKfFa6AH4VLzuKtWy3qAYapKeD/1iHV0U1YdmeG0icJDi6reUW2PB8cS8bg=="], + "@celldl/editor": ["@celldl/editor@0.20261109.0", "", { "dependencies": { "@atomic-ehr/ucum": "^0.2.5", "@celldl/metadata": "^1.20260822.0", "@celldl/rdf": "^1.20260822.0", "@ctrl/tinycolor": "^4.2.0", "@lucide/vue": "^1.39.0", "@mathjax/src": "^4.1.3", "@vueuse/core": "14.3.0", "buffer": "^6.0.3", "flatbush": "^4.5.0", "graphology": "^0.26.0", "isomorphic-fetch": "^3.0.0", "jsonschema": "^1.5.0", "mini-svg-data-uri": "^1.4.4", "primeicons": "^7.0.0", "primevue": "^4.5.5", "rbush": "^4.0.1", "svg-intersections": "github:dbrnz/svg-intersections#fix-numbers", "svg-path-commander": "^2.1.11", "systeminformation": "^5.27.11", "transformation-matrix": "^3.1.0", "tslog": "^5.4.0", "ua-parser-js": "^2.0.8", "vue": "^3.5.44", "vue-tippy": "^6.7.1" } }, "sha512-VyjG9Bp2JY//rlIwM1eu2LaxD4NIAaNiTI9mBcsxf04n5qlJhm3WN9siFoMMbWxqYWvzuJfdiPVESGyyd/NFKw=="], "@celldl/metadata": ["@celldl/metadata@1.20260822.0", "", { "dependencies": { "@celldl/rdf": "^1.20260822.0" } }, "sha512-xqew4gPCrE4JbkmHWhjS3+Khj70jx7i/L/qvFVf3CrdhVAUrH4Wi1Bodcbz4DQHsnEQUHDcBV8oXvJhXjSg1cg=="], @@ -138,7 +138,7 @@ "@csstools/selector-specificity": ["@csstools/selector-specificity@6.0.1", "", { "peerDependencies": { "postcss-selector-parser": "^7.1.1" } }, "sha512-MBuqFIIvqk4S593MGAskTkPpEUUzZNvGzh8mMxEwl3oHwjQlXmqHEDxNbN3Y0Tsh+xjmem4GZjeg7I0qkwBivg=="], - "@ctrl/tinycolor": ["@ctrl/tinycolor@4.2.2", "", {}, "sha512-9rQHijqB2Rq6qOEC/xHaA6aPlsKGi5kmCQW50O1asn9g63XFeD5NmBwysriistW5Xx0JPafyD/TA7yk2S3pXGQ=="], + "@ctrl/tinycolor": ["@ctrl/tinycolor@4.3.0", "", {}, "sha512-6V0+8vw/GmH92URQ/Ki12FPhoB3nqaQUJE+5xgbFbB5ok0OXYK5jCQsQQoyR3BzCddHsCpyCFRnIcI1PSOVYkQ=="], "@electron-internal/extract-zip": ["@electron-internal/extract-zip@1.0.8", "", {}, "sha512-8iajeLFbzNwM0m3AsQZSZgYREWglAImyGvvFSWU34FgMW8sHlzdwj9QKvv9dgN7jMP591EFUF/sLOC3Jl9YIjQ=="], @@ -164,7 +164,7 @@ "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], - "@isaacs/nonrecursive-glob-match": ["@isaacs/nonrecursive-glob-match@1.0.3", "", {}, "sha512-SI7kyKHq4XXDRozw+r9GlRZkia1ZkFrpLW98r2IC2Bpr8YkFGE3fvjzmxCG0x78A++69HmDNrDvSMYBZmHGpKA=="], + "@isaacs/nonrecursive-glob-match": ["@isaacs/nonrecursive-glob-match@1.1.0", "", {}, "sha512-GPm/Dbvk5RLmhRnxXIa7nTpK0xtl4E0wj0MslsuNiJ1FIROU+2+9lgUC/dgF7Ey4QCWhfEsgxilMRMCdHPfDfw=="], "@jeswr/pretty-turtle": ["@jeswr/pretty-turtle@1.8.2", "", { "dependencies": { "n3": "^1.25.2", "rdf-string-ttl": "^1.3.2" }, "peerDependencies": { "@rdfjs/types": "*" } }, "sha512-Os15rA6dAAxdnEqxwvcNZ8wX6fO+gKVgiXJsVmVQ3WDiu61Srf0H/nb4X475IKnDVGDMqmd0lGTL1Lk2uxtcVA=="], @@ -178,7 +178,7 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@lucide/vue": ["@lucide/vue@1.69.0", "", { "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-uxR2FtureNe37HOQk/e92CZjlTjXVuNiYlD60Mgns0rmbs0wqDnHDgZPUooP6nvmFn/v7Ie6dKRKPzgR6HW5Tw=="], + "@lucide/vue": ["@lucide/vue@1.70.0", "", { "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-kutUu3oEtfQnyaP6hh76ShG2dxa8PiL6uY3rk3DqclQe0Vd2FtPlnclezR0XMQtlFMPiEaCOXDSuhehFHW5ErQ=="], "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="], @@ -284,43 +284,43 @@ "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.16", "", { "os": "win32", "cpu": "x64" }, "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.1.0", "", {}, "sha512-K+NU2HDCEem4sClgHH4ZZXwdJeNFkJo+hRk479DIwdIviAPN2hFJH1KHHwg8CxHQXvi0M5XY4deTXINfIiSG6w=="], "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="], - "@tailwindcss/node": ["@tailwindcss/node@4.3.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.5", "jiti": "^2.7.0", "lightningcss": "1.33.0", "magic-string": "^1.1.0", "source-map-js": "^1.2.1", "tailwindcss": "4.3.4" } }, "sha512-b7QK/QG04v3j04NbpG82XLCedRyulnkuwUDwRa9oK/ypnJIzMapz2Wg+0TltVczjUzDaJJHIa1+jj9aMUOFMTQ=="], + "@tailwindcss/node": ["@tailwindcss/node@4.3.5", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.5", "jiti": "^2.7.0", "lightningcss": "1.33.0", "magic-string": "^1.1.0", "source-map-js": "^1.2.1", "tailwindcss": "4.3.5" } }, "sha512-yT53FzV4ke0cr/BguDxhWMpm5MJu1O2muzk+tKw6lKHjODAsYArBbWwFbJiaksfzFv1tpkO9QHGzmfs1hJT66g=="], - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.4", "@tailwindcss/oxide-darwin-arm64": "4.3.4", "@tailwindcss/oxide-darwin-x64": "4.3.4", "@tailwindcss/oxide-freebsd-x64": "4.3.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.4", "@tailwindcss/oxide-linux-arm64-musl": "4.3.4", "@tailwindcss/oxide-linux-x64-gnu": "4.3.4", "@tailwindcss/oxide-linux-x64-musl": "4.3.4", "@tailwindcss/oxide-wasm32-wasi": "4.3.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.4", "@tailwindcss/oxide-win32-x64-msvc": "4.3.4" } }, "sha512-riqsRw2sLF7B1sNCCkTl3KYXEqvhTiABzZ/4oxxHfZ3c3Td60bCBfOrUuUGOiHagd3E7V+zG46c+XBDWZGFBcg=="], + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.5", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.5", "@tailwindcss/oxide-darwin-arm64": "4.3.5", "@tailwindcss/oxide-darwin-x64": "4.3.5", "@tailwindcss/oxide-freebsd-x64": "4.3.5", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.5", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.5", "@tailwindcss/oxide-linux-arm64-musl": "4.3.5", "@tailwindcss/oxide-linux-x64-gnu": "4.3.5", "@tailwindcss/oxide-linux-x64-musl": "4.3.5", "@tailwindcss/oxide-wasm32-wasi": "4.3.5", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.5", "@tailwindcss/oxide-win32-x64-msvc": "4.3.5" } }, "sha512-PynqGLZsZvwM9tqD/CUxkOIfyd6K5elZ2MooauzJryP/UqXYgi3yYbtt3N0Xe+MHV/eFKPFQCEfIKAqlAS1CBg=="], - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.4", "", { "os": "android", "cpu": "arm64" }, "sha512-qkjxt+EFocOi3mtsUS3ndlj6yj4SHubwBdvBynLnS8T4mvSzX0Enn0CpypJKmBEtczUAXOHVZKA2zTGzSNU2Sg=="], + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.5", "", { "os": "android", "cpu": "arm64" }, "sha512-Se0z5xMqcSQanJD1IYmO0cGpK3iv0fp3ZsIT7xRJZ1Da5PghvNoc46zz4jIQn+aan3dV9T8AermFNN9QPmsh0Q=="], - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-i/awUA/7mJQAbA3AzCW8aQtUh5goMieUdF1zOoclLaJUCxDvsXQ8VJqpQZY3dfcHS6PJjd3tw25nNneD/mEviQ=="], + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UPrgTDuUbvmqGMZuCnmI3FKee/tYKzKdp00/kuB4oSGB213VYg8QTdF7lVhnPoBXq/mIfJ+ILoBFqNOboWcMwQ=="], - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-q56V9U6AnoYz/yj/alkDLhSQ9AZoXQSEl93f7WETa0+32CudbqM57BXdYXfMYPKudueXKaA+MG6L/0KRseF7Jg=="], + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-E+p7HEtjI0b8D7kbbWxH067cGui6d4c79COGko9fAPVE3rwc1E7ld2HFCbaG86AOGWS5VKQYqZOaf38ya4uxTQ=="], - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-r0W7A+Z9HTHh7bwihJl45E/zZKLjxD50/jToVF/E/RZHSl8+EWO+4yKKJpPMcUooT9hdoip3Zx1uWnbMg4N79g=="], + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yWEWcWyAG7rWOcTpJTjdhQ3pcaFY2h+0bqTRfgU3G2fz87ZU3AKdvdZKhslD+kyHnjBUWKvNdRYwIYC5dI1tIg=="], - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.4", "", { "os": "linux", "cpu": "arm" }, "sha512-3aih/sRxzeYfi2Bkh+bGYNf2YU9Llamb2mpA7BNF1obYzdgEeWDAMQzK6BSwzLhs/eTdOaSur92qrGfchagUtQ=="], + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.5", "", { "os": "linux", "cpu": "arm" }, "sha512-WmeVjtJhZlr+smpqcdR2kQkkn6arHIJnUIBxTO2EJcTLGY1ecVXNo2hbSn8xc0IH2aEtys936iMWKGyj0GaKnA=="], - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-ktJSrPGQou4OqL88rDEx5CkeM9BjzrwZvYo8u7B7VL1zkBa2XBm351o4MQrek95qikb2M++26Y7w7vbH6Mgl8g=="], + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-DTCSyucc6ShMp1QMqV5R94Z7xlgNgUzkGmFF1rL5HH2Qjy4eSmEqy+YLGkqqYvWa4sILtgrBt1JjRKbneQtiFQ=="], - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-rt3hjUxT7utAtcB+df6wHyaXDyxhE7Zd1x7L/nvB5Jb2qBb8dZwFUoWqeRja1BaF5OlSqkKSjBmVXj9OsevmCQ=="], + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-H9lsvO4x9E4nE4Hi8h7NHHw0O/kcVSu+iOyFHNGdYzazGgDjFHZbE6S3hQ8kMcbNXSBlMdNi67LV5EVqtpaJtw=="], - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-XE3jO2yOOWjttpeGvpwf3/b274NMi3QapQSAI/EzsgrmYNan1Tw8dXT9JQkN7K0OgvuqiC8mcCyNvGzEkpVJPQ=="], + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-4uxQvtBvPuIdl13WfUBj9xMZHH14PtHZ8OZdtLmdyDtG66sCa1t599NZT+jagkUzRtWhuul1qRZQ1nAJscXN5g=="], - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-PaAZ2FjJaZk9SQLYtKIfQWiFm/xtl7pX2MKPgCzn0mS/VU54yu+vP457AJdB2XFLxU3gO2abLCt8N7Lg4EbKNg=="], + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-0JQAzs7jwbA7O6+z/adU+Jk3AXwsu3npAhtr5escXS7TqEqWcaDV7qHuI+0Vy/KQRuVGnLQREcSJWYauyV3oqg=="], - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.4", "", { "dependencies": { "@emnapi/core": "^1.11.3", "@emnapi/runtime": "^1.11.3", "@emnapi/wasi-threads": "^1.2.3", "@napi-rs/wasm-runtime": "^1.2.2", "@tybys/wasm-util": "^0.10.3", "tslib": "^2.8.1" } }, "sha512-bjO9WmoM687p8AoCIyw1DUqzpAs4JICLvNmIPxIWDmhw8IOsdubtWQLShyJmISnDKtRy1BBApHVOUl6sNFQQJA=="], + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.5", "", { "dependencies": { "@emnapi/core": "^1.11.3", "@emnapi/runtime": "^1.11.3", "@emnapi/wasi-threads": "^1.2.3", "@napi-rs/wasm-runtime": "^1.2.2", "@tybys/wasm-util": "^0.10.3", "tslib": "^2.8.1" } }, "sha512-OJhFmlEfdzMr+avACdi40RPtnkW0B6hXyxL/8r+tnEHBWZji36CYRjEXrrvgBmaYsD3oEOBqwYFRKsYvzFe7Bw=="], - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-jCrwCvWVtt7ruMXp6dFPd5KBNU8o1tWtEdZbTL/w/hvD3lUYZA0E2mmfvTzW4QLHIx5wGQU9HeY7997R1QTrEg=="], + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-9o8jgthi+QE5eandnboiJT64VQfnIArfZzSTdgbhEK5aafdiu2jTW9Honi/mwYHxBZuH1vAC57IFyrh+0iK5Gg=="], - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.4", "", { "os": "win32", "cpu": "x64" }, "sha512-cge1wcedn1FrRSXL22lF5sNDYr8c9+VImk7zFYLPs12jkDmchSrZNfzWqOKub+5Hb/aYRdDEqERGGDKONBlOzQ=="], + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.5", "", { "os": "win32", "cpu": "x64" }, "sha512-XiKFfIy1RWe7qK4WqByxNW7Oz1/nkOy8o/BWhHminyV1PmpUd8Eedj5v2A8h2kpqeVlz6xRnwk9Fr/ZXNLcZfg=="], - "@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.4", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.4", "@tailwindcss/oxide": "4.3.4", "postcss": "^8.5.25", "tailwindcss": "4.3.4" } }, "sha512-6O0QLY+1CseO8ivlYK2Y7IxNPaQ8D5ypxrV+ws34mbxknWH824J+gEWtJ0gyFiYAudmdEeXdF8kqwgXIaZRyHw=="], + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.5", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.5", "@tailwindcss/oxide": "4.3.5", "postcss": "^8.5.25", "tailwindcss": "4.3.5" } }, "sha512-FAV37Tlx9wBiT/hkfgVD0Q6CWsP2oUX3lf28nrfjW8+ksILjnpuFulUqWDfNR4/ZiEHHDRBX8DUTzD1Rys687A=="], - "@tailwindcss/vite": ["@tailwindcss/vite@4.3.4", "", { "dependencies": { "@tailwindcss/node": "4.3.4", "@tailwindcss/oxide": "4.3.4", "tailwindcss": "4.3.4" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-wGukGWb8SVuN7+FJvSS7M5eSJWDIWwVSvUvj1q41eTIhGPsS2DAgFWjA7YURJNwjHSgty59Hz8iMUiyO+YMHBQ=="], + "@tailwindcss/vite": ["@tailwindcss/vite@4.3.5", "", { "dependencies": { "@tailwindcss/node": "4.3.5", "@tailwindcss/oxide": "4.3.5", "tailwindcss": "4.3.5" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-BCNiY0a1ZjdOHVqw7kyb7U72BwfMF3dfUuN+tQsLWky/J5JOI/r7abibglXfygrZhVO/3u3V+E25kkKTeMkXaQ=="], "@thednp/dommatrix": ["@thednp/dommatrix@3.1.2", "", {}, "sha512-wxRH2/Qdqd+JxFpjb5vw+ZMaij8Hqa/9Hgl41n2Jb+3WNRhdemrosK0FxEo/yEvhvHGW8D1tZ3oPH9N0m1QG+A=="], @@ -496,7 +496,7 @@ "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - "caniuse-lite": ["caniuse-lite@1.0.30001820", "", {}, "sha512-gfWD6MxqVCqAOwmODfZf7DchWvlrA+SRdNX7ceLfg3xRqe5ssjjpF7EgslC/0tiLiNTajdaNN/q8c/5u/7Gz8w=="], + "caniuse-lite": ["caniuse-lite@1.0.30001823", "", {}, "sha512-TQ5s/djDjNk1axreUW+4rULH+BmdCMLiJkhaXnO5wqivG7tITkSUj4ItIdzqdZHKaXcHAV8dhH3JxOM7/7bwqw=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -518,7 +518,7 @@ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - "colord": ["colord@2.10.0", "", {}, "sha512-AidJptpBJmjTclAp9BkLwJi0T93fo5epJnbaZslpg6QVzpHjAiveF55mE9AcUJiGMqRHgMDY8soMsQtuNYMHfw=="], + "colord": ["colord@2.11.0", "", {}, "sha512-R7EZD9e+sWpUQFr3F6v747NAazZCD+OhsM0VnPkHGxSaQvw4ZvJoCVz51gemztBBoZaFvMP0qdKgFyBdBurfgA=="], "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], @@ -542,8 +542,6 @@ "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], - "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], @@ -596,7 +594,7 @@ "electron-publish": ["electron-publish@26.18.0", "", { "dependencies": { "@types/fs-extra": "^9.0.11", "aws4": "^1.13.2", "builder-util": "26.16.0", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "form-data": "^4.0.5", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "sha512-MagnqaZSALSWjcdKu5HEVhYUZD/2BDL3q7XzrqWwub6gf/UfD3ItMgmthVDv9w5y9E0gH1C3JWqKaXwf6f4ROg=="], - "electron-to-chromium": ["electron-to-chromium@1.5.477", "", {}, "sha512-PN++F4zWZoVDGWinxMnSCpVqKhqFOJfLXxdU8bYJKudAEsHL/XKVITOKWVbfMt1r276Mi9RDLU9VHx9uKJ4RMQ=="], + "electron-to-chromium": ["electron-to-chromium@1.5.478", "", {}, "sha512-pftG+ma5Lm8O6/p45bhYT6StBN5ziQymaRzmCZwFl8DwWHKPPD93FdOmP1aqftcepolQYvrcyTLa17kE8Yf+jg=="], "electron-updater": ["electron-updater@6.8.11", "", { "dependencies": { "builder-util-runtime": "9.7.0", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", "semver": "~7.7.3", "tiny-typed-emitter": "^2.1.0" } }, "sha512-12U/mbzaIKQS3Q543aQ7DjyAZMXeVJy1Wpu0qdq5ne4RXpIQDpWYm25zRjgXngAoo6swMrm4C4owGyiIipelsg=="], @@ -916,7 +914,7 @@ "postcss-safe-parser": ["postcss-safe-parser@7.1.0", "", { "peerDependencies": { "postcss": "^8.4.31" } }, "sha512-1WzZxRLaAFwEh6Do+zyGpjWV3nGJNxxhuh7Ubu/q1ICImMgZJnLwhoaEpRbG4pJppp2Y1ncL9ffA2f+LhrefQg=="], - "postcss-selector-parser": ["postcss-selector-parser@7.1.7", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-wWBro/7hJ40ye6TPiXkUQoXKCw23xSsjriUvqd7Uls9Sso3oLiON1flc18cCdnjgCRXw4fBVqUJJeOdDTh3P3g=="], + "postcss-selector-parser": ["postcss-selector-parser@7.2.0", "", { "dependencies": { "util-deprecate": "^1.0.2" } }, "sha512-Khh/tERVBn0I1499dvBSHY0pWJloMJWUxDsOBM0CCDPAcVZ10Jl2L1qy1f19uEX22spa8Mv7DCdEQJc0cGwBhQ=="], "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], @@ -984,13 +982,13 @@ "rolldown": ["rolldown@1.2.16", "", { "dependencies": { "@oxc-project/types": "=0.156.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.16", "@rolldown/binding-android-arm64": "1.2.16", "@rolldown/binding-darwin-arm64": "1.2.16", "@rolldown/binding-darwin-x64": "1.2.16", "@rolldown/binding-freebsd-x64": "1.2.16", "@rolldown/binding-linux-arm-gnueabihf": "1.2.16", "@rolldown/binding-linux-arm64-gnu": "1.2.16", "@rolldown/binding-linux-arm64-musl": "1.2.16", "@rolldown/binding-linux-ppc64-gnu": "1.2.16", "@rolldown/binding-linux-s390x-gnu": "1.2.16", "@rolldown/binding-linux-x64-gnu": "1.2.16", "@rolldown/binding-linux-x64-musl": "1.2.16", "@rolldown/binding-openharmony-arm64": "1.2.16", "@rolldown/binding-win32-arm64-msvc": "1.2.16", "@rolldown/binding-win32-x64-msvc": "1.2.16" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw=="], - "rollup-plugin-visualizer": ["rollup-plugin-visualizer@7.1.2", "", { "dependencies": { "open": "^11.0.0", "picomatch": "^4.0.5", "source-map": "^0.8.0", "yargs": "^18.1.0" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-+yrEz9LJ7fxd7T0yj4GPvcKHKfJNzTzKT7FHiF3f/US+7EaWlgB8PoGbUOlobD4Al8v6TeCCl/Fgbov5FV/DaA=="], + "rollup-plugin-visualizer": ["rollup-plugin-visualizer@7.1.3", "", { "dependencies": { "open": "^11.0.0", "picomatch": "^4.0.5", "source-map": "^0.8.0", "yargs": "^18.1.0" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-P7G0p9RowDd0ahXXBtx4MOHlSVzclyW5pDeZ2Z0HensWSI9vJdu2bqdZDsD375g+0AxND8VJzWYuKD9yH38etw=="], "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - "sanitize-filename": ["sanitize-filename@1.6.4", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg=="], + "sanitize-filename": ["sanitize-filename@1.6.5", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-+dVApt46/oAFWRP0KBSnYNLRw1peY7o/b12tmpllPNFpjfwkPuZut1+DiKaD9zYx5r2SHwc+XMLSBaDiyUJw3g=="], "sax": ["sax@1.6.1", "", {}, "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q=="], @@ -1044,7 +1042,7 @@ "systeminformation": ["systeminformation@5.33.19", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-Zm7wGeCG7PqfvzegMJIzrxICopWqYCeLATVmvxOOci1isLcNVKgY4Fr0hhYQzvc7cgtK3377jQVChTFAnznOiA=="], - "tailwindcss": ["tailwindcss@4.3.4", "", {}, "sha512-PCJZTFFdG+sL3gvnP1Do9o6GbkvH2q0p04xSs3f7cSvmAeK+sttG+/7ncK1fdRwKFfC7n+wDDJaOMBSefkI9ow=="], + "tailwindcss": ["tailwindcss@4.3.5", "", {}, "sha512-FcQk9BuffmEHJP2G0XmB2SplU1aLTI7j07Kunk+U74Ny+XiWOtzXz9aujFZ/avntw1e2cOv/3PkgzflQ7gX2vQ=="], "tailwindcss-primeui": ["tailwindcss-primeui@0.6.1", "", { "peerDependencies": { "tailwindcss": ">=3.1.0" } }, "sha512-T69Rylcrmnt8zy9ik+qZvsLuRIrS9/k6rYJSIgZ1trnbEzGDDQSCIdmfyZknevqiHwpSJHSmQ9XT2C+S/hJY4A=="], @@ -1262,7 +1260,7 @@ "rdf-data-factory/@rdfjs/types": ["@rdfjs/types@1.1.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-wqpOJK1QCbmsGNtyzYnojPU8gRDPid2JO0Q0kMtb4j65xhCK880cnKAfEOwC+dX85VJcCByQx5zOwyyfCjDJsg=="], - "rimraf/glob": ["glob@14.0.3", "", { "dependencies": { "minimatch": "^11.0.2", "minipass": "^7.1.3", "path-scurry": "^3.0.0" } }, "sha512-A1vjIwC3qpdCso45Iw127gnp7sPhqgY8lUQhDrArIs+GbmfaaVPFuoyy7WyLT9EPZCRZ7j7aLdwUhQmHWesEow=="], + "rimraf/glob": ["glob@14.0.4", "", { "dependencies": { "minimatch": "^11.0.5", "minipass": "^7.1.3", "path-scurry": "^3.0.0" } }, "sha512-mMiwNKoJZ4HAIRVJxotO6LsNRReA30gQwP9E2/p94tfLv+jygCNpKDjhhSL6OLY6LE371EZm5EAf7RpYuUqlsw=="], "rollup-plugin-visualizer/yargs": ["yargs@18.2.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^8.2.1", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w=="], @@ -1318,7 +1316,7 @@ "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], - "rimraf/glob/minimatch": ["minimatch@11.0.4", "", { "dependencies": { "@isaacs/nonrecursive-glob-match": "^1.0.3", "brace-expansion": "^5.0.12" } }, "sha512-/UJ1dEoVQTjPNDh6SGlUo+ueLMmoHTas4q+8yaAR/5t6nLAIiBDJtoYUh20IYleix7ehiQkgczb84PE3uzHkxg=="], + "rimraf/glob/minimatch": ["minimatch@11.0.5", "", { "dependencies": { "@isaacs/nonrecursive-glob-match": "^1.1.0", "brace-expansion": "^5.0.12" } }, "sha512-snPlVppFcYWfLLFQGhDxUyBcHNtMlWmn3jtj7pEaO+DP8pqL0IvAzXhMYmn1z9ASNtgmwLBdFxSf4tMUB0OTKA=="], "rollup-plugin-visualizer/yargs/cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], @@ -1350,10 +1348,10 @@ "rollup-plugin-visualizer/yargs/cliui/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], - "rollup-plugin-visualizer/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.5.0", "", {}, "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ=="], + "rollup-plugin-visualizer/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.6.0", "", {}, "sha512-XZ5apbzreCbXSKwPjJstu8R0zOi247w3XYwWxtwBXgpZrCyc1Ka7p9/xY0A9/diMewmHbEKz+FIraWaEWjlN5g=="], "rollup-plugin-visualizer/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "rollup-plugin-visualizer/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.5.0", "", {}, "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ=="], + "rollup-plugin-visualizer/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.6.0", "", {}, "sha512-XZ5apbzreCbXSKwPjJstu8R0zOi247w3XYwWxtwBXgpZrCyc1Ka7p9/xY0A9/diMewmHbEKz+FIraWaEWjlN5g=="], } } diff --git a/package.json b/package.json index fbcd63dda..8937cc15f 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "url": "git+https://github.com/opencor/webapp.git" }, "type": "module", - "version": "1.20261106.0", + "version": "1.20261109.0", "engines": { "bun": ">=1.2.0" }, @@ -53,8 +53,8 @@ "xxhash-wasm": "^1.1.0" }, "devDependencies": { - "@biomejs/biome": "^2.6.5", - "@celldl/editor": "^0.20261105.0", + "@biomejs/biome": "^2.6.6", + "@celldl/editor": "^0.20261109.0", "@celldl/rdf": "^1.20260822.0", "@electron-toolkit/tsconfig": "^2.0.0", "@electron-toolkit/utils": "^4.0.0", @@ -62,8 +62,8 @@ "@physiomelinks/bg2cellml": "^0.6.2", "@primeuix/themes": "2.0.3", "@primevue/auto-import-resolver": "4.5.5", - "@tailwindcss/postcss": "^4.3.4", - "@tailwindcss/vite": "^4.3.4", + "@tailwindcss/postcss": "^4.3.5", + "@tailwindcss/vite": "^4.3.5", "@types/bun": "^1.4.4", "@types/node": "^26.6.6", "@vitejs/plugin-vue": "^6.0.10", @@ -83,10 +83,10 @@ "postcss": "^8.5.32", "primeicons": "7.0.0", "primevue": "4.5.5", - "rollup-plugin-visualizer": "^7.1.2", + "rollup-plugin-visualizer": "^7.1.3", "stylelint": "^18.0.0", "stylelint-config-standard": "^41.0.0", - "tailwindcss": "^4.3.4", + "tailwindcss": "^4.3.5", "tailwindcss-primeui": "^0.6.1", "tar": "^7.5.22", "typescript": "^7.0.2", diff --git a/src/extern/corsProxy.js b/src/extern/corsProxy.js index 5100601c4..51acd806d 100644 --- a/src/extern/corsProxy.js +++ b/src/extern/corsProxy.js @@ -1,6 +1,6 @@ // A simple CORS proxy handler for Cloudflare Workers (see https://workers.cloudflare.com/). -// Note: the proxy accepts any target URL, but it only returns content that is a CellML file, a SED-ML file, an OMEX file, -// or a CSV file (see isCsvFile()), and that is not too large (see MAX_SIZE). Such content is returned as an +// Note: the proxy accepts any target URL, but it only returns content that is a CellML file, a SED-ML file, an OMEX +// file, or a CSV file (see isCsvFile()), and that is not too large (see MAX_SIZE). Such content is returned as an // opaque, sandboxed download, so that a browser never renders or executes it. const CORS_HEADERS = { @@ -97,9 +97,9 @@ const readBytes = async (reader, maxSize) => { const readRest = async (reader, head) => { // Read the rest of some content, the start of which we have already read, and return all of it, unless it is too // large, in which case return null. - // Note: we read (up to) one more byte than allowed, so that we know whether the content is too large, in which case we - // reject it without concatenating its chunks. Otherwise, we concatenate the start of the content and the rest of - // it in one go. + // Note: we read (up to) one more byte than allowed, so that we know whether the content is too large, in which case + // we reject it without concatenating its chunks. Otherwise, we concatenate the start of the content and the + // rest of it in one go. const all = await readChunks(reader, MAX_SIZE + 1, [head]); diff --git a/src/main/ApplicationWindow.ts b/src/main/ApplicationWindow.ts index 6367403d0..2c6ed663f 100644 --- a/src/main/ApplicationWindow.ts +++ b/src/main/ApplicationWindow.ts @@ -17,9 +17,13 @@ export class ApplicationWindow extends electron.BrowserWindow { constructor(options: electron.BrowserWindowConstructorOptions, usePreload = true) { // Add some common options and call our parent constructor. - // Note: we use backgroundColor to minimise the flickering that may occur when first showing a window. + // Note #1: we use backgroundColor to minimise the flickering that may occur when first showing a window. + // Note #2: a transparent window (e.g., our splash screen window) draws its own background, so it must keep a + // transparent background. - options.backgroundColor = backgroundColor(); + const transparent = !!options.transparent; + + options.backgroundColor = transparent ? '#00000000' : backgroundColor(); options.show = false; options.useContentSize = true; @@ -43,19 +47,21 @@ export class ApplicationWindow extends electron.BrowserWindow { // when our window gets resized), so it must match our theme, which may change while we are running (e.g., if // the user changes our theme or the system one), or there would be some flickering. - const updateBackgroundColor = (): void => { - const color = backgroundColor(); + if (!transparent) { + const updateBackgroundColor = (): void => { + const color = backgroundColor(); - if (color && !this.isDestroyed()) { - this.setBackgroundColor(color); - } - }; + if (color && !this.isDestroyed()) { + this.setBackgroundColor(color); + } + }; - electron.nativeTheme.on('updated', updateBackgroundColor); + electron.nativeTheme.on('updated', updateBackgroundColor); - this.once('closed', () => { - electron.nativeTheme.removeListener('updated', updateBackgroundColor); - }); + this.once('closed', () => { + electron.nativeTheme.removeListener('updated', updateBackgroundColor); + }); + } } // Send a message to our renderer. diff --git a/src/main/MainMenu.ts b/src/main/MainMenu.ts index 69cafdb1b..fee9f6fb7 100644 --- a/src/main/MainMenu.ts +++ b/src/main/MainMenu.ts @@ -99,14 +99,14 @@ export const enableDisableMainMenu = (enable: boolean): void => { label: 'Open...', accelerator: 'CmdOrCtrl+O', click: () => { - mainWindow?.open(); + mainWindow?.showOpenDialog(); } }); fileSubMenu.push({ - label: 'Open Remote...', + label: 'Open Location...', accelerator: 'CmdOrCtrl+Shift+O', click: () => { - mainWindow?.send('open-remote'); + mainWindow?.send('open-location'); } }); fileSubMenu.push({ diff --git a/src/main/MainWindow.ts b/src/main/MainWindow.ts index 22e2742ab..f295f220b 100644 --- a/src/main/MainWindow.ts +++ b/src/main/MainWindow.ts @@ -39,7 +39,8 @@ export const canCheckForUpdates = (): boolean => { return _canCheckForUpdates; }; -// Keep track of the state of an update, i.e. whether it is being downloaded, has been downloaded, or is being installed. +// Keep track of the state of an update, i.e. whether it is being downloaded, has been downloaded, or is being +// installed. // Note: our renderer process may be reloaded while an update is being downloaded or installed (e.g., after it crashed), // in which case we must not check for updates (e.g., at startup) since an issue with checking for updates could // get forwarded as an issue with installing the update (see below) and the update could be offered (and @@ -49,6 +50,8 @@ type UpdateState = 'idle' | 'downloading' | 'downloaded' | 'installing'; let updateState: UpdateState = 'idle'; let updateDownloadPercent = 0; +let updateDownloadTransferred = 0; +let updateDownloadTotal = 0; export const checkForUpdates = (atStartup: boolean): void => { // An update has already been downloaded, but the user cancelled its installation (when asked whether to save the @@ -93,8 +96,10 @@ export const checkForUpdates = (atStartup: boolean): void => { autoUpdater.on('download-progress', (info: ProgressInfo) => { if (updateState === 'downloading') { updateDownloadPercent = info.percent; + updateDownloadTransferred = info.transferred; + updateDownloadTotal = info.total; - MainWindow.instance?.send('update-download-progress', info.percent); + MainWindow.instance?.send('update-download-progress', info.percent, info.transferred, info.total); } }); @@ -125,12 +130,15 @@ autoUpdater.on('error', (error: Error) => { export const downloadAndInstallUpdate = (): void => { updateState = 'downloading'; updateDownloadPercent = 0; + updateDownloadTransferred = 0; + updateDownloadTotal = 0; autoUpdater .downloadUpdate() .then(() => { updateState = 'downloaded'; updateDownloadPercent = 100; + updateDownloadTransferred = updateDownloadTotal; MainWindow.instance?.send('update-downloaded'); }) @@ -172,17 +180,25 @@ export const installUpdateAndRestart = (): boolean => { const resendUpdateState = (): void => { // Let our renderer know about the update that is being downloaded or installed, if any. // Note: this is needed when our renderer has been reloaded (e.g., after it crashed), so that it can show the progress - // of the update (and block its UI) and ask us to install the update once it has been downloaded (in case our - // previous renderer didn't get a chance to do so). + // of the update (and block its UI), ask us to install the update once it has been downloaded (in case our + // previous renderer didn't get a chance to do so), or show that the update is being installed (and block its + // UI) if we have already been asked to install it (in which case OpenCOR is about to be quit). if (updateState === 'idle') { return; } - MainWindow.instance?.send('update-download-progress', updateDownloadPercent); + MainWindow.instance?.send( + 'update-download-progress', + updateDownloadPercent, + updateDownloadTransferred, + updateDownloadTotal + ); if (updateState === 'downloaded') { MainWindow.instance?.send('update-downloaded'); + } else if (updateState === 'installing') { + MainWindow.instance?.send('update-installing'); } }; @@ -233,9 +249,18 @@ export const resetAll = (): void => { let recentFilePaths: string[] = []; +const recentFilePathsUpdated = (): void => { + // Let our Reopen menu and our renderer (which lists our most recent files on its start screen, see + // mainProcessRecentFilePaths in OpenCOR.vue) know about our (updated) recent file paths. + + updateReopenMenu(recentFilePaths); + + MainWindow.instance?.send('recent-files', [...recentFilePaths]); +}; + const removeRecentFilePath = (filePath: string): boolean => { // Remove the given file path from our recent file paths and return whether it was removed (in which case our Reopen - // menu needs to be updated). + // menu and our renderer need to be updated, see recentFilePathsUpdated()). let res = false; @@ -253,7 +278,7 @@ const removeRecentFilePath = (filePath: string): boolean => { export const clearRecentFiles = (): void => { recentFilePaths = []; - updateReopenMenu(recentFilePaths); + recentFilePathsUpdated(); // Let our renderer know that our recent files have been cleared, so that it can release the contents that it kept for // the files that were opened using a data URL and then closed (see OpenCOR.addRecentFile()). @@ -276,7 +301,7 @@ const forgetDataUrlRecentFilePaths = (): void => { if (filePaths.length !== recentFilePaths.length) { recentFilePaths = filePaths; - updateReopenMenu(recentFilePaths); + recentFilePathsUpdated(); } }; @@ -289,7 +314,18 @@ export const fileClosed = (filePath: string): void => { recentFilePaths.length = MAX_RECENT_FILES; } - updateReopenMenu(recentFilePaths); + recentFilePathsUpdated(); +}; + +// Reopen the given recent file, i.e. as if it was reopened using our Reopen menu. +// Note: we only reopen a file that is one of our recent files since our renderer (which asks us to reopen it, see +// onOpenRecentFileAction() in OpenCOR.vue) could otherwise use this to be allowed to save any file (see +// openFile()). + +export const reopenRecentFile = (filePath: string): void => { + if (recentFilePaths.includes(filePath)) { + MainWindow.instance?.openFile(filePath); + } }; export const recentFilePathCanonicalised = (filePath: string, canonicalFilePath: string): void => { @@ -323,7 +359,7 @@ export const recentFilePathCanonicalised = (filePath: string, canonicalFilePath: } } - updateReopenMenu(recentFilePaths); + recentFilePathsUpdated(); }; // Note: for fileIssue() and fileOpened(), filePath is the path that was used to (try to) open the file, which may be @@ -340,15 +376,15 @@ const removeRecentFilePaths = (filePath: string, canonicalFilePath: string): voi const canonicalFilePathRemoved = removeRecentFilePath(canonicalFilePath); if (filePathRemoved || canonicalFilePathRemoved) { - updateReopenMenu(recentFilePaths); + recentFilePathsUpdated(); } }; export const fileIssue = (filePath: string, canonicalFilePath: string): void => { removeRecentFilePaths(filePath, canonicalFilePath); - // A file couldn't be opened, possibly while reopening files during OpenCOR startup, in which case we need to make sure - // that it doesn't get selected and reopen the next file. + // A file couldn't be opened, possibly while reopening files during OpenCOR startup, in which case we need to make + // sure that it doesn't get selected and reopen the next file. MainWindow.instance?.fileOpenedOrNot(filePath, false); }; @@ -692,6 +728,7 @@ export class MainWindow extends ApplicationWindow { private _notRespondingAbortController: AbortController | null = null; private _rendererGone = false; private _rendererGoneCount = 0; + private _openDialogShown = false; private _pendingArguments: string[] = []; private _recentFilePathsToCanonicalise: string[] = []; private _filesToReopen: IFilesToReopen | null = null; @@ -803,7 +840,7 @@ export class MainWindow extends ApplicationWindow { recentFilePaths = electronConf.get('app.files.recent'); - updateReopenMenu(recentFilePaths); + recentFilePathsUpdated(); // Handle our command line, which will effectively be done once our renderer is ready (see rendererReady()). @@ -1106,8 +1143,8 @@ export class MainWindow extends ApplicationWindow { forgetDataUrlRecentFilePaths(); // Ignore our renderer being gone if we are being closed (e.g., because OpenCOR is being quit), whatever the reason. - // Indeed, our renderer will normally exit cleanly in that case, but it may also be killed (e.g., when logging off or - // shutting down the system) or crash while being torn down. Either way, there is nothing to recover since we are + // Indeed, our renderer will normally exit cleanly in that case, but it may also be killed (e.g., when logging off + // or shutting down the system) or crash while being torn down. Either way, there is nothing to recover since we are // going away, so we must not offer to reload OpenCOR. // Note: our renderer may also exit cleanly on its own (e.g., if the native libOpenCOR module calls exit(0)), in // which case it is gone just as if it had crashed, so we handle it as such. @@ -1186,9 +1223,9 @@ export class MainWindow extends ApplicationWindow { // should OpenCOR be quit before any file gets opened, i.e. reopen the files that might have caused the // crash the next time OpenCOR is started. // Note #2: similarly, our reloaded renderer has no files to start with, so our File|Save, File|Save As, File|Close, - // and File|Close All menu items must be disabled (they will get enabled again once a file gets (re)opened). - // Also, the changes that were made to the files that were open are lost, so there is no point in asking - // our reloaded renderer to save them when we get closed (see the constructor). + // and File|Close All menu items must be disabled (they will get enabled again once a file gets + // (re)opened). Also, the changes that were made to the files that were open are lost, so there is no point + // in asking our reloaded renderer to save them when we get closed (see the constructor). filesOpened([]); filesModified([]); @@ -1252,9 +1289,9 @@ export class MainWindow extends ApplicationWindow { fileClosed(filesNotToReopen.selected); } - // Discard the files that we were going to reopen and the arguments that we were asked to handle, so that neither our - // reloaded renderer nor OpenCOR, the next time it is started, reopens the files that might have caused the crash - // or handles the arguments that our (crashed) renderer never handled. + // Discard the files that we were going to reopen and the arguments that we were asked to handle, so that neither + // our reloaded renderer nor OpenCOR, the next time it is started, reopens the files that might have caused the + // crash or handles the arguments that our (crashed) renderer never handled. this._filesToReopen = { opened: [], selected: '' }; this._pendingArguments = []; @@ -1263,9 +1300,13 @@ export class MainWindow extends ApplicationWindow { // Our renderer is ready, so reopen previously opened files, if any, select the previously selected file, and handle // any arguments that we were asked to handle until now (e.g., our command line). // Note: our renderer may get reloaded (e.g., during development), in which case it will let us know again that it is - // ready, but there is nothing more for us to do then. + // ready, but there is nothing more for us to do then, except let it know about our recent file paths. rendererReady(): void { + // Let our (possibly reloaded) renderer know about our recent file paths (see recentFilePathsUpdated()). + + this.send('recent-files', [...recentFilePaths]); + if (this._rendererReady) { return; } @@ -1422,7 +1463,7 @@ export class MainWindow extends ApplicationWindow { openFile(filePath: string): void { allowSavingFile(filePath); - this.send('open', filePath); + this.send('open-file', filePath); } // Enable/disable our UI. @@ -1439,13 +1480,23 @@ export class MainWindow extends ApplicationWindow { // handleArguments()). Similarly, we must not enable our UI in that case since our reloaded renderer will enable // it once it is ready (see onRenderProcessGone()). Otherwise, we must enable our UI, even if our renderer isn't // ready, i.e. if its initialisation failed (in which case it never tells us that it is ready), or our UI would - // remain disabled. + // remain disabled. Also, our renderer may ask us to show our open dialog (see onOpenAction() in OpenCOR.vue) + // while it is already shown (e.g., if the user double clicks on the Open... action of our start screen before + // our UI gets disabled), in which case we ignore the request. + + showOpenDialog(): void { + if (this._openDialogShown) { + return; + } + + this._openDialogShown = true; - open(): void { this.enableDisableUi(false); const rendererGoneCount = this._rendererGoneCount; - const enableUiIfRendererNotGone = (): void => { + const openDialogClosed = (): void => { + this._openDialogShown = false; + if (this._rendererGoneCount === rendererGoneCount) { this.enableDisableUi(true); } @@ -1458,18 +1509,18 @@ export class MainWindow extends ApplicationWindow { .then(({ filePaths }) => { this.handleArguments(filePaths); - enableUiIfRendererNotGone(); + openDialogClosed(); }) .catch((error: unknown) => { console.warn('OpenCOR: failed to open file(s):', formatError(error)); - enableUiIfRendererNotGone(); + openDialogClosed(); }); } // Handle our renderer's File|Save As menu, i.e. let the user choose where to save a file and return the chosen file // path, which our renderer may then save (see canSaveFile()), or null if the user cancelled. - // Note: same as for open() when it comes to the enabling of our UI. + // Note: same as for showOpenDialog() when it comes to the enabling of our UI. async saveAs(defaultFilePath: string): Promise { this.enableDisableUi(false); @@ -1543,8 +1594,8 @@ export class MainWindow extends ApplicationWindow { // twice in a row). // Note #2: our message box gets closed if our renderer receives our request after all, since it then asks the user // whether to save those files. - // Note #3: closing ourselves normally would require our renderer to respond (e.g., to handle beforeunload), so we kill - // it first, making sure that we ignore it being gone (see onRenderProcessGone()). + // Note #3: closing ourselves normally would require our renderer to respond (e.g., to handle beforeunload), so we + // kill it first, making sure that we ignore it being gone (see onRenderProcessGone()). private waitForCloseRequestReceived(): void { if (this._closeRequestTimeoutId !== null || this._notRespondingAbortController) { diff --git a/src/main/SplashScreenWindow.ts b/src/main/SplashScreenWindow.ts index 00b6e28ab..961727e15 100644 --- a/src/main/SplashScreenWindow.ts +++ b/src/main/SplashScreenWindow.ts @@ -9,9 +9,14 @@ import { formatError } from '../renderer/src/common/common'; export class SplashScreenWindow extends ApplicationWindow { constructor() { // Initialise ourselves. + // Note: our window is transparent and has no shadow nor rounded corners, so that our splash screen can draw its own + // rounded corners, border, and background, and therefore look the same on Windows, Linux, and macOS (see + // splashscreen.css). Otherwise, our window would, for instance, have rounded corners and a border on macOS + // and Windows 11, but not on Linux. That said, on Linux, a transparent window requires a compositing window + // manager, without which the corners of our splash screen would be black rather than transparent. - const width = 413 + 24; - const height = 351 + 42; + const width = 360; + const height = 300; const state: IElectronConfState = electronConf.get('app.state'); super( @@ -25,6 +30,9 @@ export class SplashScreenWindow extends ApplicationWindow { maxWidth: width, maxHeight: height, frame: false, + transparent: true, + hasShadow: false, + roundedCorners: false, show: false, alwaysOnTop: true }, diff --git a/src/main/assets/splashscreen.css b/src/main/assets/splashscreen.css index c17c15841..69bda084d 100644 --- a/src/main/assets/splashscreen.css +++ b/src/main/assets/splashscreen.css @@ -1,11 +1,19 @@ -/* - * Note: our splash screen is not part of our app per se, which means that we don't have access to PrimeVue and must - * therefore hard-code colours. +/** + * Note #1: our splash screen is not part of our app per se, which means that we don't have access to PrimeVue and must + * therefore hard-code colours. It looks like our start screen (see BackgroundComponent.vue), i.e. our logo on + * a subtle dot grid with our version and copyright below it. + * Note #2: our window is transparent (see SplashScreenWindow.ts), so we draw our own rounded corners, border, and + * background, so that our splash screen looks the same on Windows, Linux, and macOS. */ +html, body { - background-color: #fff; /* Light version of --p-content-background. */ + height: 100%; margin: 0; + background: transparent; +} + +body { font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; @@ -13,50 +21,74 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; user-select: none; - line-height: 1.6; overflow: hidden; } -@media (prefers-color-scheme: dark) { - body { - background-color: #18181b; /* Dark version of --p-content-background. */ - } +.splash { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + box-sizing: border-box; + height: 100%; + border: 1px solid #e2e8f0; /* Light version of --p-content-border-color. */ + border-radius: 0.75rem; + background-color: #fff; /* Light version of --p-content-background. */ + overflow: hidden; } -.contents { - border-collapse: collapse; - width: 100%; -} +/** + * A subtle dot grid, which fades out towards our edges. + */ -.copyright, -.version { - border-top: 1px solid #33415522; /* Light variant version of --p-content-color. */ - padding: 0 0.5rem; - background-color: #33415511; /* Light variant version of --p-content-color. */ - color: #334155; /* Light version of --p-content-color. */ -} +.pattern { + position: absolute; + inset: 0; -@media (prefers-color-scheme: dark) { - .copyright, - .version { - border-top: 1px solid #fff2; /* Dark variant version of --p-content-color. */ - background-color: #fff1; /* Dark variant version of --p-content-color. */ - color: #fff; /* Dark version of --p-content-color. */ - } + /** + * Light version of --p-text-color. + */ + background-image: radial-gradient(rgb(51 65 85 / 18%) 1px, transparent 1px); + background-size: 1.25rem 1.25rem; + mask-image: radial-gradient(ellipse at center, #000 0%, transparent 70%); } -img.logo { - width: 413px; - height: 351px; - margin: 0.5rem; +/** + * Note: our logo's original dimensions are 828.12px x 704.92px. + */ + +.logo { + position: relative; + width: 200px; + -webkit-user-drag: none; } -td.logo { - padding: 0; - line-height: 0; +.footer { + position: absolute; + right: 0; + bottom: 0.625rem; + left: 0; + color: #64748b; /* Light version of --p-text-muted-color. */ + font-size: 0.75rem; + font-variant-numeric: tabular-nums; text-align: center; } -.version { - text-align: right; +@media (prefers-color-scheme: dark) { + .splash { + border-color: #3f3f46; /* Dark version of --p-content-border-color. */ + background-color: #18181b; /* Dark version of --p-content-background. */ + } + + .pattern { + /** + * Dark version of --p-text-color. + */ + background-image: radial-gradient(rgb(255 255 255 / 18%) 1px, transparent 1px); + } + + .footer { + color: #a1a1aa; /* Dark version of --p-text-muted-color. */ + } } diff --git a/src/main/assets/splashscreen.html b/src/main/assets/splashscreen.html index 7c81d487b..15c8295a9 100644 --- a/src/main/assets/splashscreen.html +++ b/src/main/assets/splashscreen.html @@ -10,17 +10,11 @@ - - - - - - - - -
Version
+
+
+ + +
diff --git a/src/main/assets/splashscreen.js b/src/main/assets/splashscreen.js index 58f679fa4..6e6e4777f 100644 --- a/src/main/assets/splashscreen.js +++ b/src/main/assets/splashscreen.js @@ -1,4 +1,4 @@ const searchParams = new URLSearchParams(window.location.search); -document.getElementById('copyright').textContent = searchParams.get('copyright') ?? ''; -document.getElementById('version').textContent = searchParams.get('version') ?? ''; +document.getElementById('footer').textContent = + `Version ${searchParams.get('version') ?? ''} · Copyright © ${searchParams.get('copyright') ?? ''}`; diff --git a/src/main/index.ts b/src/main/index.ts index d9ba631dc..1953b6595 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -47,6 +47,7 @@ import { MainWindow, normaliseCommandLine, recentFilePathCanonicalised, + reopenRecentFile, reportFatalErrorAndQuit, resetAll, saveFile, @@ -553,6 +554,9 @@ electron.app recentFilePathCanonicalised(filePath, canonicalFilePath); } ); + electron.ipcMain.handle('reopen-recent-file', (_event, filePath: string) => { + reopenRecentFile(filePath); + }); electron.ipcMain.handle('renderer-ready', () => { MainWindow.instance?.rendererReady(); }); @@ -571,6 +575,9 @@ electron.app electron.ipcMain.handle('save-settings', (_event, settings: ISettings) => { saveSettings(settings); }); + electron.ipcMain.handle('show-open-dialog', () => { + MainWindow.instance?.showOpenDialog(); + }); // Create our main window and pass to it our command line arguments. // Note: if that fails (e.g., our renderer server couldn't be started), then OpenCOR cannot be used, so we diff --git a/src/preload/index.ts b/src/preload/index.ts index 4f1558ec6..ce571384a 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1,10 +1,11 @@ import electron from 'electron'; import { promises as fs } from 'node:fs'; +import os from 'node:os'; import loc from '../../dist/libOpenCOR/Release/libOpenCOR.node'; import type { OpenCORTheme } from '../renderer/index'; -import type { ISettings } from '../renderer/src/common/common'; +import type { ISettings, ISystemDetails } from '../renderer/src/common/common'; // A helper function to listen to a channel from the main process. // Note: it returns a function to stop listening to that channel so that a listener can be removed (e.g., when the @@ -72,7 +73,7 @@ retrieveOperatingSystem() }); // Some bridging between our main process and renderer process. -// Note: this must be in sync with src/electronApi.ts. +// Note: this must be in sync with src/renderer/src/common/electronApi.ts. electron.contextBridge.exposeInMainWorld('electronApi', { // Some general methods. @@ -82,6 +83,20 @@ electron.contextBridge.exposeInMainWorld('electronApi', { return _operatingSystem || defaultOperatingSystem; }, + systemDetails: (): ISystemDetails => { + // Return some details about our system (see AboutDialog.vue). + // Note: os.cpus() may return an empty array (e.g., on some Linux systems where /proc is not available). + + const cpus = os.cpus(); + + return { + cpuModel: cpus[0]?.model.trim() ?? '', + logicalCoreCount: cpus.length, + totalMemory: os.totalmem(), + electronVersion: process.versions.electron, + chromiumVersion: process.versions.chrome + }; + }, // Renderer process asking the main process to do something for it. @@ -127,6 +142,7 @@ electron.contextBridge.exposeInMainWorld('electronApi', { recentFilePathCanonicalised: (filePath: string, canonicalFilePath: string) => electron.ipcRenderer.invoke('recent-file-path-canonicalised', filePath, canonicalFilePath), rendererReady: () => electron.ipcRenderer.invoke('renderer-ready'), + reopenRecentFile: (filePath: string) => electron.ipcRenderer.invoke('reopen-recent-file', filePath), resetAll: () => electron.ipcRenderer.invoke('reset-all'), saveFile: (filePath: string, contents: Uint8Array): Promise => electron.ipcRenderer.invoke('save-file', filePath, contents), @@ -135,6 +151,7 @@ electron.contextBridge.exposeInMainWorld('electronApi', { saveGitHubAccessToken: (token: string): Promise => electron.ipcRenderer.invoke('save-github-access-token', token), saveSettings: (settings: ISettings) => electron.ipcRenderer.invoke('save-settings', settings), + showOpenDialog: () => electron.ipcRenderer.invoke('show-open-dialog'), // Renderer process listening to the main process. @@ -146,9 +163,10 @@ electron.contextBridge.exposeInMainWorld('electronApi', { onCheckForUpdates: (callback: () => void) => onIpc('check-for-updates', callback), onClearRecentFiles: (callback: () => void) => onIpc('clear-recent-files', callback), onEnableDisableUi: (callback: (enable: boolean) => void) => onIpc('enable-disable-ui', callback), - onOpen: (callback: (filePath: string) => void) => onIpc('open', callback), - onOpenRemote: (callback: () => void) => onIpc('open-remote', callback), + onOpenFile: (callback: (filePath: string) => void) => onIpc('open-file', callback), + onOpenLocation: (callback: () => void) => onIpc('open-location', callback), onOpenSampleLorenz: (callback: () => void) => onIpc('open-sample-lorenz', callback), + onRecentFiles: (callback: (filePaths: string[]) => void) => onIpc('recent-files', callback), onClose: (callback: () => void) => onIpc('close', callback), onCloseAll: (callback: () => void) => onIpc('close-all', callback), onConfirmClose: (callback: () => void) => onIpc('confirm-close', callback), @@ -162,7 +180,9 @@ electron.contextBridge.exposeInMainWorld('electronApi', { onUpdateDownloaded: (callback: () => void) => onIpc('update-downloaded', callback), onUpdateDownloadError: (callback: (issue: string) => void) => onIpc('update-download-error', callback), onUpdateInstallError: (callback: (issue: string) => void) => onIpc('update-install-error', callback), - onUpdateDownloadProgress: (callback: (percent: number) => void) => onIpc('update-download-progress', callback), + onUpdateInstalling: (callback: () => void) => onIpc('update-installing', callback), + onUpdateDownloadProgress: (callback: (percent: number, transferred: number, total: number) => void) => + onIpc('update-download-progress', callback), onUpdateNotAvailable: (callback: () => void) => onIpc('update-not-available', callback) }); diff --git a/src/renderer/bun.lock b/src/renderer/bun.lock index 4bfc117aa..1f29cc471 100644 --- a/src/renderer/bun.lock +++ b/src/renderer/bun.lock @@ -5,15 +5,15 @@ "": { "name": "@opencor/opencor", "devDependencies": { - "@biomejs/biome": "^2.6.5", - "@celldl/editor": "^0.20261105.0", + "@biomejs/biome": "^2.6.6", + "@celldl/editor": "^0.20261109.0", "@celldl/rdf": "^1.20260822.0", "@opencor/libopencor-types": "^1.20261029.0", "@physiomelinks/bg2cellml": "^0.6.2", "@primeuix/themes": "2.0.3", "@primevue/auto-import-resolver": "4.5.5", - "@tailwindcss/postcss": "^4.3.4", - "@tailwindcss/vite": "^4.3.4", + "@tailwindcss/postcss": "^4.3.5", + "@tailwindcss/vite": "^4.3.5", "@types/bun": "^1.4.4", "@types/node": "^26.6.6", "@vitejs/plugin-vue": "^6.0.10", @@ -25,10 +25,10 @@ "postcss": "^8.5.32", "primeicons": "7.0.0", "primevue": "4.5.5", - "rollup-plugin-visualizer": "^7.1.2", + "rollup-plugin-visualizer": "^7.1.3", "stylelint": "^18.0.0", "stylelint-config-standard": "^41.0.0", - "tailwindcss": "^4.3.4", + "tailwindcss": "^4.3.5", "tailwindcss-primeui": "^0.6.1", "tar": "^7.5.22", "typescript": "^7.0.2", @@ -58,29 +58,29 @@ "@babel/types": ["@babel/types@7.29.11", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.11", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ=="], - "@biomejs/biome": ["@biomejs/biome@2.6.5", "", { "optionalDependencies": { "@biomejs/cli-android-arm64": "2.6.5", "@biomejs/cli-android-x64": "2.6.5", "@biomejs/cli-darwin-arm64": "2.6.5", "@biomejs/cli-darwin-x64": "2.6.5", "@biomejs/cli-linux-arm64": "2.6.5", "@biomejs/cli-linux-arm64-musl": "2.6.5", "@biomejs/cli-linux-x64": "2.6.5", "@biomejs/cli-linux-x64-musl": "2.6.5", "@biomejs/cli-win32-arm64": "2.6.5", "@biomejs/cli-win32-x64": "2.6.5" }, "bin": { "biome": "bin/biome" } }, "sha512-n0sp74QbSVEkWXlH659TKv4oJNylwAvDeZ8K8vt/PbcF1xneuNWe57z/G0Bxfh00cufhWXF6DiStLM8+jcLUpg=="], + "@biomejs/biome": ["@biomejs/biome@2.6.6", "", { "optionalDependencies": { "@biomejs/cli-android-arm64": "2.6.6", "@biomejs/cli-android-x64": "2.6.6", "@biomejs/cli-darwin-arm64": "2.6.6", "@biomejs/cli-darwin-x64": "2.6.6", "@biomejs/cli-linux-arm64": "2.6.6", "@biomejs/cli-linux-arm64-musl": "2.6.6", "@biomejs/cli-linux-x64": "2.6.6", "@biomejs/cli-linux-x64-musl": "2.6.6", "@biomejs/cli-win32-arm64": "2.6.6", "@biomejs/cli-win32-x64": "2.6.6" }, "bin": { "biome": "bin/biome" } }, "sha512-NCgVJRyrU1+hsW1ILNaRqNAb9fRL4n49bwyef+wjVJQ488/bXbp0BfnNqTTExkiNQT3ugkgYKR1vK8NxsjlNSQ=="], - "@biomejs/cli-android-arm64": ["@biomejs/cli-android-arm64@2.6.5", "", { "os": "android", "cpu": "arm64" }, "sha512-fkLzc1WRU7wRIUNmAm7cxILb4dd6eESz/VycWvVFKVn6tXx0gFQKdUbPnwT42xys8AEvIJQpFGcK00kURwqorg=="], + "@biomejs/cli-android-arm64": ["@biomejs/cli-android-arm64@2.6.6", "", { "os": "android", "cpu": "arm64" }, "sha512-bicnqNNxEX7pjkDQaptYvPRIWDfgXJLl/Ok7iZjC+F1AhfReor3FSS7SgPYnOvUy/jf++5U/KYN4T8OKH5rrNA=="], - "@biomejs/cli-android-x64": ["@biomejs/cli-android-x64@2.6.5", "", { "os": "android", "cpu": "x64" }, "sha512-fkZ9dPnDAzAcaPtPCiwqtkub5C9FQK/oGCGaU9sq+DysTaq13pE9FxBIMcEZ3ydn8/FfcMdXi/DDzKDP+qyn2Q=="], + "@biomejs/cli-android-x64": ["@biomejs/cli-android-x64@2.6.6", "", { "os": "android", "cpu": "x64" }, "sha512-N443kDSElF4kDMKjarsL+ymj/ewLA5R9VAdawA7fJ7pwgN3JVDZp0K+xhKBglH8EwsKi2+K8xC6WxXSkgziFfw=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.6.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bKd0EOREgnVjeZm4s9knQ+WdPOkWS3JHB+eWGHlx+Pq66bqQA1/KybwU0O+zuyAyXiphSCBcrLruZt3YtnJoaQ=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.6.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/vSAqWx3by3p8w5ew+HVJDgguuaTK3U6Fb5gk/oDUH21iWOopm3gOlhT6tZfy5mTaM5R4CVc3uFWkxBtEDrKyw=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.6.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-UkT9BECNOXSiW6SGddjyaRACy362Sc9n3vDaTFkccL0bC2gW7JzM16i4+JKPbEaDn1Sa+oETHV8KungnI/y40Q=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.6.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-lLdOAJKbrAoDs+3GvTyVmMW345D9IhrIE3GgoL2eeHwO1boN4VhrSlALtFYsP0p+Jy4udUp4bTguij0qjKrH8w=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.6.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-H9cVMWBqW/lkFK9NHJdt0bpM2q7PacUDEjQoff9+m0dCV09H/jmBykfTCkbDthHf8+jCuWDJWiQFJ7bDkiGOcA=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.6.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-UzJdUCg9dbEGpZOuZRyehEPrnwKjx9z4Um7sWLG5x6txu7UjeWdJZuBXVGlyAdD39w2Hx0nZeK1jKC4NFUTCNQ=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.6.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-kfc3AePW1a0/d/gUpcIz95nf3Ce8YY2/xL0Fk3fLqAhKsotqUi6PN6BaVWss4p6ijWbiuacZ9NQ4NiQtpjbYpw=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.6.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-rw2Vsoi/WZGDyRjMXx9KZH9zsHe79LIzkdWHkLqsIkeKZm6qiGxtiIApxn7cmJSoEPaSvHaH5pRj1FhTcyL/XA=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.6.5", "", { "os": "linux", "cpu": "x64" }, "sha512-e6/WMSN/+FhH+ZOoBevps/euxFQmv6BXqYNuTn1+ITR6O4XYFjHF5PeVSNZnjkgSYmvhcDkfepSX5lXczMijGA=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.6.6", "", { "os": "linux", "cpu": "x64" }, "sha512-l8/aPXc1cz8VpUyd4hb5Pm5zx+X3voQBks4aLYbp5pC8KSchuHyvqDfY5xax39qCHomI/2xn6NKEot+zebG38g=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.6.5", "", { "os": "linux", "cpu": "x64" }, "sha512-0mHVpLNt7/FugfZ2VPugUIEIiooWIyHGGJUqU9StxamBln9hz+maXRFIECU50jaf5/ZB93veGIViN5VKgw2AEA=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.6.6", "", { "os": "linux", "cpu": "x64" }, "sha512-DiKejs+GnFbOVQsBnukUpiB+T13tqUDFGwwjsTq51vatAsaflzFzVD0BOgjVUGRrqKgE0K0Xf+wNtkcmdN2chw=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.6.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-uBbfpdR/cH5up1iRJFGAPP97xKMzrgMs685CSrY7NM+Q4Unw7chY0o3JulEvJdmU6c6hGQxwqotMq7DDH0DzxA=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.6.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-DjVXwQAQeHrO1JEZAEus54k40Tnh4glyU3VFLcDBKwoz0xnxCEnqw6TCMXoujcQMQynWd3AY8bLrbR1o0XNqow=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.6.5", "", { "os": "win32", "cpu": "x64" }, "sha512-07LaCeN9vvmbAAMOMsUjMfL5UbsCDkvVeyMJIgwNRc0xYqhizBGkjVzx05LYEwCXl2AiHcdzqVPR4IOYAsI1QQ=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.6.6", "", { "os": "win32", "cpu": "x64" }, "sha512-B7KMG26A4I9lAuxwBGMmiVcjMGAeJRoz+pzFt9brRvOAsIrgOu8ptJld5rUWui6tM1uiJwxeJhhDPuAiZtxXdw=="], - "@celldl/editor": ["@celldl/editor@0.20261105.0", "", { "dependencies": { "@atomic-ehr/ucum": "^0.2.5", "@celldl/metadata": "^1.20260822.0", "@celldl/rdf": "^1.20260822.0", "@ctrl/tinycolor": "^4.2.0", "@lucide/vue": "^1.39.0", "@mathjax/src": "^4.1.3", "@vueuse/core": "14.3.0", "buffer": "^6.0.3", "flatbush": "^4.5.0", "graphology": "^0.26.0", "isomorphic-fetch": "^3.0.0", "jsonschema": "^1.5.0", "mini-svg-data-uri": "^1.4.4", "primeicons": "^7.0.0", "primevue": "^4.5.5", "rbush": "^4.0.1", "svg-intersections": "github:dbrnz/svg-intersections#fix-numbers", "svg-path-commander": "^2.1.11", "systeminformation": "^5.27.11", "transformation-matrix": "^3.1.0", "tslog": "^5.4.0", "ua-parser-js": "^2.0.8", "vue": "^3.5.44", "vue-tippy": "^6.7.1" } }, "sha512-xTUm7r56Q9uNmJtRc/iyCzim1S0fKfFa6AH4VLzuKtWy3qAYapKeD/1iHV0U1YdmeG0icJDi6reUW2PB8cS8bg=="], + "@celldl/editor": ["@celldl/editor@0.20261109.0", "", { "dependencies": { "@atomic-ehr/ucum": "^0.2.5", "@celldl/metadata": "^1.20260822.0", "@celldl/rdf": "^1.20260822.0", "@ctrl/tinycolor": "^4.2.0", "@lucide/vue": "^1.39.0", "@mathjax/src": "^4.1.3", "@vueuse/core": "14.3.0", "buffer": "^6.0.3", "flatbush": "^4.5.0", "graphology": "^0.26.0", "isomorphic-fetch": "^3.0.0", "jsonschema": "^1.5.0", "mini-svg-data-uri": "^1.4.4", "primeicons": "^7.0.0", "primevue": "^4.5.5", "rbush": "^4.0.1", "svg-intersections": "github:dbrnz/svg-intersections#fix-numbers", "svg-path-commander": "^2.1.11", "systeminformation": "^5.27.11", "transformation-matrix": "^3.1.0", "tslog": "^5.4.0", "ua-parser-js": "^2.0.8", "vue": "^3.5.44", "vue-tippy": "^6.7.1" } }, "sha512-VyjG9Bp2JY//rlIwM1eu2LaxD4NIAaNiTI9mBcsxf04n5qlJhm3WN9siFoMMbWxqYWvzuJfdiPVESGyyd/NFKw=="], "@celldl/metadata": ["@celldl/metadata@1.20260822.0", "", { "dependencies": { "@celldl/rdf": "^1.20260822.0" } }, "sha512-xqew4gPCrE4JbkmHWhjS3+Khj70jx7i/L/qvFVf3CrdhVAUrH4Wi1Bodcbz4DQHsnEQUHDcBV8oXvJhXjSg1cg=="], @@ -100,11 +100,11 @@ "@csstools/selector-specificity": ["@csstools/selector-specificity@6.0.1", "", { "peerDependencies": { "postcss-selector-parser": "^7.1.1" } }, "sha512-MBuqFIIvqk4S593MGAskTkPpEUUzZNvGzh8mMxEwl3oHwjQlXmqHEDxNbN3Y0Tsh+xjmem4GZjeg7I0qkwBivg=="], - "@ctrl/tinycolor": ["@ctrl/tinycolor@4.2.2", "", {}, "sha512-9rQHijqB2Rq6qOEC/xHaA6aPlsKGi5kmCQW50O1asn9g63XFeD5NmBwysriistW5Xx0JPafyD/TA7yk2S3pXGQ=="], + "@ctrl/tinycolor": ["@ctrl/tinycolor@4.3.0", "", {}, "sha512-6V0+8vw/GmH92URQ/Ki12FPhoB3nqaQUJE+5xgbFbB5ok0OXYK5jCQsQQoyR3BzCddHsCpyCFRnIcI1PSOVYkQ=="], "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], - "@isaacs/nonrecursive-glob-match": ["@isaacs/nonrecursive-glob-match@1.0.3", "", {}, "sha512-SI7kyKHq4XXDRozw+r9GlRZkia1ZkFrpLW98r2IC2Bpr8YkFGE3fvjzmxCG0x78A++69HmDNrDvSMYBZmHGpKA=="], + "@isaacs/nonrecursive-glob-match": ["@isaacs/nonrecursive-glob-match@1.1.0", "", {}, "sha512-GPm/Dbvk5RLmhRnxXIa7nTpK0xtl4E0wj0MslsuNiJ1FIROU+2+9lgUC/dgF7Ey4QCWhfEsgxilMRMCdHPfDfw=="], "@jeswr/pretty-turtle": ["@jeswr/pretty-turtle@1.8.2", "", { "dependencies": { "n3": "^1.25.2", "rdf-string-ttl": "^1.3.2" }, "peerDependencies": { "@rdfjs/types": "*" } }, "sha512-Os15rA6dAAxdnEqxwvcNZ8wX6fO+gKVgiXJsVmVQ3WDiu61Srf0H/nb4X475IKnDVGDMqmd0lGTL1Lk2uxtcVA=="], @@ -118,7 +118,7 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@lucide/vue": ["@lucide/vue@1.69.0", "", { "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-uxR2FtureNe37HOQk/e92CZjlTjXVuNiYlD60Mgns0rmbs0wqDnHDgZPUooP6nvmFn/v7Ie6dKRKPzgR6HW5Tw=="], + "@lucide/vue": ["@lucide/vue@1.70.0", "", { "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-kutUu3oEtfQnyaP6hh76ShG2dxa8PiL6uY3rk3DqclQe0Vd2FtPlnclezR0XMQtlFMPiEaCOXDSuhehFHW5ErQ=="], "@mathjax/mathjax-newcm-font": ["@mathjax/mathjax-newcm-font@4.1.3", "", {}, "sha512-gzAB3dFHilHX1l5x2xUqRL+1jDQt3Fyza1DkEMVXWC4E8SvsGdlgEza47HYi2WhVcgfkvf4zgUGzuhbq3Pjlew=="], @@ -184,39 +184,39 @@ "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.16", "", { "os": "win32", "cpu": "x64" }, "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.1.0", "", {}, "sha512-K+NU2HDCEem4sClgHH4ZZXwdJeNFkJo+hRk479DIwdIviAPN2hFJH1KHHwg8CxHQXvi0M5XY4deTXINfIiSG6w=="], - "@tailwindcss/node": ["@tailwindcss/node@4.3.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.5", "jiti": "^2.7.0", "lightningcss": "1.33.0", "magic-string": "^1.1.0", "source-map-js": "^1.2.1", "tailwindcss": "4.3.4" } }, "sha512-b7QK/QG04v3j04NbpG82XLCedRyulnkuwUDwRa9oK/ypnJIzMapz2Wg+0TltVczjUzDaJJHIa1+jj9aMUOFMTQ=="], + "@tailwindcss/node": ["@tailwindcss/node@4.3.5", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.5", "jiti": "^2.7.0", "lightningcss": "1.33.0", "magic-string": "^1.1.0", "source-map-js": "^1.2.1", "tailwindcss": "4.3.5" } }, "sha512-yT53FzV4ke0cr/BguDxhWMpm5MJu1O2muzk+tKw6lKHjODAsYArBbWwFbJiaksfzFv1tpkO9QHGzmfs1hJT66g=="], - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.4", "@tailwindcss/oxide-darwin-arm64": "4.3.4", "@tailwindcss/oxide-darwin-x64": "4.3.4", "@tailwindcss/oxide-freebsd-x64": "4.3.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.4", "@tailwindcss/oxide-linux-arm64-musl": "4.3.4", "@tailwindcss/oxide-linux-x64-gnu": "4.3.4", "@tailwindcss/oxide-linux-x64-musl": "4.3.4", "@tailwindcss/oxide-wasm32-wasi": "4.3.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.4", "@tailwindcss/oxide-win32-x64-msvc": "4.3.4" } }, "sha512-riqsRw2sLF7B1sNCCkTl3KYXEqvhTiABzZ/4oxxHfZ3c3Td60bCBfOrUuUGOiHagd3E7V+zG46c+XBDWZGFBcg=="], + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.5", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.5", "@tailwindcss/oxide-darwin-arm64": "4.3.5", "@tailwindcss/oxide-darwin-x64": "4.3.5", "@tailwindcss/oxide-freebsd-x64": "4.3.5", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.5", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.5", "@tailwindcss/oxide-linux-arm64-musl": "4.3.5", "@tailwindcss/oxide-linux-x64-gnu": "4.3.5", "@tailwindcss/oxide-linux-x64-musl": "4.3.5", "@tailwindcss/oxide-wasm32-wasi": "4.3.5", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.5", "@tailwindcss/oxide-win32-x64-msvc": "4.3.5" } }, "sha512-PynqGLZsZvwM9tqD/CUxkOIfyd6K5elZ2MooauzJryP/UqXYgi3yYbtt3N0Xe+MHV/eFKPFQCEfIKAqlAS1CBg=="], - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.4", "", { "os": "android", "cpu": "arm64" }, "sha512-qkjxt+EFocOi3mtsUS3ndlj6yj4SHubwBdvBynLnS8T4mvSzX0Enn0CpypJKmBEtczUAXOHVZKA2zTGzSNU2Sg=="], + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.5", "", { "os": "android", "cpu": "arm64" }, "sha512-Se0z5xMqcSQanJD1IYmO0cGpK3iv0fp3ZsIT7xRJZ1Da5PghvNoc46zz4jIQn+aan3dV9T8AermFNN9QPmsh0Q=="], - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-i/awUA/7mJQAbA3AzCW8aQtUh5goMieUdF1zOoclLaJUCxDvsXQ8VJqpQZY3dfcHS6PJjd3tw25nNneD/mEviQ=="], + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UPrgTDuUbvmqGMZuCnmI3FKee/tYKzKdp00/kuB4oSGB213VYg8QTdF7lVhnPoBXq/mIfJ+ILoBFqNOboWcMwQ=="], - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-q56V9U6AnoYz/yj/alkDLhSQ9AZoXQSEl93f7WETa0+32CudbqM57BXdYXfMYPKudueXKaA+MG6L/0KRseF7Jg=="], + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-E+p7HEtjI0b8D7kbbWxH067cGui6d4c79COGko9fAPVE3rwc1E7ld2HFCbaG86AOGWS5VKQYqZOaf38ya4uxTQ=="], - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-r0W7A+Z9HTHh7bwihJl45E/zZKLjxD50/jToVF/E/RZHSl8+EWO+4yKKJpPMcUooT9hdoip3Zx1uWnbMg4N79g=="], + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yWEWcWyAG7rWOcTpJTjdhQ3pcaFY2h+0bqTRfgU3G2fz87ZU3AKdvdZKhslD+kyHnjBUWKvNdRYwIYC5dI1tIg=="], - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.4", "", { "os": "linux", "cpu": "arm" }, "sha512-3aih/sRxzeYfi2Bkh+bGYNf2YU9Llamb2mpA7BNF1obYzdgEeWDAMQzK6BSwzLhs/eTdOaSur92qrGfchagUtQ=="], + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.5", "", { "os": "linux", "cpu": "arm" }, "sha512-WmeVjtJhZlr+smpqcdR2kQkkn6arHIJnUIBxTO2EJcTLGY1ecVXNo2hbSn8xc0IH2aEtys936iMWKGyj0GaKnA=="], - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-ktJSrPGQou4OqL88rDEx5CkeM9BjzrwZvYo8u7B7VL1zkBa2XBm351o4MQrek95qikb2M++26Y7w7vbH6Mgl8g=="], + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-DTCSyucc6ShMp1QMqV5R94Z7xlgNgUzkGmFF1rL5HH2Qjy4eSmEqy+YLGkqqYvWa4sILtgrBt1JjRKbneQtiFQ=="], - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-rt3hjUxT7utAtcB+df6wHyaXDyxhE7Zd1x7L/nvB5Jb2qBb8dZwFUoWqeRja1BaF5OlSqkKSjBmVXj9OsevmCQ=="], + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-H9lsvO4x9E4nE4Hi8h7NHHw0O/kcVSu+iOyFHNGdYzazGgDjFHZbE6S3hQ8kMcbNXSBlMdNi67LV5EVqtpaJtw=="], - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-XE3jO2yOOWjttpeGvpwf3/b274NMi3QapQSAI/EzsgrmYNan1Tw8dXT9JQkN7K0OgvuqiC8mcCyNvGzEkpVJPQ=="], + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-4uxQvtBvPuIdl13WfUBj9xMZHH14PtHZ8OZdtLmdyDtG66sCa1t599NZT+jagkUzRtWhuul1qRZQ1nAJscXN5g=="], - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-PaAZ2FjJaZk9SQLYtKIfQWiFm/xtl7pX2MKPgCzn0mS/VU54yu+vP457AJdB2XFLxU3gO2abLCt8N7Lg4EbKNg=="], + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-0JQAzs7jwbA7O6+z/adU+Jk3AXwsu3npAhtr5escXS7TqEqWcaDV7qHuI+0Vy/KQRuVGnLQREcSJWYauyV3oqg=="], - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.4", "", { "dependencies": { "@emnapi/core": "^1.11.3", "@emnapi/runtime": "^1.11.3", "@emnapi/wasi-threads": "^1.2.3", "@napi-rs/wasm-runtime": "^1.2.2", "@tybys/wasm-util": "^0.10.3", "tslib": "^2.8.1" } }, "sha512-bjO9WmoM687p8AoCIyw1DUqzpAs4JICLvNmIPxIWDmhw8IOsdubtWQLShyJmISnDKtRy1BBApHVOUl6sNFQQJA=="], + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.5", "", { "dependencies": { "@emnapi/core": "^1.11.3", "@emnapi/runtime": "^1.11.3", "@emnapi/wasi-threads": "^1.2.3", "@napi-rs/wasm-runtime": "^1.2.2", "@tybys/wasm-util": "^0.10.3", "tslib": "^2.8.1" } }, "sha512-OJhFmlEfdzMr+avACdi40RPtnkW0B6hXyxL/8r+tnEHBWZji36CYRjEXrrvgBmaYsD3oEOBqwYFRKsYvzFe7Bw=="], - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-jCrwCvWVtt7ruMXp6dFPd5KBNU8o1tWtEdZbTL/w/hvD3lUYZA0E2mmfvTzW4QLHIx5wGQU9HeY7997R1QTrEg=="], + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-9o8jgthi+QE5eandnboiJT64VQfnIArfZzSTdgbhEK5aafdiu2jTW9Honi/mwYHxBZuH1vAC57IFyrh+0iK5Gg=="], - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.4", "", { "os": "win32", "cpu": "x64" }, "sha512-cge1wcedn1FrRSXL22lF5sNDYr8c9+VImk7zFYLPs12jkDmchSrZNfzWqOKub+5Hb/aYRdDEqERGGDKONBlOzQ=="], + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.5", "", { "os": "win32", "cpu": "x64" }, "sha512-XiKFfIy1RWe7qK4WqByxNW7Oz1/nkOy8o/BWhHminyV1PmpUd8Eedj5v2A8h2kpqeVlz6xRnwk9Fr/ZXNLcZfg=="], - "@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.4", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.4", "@tailwindcss/oxide": "4.3.4", "postcss": "^8.5.25", "tailwindcss": "4.3.4" } }, "sha512-6O0QLY+1CseO8ivlYK2Y7IxNPaQ8D5ypxrV+ws34mbxknWH824J+gEWtJ0gyFiYAudmdEeXdF8kqwgXIaZRyHw=="], + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.5", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.5", "@tailwindcss/oxide": "4.3.5", "postcss": "^8.5.25", "tailwindcss": "4.3.5" } }, "sha512-FAV37Tlx9wBiT/hkfgVD0Q6CWsP2oUX3lf28nrfjW8+ksILjnpuFulUqWDfNR4/ZiEHHDRBX8DUTzD1Rys687A=="], - "@tailwindcss/vite": ["@tailwindcss/vite@4.3.4", "", { "dependencies": { "@tailwindcss/node": "4.3.4", "@tailwindcss/oxide": "4.3.4", "tailwindcss": "4.3.4" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-wGukGWb8SVuN7+FJvSS7M5eSJWDIWwVSvUvj1q41eTIhGPsS2DAgFWjA7YURJNwjHSgty59Hz8iMUiyO+YMHBQ=="], + "@tailwindcss/vite": ["@tailwindcss/vite@4.3.5", "", { "dependencies": { "@tailwindcss/node": "4.3.5", "@tailwindcss/oxide": "4.3.5", "tailwindcss": "4.3.5" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-BCNiY0a1ZjdOHVqw7kyb7U72BwfMF3dfUuN+tQsLWky/J5JOI/r7abibglXfygrZhVO/3u3V+E25kkKTeMkXaQ=="], "@thednp/dommatrix": ["@thednp/dommatrix@3.1.2", "", {}, "sha512-wxRH2/Qdqd+JxFpjb5vw+ZMaij8Hqa/9Hgl41n2Jb+3WNRhdemrosK0FxEo/yEvhvHGW8D1tZ3oPH9N0m1QG+A=="], @@ -312,7 +312,7 @@ "alien-signals": ["alien-signals@3.2.1", "", {}, "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g=="], - "ansi-regex": ["ansi-regex@6.5.0", "", {}, "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ=="], + "ansi-regex": ["ansi-regex@6.6.0", "", {}, "sha512-XZ5apbzreCbXSKwPjJstu8R0zOi247w3XYwWxtwBXgpZrCyc1Ka7p9/xY0A9/diMewmHbEKz+FIraWaEWjlN5g=="], "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -336,7 +336,7 @@ "bundle-name": ["bundle-name@4.1.1", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA=="], - "caniuse-lite": ["caniuse-lite@1.0.30001820", "", {}, "sha512-gfWD6MxqVCqAOwmODfZf7DchWvlrA+SRdNX7ceLfg3xRqe5ssjjpF7EgslC/0tiLiNTajdaNN/q8c/5u/7Gz8w=="], + "caniuse-lite": ["caniuse-lite@1.0.30001823", "", {}, "sha512-TQ5s/djDjNk1axreUW+4rULH+BmdCMLiJkhaXnO5wqivG7tITkSUj4ItIdzqdZHKaXcHAV8dhH3JxOM7/7bwqw=="], "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], @@ -344,7 +344,7 @@ "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], - "colord": ["colord@2.10.0", "", {}, "sha512-AidJptpBJmjTclAp9BkLwJi0T93fo5epJnbaZslpg6QVzpHjAiveF55mE9AcUJiGMqRHgMDY8soMsQtuNYMHfw=="], + "colord": ["colord@2.11.0", "", {}, "sha512-R7EZD9e+sWpUQFr3F6v747NAazZCD+OhsM0VnPkHGxSaQvw4ZvJoCVz51gemztBBoZaFvMP0qdKgFyBdBurfgA=="], "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], @@ -354,8 +354,6 @@ "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], - "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "default-browser": ["default-browser@5.5.1", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw=="], @@ -368,7 +366,7 @@ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "electron-to-chromium": ["electron-to-chromium@1.5.477", "", {}, "sha512-PN++F4zWZoVDGWinxMnSCpVqKhqFOJfLXxdU8bYJKudAEsHL/XKVITOKWVbfMt1r276Mi9RDLU9VHx9uKJ4RMQ=="], + "electron-to-chromium": ["electron-to-chromium@1.5.478", "", {}, "sha512-pftG+ma5Lm8O6/p45bhYT6StBN5ziQymaRzmCZwFl8DwWHKPPD93FdOmP1aqftcepolQYvrcyTLa17kE8Yf+jg=="], "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], @@ -402,7 +400,7 @@ "get-east-asian-width": ["get-east-asian-width@1.7.0", "", {}, "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q=="], - "glob": ["glob@14.0.3", "", { "dependencies": { "minimatch": "^11.0.2", "minipass": "^7.1.3", "path-scurry": "^3.0.0" } }, "sha512-A1vjIwC3qpdCso45Iw127gnp7sPhqgY8lUQhDrArIs+GbmfaaVPFuoyy7WyLT9EPZCRZ7j7aLdwUhQmHWesEow=="], + "glob": ["glob@14.0.4", "", { "dependencies": { "minimatch": "^11.0.5", "minipass": "^7.1.3", "path-scurry": "^3.0.0" } }, "sha512-mMiwNKoJZ4HAIRVJxotO6LsNRReA30gQwP9E2/p94tfLv+jygCNpKDjhhSL6OLY6LE371EZm5EAf7RpYuUqlsw=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], @@ -476,7 +474,7 @@ "mini-svg-data-uri": ["mini-svg-data-uri@1.4.4", "", { "bin": { "mini-svg-data-uri": "cli.js" } }, "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg=="], - "minimatch": ["minimatch@11.0.4", "", { "dependencies": { "@isaacs/nonrecursive-glob-match": "^1.0.3", "brace-expansion": "^5.0.12" } }, "sha512-/UJ1dEoVQTjPNDh6SGlUo+ueLMmoHTas4q+8yaAR/5t6nLAIiBDJtoYUh20IYleix7ehiQkgczb84PE3uzHkxg=="], + "minimatch": ["minimatch@11.0.5", "", { "dependencies": { "@isaacs/nonrecursive-glob-match": "^1.1.0", "brace-expansion": "^5.0.12" } }, "sha512-snPlVppFcYWfLLFQGhDxUyBcHNtMlWmn3jtj7pEaO+DP8pqL0IvAzXhMYmn1z9ASNtgmwLBdFxSf4tMUB0OTKA=="], "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], @@ -520,7 +518,7 @@ "postcss-safe-parser": ["postcss-safe-parser@7.1.0", "", { "peerDependencies": { "postcss": "^8.4.31" } }, "sha512-1WzZxRLaAFwEh6Do+zyGpjWV3nGJNxxhuh7Ubu/q1ICImMgZJnLwhoaEpRbG4pJppp2Y1ncL9ffA2f+LhrefQg=="], - "postcss-selector-parser": ["postcss-selector-parser@7.1.7", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-wWBro/7hJ40ye6TPiXkUQoXKCw23xSsjriUvqd7Uls9Sso3oLiON1flc18cCdnjgCRXw4fBVqUJJeOdDTh3P3g=="], + "postcss-selector-parser": ["postcss-selector-parser@7.2.0", "", { "dependencies": { "util-deprecate": "^1.0.2" } }, "sha512-Khh/tERVBn0I1499dvBSHY0pWJloMJWUxDsOBM0CCDPAcVZ10Jl2L1qy1f19uEX22spa8Mv7DCdEQJc0cGwBhQ=="], "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], @@ -550,7 +548,7 @@ "rolldown": ["rolldown@1.2.16", "", { "dependencies": { "@oxc-project/types": "=0.156.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.16", "@rolldown/binding-android-arm64": "1.2.16", "@rolldown/binding-darwin-arm64": "1.2.16", "@rolldown/binding-darwin-x64": "1.2.16", "@rolldown/binding-freebsd-x64": "1.2.16", "@rolldown/binding-linux-arm-gnueabihf": "1.2.16", "@rolldown/binding-linux-arm64-gnu": "1.2.16", "@rolldown/binding-linux-arm64-musl": "1.2.16", "@rolldown/binding-linux-ppc64-gnu": "1.2.16", "@rolldown/binding-linux-s390x-gnu": "1.2.16", "@rolldown/binding-linux-x64-gnu": "1.2.16", "@rolldown/binding-linux-x64-musl": "1.2.16", "@rolldown/binding-openharmony-arm64": "1.2.16", "@rolldown/binding-win32-arm64-msvc": "1.2.16", "@rolldown/binding-win32-x64-msvc": "1.2.16" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw=="], - "rollup-plugin-visualizer": ["rollup-plugin-visualizer@7.1.2", "", { "dependencies": { "open": "^11.0.0", "picomatch": "^4.0.5", "source-map": "^0.8.0", "yargs": "^18.1.0" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-+yrEz9LJ7fxd7T0yj4GPvcKHKfJNzTzKT7FHiF3f/US+7EaWlgB8PoGbUOlobD4Al8v6TeCCl/Fgbov5FV/DaA=="], + "rollup-plugin-visualizer": ["rollup-plugin-visualizer@7.1.3", "", { "dependencies": { "open": "^11.0.0", "picomatch": "^4.0.5", "source-map": "^0.8.0", "yargs": "^18.1.0" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-P7G0p9RowDd0ahXXBtx4MOHlSVzclyW5pDeZ2Z0HensWSI9vJdu2bqdZDsD375g+0AxND8VJzWYuKD9yH38etw=="], "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], @@ -582,7 +580,7 @@ "systeminformation": ["systeminformation@5.33.19", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-Zm7wGeCG7PqfvzegMJIzrxICopWqYCeLATVmvxOOci1isLcNVKgY4Fr0hhYQzvc7cgtK3377jQVChTFAnznOiA=="], - "tailwindcss": ["tailwindcss@4.3.4", "", {}, "sha512-PCJZTFFdG+sL3gvnP1Do9o6GbkvH2q0p04xSs3f7cSvmAeK+sttG+/7ncK1fdRwKFfC7n+wDDJaOMBSefkI9ow=="], + "tailwindcss": ["tailwindcss@4.3.5", "", {}, "sha512-FcQk9BuffmEHJP2G0XmB2SplU1aLTI7j07Kunk+U74Ny+XiWOtzXz9aujFZ/avntw1e2cOv/3PkgzflQ7gX2vQ=="], "tailwindcss-primeui": ["tailwindcss-primeui@0.6.1", "", { "peerDependencies": { "tailwindcss": ">=3.1.0" } }, "sha512-T69Rylcrmnt8zy9ik+qZvsLuRIrS9/k6rYJSIgZ1trnbEzGDDQSCIdmfyZknevqiHwpSJHSmQ9XT2C+S/hJY4A=="], diff --git a/src/renderer/index.html b/src/renderer/index.html index df3af1f42..dab2e08c2 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -25,10 +25,116 @@ background-color: #18181b; /* Dark version of --p-content-background. */ color: #ffffff; /* Dark version of --p-content-color. */ } + /** + * What we show until OpenCOR has been loaded (and mounted, which replaces the contents of #app), i.e. the message + * that OpenCOR shows while it is being initialised (see BlockingMessageComponent.vue), at 0%, so that both look + * like a single step to the user. + * Note: OpenCOR is not loaded yet, so we don't have access to PrimeVue and must therefore hard-code colours. + */ + .loading { + position: absolute; + top: 50%; + left: 50%; + display: flex; + flex-direction: column; + gap: 0.625rem; + box-sizing: border-box; + width: max-content; + min-width: min(16rem, calc(100% - 1rem)); + max-width: min(24rem, calc(100% - 1rem)); + padding: 0.75rem 1rem; + border: 1px solid #e2e8f0; /* Light version of --p-content-border-color. */ + border-radius: 0.75rem; + background-color: #ffffff; /* Light version of --p-content-background. */ + box-shadow: 0 0.25rem 1rem rgb(0 0 0 / 12%); + font-family: + Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", + "Droid Sans", "Helvetica Neue", sans-serif; + line-height: 1.5; + transform: translate(-50%, -50%); + -webkit-font-smoothing: antialiased; + } + .loading-header { + display: flex; + align-items: center; + gap: 0.625rem; + } + .loading-spinner { + flex-shrink: 0; + width: 1.125rem; + height: 1.125rem; + box-sizing: border-box; + border: 2px solid rgb(16 185 129 / 20%); /* Light version of --p-primary-color. */ + border-top-color: #10b981; /* Light version of --p-primary-color. */ + border-radius: 50%; + animation: loading-spin 0.8s linear infinite; + } + @keyframes loading-spin { + to { + transform: rotate(360deg); + } + } + .loading-text { + flex-grow: 1; + font-size: 0.875rem; + font-weight: 500; + white-space: nowrap; + } + .loading-progress { + color: #64748b; /* Light version of --p-text-muted-color. */ + font-size: 0.75rem; + font-variant-numeric: tabular-nums; + } + .loading-progress-bar { + height: 0.25rem; + border-radius: 6px; /* --p-content-border-radius. */ + background-color: #e2e8f0; /* Light version of --p-content-border-color. */ + } + @media (prefers-color-scheme: dark) { + :root:not(.opencor-light-mode) .loading { + border-color: #3f3f46; /* Dark version of --p-content-border-color. */ + background-color: #18181b; /* Dark version of --p-content-background. */ + box-shadow: 0 0.25rem 1rem rgb(0 0 0 / 50%); /* Dark version of --opencor-card-shadow. */ + } + :root:not(.opencor-light-mode) .loading-spinner { + border-color: rgb(52 211 153 / 20%); /* Dark version of --p-primary-color. */ + border-top-color: #34d399; /* Dark version of --p-primary-color. */ + } + :root:not(.opencor-light-mode) .loading-progress { + color: #a1a1aa; /* Dark version of --p-text-muted-color. */ + } + :root:not(.opencor-light-mode) .loading-progress-bar { + background-color: #3f3f46; /* Dark version of --p-content-border-color. */ + } + } + :root.opencor-dark-mode .loading { + border-color: #3f3f46; /* Dark version of --p-content-border-color. */ + background-color: #18181b; /* Dark version of --p-content-background. */ + box-shadow: 0 0.25rem 1rem rgb(0 0 0 / 50%); /* Dark version of --opencor-card-shadow. */ + } + :root.opencor-dark-mode .loading-spinner { + border-color: rgb(52 211 153 / 20%); /* Dark version of --p-primary-color. */ + border-top-color: #34d399; /* Dark version of --p-primary-color. */ + } + :root.opencor-dark-mode .loading-progress { + color: #a1a1aa; /* Dark version of --p-text-muted-color. */ + } + :root.opencor-dark-mode .loading-progress-bar { + background-color: #3f3f46; /* Dark version of --p-content-border-color. */ + } -
+
+
+
+ + Initialising OpenCOR... + 0% +
+
+
+
diff --git a/src/renderer/package.json b/src/renderer/package.json index 497d65aea..66b48911d 100644 --- a/src/renderer/package.json +++ b/src/renderer/package.json @@ -42,7 +42,7 @@ }, "./style.css": "./dist/opencor.css" }, - "version": "1.20261106.0", + "version": "1.20261109.0", "libopencorVersion": "1.20261029.0", "scripts": { "build": "vite build && bun scripts/generate.version.ts", @@ -62,15 +62,15 @@ "vue": "^3.5.45" }, "devDependencies": { - "@biomejs/biome": "^2.6.5", - "@celldl/editor": "^0.20261105.0", + "@biomejs/biome": "^2.6.6", + "@celldl/editor": "^0.20261109.0", "@celldl/rdf": "^1.20260822.0", "@opencor/libopencor-types": "^1.20261029.0", "@physiomelinks/bg2cellml": "^0.6.2", "@primeuix/themes": "2.0.3", "@primevue/auto-import-resolver": "4.5.5", - "@tailwindcss/postcss": "^4.3.4", - "@tailwindcss/vite": "^4.3.4", + "@tailwindcss/postcss": "^4.3.5", + "@tailwindcss/vite": "^4.3.5", "@types/bun": "^1.4.4", "@types/node": "^26.6.6", "@vitejs/plugin-vue": "^6.0.10", @@ -82,10 +82,10 @@ "postcss": "^8.5.32", "primeicons": "7.0.0", "primevue": "4.5.5", - "rollup-plugin-visualizer": "^7.1.2", + "rollup-plugin-visualizer": "^7.1.3", "stylelint": "^18.0.0", "stylelint-config-standard": "^41.0.0", - "tailwindcss": "^4.3.4", + "tailwindcss": "^4.3.5", "tailwindcss-primeui": "^0.6.1", "tar": "^7.5.22", "typescript": "^7.0.2", diff --git a/src/renderer/scripts/download.libopencor.js.ts b/src/renderer/scripts/download.libopencor.js.ts index eb611a331..db50defc9 100644 --- a/src/renderer/scripts/download.libopencor.js.ts +++ b/src/renderer/scripts/download.libopencor.js.ts @@ -44,5 +44,5 @@ export const downloadLibopencorJsIfNeeded = async (destDir: string): Promise .p-contextmenu-item-content, + .p-contextmenu-item.p-focus > .p-contextmenu-item-content { + .context-menu-item-icon, + .context-menu-item-chevron { + color: var(--p-text-color); + } + } + } + + /** + * Note: the contents of our popovers (e.g., our issues view) have their own padding, so our popovers don't need + * any. + */ + + .p-popover { + --p-popover-border-radius: 0.5rem; + --p-popover-shadow: var(--opencor-overlay-shadow); + --p-popover-content-padding: 0; + } + } +} + +/** + * Our colours in dark mode (see above). + */ + +.opencor-dark-mode .opencor { + --px-mask-background: rgb(0 0 0 / 50%); + --opencor-scrollbar-color: var(--p-surface-600); + --opencor-overlay-shadow: 0 8px 24px -4px rgb(0 0 0 / 50%), 0 2px 6px -2px rgb(0 0 0 / 40%); + --opencor-card-shadow: 0 0.25rem 1rem rgb(0 0 0 / 50%); + --opencor-success-background: color-mix(in sRGB, var(--p-green-500), transparent 84%); + --opencor-success-border-color: color-mix(in sRGB, var(--p-green-700), transparent 64%); + --opencor-success-color: var(--p-green-500); + --opencor-info-background: color-mix(in sRGB, var(--p-blue-500), transparent 84%); + --opencor-info-border-color: color-mix(in sRGB, var(--p-blue-700), transparent 64%); + --opencor-info-color: var(--p-blue-500); + --opencor-warn-background: color-mix(in sRGB, var(--p-yellow-500), transparent 84%); + --opencor-warn-border-color: color-mix(in sRGB, var(--p-yellow-700), transparent 64%); + --opencor-warn-color: var(--p-yellow-500); + --opencor-error-background: color-mix(in sRGB, var(--p-red-500), transparent 84%); + --opencor-error-border-color: color-mix(in sRGB, var(--p-red-700), transparent 64%); + --opencor-error-color: var(--p-red-500); +} + +/** + * A spinner, i.e. a ring that is partly coloured using our primary colour, which size can be changed using + * --opencor-spinner-size. + */ + +.opencor-spinner { + flex-shrink: 0; + width: var(--opencor-spinner-size, 1.125rem); + height: var(--opencor-spinner-size, 1.125rem); + border: 2px solid color-mix(in sRGB, var(--p-primary-color) 20%, transparent); + border-top-color: var(--p-primary-color); + border-radius: 50%; + animation: opencor-spinner-spin 0.8s linear infinite; +} + +@keyframes opencor-spinner-spin { + to { + transform: rotate(360deg); + } +} + +/** + * A section title (e.g., in a dialog), and a small one (e.g., in a side panel). + */ + +.opencor-section-title { + margin: 0; + color: var(--p-text-muted-color); + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.opencor-section-title-small { + font-size: 0.6875rem; +} + +/** + * A panel, i.e. a bordered area with rounded corners. + */ + +.opencor-panel { + border: 1px solid var(--p-content-border-color); + border-radius: 0.5rem; + background-color: var(--p-content-background); +} + +/** + * Our hints (see common/hint.ts), which look like the tooltip of our toolbar buttons (see ToolbarButtonWidget.vue). + * Note: a hint may be long (e.g., the path of a file), in which case it gets wrapped (preferably after a path + * separator, see displayedText() in common/hint.ts). + */ + +.opencor-hint { + position: fixed; + z-index: 100000; + max-width: min(40rem, calc(100vw - 0.5rem)); + padding: 0.25rem 0.5rem; + border: 1px solid var(--opencor-hint-border-color); + border-radius: 0.375rem; + background-color: var(--opencor-hint-background); + box-shadow: var(--opencor-overlay-shadow); + color: var(--opencor-hint-color); + font-size: 0.75rem; + font-weight: 500; + line-height: 1rem; + overflow-wrap: anywhere; + pointer-events: none; + user-select: none; +} + +/** + * Our rich hints, which may contain some paragraphs, some code, or a table (e.g., the values of the simulation inputs + * of a run). + */ + +.opencor-hint-rich { + max-width: min(22rem, calc(100vw - 0.5rem)); + padding: 0.375rem 0.625rem; + font-weight: 400; + line-height: 1.125rem; + + code { + padding: 0 0.1875rem; + border-radius: 0.25rem; + background-color: var(--opencor-hint-code-background); + font-size: 0.6875rem; + } + + table { + border: none; + border-collapse: collapse; + font-size: inherit; + } + + th, + td { + padding: 0.125rem 0; + border: none; + vertical-align: top; + } + + th { + font-weight: 600; + text-align: start; + } + + th + td, + td + td { + padding-left: 0.5rem; } } diff --git a/src/renderer/src/common/celldl.ts b/src/renderer/src/common/celldl.ts index 1c82ac55b..931950941 100644 --- a/src/renderer/src/common/celldl.ts +++ b/src/renderer/src/common/celldl.ts @@ -11,8 +11,8 @@ import * as dependencies from './dependencies'; // Note #2: this relies on the BG-RDF framework, which CellDL Editor only makes available once it has been initialised // (otherwise, bgRdfStatements() returns no statement), hence it must only be called once CellDL Editor has // been loaded. -// Note #3: CellDL Editor comes with its own copy of Oxigraph (bundled from @celldl/rdf), but it doesn't expose it, hence -// we need our own copy (which @physiomelinks/bg2cellml expects as a peer dependency). +// Note #3: CellDL Editor comes with its own copy of Oxigraph (bundled from @celldl/rdf), but it doesn't expose it, +// hence we need our own copy (which @physiomelinks/bg2cellml expects as a peer dependency). let converterInitialisation: Promise | null = null; let converterInitialised = false; diff --git a/src/renderer/src/common/common.ts b/src/renderer/src/common/common.ts index c80846498..b79e9a16d 100644 --- a/src/renderer/src/common/common.ts +++ b/src/renderer/src/common/common.ts @@ -14,13 +14,21 @@ export interface ISettings { general: ISettingsGeneral; } +export interface ISystemDetails { + cpuModel: string; + logicalCoreCount: number; + totalMemory: number; + electronVersion: string; + chromiumVersion: string; +} + // Some methods to determine the operating system, whether the application is running on a mobile device, etc. // Note: the order of the checks in osName() is important. For instance, we need to check for "iPhone" before checking // for "Mac" since the user agent of iPhones contains both "iPhone" and "Mac". let _osName: string | null = null; -const osName = (): string => { +export const osName = (): string => { if (_osName) { return _osName; } @@ -72,6 +80,29 @@ export const isCtrlOrCmd = (event: KeyboardEvent): boolean => { return isMacOs() ? event.metaKey : event.ctrlKey; }; +// A method to determine whether a keydown event is processed by an input method editor (IME), e.g., for Chinese or +// Japanese, in which case it is not for us to handle (e.g., Enter to confirm some text being composed). +// Note: Safari ends a composition before dispatching the keydown event that confirms it, so that event isn't flagged as +// composing, but its key code is 229 (as for any keydown event that is processed by an IME). + +export const isImeKeyDown = (event: KeyboardEvent): boolean => { + return event.isComposing || event.keyCode === 229; +}; + +// Some methods to let OpenCOR own the title of its document (e.g., to show the name of the selected file in it, see +// ContentsComponent.vue), which is only the case when OpenCOR is not used as a library (see main.ts) since the title +// of a page that embeds OpenCOR is not ours to change. + +let _documentTitleOwned = false; + +export const ownDocumentTitle = (): void => { + _documentTitleOwned = true; +}; + +export const isDocumentTitleOwned = (): boolean => { + return _documentTitleOwned; +}; + // A method to enable/disable the main menu. export const enableDisableMainMenu = (enable: boolean): void => { @@ -343,6 +374,49 @@ export const waitForNextAnimationFrame = (): Promise => { }); }; +// A method to wait for our UI to be painted. +// Note #1: we wait for two animation frames since the first one is just before the next paint while the second one is +// after it (i.e. just before the following paint). +// Note #2: there is no need to wait for vue.nextTick() first since Vue updates the DOM in a microtask, i.e. before the +// next animation frame. +// Note #3: no animation frames are run while our document is hidden (e.g., when our window is minimised), in which case +// there is nothing to paint for people to see, so we don't wait (or stop waiting) for our UI to be painted. +// Otherwise, we would wait until our document is visible again (e.g., a simulation run would only finish +// once our window is restored). + +const waitForNextAnimationFrameUnlessHidden = (): Promise => { + return new Promise((resolve) => { + if (document.visibilityState === 'hidden') { + resolve(); + + return; + } + + const onDone = (): void => { + cancelAnimationFrame(frame); + + document.removeEventListener('visibilitychange', onVisibilityChange); + + resolve(); + }; + + const onVisibilityChange = (): void => { + if (document.visibilityState === 'hidden') { + onDone(); + } + }; + + const frame = requestAnimationFrame(onDone); + + document.addEventListener('visibilitychange', onVisibilityChange); + }); +}; + +export const waitForNextPaint = async (): Promise => { + await waitForNextAnimationFrameUnlessHidden(); + await waitForNextAnimationFrameUnlessHidden(); +}; + // Some constants related to simulation data values. export const EMPTY_FLOAT64_ARRAY = Object.freeze(new Float64Array(0)); diff --git a/src/renderer/src/common/constants.ts b/src/renderer/src/common/constants.ts index 3680d12de..24807487e 100644 --- a/src/renderer/src/common/constants.ts +++ b/src/renderer/src/common/constants.ts @@ -7,10 +7,18 @@ export const SHORT_DELAY: number = 69; export const MEDIUM_DELAY: number = 169; export const LONG_DELAY: number = 369; +export const STOP_RUN_TIMEOUT: number = 5000; + export const TOAST_LIFE: number = 3000; +export const COPY_FEEDBACK_LIFE: number = 2000; + export const MAX_RECENT_FILES: number = 10; +export const HINT_DELAY: number = 500; + +export const NARROW_LAYOUT_WIDTH: number = 640; + export const SPIN_INITIAL_DELAY: number = 500; export const SPIN_REPEAT_DELAY: number = 40; export const SPIN_LARGE_STEP_FACTOR: number = 10; diff --git a/src/renderer/src/common/dependencies.ts b/src/renderer/src/common/dependencies.ts index 448464030..b704b8303 100644 --- a/src/renderer/src/common/dependencies.ts +++ b/src/renderer/src/common/dependencies.ts @@ -26,9 +26,9 @@ export const setUPlot = (module: Module): void => { }; // Initialise xxHash. -// Note: this compiles xxHash's WASM, so we only do it when asked to (i.e. by our renderer, see initialisation.ts) rather -// than when this module is imported. Indeed, this module is also (indirectly) imported by our main process, which -// doesn't need xxHash. +// Note: this compiles xxHash's WASM, so we only do it when asked to (i.e. by our renderer, see initialisation.ts) +// rather than when this module is imported. Indeed, this module is also (indirectly) imported by our main +// process, which doesn't need xxHash. let xxhashInitialisation: Promise | null = null; diff --git a/src/renderer/src/common/electronApi.ts b/src/renderer/src/common/electronApi.ts index 8793cdf10..c307f5fec 100644 --- a/src/renderer/src/common/electronApi.ts +++ b/src/renderer/src/common/electronApi.ts @@ -1,6 +1,6 @@ import type { OpenCORTheme } from '../../index'; -import type { ISettings } from './common'; +import type { ISettings, ISystemDetails } from './common'; interface IElectronApi { // Note: this must be in sync with src/preload/index.ts. @@ -8,6 +8,7 @@ interface IElectronApi { // Some general methods. operatingSystem: () => string; + systemDetails: () => ISystemDetails; // Renderer process asking the main process to do something for it. @@ -35,11 +36,13 @@ interface IElectronApi { loadSettings: () => Promise; recentFilePathCanonicalised: (filePath: string, canonicalFilePath: string) => void; rendererReady: () => Promise; + reopenRecentFile: (filePath: string) => void; resetAll: () => void; saveFile: (filePath: string, contents: Uint8Array) => Promise; saveFileAs: (defaultFilePath: string) => Promise; saveGitHubAccessToken: (token: string) => Promise; saveSettings: (settings: ISettings) => void; + showOpenDialog: () => void; // Renderer process listening to the main process. @@ -50,9 +53,10 @@ interface IElectronApi { onCheckForUpdates: (callback: () => void) => () => void; onClearRecentFiles: (callback: () => void) => () => void; onEnableDisableUi: (callback: (enable: boolean) => void) => () => void; - onOpen: (callback: (filePath: string) => void) => () => void; - onOpenRemote: (callback: () => void) => () => void; + onOpenFile: (callback: (filePath: string) => void) => () => void; + onOpenLocation: (callback: () => void) => () => void; onOpenSampleLorenz: (callback: () => void) => () => void; + onRecentFiles: (callback: (filePaths: string[]) => void) => () => void; onClose: (callback: () => void) => () => void; onCloseAll: (callback: () => void) => () => void; onConfirmClose: (callback: () => void) => () => void; @@ -66,7 +70,8 @@ interface IElectronApi { onUpdateDownloaded: (callback: () => void) => () => void; onUpdateDownloadError: (callback: (issue: string) => void) => () => void; onUpdateInstallError: (callback: (issue: string) => void) => () => void; - onUpdateDownloadProgress: (callback: (percent: number) => void) => () => void; + onUpdateInstalling: (callback: () => void) => () => void; + onUpdateDownloadProgress: (callback: (percent: number, transferred: number, total: number) => void) => () => void; onUpdateNotAvailable: (callback: () => void) => () => void; } diff --git a/src/renderer/src/common/hint.ts b/src/renderer/src/common/hint.ts new file mode 100644 index 000000000..c9c56f932 --- /dev/null +++ b/src/renderer/src/common/hint.ts @@ -0,0 +1,529 @@ +import type * as vue from 'vue'; + +import { HINT_DELAY } from './constants'; + +// A directive that shows a hint for an element (e.g., v-hint="'Close'"), i.e. a replacement for the native tooltip of +// an element (which we can't style and which is shown after a long delay), which looks like the tooltip of our toolbar +// buttons (see ToolbarButtonWidget.vue). +// Note #1: our hint is shown, without any animation, after a short delay when our element is hovered, and straightaway +// when our element (or an element within it, see Note #7) gets a focus ring (i.e. when it is focused using the +// keyboard or, for a text field, when it is focused in any way). It is hidden as soon as our element is left +// (unless it has a focus ring, so that our hint doesn't disappear while our element is focused, e.g., some +// help about an input field while the user is typing something in it), pressed (using a pointing device), +// blurred, or hidden (see Note #9), or Escape is pressed (see Note #10), and it isn't shown if our element is +// no longer visible by the time it is to be shown. +// Note #2: our hint is shown below our element (or above it if there is not enough room below it) and is kept within +// the window. It has a fixed position and is appended to our OpenCOR instance (rather than to our element) so +// that it doesn't get clipped by an ancestor of our element (e.g., a scroll panel), while still being styled +// like the rest of our OpenCOR instance (see .opencor-hint in app.css). +// Note #3: only one hint is shown at any given time. +// Note #4: like a native tooltip, our hint isn't shown when hovering an element that ignores pointer events (e.g., the +// reset button of one of our input widgets when it is disabled), although it is still shown if that element +// gets a focus ring. +// Note #5: our hint can contain some HTML (e.g., v-hint.html="'Some help'"), in which case it is a "rich" hint +// (e.g., some help about an input field or a table of values). A rich hint wraps sooner (to keep its lines +// easy to read) and, unlike a plain hint, it isn't hidden when our element is pressed (so that, for instance, +// some help about an input field doesn't disappear as soon as the user clicks in it to type something). The +// HTML of a rich hint is used as is, so any untrusted text in it (e.g., the name of a simulation input) must +// be escaped (see common.escapeHtml()). +// Note #6: our element may contain an element that has its own hint (e.g., the close button of a file tab), in which +// case the hint of the inner element replaces ours while it is hovered, and ours is shown again (after the +// usual delay) when the inner element is left for our element. Similarly, only the hint of the inner element +// is shown when it (or an element within it) is focused using the keyboard. +// Note #7: we listen to focusin/focusout (rather than focus/blur, which don't bubble) so that our hint is also shown +// when our element contains the focused element (e.g., a float label that contains an input field). +// Note #8: our hint is hidden from assistive technologies (since it is a visual aid that isn't linked to our element), +// so a plain hint is also used as the accessible name of our element if our element has no accessible name of +// its own (e.g., an icon-only button), i.e. no text, no aria-label, aria-labelledby, title, or alt attribute, +// and no label (e.g., an input field with a