diff --git a/declarations.d.ts b/declarations.d.ts index 591b6b3..5b519be 100644 --- a/declarations.d.ts +++ b/declarations.d.ts @@ -1,4 +1,4 @@ -declare module '*.css' { - const classes: Record; - export default classes; -} +/// + +declare module '*.css'; +declare module '~icons/*'; diff --git a/package-lock.json b/package-lock.json index 98de1af..f387614 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,19 @@ { "name": "folder-pane", - "version": "4.0.0-0", + "version": "4.0.0-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "folder-pane", - "version": "4.0.0-0", + "version": "4.0.0-1", "license": "MIT", "dependencies": { - "rdflib": "2.4.0", - "solid-logic": "6.0.0-0", - "solid-ui": "5.0.0-0", + "@lit/context": "^1.1.6", + "pane-registry": "5.0.0-1", + "rdflib": "2.4.1", + "solid-logic": "6.0.0-2", + "solid-ui": "5.0.0-2", "vite": "^8.0.16", "vitest": "^4.0.18", "vitest-axe": "^0.1.0" @@ -19,6 +21,7 @@ "devDependencies": { "@eslint/eslintrc": "^3.3.3", "@eslint/js": "^10.0.1", + "@iconify-json/lucide": "^1.2.116", "@types/node": "^24.13.3", "@typescript-eslint/parser": "^8.53.1", "@vitest/coverage-v8": "^4.0.18", @@ -43,9 +46,9 @@ } }, "node_modules/@awesome.me/webawesome": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/@awesome.me/webawesome/-/webawesome-3.12.0.tgz", - "integrity": "sha512-cO/vSvlyZqiki1upynxpk9q2N48U2+nflIj40OtCKYOs6ZXa4zXhW6+asXKOfeNw1AnD4nzsSIdZSt/Ti9QONA==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@awesome.me/webawesome/-/webawesome-3.13.0.tgz", + "integrity": "sha512-egDzZAHCMj31xaYW2AFhwxl5XMj4lAFGxh9Q0WMQjRHdC6sy45JofA8ojGl28LuztDhrjfmz6tP5KG4anpjT8w==", "license": "MIT", "dependencies": { "@ctrl/tinycolor": "4.1.0", @@ -479,9 +482,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", - "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -1544,9 +1547,9 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", - "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.9.tgz", + "integrity": "sha512-FFwIwzU+7SCOuxxV4YtJql6T9981ZVTm+FHO5GhVsRqCTdy0WwrEZh3l42ARpXruJUDGOptdduHW6Zr7pNPLNg==", "dev": true, "license": "MIT", "dependencies": { @@ -1842,6 +1845,30 @@ "node": ">=18" } }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, "node_modules/@codemirror/autocomplete": { "version": "6.20.3", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz", @@ -1855,9 +1882,9 @@ } }, "node_modules/@codemirror/commands": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.0.tgz", - "integrity": "sha512-/K4Rl5BN0OtTiPWmJCdqODu38XnDMsDxKY5rgrPnCkutPTJf2wVbkoixLfealF5Kwse/s8P8M5jAiURiwSwnFA==", + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.1.tgz", + "integrity": "sha512-O/4hG3SC1YwcmQ0d2UVNDs+AsaNWd1iHVxbTeEBuqH+6bExAiPK3iS/BvpY6rZGURALv4ZD3sIgcCmRvw3ehBg==", "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", @@ -1965,9 +1992,9 @@ } }, "node_modules/@codemirror/legacy-modes": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.3.tgz", - "integrity": "sha512-xCsmIzH78MyWkib9jlPaaun57XNkfbMIhagfaZVd0iLTqlpw3jXaIcbZm72MTmmn64eTZpBVNjbyYh+QXnxRsg==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.4.tgz", + "integrity": "sha512-/cZr6qZyl08iYNLGsJ862CXXNI51LryRFRE40ejgoIjXZz0C1rGkD3/Ek5jM/8w1ceRjqtt4qx/KLMh4zBTgew==", "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0" @@ -1985,18 +2012,18 @@ } }, "node_modules/@codemirror/state": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.1.tgz", - "integrity": "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==", + "version": "6.7.6", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.6.tgz", + "integrity": "sha512-kAz+AncRtKuIknedxT1bq4XwXv4UowhbkHU1myPrtVb/jZtImWuV5BXzv5vK6i3kYACsdiZiQKFQQ5Mq7elW8w==", "license": "MIT", "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "node_modules/@codemirror/view": { - "version": "6.43.9", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.9.tgz", - "integrity": "sha512-sTuUzTpPMFebRhg6dawChoKKgndIwfjmJgKVxBefPElcU2NwQ6AFroupk0SFqEerQyZOGRfDNnSN8Dw/lMAsXw==", + "version": "6.43.13", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.13.tgz", + "integrity": "sha512-sihaFrUzAsYBQsL9J2t69y8nfMQGwcYmggAZsk+kjPbjYZMyuf2hU8tUNTZ+P+isb6XRr8JE22TZlJxBoVdH1A==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.7.0", @@ -2053,9 +2080,9 @@ } }, "node_modules/@csstools/css-tokenizer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", - "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.1.tgz", + "integrity": "sha512-bPlN9S9O1A0euCpEWE4qnvB5YDuyYVsUTrxSgmAM1Is0j4tICHoVyOVAXfWMP/kS9ZrjvyIXWV2PmomiAXXqOw==", "dev": true, "funding": [ { @@ -2186,9 +2213,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2241,9 +2268,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", - "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.7.tgz", + "integrity": "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==", "dev": true, "license": "MIT", "dependencies": { @@ -2253,7 +2280,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.3.0", + "js-yaml": "^4.3.2", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -2309,9 +2336,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", - "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2422,6 +2449,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@iconify-json/lucide": { + "version": "1.2.136", + "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.136.tgz", + "integrity": "sha512-Wk+OeDVKkw+1LELZqRR3gXWIy5jNx5XjNMvVc9KoygJYmGqclv36FYqseRuJuxBIKDggdoZWNBxNRrOkiRbtwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@iconify/types": "*" + } + }, "node_modules/@iconify/types": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", @@ -2430,17 +2467,31 @@ "license": "MIT" }, "node_modules/@iconify/utils": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.4.tgz", - "integrity": "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.7.tgz", + "integrity": "sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w==", "dev": true, "license": "MIT", "dependencies": { - "@antfu/install-pkg": "^1.1.0", + "@antfu/install-pkg": "^2.0.1", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, + "node_modules/@iconify/utils/node_modules/@antfu/install-pkg": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-2.1.0.tgz", + "integrity": "sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.8.0", + "tinyexec": "^1.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -2474,9 +2525,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -2490,6 +2541,30 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@konnorr/qr-creator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@konnorr/qr-creator/-/qr-creator-1.1.0.tgz", @@ -2503,9 +2578,9 @@ "license": "MIT" }, "node_modules/@lezer/css": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.6.tgz", - "integrity": "sha512-YJE78Wcg+zX8f10hiHWQ4Az48Qr/c13eId0VtRQYLBpxHDmDeSrXIlkbl+fJGW42rWC/uoUco9mhBZeVWP/A1g==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.8.tgz", + "integrity": "sha512-EJn1zcL9qoDptief6ipWKZKLiOpXkxSe0+t8CH9oiMVcZlq7NBWrjCqnc/41EIjeo/ITj1gFFiATdTkaJDL+Og==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -2534,9 +2609,9 @@ } }, "node_modules/@lezer/javascript": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", - "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.5.tgz", + "integrity": "sha512-sWg4yX1J6XW67AaAynVt0iwF0M5c+np36TEu+P2ifAJ8haRYvHnWDV28r1jdwnJehWCwXECutAUy56K4RBZIyg==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -2692,12 +2767,12 @@ "license": "MIT" }, "node_modules/@noble/curves": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.3.0.tgz", - "integrity": "sha512-v7cY+4oWYPQszRj6ZFGzTVL7uP2TaLo1xMhWHzYC5wj0ZhOXQ5x+sBre8rF3hi8cAoi0bh1qXoovoOkdFtvqEg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.4.0.tgz", + "integrity": "sha512-P4/62zrgfH33CneE3Dn4WhJVA22YUU0eR51wKIan4NVRvwsA0YnPTwWGpNbpuacSujmSFLvyzpyuR30+fbq2Ew==", "license": "MIT", "dependencies": { - "@noble/hashes": "2.3.0" + "@noble/hashes": "2.4.0" }, "engines": { "node": ">= 20.19.0" @@ -2707,9 +2782,9 @@ } }, "node_modules/@noble/hashes": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz", - "integrity": "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -2719,12 +2794,12 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.146.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz", - "integrity": "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==", + "version": "0.150.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.150.0.tgz", + "integrity": "sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==", "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@parse5/tools": { @@ -2746,9 +2821,9 @@ } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.5.tgz", - "integrity": "sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.9.tgz", + "integrity": "sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==", "cpu": [ "arm" ], @@ -2762,9 +2837,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.5.tgz", - "integrity": "sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.9.tgz", + "integrity": "sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==", "cpu": [ "arm64" ], @@ -2778,9 +2853,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.5.tgz", - "integrity": "sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.9.tgz", + "integrity": "sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==", "cpu": [ "arm64" ], @@ -2794,9 +2869,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.5.tgz", - "integrity": "sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.9.tgz", + "integrity": "sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==", "cpu": [ "x64" ], @@ -2810,9 +2885,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.5.tgz", - "integrity": "sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.9.tgz", + "integrity": "sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==", "cpu": [ "x64" ], @@ -2826,9 +2901,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.5.tgz", - "integrity": "sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.9.tgz", + "integrity": "sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==", "cpu": [ "arm" ], @@ -2842,12 +2917,15 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.5.tgz", - "integrity": "sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.9.tgz", + "integrity": "sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2858,12 +2936,15 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.5.tgz", - "integrity": "sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.9.tgz", + "integrity": "sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2874,12 +2955,15 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.5.tgz", - "integrity": "sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.9.tgz", + "integrity": "sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==", "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2890,12 +2974,15 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.5.tgz", - "integrity": "sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.9.tgz", + "integrity": "sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==", "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2906,12 +2993,15 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.5.tgz", - "integrity": "sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.9.tgz", + "integrity": "sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2922,12 +3012,15 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.5.tgz", - "integrity": "sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.9.tgz", + "integrity": "sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2938,9 +3031,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.5.tgz", - "integrity": "sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.9.tgz", + "integrity": "sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==", "cpu": [ "arm64" ], @@ -2954,9 +3047,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.5.tgz", - "integrity": "sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.9.tgz", + "integrity": "sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==", "cpu": [ "arm64" ], @@ -2970,9 +3063,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.5.tgz", - "integrity": "sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.9.tgz", + "integrity": "sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==", "cpu": [ "x64" ], @@ -2986,13 +3079,13 @@ } }, "node_modules/@rolldown/plugin-babel": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/plugin-babel/-/plugin-babel-0.2.3.tgz", - "integrity": "sha512-+zEk16yGlz1F9STiRr6uG9hmIXb6nprjLczV/htGptYuLoCuxb+itZ03RKCEeOhBpDDd1NU7qF6x1VLMUp62bw==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/plugin-babel/-/plugin-babel-0.2.4.tgz", + "integrity": "sha512-ygmm2j/mN+aCvstHo0x3IOu4uc6LqXT9SJ3q4kpfG/+Yhfz0B+E47FITcC0FljZADwUFARP6E2kiE+Lc26D8wg==", "dev": true, "license": "MIT", "dependencies": { - "picomatch": "^4.0.4" + "picomatch": "^4.0.7" }, "engines": { "node": ">=22.12.0 || ^24.0.0" @@ -3111,18 +3204,18 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.13.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", - "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "version": "24.13.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.6.tgz", + "integrity": "sha512-SGrw/h3KPFshy3OE6ZL53LMBG5vGQQ8/gIpiqz/kRZhPJ7HgwCEs8LBuNtWLa8dvGZVpSF7+Bf+c11HUrCb/yg==", "license": "MIT", "dependencies": { "undici-types": "~7.18.0" } }, "node_modules/@types/react": { - "version": "19.2.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", - "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", "license": "MIT", "peer": true, "dependencies": { @@ -3136,16 +3229,16 @@ "license": "MIT" }, "node_modules/@typescript-eslint/parser": { - "version": "8.68.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.68.0.tgz", - "integrity": "sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.70.1.tgz", + "integrity": "sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.68.0", - "@typescript-eslint/types": "8.68.0", - "@typescript-eslint/typescript-estree": "8.68.0", - "@typescript-eslint/visitor-keys": "8.68.0", + "@typescript-eslint/scope-manager": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", "debug": "^4.4.3" }, "engines": { @@ -3161,14 +3254,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.68.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.68.0.tgz", - "integrity": "sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.1.tgz", + "integrity": "sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.68.0", - "@typescript-eslint/types": "^8.68.0", + "@typescript-eslint/tsconfig-utils": "^8.70.1", + "@typescript-eslint/types": "^8.70.1", "debug": "^4.4.3" }, "engines": { @@ -3183,14 +3276,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.68.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.68.0.tgz", - "integrity": "sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.70.1.tgz", + "integrity": "sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.68.0", - "@typescript-eslint/visitor-keys": "8.68.0" + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3201,9 +3294,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.68.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.68.0.tgz", - "integrity": "sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.1.tgz", + "integrity": "sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==", "dev": true, "license": "MIT", "engines": { @@ -3218,9 +3311,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.68.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.68.0.tgz", - "integrity": "sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.70.1.tgz", + "integrity": "sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==", "dev": true, "license": "MIT", "engines": { @@ -3232,16 +3325,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.68.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.68.0.tgz", - "integrity": "sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.1.tgz", + "integrity": "sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.68.0", - "@typescript-eslint/tsconfig-utils": "8.68.0", - "@typescript-eslint/types": "8.68.0", - "@typescript-eslint/visitor-keys": "8.68.0", + "@typescript-eslint/project-service": "8.70.1", + "@typescript-eslint/tsconfig-utils": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -3270,9 +3363,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3299,13 +3392,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.68.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.68.0.tgz", - "integrity": "sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.1.tgz", + "integrity": "sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/types": "8.70.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3543,15 +3636,6 @@ "node": ">=6.5" } }, - "node_modules/abs": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/abs/-/abs-1.3.15.tgz", - "integrity": "sha512-bpFChpVyZ2F2ppgx7qjZ5TTEO6VVwBauUZDZibpclRGhfcXTHyj11nlqwrg5dN1knxCchssROehm76uCcCayRA==", - "license": "MIT", - "dependencies": { - "ul": "^5.0.0" - } - }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", @@ -3618,9 +3702,9 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz", - "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.7.tgz", + "integrity": "sha512-kFL68AG6ajd8fg248zwM9GQrUWEp79gsmjum34OEXjs4yHuUMZfYKwOLW9GMmB4oNvVrj+EAGxsP7ye2UR9UlA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -3718,9 +3802,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.11.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.19.tgz", - "integrity": "sha512-Grytf1xOxOEMTGRwx6rLGKkTabd4vMg3VrKdj/7joCmV0qgh4QwMMO6xh34YEXQqirAuUdgQGa5orJQQ+69RBw==", + "version": "2.11.25", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.25.tgz", + "integrity": "sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3731,9 +3815,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -3742,9 +3826,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", - "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "version": "4.29.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz", + "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==", "dev": true, "funding": [ { @@ -3762,11 +3846,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.11.12", - "caniuse-lite": "^1.0.30001809", - "electron-to-chromium": "^1.5.402", - "node-releases": "^2.0.53", - "update-browserslist-db": "^1.3.0" + "baseline-browser-mapping": "^2.11.23", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.427", + "node-releases": "^2.0.55", + "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" @@ -3799,6 +3883,20 @@ "ieee754": "^1.2.1" } }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3839,18 +3937,6 @@ "canonicalize": "bin/canonicalize.js" } }, - "node_modules/capture-stack-trace": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.2.tgz", - "integrity": "sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -3896,9 +3982,9 @@ "license": "MIT" }, "node_modules/confbox": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", - "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", + "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", "dev": true, "license": "MIT" }, @@ -3925,24 +4011,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw==", - "license": "MIT", - "dependencies": { - "capture-stack-trace": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/crelt": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", @@ -4007,15 +4075,6 @@ } } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4023,15 +4082,6 @@ "dev": true, "license": "MIT" }, - "node_modules/deffy": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/deffy/-/deffy-2.2.5.tgz", - "integrity": "sha512-6TX2cfIo97eKqWmqgMDAUulCwnveAe3K+4VGsTGPJsL3NtSEnSBFZ3sUXdS4EBhZ8GbdaZBzXQ04ton18dJrug==", - "license": "MIT", - "dependencies": { - "typpy": "^2.0.0" - } - }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -4047,56 +4097,17 @@ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "license": "MIT" }, - "node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", - "license": "BSD-3-Clause", - "dependencies": { - "readable-stream": "^2.0.2" - } - }, - "node_modules/duplexer2/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/duplexer2/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/duplexer2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/electron-to-chromium": { - "version": "1.5.415", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.415.tgz", - "integrity": "sha512-958V+Kbhtgz+SxXeEVKBjrlKRBIDAYvUJfwhjxMZ5S6ut9jAl7l9ZKBkBrvjyjZE36PabLUo2L8kEeV5O4vgJg==", + "version": "1.5.436", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.436.tgz", + "integrity": "sha512-iG79/xCF3iThADicbgbES2+liFYmTvelo7jWcEfg6lQ+Adrh7zShrXSg2ZdEDLbKjJ298kGAKMnnzVWobp0L+g==", "dev": true, "license": "ISC" }, "node_modules/enhanced-resolve": { - "version": "5.24.5", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", - "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "version": "5.25.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.25.1.tgz", + "integrity": "sha512-nGXts5znJzmWPu+mIE9izCOzdg63oJca2mDzGWWTth7sr4aCToKcoyFVBQwN75Ij5Pf6p510EwkTqViTRzDV+w==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -4118,28 +4129,11 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/err": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/err/-/err-1.1.1.tgz", - "integrity": "sha512-N97Ybd2jJHVQ+Ft3Q5+C2gM3kgygkdeQmEqbN2z15UTVyyEsIwLA1VK39O1DHEJhXbwIFcJLqm6iARNhFANcQA==", - "license": "MIT", - "dependencies": { - "typpy": "^2.2.0" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4181,9 +4175,9 @@ } }, "node_modules/eslint": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.9.1.tgz", - "integrity": "sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A==", + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.11.0.tgz", + "integrity": "sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -4195,7 +4189,7 @@ "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.2", + "@eslint/plugin-kit": "^0.7.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -4210,7 +4204,7 @@ "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", + "file-entry-cache": "11.1.5 || >11.1.6 <12", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", @@ -4282,9 +4276,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4432,16 +4426,6 @@ "node": ">=0.8.x" } }, - "node_modules/exec-limiter": { - "version": "3.2.14", - "resolved": "https://registry.npmjs.org/exec-limiter/-/exec-limiter-3.2.14.tgz", - "integrity": "sha512-ZQjJmAnXD+1kQ6ejMZAS5Vxdt7LLMz0Eq7mEu6+7NhlauykuyLihhUkpp4S784QKsmJQIpuuERhQ8Tav8bF3zQ==", - "license": "MIT", - "dependencies": { - "limit-it": "^3.0.0", - "typpy": "^2.1.0" - } - }, "node_modules/expect-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", @@ -4520,16 +4504,13 @@ } }, "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" + "flat-cache": "^6.1.23" } }, "node_modules/find-up": { @@ -4550,17 +4531,15 @@ } }, "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", "dev": true, "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" } }, "node_modules/flatted": { @@ -4600,20 +4579,12 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function.name": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/function.name/-/function.name-1.0.14.tgz", - "integrity": "sha512-s99L814NRuLxwF2sJMIcLhkQhueGXb3oKyvorzrUKKwlVB0SBbWrgZt4+EwKAo3ujCXnT7vshmCvXgZA09kCMw==", - "license": "MIT", - "dependencies": { - "noop6": "^1.0.1" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -4624,51 +4595,6 @@ "node": ">=6.9.0" } }, - "node_modules/git-package-json": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/git-package-json/-/git-package-json-1.4.11.tgz", - "integrity": "sha512-A/P5K2qqQ52+BwBf+qyrjtdauMlb7n1WVa++/VPDxTcgKZ2X5/Eh/EQwbxNvRKBsKAkMAeyV/UIdnb/saVFnnQ==", - "license": "MIT", - "dependencies": { - "deffy": "^2.2.1", - "err": "^1.1.1", - "gry": "^5.0.0", - "normalize-package-data": "^2.3.5", - "oargv": "^3.4.1", - "one-by-one": "^3.1.0", - "r-json": "^1.2.1", - "r-package-json": "^1.0.0", - "tmp": "0.0.28" - } - }, - "node_modules/git-source": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/git-source/-/git-source-1.1.11.tgz", - "integrity": "sha512-oubUf/uply9xvR5olZxxPpip19wMEpESN3bFfPcFMvl/0fwrVrcAppwOJ7Dghcguze68WAIjs/A1YrdMDIW8XA==", - "license": "MIT", - "dependencies": { - "git-url-parse": "^5.0.1" - } - }, - "node_modules/git-up": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz", - "integrity": "sha512-SRVN3rOLACva8imc7BFrB6ts5iISWKH1/h/1Z+JZYoUI7UVQM7gQqk4M2yxUENbq2jUUT09NEND5xwP1i7Ktlw==", - "license": "MIT", - "dependencies": { - "is-ssh": "^1.0.0", - "parse-url": "^1.0.0" - } - }, - "node_modules/git-url-parse": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz", - "integrity": "sha512-4uSiOgrryNEMBX+gTWogenYRUh2j1D+95STTSEF2RCTgLkfJikl8c7BGr0Bn274hwuxTsbS2/FQ5pVS9FoXegQ==", - "license": "MIT", - "dependencies": { - "git-up": "^1.0.0" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4682,10 +4608,17 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/globals": { - "version": "17.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz", - "integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==", + "version": "17.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz", + "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==", "dev": true, "license": "MIT", "engines": { @@ -4695,81 +4628,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/got": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-5.6.0.tgz", - "integrity": "sha512-MnypzkaW8dldA8AbJFjMs7y14+ykd2V8JCLKSvX1Gmzx1alH3Y+3LArywHDoAF2wS3pnZp4gacoYtvqBeF6drQ==", - "license": "MIT", - "dependencies": { - "create-error-class": "^3.0.1", - "duplexer2": "^0.1.4", - "is-plain-obj": "^1.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "node-status-codes": "^1.0.0", - "object-assign": "^4.0.1", - "parse-json": "^2.1.0", - "pinkie-promise": "^2.0.0", - "read-all-stream": "^3.0.0", - "readable-stream": "^2.0.5", - "timed-out": "^2.0.0", - "unzip-response": "^1.0.0", - "url-parse-lax": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/got/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/got/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/got/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "node_modules/gry": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/gry/-/gry-5.0.8.tgz", - "integrity": "sha512-meq9ZjYVpLzZh3ojhTg7IMad9grGsx6rUUKHLqPnhLXzJkRQvEL2U3tQpS5/WentYTtHtxkT3Ew/mb10D6F6/g==", - "license": "MIT", - "dependencies": { - "abs": "^1.2.1", - "exec-limiter": "^3.0.0", - "one-by-one": "^3.0.0", - "ul": "^5.0.0" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -4780,10 +4644,24 @@ "node": ">=8" } }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4792,11 +4670,12 @@ "node": ">= 0.4" } }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "license": "ISC" + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" }, "node_modules/html-escaper": { "version": "2.0.2", @@ -4882,31 +4761,14 @@ "node": ">=8" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, "node_modules/is-core-module": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", - "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz", + "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==", + "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.3" + "hasown": "^2.0.4" }, "engines": { "node": ">= 0.4" @@ -4938,57 +4800,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-ssh": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.1.tgz", - "integrity": "sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==", - "license": "MIT", - "dependencies": { - "protocols": "^2.0.1" - } - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5035,12 +4846,6 @@ "node": ">=8" } }, - "node_modules/iterate-object": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/iterate-object/-/iterate-object-1.3.5.tgz", - "integrity": "sha512-eL23u8oFooYTq6TtJKjp2RYjZnCkUYQvC0T/6fJfWykXJ3quvdDdzKZ3CEjy8b3JGOvLTjDYMEMIp5243R906A==", - "license": "MIT" - }, "node_modules/jose": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", @@ -5058,9 +4863,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ { @@ -5093,13 +4898,6 @@ "node": ">=6" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -5143,13 +4941,13 @@ } }, "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "dev": true, "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" + "@keyv/serialize": "^1.1.1" } }, "node_modules/kolorist": { @@ -5321,6 +5119,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5341,6 +5142,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5361,6 +5165,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5381,6 +5188,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5434,15 +5244,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/limit-it": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/limit-it/-/limit-it-3.2.11.tgz", - "integrity": "sha512-VdLa1lZYZnzT98oLMeCDl6Lwd9cEYIMQlPg34qL6CYuA+yQKoG7K12tfgI5K6bRC51kRM8v1UX67IhpNsnvo3A==", - "license": "MIT", - "dependencies": { - "typpy": "^2.0.0" - } - }, "node_modules/lit": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.3.tgz", @@ -5521,15 +5322,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -5552,9 +5344,9 @@ } }, "node_modules/magicast": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", - "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.5.tgz", + "integrity": "sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -5638,15 +5430,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/mlly": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", @@ -5687,9 +5470,9 @@ "license": "MIT" }, "node_modules/n3": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/n3/-/n3-2.6.0.tgz", - "integrity": "sha512-iG8yKHcAZNxI+ModlltpLXncdzdgNZWFqA7Hux/p9VSPqyiK2ET5c7Q9+aka/6nWuuamapFw8L4xN4ZFx9KBqQ==", + "version": "2.7.12", + "resolved": "https://registry.npmjs.org/n3/-/n3-2.7.12.tgz", + "integrity": "sha512-Hy6dfGg9yniLQpSBirvH2E2yO3EG7dSmA3aefRbtO411oqtzG8roD3h1kTc/N065bivCOPfTFIjf0KlwW7KlnQ==", "license": "MIT", "dependencies": { "buffer": "^6.0.3", @@ -5765,83 +5548,19 @@ } }, "node_modules/node-releases": { - "version": "2.0.53", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", - "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "version": "2.0.56", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.56.tgz", + "integrity": "sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, - "node_modules/node-status-codes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", - "integrity": "sha512-1cBMgRxdMWE8KeWCqk2RIOrvUb0XCwYfEsY5/y2NlXyq4Y/RumnOZvTj4Nbr77+Vb2C+kyBoRTdkNOS8L3d/aQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/noop6": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/noop6/-/noop6-1.0.10.tgz", - "integrity": "sha512-WZvuCILZFZHK+WuqCQwxLBGllkBK1ct8s8Mu9FMDbEsBE6/bqNxyFGbX7Xky+6bYFL8X2Ou4Cis4CJyrwXLvQA==", - "license": "MIT" - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/oargv": { - "version": "3.4.11", - "resolved": "https://registry.npmjs.org/oargv/-/oargv-3.4.11.tgz", - "integrity": "sha512-FGTon9C71936EnOjx/NTsMxlLeWmw8zQQld4KDmgRxRtZ8fH1XpbLLRHmOioeZs/WoURz2OGR4KmDoTaL4ErJQ==", - "license": "MIT", - "dependencies": { - "iterate-object": "^1.1.0", - "ul": "^5.0.0" - } - }, - "node_modules/obj-def": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/obj-def/-/obj-def-1.0.10.tgz", - "integrity": "sha512-RJpNUkO+1r/rXTBs82iU4scoC9Q1yp9HZbSk0ldpFe8362S6eTjUjSgTmECa1TtOBIe5pn4pwSzxIiWc8+jmWg==", - "license": "MIT", - "dependencies": { - "deffy": "^2.2.2" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/obug": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", - "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", "funding": [ "https://github.com/sponsors/sxzz", "https://opencollective.com/debug" @@ -5851,16 +5570,6 @@ "node": ">=12.20.0" } }, - "node_modules/one-by-one": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/one-by-one/-/one-by-one-3.2.9.tgz", - "integrity": "sha512-H10TAq02LKrkSRTQz1mgvcKb64rRajZ+B5HWHBvkGigYNCPqL0Q/tLIN3vfha/DqZxXeKNfyCmgfEYo2hgFQgA==", - "license": "MIT", - "dependencies": { - "obj-def": "^1.0.0", - "sliced": "^1.0.1" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5879,15 +5588,6 @@ "node": ">= 0.8.0" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -5920,44 +5620,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-json": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz", - "integrity": "sha512-PRg65iXMTt/uK8Rfh5zvzkUbfAPitF17YaCY+IbHsYgksiLvtzWWTUildHth3mVaZ7871OJ7gtP4LBRBlmAdXg==", - "license": "MIT", - "dependencies": { - "got": "^5.0.0", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/package-json-path": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/package-json-path/-/package-json-path-1.0.10.tgz", - "integrity": "sha512-DOlmVIfx+qDHHWaaxg573brZ8mH0Nxo4ecYA4SKkrpCOhCP64NXk7VxJtWVKZQ9urfU2Ivl74HeYUO42PLCpLw==", - "license": "MIT", - "dependencies": { - "abs": "^1.2.1" - } - }, - "node_modules/package-json/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/package-lock.json": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-lock.json/-/package-lock.json-1.0.0.tgz", - "integrity": "sha512-+yEXtNdlCs5N0Zy/9uvkifgf/RqnGu0WqP4j9Wu1Us4YReFe1YNBh2Krmf8B1xGxjpYnta63K55QP8bkafnOzA==" - }, "node_modules/package-manager-detector": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", @@ -5965,26 +5627,14 @@ "dev": true, "license": "MIT" }, - "node_modules/package.json": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz", - "integrity": "sha512-pSxZ6XR5yEawRN2ekxx9IKgPN5uNAYco7MCPxtBEWMKO3UKWa1X2CtQMzMgloeGj2g2o6cue3Sb5iPkByIJqlw==", - "deprecated": "Use pkg.json instead.", - "license": "MIT", - "dependencies": { - "git-package-json": "^1.4.0", - "git-source": "^1.1.0", - "package-json": "^2.3.1" - } - }, "node_modules/pane-registry": { - "version": "5.0.0-0", - "resolved": "https://registry.npmjs.org/pane-registry/-/pane-registry-5.0.0-0.tgz", - "integrity": "sha512-Yb+qcM4fIDFHxF9CM9PkhIAR1BteaRUCUpmoPOZyGVkqAs4pRbpKsh1QKr0r+ibfTnNUZZ2uBfybg1QUVlmlLA==", + "version": "5.0.0-1", + "resolved": "https://registry.npmjs.org/pane-registry/-/pane-registry-5.0.0-1.tgz", + "integrity": "sha512-9gZMjpFjdiCsd+XGIjx2+22S6qMJtwC6v+0r76zG2WPMByNejLlIrUitaYK2s2NxmWmcq8AY/TrOD4nyfKcgyA==", "license": "MIT", "dependencies": { - "rdflib": "2.4.0", - "solid-logic": "6.0.0-0" + "rdflib": "2.4.1", + "solid-logic": "6.0.0-2" } }, "node_modules/parent-module": { @@ -6000,34 +5650,6 @@ "node": ">=6" } }, - "node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "license": "MIT", - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-url": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-1.3.11.tgz", - "integrity": "sha512-1wj9nkgH/5EboDxLwaTMGJh3oH3f+Gue+aGdh631oCqoSBpokzmMmOldvOeBPtB8GJBYJbaF93KPzlkU+Y1ksg==", - "license": "MIT", - "dependencies": { - "is-ssh": "^1.3.0", - "protocols": "^1.4.0" - } - }, - "node_modules/parse-url/node_modules/protocols": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz", - "integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==", - "license": "MIT" - }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -6071,6 +5693,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, "license": "MIT" }, "node_modules/pathe": { @@ -6097,43 +5720,22 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "license": "MIT", - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pkg-types": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", - "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.3.tgz", + "integrity": "sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w==", "dev": true, "license": "MIT", "dependencies": { - "confbox": "^0.2.4", - "exsolve": "^1.0.8", + "confbox": "^0.3.1", + "exsolve": "^1.1.1", "pathe": "^2.0.3" } }, "node_modules/postcss": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "funding": [ { "type": "opencollective", @@ -6150,7 +5752,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.17", + "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -6188,9 +5790,9 @@ } }, "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.18", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", - "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", "funding": [ { "type": "github", @@ -6215,15 +5817,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -6233,18 +5826,6 @@ "node": ">= 0.6.0" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/protocols": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz", - "integrity": "sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==", - "license": "MIT" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6255,6 +5836,26 @@ "node": ">=6" } }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" + }, "node_modules/quansync": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", @@ -6272,49 +5873,6 @@ ], "license": "MIT" }, - "node_modules/r-json": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/r-json/-/r-json-1.3.1.tgz", - "integrity": "sha512-5nhRFfjVMQdrwKUfUlRpDUCocdKtjSnYZ1R/86mpZDV3MfsZ3dYYNjSGuMX+mPBvFvQBhdzxSqxkuLPLv4uFGg==", - "license": "MIT", - "dependencies": { - "w-json": "1.3.10" - } - }, - "node_modules/r-package-json": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/r-package-json/-/r-package-json-1.0.10.tgz", - "integrity": "sha512-g+KLu+aq3tkhW6gzjsfdWAyd+ZkueLTzkX2zpB2GIW7M/lOXal3nB8U36XOrIBGogJsz2H//xWA4mj9uGlcigw==", - "license": "MIT", - "dependencies": { - "package-json-path": "^1.0.0", - "r-json": "^1.2.1" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/rdf-canonize": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/rdf-canonize/-/rdf-canonize-5.0.0.tgz", @@ -6328,9 +5886,9 @@ } }, "node_modules/rdflib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/rdflib/-/rdflib-2.4.0.tgz", - "integrity": "sha512-DPBFlnkA7lWgskbgyPsRxHE5S/9Ni5KHNgwzrq8CucG+TBxEHTGRSeMKjWhZlZhBhmQFu0YQGjOYyrzmkX/gwg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/rdflib/-/rdflib-2.4.1.tgz", + "integrity": "sha512-VU4bvNcbgxKTLAzj9sWcjpWjT9Yhbpkc456tGhwcn/hXU2UXf2aTCJ+nKPVnWNZYP09zGrd0sJUL6wwTvt6IZg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.2", @@ -6340,54 +5898,9 @@ "cross-fetch": "^4.1.0", "jsonld": "^9.0.0", "n3": "^2.0.3", - "package-lock.json": "^1.0.0", - "package.json": "^2.0.1", "solid-namespace": "^0.5.4" } }, - "node_modules/read-all-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", - "integrity": "sha512-DI1drPHbmBcUDWrJ7ull/F2Qb8HkwBncVx8/RpKYFSIACYaVRQReISYPdZz/mt1y1+qMCOrfReTopERmaxtP6w==", - "license": "MIT", - "dependencies": { - "pinkie-promise": "^2.0.0", - "readable-stream": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-all-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/read-all-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/read-all-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/readable-stream": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", @@ -6455,28 +5968,6 @@ "node": ">=4" } }, - "node_modules/registry-auth-token": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", - "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", - "license": "MIT", - "dependencies": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", - "license": "MIT", - "dependencies": { - "rc": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/regjsgen": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", @@ -6485,9 +5976,9 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", - "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.3.tgz", + "integrity": "sha512-ycwFAS14Jw4mppvmK4GR/J6u3WpWpjkEApehuHtLc/8VpPNpDMbQ4WjqwplXifGeyKOzHSFLmSPqzksDQE2Sfg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -6501,6 +5992,7 @@ "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6529,12 +6021,12 @@ } }, "node_modules/rolldown": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.5.tgz", - "integrity": "sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.9.tgz", + "integrity": "sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.146.0", + "@oxc-project/types": "=0.150.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6544,21 +6036,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.5", - "@rolldown/binding-android-arm64": "1.2.5", - "@rolldown/binding-darwin-arm64": "1.2.5", - "@rolldown/binding-darwin-x64": "1.2.5", - "@rolldown/binding-freebsd-x64": "1.2.5", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.5", - "@rolldown/binding-linux-arm64-gnu": "1.2.5", - "@rolldown/binding-linux-arm64-musl": "1.2.5", - "@rolldown/binding-linux-ppc64-gnu": "1.2.5", - "@rolldown/binding-linux-s390x-gnu": "1.2.5", - "@rolldown/binding-linux-x64-gnu": "1.2.5", - "@rolldown/binding-linux-x64-musl": "1.2.5", - "@rolldown/binding-openharmony-arm64": "1.2.5", - "@rolldown/binding-win32-arm64-msvc": "1.2.5", - "@rolldown/binding-win32-x64-msvc": "1.2.5" + "@rolldown/binding-android-arm-eabi": "1.2.9", + "@rolldown/binding-android-arm64": "1.2.9", + "@rolldown/binding-darwin-arm64": "1.2.9", + "@rolldown/binding-darwin-x64": "1.2.9", + "@rolldown/binding-freebsd-x64": "1.2.9", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.9", + "@rolldown/binding-linux-arm64-gnu": "1.2.9", + "@rolldown/binding-linux-arm64-musl": "1.2.9", + "@rolldown/binding-linux-ppc64-gnu": "1.2.9", + "@rolldown/binding-linux-s390x-gnu": "1.2.9", + "@rolldown/binding-linux-x64-gnu": "1.2.9", + "@rolldown/binding-linux-x64-musl": "1.2.9", + "@rolldown/binding-openharmony-arm64": "1.2.9", + "@rolldown/binding-win32-arm64-msvc": "1.2.9", + "@rolldown/binding-win32-x64-msvc": "1.2.9" } }, "node_modules/safe-buffer": { @@ -6629,17 +6121,10 @@ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "license": "ISC" }, - "node_modules/sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==", - "deprecated": "Unsupported", - "license": "MIT" - }, "node_modules/solid-logic": { - "version": "6.0.0-0", - "resolved": "https://registry.npmjs.org/solid-logic/-/solid-logic-6.0.0-0.tgz", - "integrity": "sha512-ApPkheoF5Nu+7//WkPJw+tHZuGcktJ0lrkvbTsjNb594RW1DFUtLZY+OXRck45GJW0owoyTtJK7A0jZtlA33tw==", + "version": "6.0.0-2", + "resolved": "https://registry.npmjs.org/solid-logic/-/solid-logic-6.0.0-2.tgz", + "integrity": "sha512-FflmU5CrX910/Ccb22LfWoS9yqniHpT2C7KhCdycB2HrDZbyqppfdqfF1/KI2dSrDtDfLcNQBKfko9WvEFTpfQ==", "license": "MIT", "dependencies": { "@uvdsl/solid-oidc-client-browser": "^0.2.3", @@ -6658,9 +6143,9 @@ "license": "MIT" }, "node_modules/solid-ui": { - "version": "5.0.0-0", - "resolved": "https://registry.npmjs.org/solid-ui/-/solid-ui-5.0.0-0.tgz", - "integrity": "sha512-AHHhqeUcggQmudCHKi90ugJMdEHtFsT46MSl7oNbM/TGW2WpkpQv1d17Z4NU1ezq3YFhJPEK7j9iea5Nx5lHMw==", + "version": "5.0.0-2", + "resolved": "https://registry.npmjs.org/solid-ui/-/solid-ui-5.0.0-2.tgz", + "integrity": "sha512-04avmEb3IQcExPuKTWebzcx8ZrJLmmXFzTA4eECUSU8gkBszFjOS1J0P5sXzf5cAojQowBDUHmqVEUxhUjO3ug==", "license": "MIT", "dependencies": { "@awesome.me/webawesome": "^3.9.0", @@ -6683,9 +6168,9 @@ "escape-html": "^1.0.3", "lit": "^3.3.3", "mime-types": "^3.0.2", - "pane-registry": "5.0.0-0", - "rdflib": "2.4.0", - "solid-logic": "6.0.0-0", + "pane-registry": "5.0.0-1", + "rdflib": "2.4.1", + "solid-logic": "6.0.0-2", "solid-namespace": "^0.5.4", "uuid": "^14.0.0" }, @@ -6752,38 +6237,6 @@ "node": ">=0.10.0" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", - "license": "CC0-1.0" - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -6831,9 +6284,9 @@ } }, "node_modules/style-mod": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", - "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.4.tgz", + "integrity": "sha512-XXWIQt633/EpAFx8aZDOTjBzrCaGmhvEQlQo6MVPfa2OzO2cWo+4hV9h+6UkHYlXGfy+ODXKUdP7Pthmcu5ATw==", "license": "MIT" }, "node_modules/supports-color": { @@ -6853,6 +6306,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6874,15 +6328,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/timed-out": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", - "integrity": "sha512-pqqJOi1rF5zNs/ps4vmbE4SFCrM4iR7LW+GHAsHqO/EumqbIWceioevYLM5xZRgQSH6gFgL9J/uB7EcJhQ9niQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -6890,9 +6335,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", - "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "license": "MIT", "engines": { "node": ">=18" @@ -6923,18 +6368,6 @@ "node": ">=14.0.0" } }, - "node_modules/tmp": { - "version": "0.0.28", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz", - "integrity": "sha512-c2mmfiBmND6SOVxzogm1oda0OJ1HZVIk/5n26N59dDTh80MUeavpiCls4PGAdkX1PFkKokLpcf7prSjCeXLsJg==", - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -6981,15 +6414,6 @@ "node": ">=14.17" } }, - "node_modules/typpy": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/typpy/-/typpy-2.4.0.tgz", - "integrity": "sha512-a16Uv5doNtvHzaG4wZCHmXN+l9xxmTMpyODtPz7B3DSTsDVNXilTSJGuNw68sUh0Un4bf+ghRMbEcJCI6r06mQ==", - "license": "MIT", - "dependencies": { - "function.name": "^1.0.3" - } - }, "node_modules/ufo": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", @@ -6997,20 +6421,10 @@ "dev": true, "license": "MIT" }, - "node_modules/ul": { - "version": "5.2.16", - "resolved": "https://registry.npmjs.org/ul/-/ul-5.2.16.tgz", - "integrity": "sha512-v1YrSEsJZpJsywzF/MKgsQwMdOwBlwwmNiUOJh/yX6FHrq7dYjeua1YOhLV0q0KioqEFZC4P7MsKmpEsGdZz3w==", - "license": "MIT", - "dependencies": { - "deffy": "^2.2.2", - "typpy": "^2.3.4" - } - }, "node_modules/undici": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", - "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", + "version": "6.28.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.1.tgz", + "integrity": "sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA==", "license": "MIT", "engines": { "node": ">=18.17" @@ -7083,9 +6497,9 @@ } }, "node_modules/unplugin-dts": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/unplugin-dts/-/unplugin-dts-1.0.3.tgz", - "integrity": "sha512-/GR887wfG4r1cWyt1UZsLRuMIjsmEbGkS9yJrz+0dsToHAYUD5CTyP3JMGVLv25j9K0mJcwAVvZno/aTuSUvNg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unplugin-dts/-/unplugin-dts-1.1.1.tgz", + "integrity": "sha512-Z2VcabC0hjYuzF62oWmZXVoxagkM8bUWgqUy+dotoJTPnooR5mNaxbpv63JCmcaxdGCW2Bb75omK6FOryOYCRg==", "dev": true, "license": "MIT", "dependencies": { @@ -7093,6 +6507,7 @@ "@volar/typescript": "^2.4.26", "compare-versions": "^6.1.1", "debug": "^4.4.0", + "glob-to-regexp": "0.4.1", "kolorist": "^1.8.0", "local-pkg": "^1.1.1", "magic-string": "^0.30.17", @@ -7173,19 +6588,10 @@ } } }, - "node_modules/unzip-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", - "integrity": "sha512-pwCcjjhEcpW45JZIySExBHYv5Y9EeL2OIGEfrSKp2dMUFGFv4CpvZkwJbVge8OvGH2BNNtJBx67DuKuJhf+N5Q==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, "node_modules/update-browserslist-db": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", - "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", "dev": true, "funding": [ { @@ -7223,24 +6629,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==", - "license": "MIT", - "dependencies": { - "prepend-http": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, "node_modules/uuid": { "version": "14.0.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.2.tgz", @@ -7254,26 +6642,16 @@ "uuid": "dist-node/bin/uuid" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, "node_modules/vite": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", - "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz", + "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==", "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.26", - "rolldown": "~1.2.4", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.6", "tinyglobby": "^0.2.17" }, "bin": { @@ -7290,7 +6668,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -7477,12 +6855,6 @@ "dev": true, "license": "MIT" }, - "node_modules/w-json": { - "version": "1.3.10", - "resolved": "https://registry.npmjs.org/w-json/-/w-json-1.3.10.tgz", - "integrity": "sha512-XadVyw0xE+oZ5FGApXsdswv96rOhStzKqL53uSe5UaTadABGkWIg1+DTx8kiZ/VqTZTBneoL0l65RcPe4W3ecw==", - "license": "MIT" - }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", diff --git a/package.json b/package.json index 54838df..a50031e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "folder-pane", - "version": "4.0.0-0", + "version": "4.0.0-1", "description": "Solid-compatible Panes: File browser", "main": "dist/index.cjs.js", "types": "dist/index.d.ts", @@ -12,7 +12,8 @@ } }, "sideEffects": [ - "**/*.css" + "**/*.css", + "src/components/**/*.ts" ], "scripts": { "clean": "rm -rf dist", @@ -58,6 +59,7 @@ "devDependencies": { "@eslint/eslintrc": "^3.3.3", "@eslint/js": "^10.0.1", + "@iconify-json/lucide": "^1.2.116", "@types/node": "^24.13.3", "@typescript-eslint/parser": "^8.53.1", "@vitest/coverage-v8": "^4.0.18", @@ -67,9 +69,11 @@ "typescript": "^5.9.3" }, "dependencies": { - "rdflib": "2.4.0", - "solid-logic": "6.0.0-0", - "solid-ui": "5.0.0-0", + "@lit/context": "^1.1.6", + "pane-registry": "5.0.0-1", + "rdflib": "2.4.1", + "solid-logic": "6.0.0-2", + "solid-ui": "5.0.0-2", "vite": "^8.0.16", "vitest": "^4.0.18", "vitest-axe": "^0.1.0" diff --git a/src/components/storage-container-pane/StorageContainerPane.styles.css b/src/components/storage-container-pane/StorageContainerPane.styles.css new file mode 100644 index 0000000..d0d9e6e --- /dev/null +++ b/src/components/storage-container-pane/StorageContainerPane.styles.css @@ -0,0 +1,257 @@ +/* + * Future shadow-DOM layout for the container pane. + * These rules are mirrored in src/styles/folderPane.css for now because the + * storage shell still renders into light DOM. Once the shell can move back to + * shadow DOM, this stylesheet can be reattached via static styles and the + * light-DOM copy can be removed. + */ + +:host { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + min-height: 0; + overflow: hidden; + flex: 1 1 auto; +} + +:host > .storage-container-pane-content { + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-height: 0; + width: 100%; + overflow-y: auto; + overflow-x: hidden; + scrollbar-width: thin; + scrollbar-color: transparent transparent; +} + +:host > .storage-container-pane-content::-webkit-scrollbar { + width: 6px; +} + +:host > .storage-container-pane-content::-webkit-scrollbar-track { + background: transparent; +} + +:host > .storage-container-pane-content::-webkit-scrollbar-thumb { + background: transparent; + border-radius: 999px; +} + +:host > .storage-container-pane-content:hover { + scrollbar-color: #c7cbd1 transparent; +} + +:host > .storage-container-pane-content:hover::-webkit-scrollbar-thumb { + background: #c7cbd1; +} + +:host > .storage-container-pane-content > .storage-container-pane-resource-drop-zone { + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-height: 0; + width: 100%; +} + +.resource-grid, +.resource-list { + flex: 1 1 auto; + width: 100%; + overflow: visible; + min-height: 0; + margin: 0; + padding: 20px; +} + +.resource-list { + flex-direction: column; +} + +.resource-list-header { + display: flex; + align-items: center; + justify-content: space-between; + color: var(--solid-ui-color-gray-600, #4A5565); + font-size: var(--solid-ui-font-size-sm, .875rem); + font-weight: 500; + list-style: none; +} + +.resource-list-main { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; + flex: 1 1 auto; +} + +.resource-grid { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: 20px; +} + +storage-content-view { + display: block; + width: 100%; + min-height: 0; + flex: 0 0 auto; +} + +storage-creation-area { + margin-top: auto; + align-self: stretch; + width: 100%; + box-sizing: border-box; + flex: 0 0 auto; + padding: 10px 20px; + --storage-creation-area-message-font-size: var(--solid-ui-font-size-sm, .875rem); + --storage-creation-area-padding: 28px 20px; + --storage-creation-icon-size: 26px; + --storage-creation-gap: 10px; + --storage-creation-area-border-radius: 12px; +} + +.storage-container-pane-resource-drop-zone { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; + width: 100%; +} + +.resource-actions-menu { + display: flex; + align-items: center; + flex: 0 0 auto; + justify-content: flex-end; +} + +.resource-actions-menu--grid { + position: absolute; + top: 10px; + right: 10px; +} + +.resource-actions-menu--list { + margin-left: auto; +} + +.resource-grid-item { + position: relative; +} + +.resource-grid-item.drop-target, +.resource-list-item.drop-target { + outline: 2px solid var(--solid-ui-color-lavender-300, #C4B5FD); + outline-offset: -2px; + background: color-mix(in srgb, var(--solid-ui-color-lavender-200, #DDD6FE) 35%, white); +} + +.resource-grid-top { + position: relative; +} + +.resource-grid-icon { + display: flex; + width: 80px; + height: 80px; + padding: 0; + justify-content: center; + align-items: center; + aspect-ratio: 1/1; + margin: 12px auto 0; +} + +.resource-grid-icon .resource-pane-icon { + width: 80px; + height: 80px; + object-fit: contain; +} + +.resource-grid-icon--container { + width: 37.282px; + height: 37.282px; + padding: 0 9.32px; + margin: 12px 0 0 12px; + justify-content: flex-start; +} + +.resource-grid-label--container { + color: var(--solid-ui-color-gray-700, #364153); + font-size: var(--solid-ui-font-size-md, 1rem); + font-weight: 500; +} + +icon-lucide-folder, +icon-lucide-file, +icon-lucide-globe, +icon-lucide-lock-keyhole { + width: 15px; + height: 15px; +} + +.storage-container-pane-empty-message { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + min-height: 100%; + text-align: center; + color: var(--solid-ui-color-gray-700, #364153); +} + +.storage-container-pane-empty-message-icon { + display: flex; + width: 64px; + height: 64px; + padding: 0 16.785px; + justify-content: center; + align-items: center; + border-radius: 16px; + border: 1px solid var(--solid-ui-color-slate-200, #E2E8F0); + background: var(--solid-ui-color-white, #FFF); +} + +.storage-container-pane-empty-message-icon icon-lucide-file-box { + width: 32px; + height: 32px; + color: var(--solid-ui-color-gray-300, #D1D5DC); + stroke-width: 2px; +} + +.storage-container-pane-empty-message-title { + margin: 0; + color: var(--solid-ui-color-gray-900, #030712); + font-size: 1.25rem; + font-weight: 700; + line-height: 1.2; +} + +.storage-container-pane-empty-message-body { + max-width: 24rem; + margin: 0; + color: var(--solid-ui-color-gray-600, #4A5565); + font-size: var(--solid-ui-font-size-sm, .875rem); + line-height: 21.125px; +} + +.storage-container-pane-empty-message--search { + min-height: 100%; +} + +.storage-container-pane-empty-message-button { + --text-color: var(--solid-ui-color-primary); + --border-color: var(--solid-ui-color-gray-200, #e5e7eb); + --background-color: var(--solid-ui-color-white, #ffffff); + --hover-text-color: var(--solid-ui-color-primary); + --hover-border-color: var(--solid-ui-color-gray-300, #d1d5db); + --hover-background-color: var(--solid-ui-color-white, #ffffff); + align-self: center; +} diff --git a/src/components/storage-container-pane/StorageContainerPane.ts b/src/components/storage-container-pane/StorageContainerPane.ts new file mode 100644 index 0000000..2e4b371 --- /dev/null +++ b/src/components/storage-container-pane/StorageContainerPane.ts @@ -0,0 +1,810 @@ +import { html, nothing } from 'lit' +import { property, query, state } from 'lit/decorators.js' +import type { PropertyValues } from 'lit' +import { consume } from '@lit/context' +import '../storage-header' +import '../storage-creation-area' +import type { LiveStore, NamedNode } from 'rdflib' +import type { DataBrowserContext } from 'pane-registry' +import { byName } from 'pane-registry' +import type { FileExplorerContext } from 'solid-ui' +import type { StorageContext } from '../storage-provider/context' +import { storageContext, DEFAULT_STORAGE_CONTEXT } from '../storage-provider/context' +import { solidLogicSingleton } from 'solid-logic' +import { customElement, DEFAULT_STORE, fileExplorerContext, storeContext, utils, WebComponent, authContext, DEFAULT_AUTH_CONTEXT, type AuthContext } from 'solid-ui' +import type { PaneDefinition } from 'pane-registry' +import { Resource, type ResourceMap, StoragePaneOutliner } from '../../types' +import { getDraggedResourceUri, getResourcesForContainer, getResourcesFromSearchQuery, handleContainerDragOver, handleContainerDrop, loadResourcesForContainer, setDraggedResource } from '../../helpers' +import { getRelevantPanes, getRelevantPane } from 'solid-ui' +import { buildResourceActionsMenuBindings, loadDiscoveryState, toggleDiscoveryState } from 'solid-ui/components/resource-actions-menu' +import type { ResourceActionMenuItem as ResourcePaneMenuItem } from 'solid-ui/components/resource-actions-menu' +import { DEFAULT_DISCOVER_CLASS } from 'solid-ui' +import { createNewResource } from '../storage-creation-menu/mintPaneInstance' +import StorageCreationArea from '../storage-creation-area/StorageCreationArea' +import styles from './StorageContainerPane.styles.css' +import 'solid-ui/components/file-explorer-header' +import 'solid-ui/components/resource-actions-menu' +import '~icons/lucide/folder' +import '~icons/lucide/file' +import '~icons/lucide/globe' +import '~icons/lucide/lock-keyhole' +import '~icons/lucide/file-box' + +@customElement('storage-container-pane') +export default class StorageContainerPane extends WebComponent { + static styles = styles + + @property({ attribute: false }) + accessor subject: NamedNode | undefined = undefined + + @property({ attribute: false }) + accessor outliner: StoragePaneOutliner | undefined = undefined + + @property({ attribute: false }) + accessor browserContext: DataBrowserContext | null = null + + @consume({ context: storeContext, subscribe: true }) + accessor store: LiveStore = DEFAULT_STORE + + @consume({ context: fileExplorerContext, subscribe: true }) + accessor fileExplorerContext: FileExplorerContext = undefined as unknown as FileExplorerContext + + @consume({ context: storageContext, subscribe: true }) + accessor storageContext: StorageContext = DEFAULT_STORAGE_CONTEXT + + @consume({ context: authContext, subscribe: true }) + accessor auth: AuthContext = DEFAULT_AUTH_CONTEXT + + @state() + accessor resources: ResourceMap = new Map() + + @state() + accessor resourceAccess: Map = new Map() + + @state() + accessor resourcePaneIcons: Map = new Map() + + @state() + accessor resourcePaneItems: Map = new Map() + + @state() + accessor resourceRelevantPanes: Map = new Map() + + @state() + accessor resourceDiscovery: Map = new Map() + + private resourceAccessLoading = new Set() + private resourcePaneLoading = new Set() + private resourceDiscoveryLoading = new Set() + private resourceSyncGeneration = 0 + + @state() + accessor isLoadingResources = false + + @state() + accessor dropTargetResourceUri: string | undefined = undefined + + @query('storage-creation-area') + private accessor storageCreationArea: StorageCreationArea | null = null + + protected createRenderRoot () { + return this + } + + protected firstUpdated () { + void this.syncResources() + } + + private get currentSubject (): NamedNode | undefined { + const subjectUri = this.subject?.uri ?? this.fileExplorerContext?.subjectUri + + return subjectUri ? this.store.sym(subjectUri) : undefined + } + + private get selectedResource (): NamedNode | undefined { + return this.storageContext.selectedResource ?? this.currentSubject + } + + private ensureResourceAccess (resource: Resource) { + if (this.resourceAccess.has(resource.id) || this.resourceAccessLoading.has(resource.id)) { + return + } + + this.resourceAccessLoading.add(resource.id) + + void solidLogicSingleton.resource.fetchMetadata(resource.subject) + .then((metadata) => { + this.resourceAccess = new Map(this.resourceAccess).set(resource.id, { + isPublic: metadata.access.isPublic, + canDelete: metadata.access.canDelete, + }) + }) + .catch(() => { + // Unknown access stays unknown; hide access-dependent actions and indicators. + }) + .finally(() => { + this.resourceAccessLoading.delete(resource.id) + }) + } + + private syncResources = async (forceReload = false) => { + const subject = this.currentSubject + + if (!this.store || !subject) { + return + } + + const syncGeneration = ++this.resourceSyncGeneration + this.isLoadingResources = true + this.resources = new Map() + + try { + if (forceReload) { + await this.store.fetcher.load(subject, { force: true, clearPreviousData: true }) + } + + const loadedResources = await loadResourcesForContainer(this.store, subject) + + if (syncGeneration !== this.resourceSyncGeneration) { + return + } + + this.resources = loadedResources + } catch (_error) { + if (syncGeneration !== this.resourceSyncGeneration) { + return + } + + this.resources = getResourcesForContainer(this.store, subject) + } + + if (syncGeneration !== this.resourceSyncGeneration) { + return + } + + this.isLoadingResources = false + + for (const resource of this.resources.values()) { + this.ensureResourceAccess(resource) + } + } + + private get searchQuery () { + return this.storageContext.searchQuery.trim() + } + + private get visibleResources () { + if (this.searchQuery) { + return Array.from(getResourcesFromSearchQuery(this.store, this.searchQuery).values()) + } + + return Array.from(this.resources.values()) + } + + private selectResource (resource: Resource) { + this.storageContext.selectResource(resource.subject) + } + + private get folderPane () { + return this.browserContext?.session.paneRegistry.byName('folder') ?? undefined + } + + private get canCreateFolder () { + return !!this.auth.account && !!this.browserContext && !!this.currentSubject && !!this.folderPane + } + + private handleCreateNewFolder = async () => { + event?.stopPropagation() + + if (!this.browserContext || !this.currentSubject || !this.folderPane || !this.auth.account) { + return + } + + await createNewResource({ + browserContext: this.browserContext, + container: this.currentSubject, + pane: this.folderPane, + statusArea: this, + storageContext: this.storageContext, + }) + } + + private onEmptyStateClick = () => { + if (!this.auth.account) { + return + } + + this.storageCreationArea?.openChooser() + } + + private onEmptyStateKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + this.onEmptyStateClick() + } + } + + private onEmptyStateDragOver = (event: DragEvent) => { + event.preventDefault() + event.stopPropagation() + event.dataTransfer!.dropEffect = 'copy' + } + + private onEmptyStateDrop = (event: DragEvent) => { + event.preventDefault() + event.stopPropagation() + + const subject = this.currentSubject + + if (!this.store || !subject || !this.auth.account) { + return + } + + const draggedResourceUri = getDraggedResourceUri(event.dataTransfer) + if (draggedResourceUri) { + void (async () => { + await this.storageContext.moveResource(this.store.sym(draggedResourceUri), subject) + this.dropTargetResourceUri = undefined + await this.syncResources(true) + })() + return + } + + if (handleContainerDrop(event, this.store, subject, () => { void this.syncResources() })) { + return + } + } + + private getPaneIconSource (pane: PaneDefinition, subject: NamedNode) { + if (!this.browserContext) { + return undefined + } + + const paneIcon = pane.icon as unknown + + return typeof paneIcon === 'function' + ? (paneIcon as (subject: NamedNode, context: DataBrowserContext) => string)(subject, this.browserContext) + : paneIcon as string | undefined + } + + private async ensureResourcePaneItems (resource: Resource) { + if (!this.browserContext || this.resourcePaneLoading.has(resource.id) || this.resourcePaneItems.has(resource.id)) { + return + } + + this.resourcePaneLoading.add(resource.id) + + try { + const relevantPanes = await getRelevantPanes(resource.subject, this.browserContext) + + if (relevantPanes.length === 0) { + this.resourcePaneItems = new Map(this.resourcePaneItems).set(resource.id, []) + this.resourceRelevantPanes = new Map(this.resourceRelevantPanes).set(resource.id, []) + return + } + + const selectedPane = getRelevantPane(relevantPanes, resource.subject) ?? relevantPanes[0] + const selectedPaneIcon = selectedPane ? this.getPaneIconSource(selectedPane, resource.subject) : undefined + + const menuItems = relevantPanes + .map((pane): ResourcePaneMenuItem | null => { + const label = pane.label(resource.subject, this.browserContext as unknown as DataBrowserContext) + + if (!label) { + return null + } + + const iconSource = this.getPaneIconSource(pane, resource.subject) + const icon = iconSource ? utils.AJARImage(iconSource, label, label, this.browserContext?.dom ?? document) : undefined + + if (icon) { + icon.setAttribute('slot', 'left-icon') + } + + return { + kind: 'custom', + label, + icon, + action: (event: Event) => { + event.stopPropagation() + if (selectedPane && pane.name === selectedPane.name) { + this.storageContext.selectResource(resource.subject) + return + } + + this.openResourcePane(resource, pane) + }, + } + }) + .filter((item): item is ResourcePaneMenuItem => item !== null) + + if (selectedPaneIcon) { + this.resourcePaneIcons = new Map(this.resourcePaneIcons).set(resource.id, selectedPaneIcon) + } + + this.resourceRelevantPanes = new Map(this.resourceRelevantPanes).set(resource.id, relevantPanes) + this.resourcePaneItems = new Map(this.resourcePaneItems).set(resource.id, menuItems) + + const discoverClass = this.resolveResourceDiscoverClass(resource, selectedPane, relevantPanes) + await this.ensureResourceDiscovery(resource, discoverClass) + } finally { + this.resourcePaneLoading.delete(resource.id) + } + } + + private resolveResourceDiscoverClass (resource: Resource, selectedPane: PaneDefinition | null, relevantPanes: PaneDefinition[]) { + return ( + (resource.isContainer ? solidLogicSingleton.resource.getContainerMintClass(resource.subject) : undefined) ?? + this.fileExplorerContext.discoverClass ?? + selectedPane?.mintClass ?? + relevantPanes.find((pane) => pane.mintClass)?.mintClass ?? + DEFAULT_DISCOVER_CLASS + ) + } + + private async ensureResourceDiscovery (resource: Resource, discoverClass?: NamedNode) { + if (!this.browserContext || this.resourceDiscoveryLoading.has(resource.id)) { + return + } + + const currentDiscovery = this.resourceDiscovery.get(resource.id) + if (currentDiscovery?.discoverClassUri === discoverClass?.uri) { + return + } + + this.resourceDiscoveryLoading.add(resource.id) + + try { + const discoveryState = await loadDiscoveryState(resource.subject) + + this.resourceDiscovery = new Map(this.resourceDiscovery).set(resource.id, { + discoverClassUri: discoverClass?.uri, + public: discoveryState.public, + private: discoveryState.private, + }) + } finally { + this.resourceDiscoveryLoading.delete(resource.id) + } + } + + private async toggleResourceDiscovery (resource: Resource, visibility: 'public' | 'private') { + const discovery = this.resourceDiscovery.get(resource.id) + const resolvedDiscoverClassUri = discovery?.discoverClassUri ?? this.resolveResourceDiscoverClass(resource, null, this.resourceRelevantPanes.get(resource.id) ?? [])?.uri + if (!resolvedDiscoverClassUri) { + return + } + + const discoverClass = this.store.sym(resolvedDiscoverClassUri) + + try { + const updatedDiscovery = await toggleDiscoveryState(resource.subject, discoverClass, visibility, discovery) + + this.resourceDiscovery = new Map(this.resourceDiscovery).set(resource.id, { + discoverClassUri: discoverClass.uri, + public: updatedDiscovery.public, + private: updatedDiscovery.private, + }) + } catch (error) { + console.error('[storage-container-pane.toggleResourceDiscovery] failed', error) + globalThis.alert(`Error updating ${visibility} discovery`) + } + } + + private openResourcePane (resource: Resource, pane: PaneDefinition) { + this.storageContext.selectResource(resource.subject, pane.name) + } + + private async deleteResource (resource: Resource) { + try { + await this.storageContext.deleteResource(resource.subject) + + const resources = new Map(this.resources) + resources.delete(resource.id) + this.resources = resources + + const resourceAccess = new Map(this.resourceAccess) + resourceAccess.delete(resource.id) + this.resourceAccess = resourceAccess + + const resourcePaneIcons = new Map(this.resourcePaneIcons) + resourcePaneIcons.delete(resource.id) + this.resourcePaneIcons = resourcePaneIcons + + const resourcePaneItems = new Map(this.resourcePaneItems) + resourcePaneItems.delete(resource.id) + this.resourcePaneItems = resourcePaneItems + + const resourceRelevantPanes = new Map(this.resourceRelevantPanes) + resourceRelevantPanes.delete(resource.id) + this.resourceRelevantPanes = resourceRelevantPanes + + const resourceDiscovery = new Map(this.resourceDiscovery) + resourceDiscovery.delete(resource.id) + this.resourceDiscovery = resourceDiscovery + } catch (error) { + // need error handling here + } + } + + private renderResourceActionsMenu (resource: Resource, placement: 'grid' | 'list') { + const sharingPane = byName('sharing') + const canDelete = this.resourceAccess.get(resource.id)?.canDelete ?? false + const menuItems = [ + ...(this.resourcePaneItems.get(resource.id) ?? []) + ] + + const discovery = this.resourceDiscovery.get(resource.id) + const resourceActions = buildResourceActionsMenuBindings({ + subject: resource.subject, + discoveryState: discovery, + handleAccessClick: sharingPane + ? () => { + this.storageContext.selectResource(resource.subject, sharingPane.name) + } + : undefined, + canDelete, + handleDeleteClick: () => { void this.deleteResource(resource) }, + handleDiscoverPublicClick: discovery ? () => { void this.toggleResourceDiscovery(resource, 'public') } : undefined, + handleDiscoverPrivateClick: discovery ? () => { void this.toggleResourceDiscovery(resource, 'private') } : undefined, + }) + + return html` +
event.stopPropagation()}> + +
+ ` + } + + private onContainerDragOver (resource: Resource, event: DragEvent) { + if (!resource.isContainer) { + return + } + + const draggedResourceUri = getDraggedResourceUri(event.dataTransfer) + if (draggedResourceUri) { + event.preventDefault() + event.stopPropagation() + event.dataTransfer!.dropEffect = 'move' + this.dropTargetResourceUri = resource.id + return + } + + handleContainerDragOver(event, this.store, resource.subject) + } + + private onContainerDrop (resource: Resource, event: DragEvent) { + if (!resource.isContainer) { + return + } + + const draggedResourceUri = getDraggedResourceUri(event.dataTransfer) + + if (draggedResourceUri) { + event.preventDefault() + event.stopPropagation() + void (async () => { + await this.storageContext.moveResource(this.store.sym(draggedResourceUri), resource.subject) + this.dropTargetResourceUri = undefined + await this.syncResources(true) + })() + return + } + + handleContainerDrop(event, this.store, resource.subject, () => { void this.syncResources() }) + } + + private onCurrentContainerDragOver = (event: DragEvent) => { + if (!this.currentSubject) { + return + } + + const draggedResourceUri = getDraggedResourceUri(event.dataTransfer) + if (draggedResourceUri) { + event.preventDefault() + event.stopPropagation() + event.dataTransfer!.dropEffect = 'move' + return + } + + handleContainerDragOver(event, this.store, this.currentSubject) + } + + private onCurrentContainerDrop = (event: DragEvent) => { + const subject = this.currentSubject + + if (!subject) { + return + } + + const draggedResourceUri = getDraggedResourceUri(event.dataTransfer) + if (draggedResourceUri) { + event.preventDefault() + event.stopPropagation() + void (async () => { + await this.storageContext.moveResource(this.store.sym(draggedResourceUri), subject) + this.dropTargetResourceUri = undefined + await this.syncResources(true) + })() + return + } + + handleContainerDrop(event, this.store, subject, () => { void this.syncResources() }) + } + + private isSelectedResource (resource: Resource) { + return this.selectedResource?.sameTerm(resource.subject) ?? false + } + + private renderResourceIcon (resource: Resource) { + if (resource.isContainer) { + return html`` + } + + const paneIconSource = this.resourcePaneIcons.get(resource.id) + return paneIconSource && this.browserContext + ? utils.AJARImage(paneIconSource, utils.label(resource.subject), utils.label(resource.subject), this.browserContext.dom ?? document) + : html`` + } + + private renderResourcePaneIcon (resource: Resource) { + const paneIconSource = this.resourcePaneIcons.get(resource.id) + + if (!paneIconSource || !this.browserContext) { + return html`` + } + + const paneIcon = utils.AJARImage( + paneIconSource, + utils.label(resource.subject), + utils.label(resource.subject), + this.browserContext.dom ?? document + ) + + paneIcon.classList.add('resource-pane-icon') + return paneIcon + } + + private renderResourceGridItem (resource: Resource, depth: number) { + const selected = this.isSelectedResource(resource) + void this.ensureResourcePaneItems(resource) + this.ensureResourceAccess(resource) + const isPublic = this.resourceAccess.get(resource.id)?.isPublic + const { isContainer, getContainerVisibleItemCount } = solidLogicSingleton.resource + + if (isContainer(resource.subject)) { + return html` +
  • this.selectResource(resource)} + @dragstart=${(event: DragEvent) => setDraggedResource(event, resource.subject)} + @dragover=${(event: DragEvent) => this.onContainerDragOver(resource, event)} + @dragleave=${() => { this.dropTargetResourceUri = undefined }} + @drop=${(event: DragEvent) => this.onContainerDrop(resource, event)} + @keydown=${(event: KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + this.selectResource(resource) + } + }} + > + + + + ${utils.label(resource.subject)} + + ${this.renderResourceActionsMenu(resource, 'grid')} +
  • + ` + } + + return html` +
  • this.selectResource(resource)} + @dragstart=${(event: DragEvent) => setDraggedResource(event, resource.subject)} + @dragover=${(event: DragEvent) => this.onContainerDragOver(resource, event)} + @drop=${(event: DragEvent) => this.onContainerDrop(resource, event)} + @keydown=${(event: KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + this.selectResource(resource) + } + }} + > +
    + + ${this.renderResourcePaneIcon(resource)} + + ${this.renderResourceActionsMenu(resource, 'grid')} +
    + +
  • + ` + } + + private renderResourceListItem (resource: Resource, depth: number) { + const selected = this.isSelectedResource(resource) + void this.ensureResourcePaneItems(resource) + this.ensureResourceAccess(resource) + const isPublic = this.resourceAccess.get(resource.id)?.isPublic + const { isContainer, getContainerVisibleItemCount } = solidLogicSingleton.resource + + return html` +
  • this.selectResource(resource)} + @dragstart=${(event: DragEvent) => setDraggedResource(event, resource.subject)} + @dragover=${(event: DragEvent) => this.onContainerDragOver(resource, event)} + @dragleave=${() => { this.dropTargetResourceUri = undefined }} + @drop=${(event: DragEvent) => this.onContainerDrop(resource, event)} + @keydown=${(event: KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + this.selectResource(resource) + } + }} + > + + + ${this.renderResourceIcon(resource)} + + ${utils.label(resource.subject)} + ${isContainer(resource.subject) ? html`${getContainerVisibleItemCount(resource.subject)} items` : nothing} + ${isPublic === undefined ? nothing : isPublic ? html`` : html``} + + ${this.renderResourceActionsMenu(resource, 'list')} +
  • + ` + } + + protected willUpdate (changedProperties: PropertyValues) { + super.willUpdate(changedProperties) + + const previousStorageContext = changedProperties.get('storageContext') as StorageContext | undefined + const resourceRevisionChanged = changedProperties.has('storageContext') && + previousStorageContext?.resourceRevision !== this.storageContext.resourceRevision + + if ( + changedProperties.has('store') || + changedProperties.has('subject') || + changedProperties.has('fileExplorerContext') || + resourceRevisionChanged + ) { + void this.syncResources(resourceRevisionChanged) + } + } + + private renderListView () { + return html` +
      + + ${this.visibleResources.map((resource) => this.renderResourceListItem(resource, 0))} +
    + ` + } + + private renderGridView () { + return html` +
      + ${this.visibleResources.map((resource) => this.renderResourceGridItem(resource, 0))} +
    + ` + } + + private renderEmptyContainer(){ + return html` +
    +
    + +
    +

    This folder is empty

    +

    Create a folder or drop files or folder here or click to choose.

    + + Create New Folder + +
    ` + } + + private renderResourceListArea (searchQuery: string, visibleResources: Resource[]) { + if (this.isLoadingResources && !searchQuery) { + return html`
    Loading resources...
    ` + } + + if (visibleResources.length > 0) { + return html` +
    + ${this.storageContext.view === 'grid' ? this.renderGridView() : this.renderListView()} +
    + ` + } + + if (searchQuery) { + return html`` + } + + return this.renderEmptyContainer() + } + + render () { + const visibleResources = this.visibleResources + const searchQuery = this.searchQuery + + return html` +
    + ${this.renderResourceListArea(searchQuery, visibleResources)} +
    + + ` + } +} diff --git a/src/components/storage-container-pane/index.ts b/src/components/storage-container-pane/index.ts new file mode 100644 index 0000000..fc70ddd --- /dev/null +++ b/src/components/storage-container-pane/index.ts @@ -0,0 +1 @@ +export { default } from './StorageContainerPane' \ No newline at end of file diff --git a/src/components/storage-content-view/StorageContentView.style.css b/src/components/storage-content-view/StorageContentView.style.css new file mode 100644 index 0000000..e69de29 diff --git a/src/components/storage-content-view/StorageContentView.styles.css b/src/components/storage-content-view/StorageContentView.styles.css new file mode 100644 index 0000000..b6c08a7 --- /dev/null +++ b/src/components/storage-content-view/StorageContentView.styles.css @@ -0,0 +1,5 @@ +:host { + display: block; + height: auto; + min-height: 0; +} diff --git a/src/components/storage-content-view/StorageContentView.ts b/src/components/storage-content-view/StorageContentView.ts new file mode 100644 index 0000000..f0f44f5 --- /dev/null +++ b/src/components/storage-content-view/StorageContentView.ts @@ -0,0 +1,14 @@ +import styles from './StorageContentView.styles.css' +import { customElement, WebComponent } from 'solid-ui' +import { html } from 'lit' + +@customElement('storage-content-view') +export default class StorageContentView extends WebComponent { + static styles = styles + + render () { + return html` + + ` + } +} diff --git a/src/components/storage-content-view/index.ts b/src/components/storage-content-view/index.ts new file mode 100644 index 0000000..4bbd74f --- /dev/null +++ b/src/components/storage-content-view/index.ts @@ -0,0 +1 @@ +export { default } from './StorageContentView' diff --git a/src/components/storage-creation-area/StorageCreationArea.styles.css b/src/components/storage-creation-area/StorageCreationArea.styles.css new file mode 100644 index 0000000..bc8a9ef --- /dev/null +++ b/src/components/storage-creation-area/StorageCreationArea.styles.css @@ -0,0 +1,42 @@ +:host { + --storage-creation-area-message-font-size: var(--solid-ui-font-size-xs, .75rem); + --storage-creation-area-padding: 14px 10px; + --storage-creation-icon-size: 22px; + --storage-creation-gap: 6px; + --storage-creation-area-border-radius: 10px; + + .storage-creation-area { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + width: 100%; + box-sizing: border-box; + border: 1px dashed var(--solid-ui-color-gray-200, #E5E7EB); + padding: var(--storage-creation-area-padding); + color: var(--solid-ui-color-primary, #7c4dff); + min-height: 73.391px; + gap: var(--storage-creation-gap); + align-self: stretch; + border-radius: var(--storage-creation-area-border-radius); + background: rgba(139, 92, 246, 0.02); + cursor: pointer; + transition: border-color 0.15s ease, background-color 0.15s ease; + } + + .storage-creation-area:hover, + .storage-creation-area:focus-visible { + border-color: var(--solid-ui-color-primary, #7c4dff); + } + + .storage-creation-area-message { + font-size: var(--storage-creation-area-message-font-size); + } + + icon-lucide-cloud-upload { + width: var(--storage-creation-icon-size); + height: var(--storage-creation-icon-size); + flex-shrink: 0; + } +} + diff --git a/src/components/storage-creation-area/StorageCreationArea.ts b/src/components/storage-creation-area/StorageCreationArea.ts new file mode 100644 index 0000000..f903e8b --- /dev/null +++ b/src/components/storage-creation-area/StorageCreationArea.ts @@ -0,0 +1,124 @@ +import { authn } from 'solid-logic' +import { customElement, DEFAULT_STORE, storeContext, WebComponent } from 'solid-ui' +import { html, nothing } from 'lit' +import styles from './StorageCreationArea.styles.css' +import '~icons/lucide/cloud-upload' +import type { NamedNode } from 'rdflib' +import { LiveStore } from 'rdflib' +import { property, query } from 'lit/decorators.js' +import { consume } from '@lit/context' +import { uploadFilesIntoContainer } from '../../helpers' + + +@customElement('storage-creation-area') +export default class StorageCreationArea extends WebComponent { + static styles = styles + + @consume({ context: storeContext, subscribe: true }) + accessor store: LiveStore = DEFAULT_STORE + + @property({ attribute: false }) + accessor subject: NamedNode | undefined = undefined + + @property({ attribute: false }) + accessor message: String | null = null + + @query('input[type="file"]') + private accessor fileInput: HTMLInputElement | null = null + + public openChooser () { + this.fileInput?.click() + } + + private onDragOver (event: DragEvent) { + event.preventDefault() + event.stopPropagation() + event.dataTransfer!.dropEffect = 'copy' + } + + private onDrop (event: DragEvent) { + event.preventDefault() + event.stopPropagation() + + const subject = this.subject + + if (!this.store || !subject) { + console.error('Store or subject is not defined for StorageCreationArea') + return + } + + const files = event.dataTransfer?.files ?? [] + + uploadFilesIntoContainer(this.store, subject, files, (resource) => { + this.dispatchEvent(new CustomEvent('resource-created', { + detail: { resource }, + bubbles: true, + composed: true, + })) + }) + } + + private onChooseFiles = (event: Event) => { + const input = event.target as HTMLInputElement + const subject = this.subject + + if (!this.store || !subject) { + console.error('Store or subject is not defined for StorageCreationArea') + return + } + + const files = input.files ?? new FileList() + + uploadFilesIntoContainer(this.store, subject, files, (resource) => { + this.dispatchEvent(new CustomEvent('resource-created', { + detail: { resource }, + bubbles: true, + composed: true, + })) + }) + + input.value = '' + } + + private onClick = () => { + this.openChooser() + } + + private onKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + this.openChooser() + } + } + + render() { + const me = authn.currentUser() + + return html` + ${me + ? html` +
    + + + ${this.message ?? 'Drop files or folders here or click to choose files'} +
    + ` + : nothing} + ` + } +} diff --git a/src/components/storage-creation-area/index.ts b/src/components/storage-creation-area/index.ts new file mode 100644 index 0000000..f9b861e --- /dev/null +++ b/src/components/storage-creation-area/index.ts @@ -0,0 +1 @@ +export { default } from './StorageCreationArea' \ No newline at end of file diff --git a/src/components/storage-creation-dialog/StorageCreationDialog.styles.css b/src/components/storage-creation-dialog/StorageCreationDialog.styles.css new file mode 100644 index 0000000..caa5912 --- /dev/null +++ b/src/components/storage-creation-dialog/StorageCreationDialog.styles.css @@ -0,0 +1,63 @@ +:host { + display: block; + + .storage-creation-dialog-header { + padding: 20px 25px; + gap: 25px; + border-radius: 5px 5px 0 0; + border: 1px solid var(--solid-ui-color-slate-200, #E2E8F0); + background: var(--solid-ui-color-slate-50, #F8FAFC); + } + + h1 { + margin: 0; + font-weight: 500; + color: var(--solid-ui-color-gray-800, #1E2939); + font-size: var(--solid-ui-font-size-xl, 1.25rem); + } + + .storage-creation-dialog-label { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 5px; + flex: 1 0 0; + align-self: stretch; + color: var(--solid-ui-color-gray-600, #4A5565); + font-size: var(--solid-ui-font-size-sm, .875rem); + } + + input { + display: block; + box-sizing: border-box; + width: 100%; + height: 40px; + padding: 10px; + border-radius: 5px; + border: 1px solid var(--solid-ui-color-gray-400, #99A1AF); + background: var(--solid-ui-color-white, #FFF); + color: var(--solid-ui-color-gray-900, #101828); + font-size: var(--solid-ui-font-size-sm, .875rem); + font-weight: 500; + } + + solid-ui-dialog-content { + display: flex; + padding: 25px 25px 30px 25px; + flex-direction: column; + align-items: flex-start; + gap: 20px; + align-self: stretch; + } + + solid-ui-dialog-footer { + display: flex; + padding: 20px 30px; + justify-content: flex-end; + align-items: center; + gap: 15px; + align-self: stretch; + border-top: 1px solid var(--solid-ui-color-slate-200, #E2E8F0); + background: var(--solid-ui-color-gray-50, #F9FAFB); + } +} diff --git a/src/components/storage-creation-dialog/StorageCreationDialog.ts b/src/components/storage-creation-dialog/StorageCreationDialog.ts new file mode 100644 index 0000000..5d1303e --- /dev/null +++ b/src/components/storage-creation-dialog/StorageCreationDialog.ts @@ -0,0 +1,83 @@ +import { customElement, DialogComponent } from 'solid-ui' +import { html } from 'lit' +import { property, query, state } from 'lit/decorators.js' +import styles from './StorageCreationDialog.styles.css' +import 'solid-ui/components/button' +import 'solid-ui/components/dialog' +import 'solid-ui/components/dialog-content' +import 'solid-ui/components/dialog-footer' +import 'solid-ui/components/dialog-header' +import '~icons/lucide/x' + +@customElement('storage-creation-dialog') +export default class StorageCreationDialog extends DialogComponent { + static styles = styles + + @property({ attribute: false }) + accessor label: string | null = null + + @state() + accessor name = '' + + @query('input') + private accessor nameInput: HTMLInputElement | null = null + + private cancel = () => this.close() + + private confirm = () => { + const name = this.name.trim() + + if (!name) { + return + } + + this.close(name) + } + + private onNameInput = (event: Event) => { + this.name = (event.target as HTMLInputElement).value + } + + protected firstUpdated () { + requestAnimationFrame(() => { + this.nameInput?.focus() + this.nameInput?.setSelectionRange(0, this.nameInput.value.length) + }) + } + + protected render () { + const paneLabel = this.label ?? 'Container' + const fieldLabel = `${paneLabel} Name` + const placeholder = `Untitled ${paneLabel}` + + return html` + + +

    New ${paneLabel}

    + + + +
    + + + + + + Cancel + + + Create + + +
    + ` + } +} diff --git a/src/components/storage-creation-dialog/index.ts b/src/components/storage-creation-dialog/index.ts new file mode 100644 index 0000000..28ba6f0 --- /dev/null +++ b/src/components/storage-creation-dialog/index.ts @@ -0,0 +1 @@ +export { default } from './StorageCreationDialog' \ No newline at end of file diff --git a/src/components/storage-creation-menu/StorageCreationMenu.styles.css b/src/components/storage-creation-menu/StorageCreationMenu.styles.css new file mode 100644 index 0000000..f25a16d --- /dev/null +++ b/src/components/storage-creation-menu/StorageCreationMenu.styles.css @@ -0,0 +1,9 @@ + +img[slot="left-icon"] { + display: block; + width: 1.25rem; + height: 1.25rem; + object-fit: contain; + flex: 0 0 auto; +} + diff --git a/src/components/storage-creation-menu/StorageCreationMenu.ts b/src/components/storage-creation-menu/StorageCreationMenu.ts new file mode 100644 index 0000000..9dffd78 --- /dev/null +++ b/src/components/storage-creation-menu/StorageCreationMenu.ts @@ -0,0 +1,111 @@ +import { customElement, WebComponent, authContext, DEFAULT_AUTH_CONTEXT, login } from 'solid-ui' +import { html, nothing } from 'lit' +import { consume } from '@lit/context' +import { property, state } from 'lit/decorators.js' +import type { PropertyValues } from 'lit' +import type { DataBrowserContext, PaneDefinition } from 'pane-registry' +import type { NamedNode } from 'rdflib' +import type { AuthContext } from 'solid-ui' +import { DEFAULT_STORAGE_CONTEXT, storageContext, type StorageContext } from '../storage-provider/context' +import { solidLogicSingleton } from 'solid-logic' +import 'solid-ui/components/button' +import 'solid-ui/components/menu' +import 'solid-ui/components/menu-item' +import '~icons/lucide/chevron-down' +import '~icons/lucide/plus' +import { createNewResource, getPaneLabel } from './mintPaneInstance' +import styles from './StorageCreationMenu.styles.css' + + +@customElement('storage-creation-menu') +export default class StorageCreationMenu extends WebComponent { + static styles = styles + + @property({ attribute: false }) + accessor browserContext: DataBrowserContext | null = null + + @property({ attribute: false }) + accessor container: NamedNode | null = null + + @property({ attribute: false }) + accessor paneList: PaneDefinition[] = [] + + // Resolved on click, because the target is not rendered yet on first paint. + @property({ attribute: false }) + accessor getStatusArea: (() => HTMLElement | null) | null = null + + @consume({ context: authContext, subscribe: true }) + private accessor auth: AuthContext = DEFAULT_AUTH_CONTEXT + + @consume({ context: storageContext, subscribe: true }) + private accessor storageContext: StorageContext = DEFAULT_STORAGE_CONTEXT + + @state() + accessor availablePanes: PaneDefinition[] = [] + + private unsubscribeSessionUpdated?: () => void + + connectedCallback () { + super.connectedCallback() + + this.unsubscribeSessionUpdated = this.auth.onSessionUpdated(() => this.requestUpdate()) + } + + disconnectedCallback () { + super.disconnectedCallback() + + this.unsubscribeSessionUpdated?.() + } + + protected async updated (changedProperties: PropertyValues) { + if (changedProperties.has('paneList')) { + const audiencePanes = await login.filterAvailablePanes(this.paneList ?? []) + // filterAvailablePanes only filters by audience; only minting panes can create anything. + this.availablePanes = audiencePanes.filter((pane) => pane.mintNew) + } + } + + private get canAddToCurrentResource () { + return !!this.container && solidLogicSingleton.resource.isContainer(this.container) + } + + private get addTooltip () { + return this.canAddToCurrentResource + ? 'Add a new item in the selected container' + : 'Select a container to add items here' + } + + private async handlePaneSelected (pane: PaneDefinition) { + if (!this.browserContext || !this.container || !this.auth.account) { + return + } + + await createNewResource({ + browserContext: this.browserContext, + container: this.container, + pane, + statusArea: this.getStatusArea?.() ?? this, + storageContext: this.storageContext + }) + } + + render () { + const isLoggedIn = !!this.auth.account + + return html` + + + + Add + + + ${this.availablePanes.map((pane) => html` + this.handlePaneSelected(pane)}> + ${pane.icon ? html`` : nothing} + New ${getPaneLabel(pane)} + + `)} + + ` + } +} diff --git a/src/components/storage-creation-menu/mintPaneInstance.ts b/src/components/storage-creation-menu/mintPaneInstance.ts new file mode 100644 index 0000000..8a6df1b --- /dev/null +++ b/src/components/storage-creation-menu/mintPaneInstance.ts @@ -0,0 +1,104 @@ +import { authn } from 'solid-logic' +import { DataBrowserContext, PaneDefinition } from 'pane-registry' +import { NamedNode } from 'rdflib' +import { ns, showDialog, utils } from 'solid-ui' +import StorageCreationDialog from '../storage-creation-dialog' +import type { StorageContext } from '../storage-provider/context' + +// This code was from newThingUI in solid-ui, we don't need the UI part +// anymore we just need to create the new instance and add it to the container. +export type MakeNewAppInstanceOptions = { + browserContext: DataBrowserContext + container: NamedNode + pane: PaneDefinition + name: string + // Panes render their own "created" notice into this element. + statusArea: HTMLElement +} + +export function getPaneLabel (pane: PaneDefinition): string { + if (!pane.mintClass) { + return pane.name.charAt(0).toUpperCase() + pane.name.slice(1) + } + + return utils.label(pane.mintClass) +} + +export async function makeNewAppInstance (options: MakeNewAppInstanceOptions): Promise { + const { browserContext, container, pane, name, statusArea } = options + const kb = browserContext.session.store + const me = authn.currentUser() + if (!me) { + throw new Error('makeNewAppInstance: must be logged in') + } + + const noun = getPaneLabel(pane) + const containerUri = container.uri.endsWith('/') ? container.uri : container.uri + '/' + const newBase = containerUri + encodeURIComponent(name) + '/' + + const created = await pane.mintNew!(browserContext, { + newBase, + // solid-ui mintNew implementations still expect this to be called `folder`. + folder: container, + pane, + div: statusArea, + dom: browserContext.dom, + me, + noun, + appPathSegment: noun.charAt(0).toUpperCase() + noun.slice(1), + noIndexHTML: true + } as any) + + if (!created || !created.newInstance) { + throw new Error('Cannot mint new thing - missing newInstance') + } + + const newResource = created.newInstance + + kb.add(container, ns.ldp('contains'), newResource, container.doc()) + + return newResource +} + +export type CreateNewResourceOptions = { + browserContext: DataBrowserContext + container: NamedNode + pane: PaneDefinition + statusArea: HTMLElement + storageContext: StorageContext +} + +export async function createNewResource (options: CreateNewResourceOptions): Promise { + const { browserContext, container, pane, statusArea, storageContext } = options + + statusArea.replaceChildren() + + const name = await new Promise((resolve) => { + showDialog(StorageCreationDialog, { + props: { + label: getPaneLabel(pane) + }, + onClose: (result) => resolve(result) + }) + }) + + if (!name) { + return undefined + } + + const newResource = await makeNewAppInstance({ + browserContext, + container, + pane, + name, + statusArea + }) + + const selectedPaneName = pane.name === 'folder' || pane.name === 'Dokieli' + ? undefined + : pane.name + + storageContext.selectResource(newResource, selectedPaneName) + + return newResource +} diff --git a/src/components/storage-header/StorageHeader.styles.css b/src/components/storage-header/StorageHeader.styles.css new file mode 100644 index 0000000..e6bb3a1 --- /dev/null +++ b/src/components/storage-header/StorageHeader.styles.css @@ -0,0 +1,137 @@ +:host { + display: block; + min-width: 0; + position: sticky; + top: 0; + z-index: 100; + background: var(--solid-ui-color-white, #fff); + flex-shrink: 0; + + .storage-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 0; + } + + .storage-header-toolbar { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 0.5rem; + min-width: 0; + } + + .storage-header-search { + position: relative; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; + width: 210px; + height: 36px; + } + + .storage-header-search input { + flex: 1 1 auto; + width: 100%; + height: 100%; + min-width: 0; + padding: 0 10px; + box-sizing: border-box; + font-size: var(--solid-ui-font-size-xs, .75rem); + text-align: right; + } + + /* The icon must sit beside the label, which a native placeholder cannot do. */ + .storage-header-search-placeholder { + position: absolute; + right: 10px; + display: flex; + align-items: center; + gap: 8px; + pointer-events: none; + font-size: var(--solid-ui-font-size-xs, .75rem); + color: var(--solid-ui-color-gray-500, #6A7282); + } + + .storage-header-search icon-lucide-search { + width: 15.001px; + height: 15px; + flex: 0 0 auto; + } + + .storage-header-actions { + display: inline-flex; + align-items: center; + gap: 0.35rem; + flex: 0 0 auto; + } + + .storage-header-create-menu-trigger { + display: inline-flex; + flex: 0 0 auto; + } + + /* Outer-tree rule so it wins over the menu item's ::slotted icon sizing. */ + storage-creation-menu img[slot="left-icon"] { + width: 13.333px; + height: 12.664px; + } + + icon-lucide-folder-open { + width: 12px; + height: 12px; + } + + icon-lucide-layout-grid, + icon-lucide-list { + height: 16px; + width: 16px; + flex-shrink: 0; + align-self: stretch; + } + + icon-lucide-layout-grid { + color: var(--solid-ui-color-primary); + } + + .storage-header-breadcrumbs { + display: flex; + align-items: center; + gap: 0.35rem; + min-width: 0; + overflow-x: auto; + overflow-y: hidden; + } + + .storage-header-breadcrumbs icon-lucide-folder-open { + flex: 0 0 auto; + } + + .storage-header-breadcrumbs ol { + display: flex; + flex-wrap: nowrap; + align-items: center; + gap: 0.35rem; + list-style: none; + margin: 0; + padding: 0; + min-width: 0; + color: var(--solid-ui-gray-500, #6A7282); + font-size: var(--solid-ui-font-size-sm, .875rem); + } + + .storage-header-breadcrumbs li { + display: inline-flex; + align-items: center; + gap: 0.35rem; + min-width: 0; + } + + .storage-header-breadcrumbs .crumb, + .storage-header-breadcrumbs .current, + .storage-header-breadcrumbs .separator { + white-space: nowrap; + } +} diff --git a/src/components/storage-header/StorageHeader.ts b/src/components/storage-header/StorageHeader.ts new file mode 100644 index 0000000..c86c6bd --- /dev/null +++ b/src/components/storage-header/StorageHeader.ts @@ -0,0 +1,170 @@ +import { html, nothing } from 'lit' +import { consume } from '@lit/context' +import { property } from 'lit/decorators.js' +import type { DataBrowserContext } from 'pane-registry' +import { LiveStore, NamedNode } from 'rdflib' +import { customElement, DEFAULT_STORE, FileExplorerContext, fileExplorerContext, storeContext, WebComponent } from 'solid-ui' +import '~icons/lucide/folder-open' +import '~icons/lucide/search' +import '~icons/lucide/layout-grid' +import '~icons/lucide/list' +import '../storage-creation-menu/StorageCreationMenu' +import { isStorageRoot } from '../../helpers' +import styles from './StorageHeader.styles.css' +import { DEFAULT_STORAGE_CONTEXT, StorageContext, storageContext } from '../storage-provider/context' + +@customElement('storage-header') +export default class StorageHeader extends WebComponent { + static styles = styles + + @consume({ context: storeContext, subscribe: true }) + accessor store: LiveStore = DEFAULT_STORE + + @consume({ context: fileExplorerContext, subscribe: true }) + accessor fileExplorerContext: FileExplorerContext = undefined as unknown as FileExplorerContext + + @consume({ context: storageContext, subscribe: true }) + accessor storageContext: StorageContext = DEFAULT_STORAGE_CONTEXT + + @property({ attribute: false }) + accessor browserContext: DataBrowserContext | null = null + + @property({ attribute: false }) + accessor getStatusArea: (() => HTMLElement | null) | null = null + + private get currentSubject (): NamedNode | undefined { + return this.fileExplorerContext?.subjectUri ? new NamedNode(this.fileExplorerContext.subjectUri) : undefined + } + + private getBreadcrumbSegments (resource: NamedNode) { + const segments: NamedNode[] = [] + let current: NamedNode | null = resource + + while (current) { + segments.unshift(current) + if (this.store && isStorageRoot(this.store, current)) { + break + } + + const parent = current.dir() + if (!parent || parent.sameTerm(current)) { + break + } + current = parent + } + + return segments + } + + private getBreadcrumbLabel (resource: NamedNode) { + if (this.store && isStorageRoot(this.store, resource)) { + return 'Storage' + } + + try { + const url = new URL(resource.uri) + const pathSegments = url.pathname.split('/').filter(Boolean) + + if (pathSegments.length > 0) { + return decodeURIComponent(pathSegments[pathSegments.length - 1]) + } + + return 'Storage' + } catch (_error) { + return resource.uri + } + } + + private onSearchInput = (event: Event) => { + this.storageContext.setSearchQuery((event.target as HTMLInputElement).value) + } + + private renderBreadcrumbs (resource: NamedNode) { + const segments = this.getBreadcrumbSegments(resource) + const specialCrumb = this.getBreadcrumbLabel(resource).toLowerCase() === 'public' + ? 'Public' + : 'Home' + + const breadcrumbItems: Array = [...segments] + + if (breadcrumbItems.length > 0) { + breadcrumbItems.splice(1, 0, specialCrumb) + } + + return html` + + ` + } + + render() { + const activeResource = this.storageContext.selectedResource ?? this.currentSubject + const searchQuery = this.storageContext.searchQuery + + return html` +
    + ${activeResource ? this.renderBreadcrumbs(activeResource) : ''} +
    + +
    + this.storageContext.setView('grid')}> + + + this.storageContext.setView('list')}> + + +
    +
    + ${activeResource && this.browserContext + ? html` + + ` + : nothing} +
    +
    +
    + ` + } +} diff --git a/src/components/storage-header/index.ts b/src/components/storage-header/index.ts new file mode 100644 index 0000000..439855d --- /dev/null +++ b/src/components/storage-header/index.ts @@ -0,0 +1 @@ +export { default } from './StorageHeader' diff --git a/src/components/storage-pane-view/StoragePaneView.styles.css b/src/components/storage-pane-view/StoragePaneView.styles.css new file mode 100644 index 0000000..f5a2d2e --- /dev/null +++ b/src/components/storage-pane-view/StoragePaneView.styles.css @@ -0,0 +1,36 @@ +/* + * Future shadow-DOM layout for the storage pane shell. + * This file is kept as the source of truth for when storage-pane-view can + * move back to shadow DOM. The live light-DOM layout still lives in + * src/styles/folderPane.css because the shell currently renders into light DOM. + */ + +:host { + display: block; + width: 100%; + height: 100%; + min-height: 0; +} + +.storage-pane-full-view { + width: 100%; + height: 100%; + min-height: 0; +} + +.storage-pane-status { + width: 100%; +} + +.storage-pane-access-denied-message { + display: flex; + width: 100%; + min-height: 100%; + align-items: center; + justify-content: center; + text-align: center; +} + +.storage-pane-access-denied-message-body { + max-width: 24rem; +} \ No newline at end of file diff --git a/src/components/storage-pane-view/StoragePaneView.ts b/src/components/storage-pane-view/StoragePaneView.ts new file mode 100644 index 0000000..5f6ef08 --- /dev/null +++ b/src/components/storage-pane-view/StoragePaneView.ts @@ -0,0 +1,342 @@ +import { customElement, DEFAULT_STORE, log, storeContext, WebComponent } from 'solid-ui' +import { DEFAULT_STORAGE_CONTEXT, storageContext } from '../storage-provider/context' +import { html } from 'lit' +import { property, query } from 'lit/decorators.js' +import type { PropertyValues } from 'lit' +import { consume } from '@lit/context' +import { fileExplorerContext, getRelevantPane, getRelevantPanes, type FileExplorerContext } from 'solid-ui' +import type { DataBrowserContext, PaneDefinition } from 'pane-registry' +import { byName } from 'pane-registry' +import '../storage-header' +import '../storage-container-pane' +import '../storage-resource-sidebar' +import '../storage-content-view' +import { LiveStore, NamedNode } from 'rdflib' +import { StoragePaneOutliner } from '../../types' +import type { StorageContext } from '../storage-provider/context' +import { containerHasIndexDocument, getContainerIndexThing, renderSelectedResourceInContentView } from '../../helpers' +import { solidLogicSingleton } from 'solid-logic' +@customElement('storage-pane-view') +export default class StoragePaneView extends WebComponent { + + @property({ attribute: false }) + accessor browserContext: DataBrowserContext | null = null + + @consume({ context: storeContext, subscribe: true }) + accessor store: LiveStore = DEFAULT_STORE + + @consume({ context: fileExplorerContext, subscribe: true }) + accessor fileExplorerContext: FileExplorerContext = undefined as unknown as FileExplorerContext + + @consume({ context: storageContext, subscribe: true }) + accessor storageContext: StorageContext = DEFAULT_STORAGE_CONTEXT + + @query('storage-content-view') + private accessor contentView: HTMLElement | null = null + + @query('.storage-pane-full-view') + private accessor fullView: HTMLElement | null = null + + @query('.storage-pane-status') + private accessor statusArea: HTMLElement | null = null + + private renderedSelectionKey: string | undefined = undefined + + // Keep the storage shell in light DOM: the legacy panes rendered into the + // content view are styled by global stylesheets, which cannot cross a shadow boundary. + protected createRenderRoot () { + return this + } + + protected updated (_changedProperties: PropertyValues) { + const selectedResource = this.currentSelectedResource + const selectedPaneName = this.storageContext.selectedPaneName + + if (!selectedResource) { + return + } + + this.setOuterNavbarHidden(selectedPaneName === 'resource' || selectedPaneName === 'internal') + + const selectionKey = `${selectedResource.uri}::${selectedPaneName ?? ''}` + + if (this.renderedSelectionKey !== selectionKey) { + this.renderedSelectionKey = selectionKey + + if (selectedPaneName === 'resource') { + void this.showResourceInFullView(selectedResource) + } else if (selectedPaneName === 'internal') { + void this.showSelectedPaneInFullView(selectedResource, selectedPaneName) + } else if (selectedPaneName) { + void this.showSelectedPaneInContentView(selectedResource, selectedPaneName) + } else { + void this.showResourceInContentView(selectedResource) + } + } + } + + private setOuterNavbarHidden (hidden: boolean) { + const navbar = document.querySelector('solid-panes-navbar') + + if (!navbar) { + return + } + + navbar.classList.toggle('navbar--hidden', hidden) + } + + private get currentSelectedResource (): NamedNode | undefined { + const subject = this.fileExplorerContext?.subjectUri + ? new NamedNode(this.fileExplorerContext.subjectUri) + : undefined + + return this.storageContext.selectedResource ?? subject + } + + private renderContainerPane (selectedResource: NamedNode) { + if (!this.contentView) return + + const containerPane = document.createElement('storage-container-pane') as HTMLElement & { + outliner?: StoragePaneOutliner + browserContext?: DataBrowserContext | null + subject?: NamedNode + } + + containerPane.subject = selectedResource + containerPane.browserContext = this.browserContext + containerPane.outliner = this.browserContext?.getOutliner(this.browserContext?.dom) as StoragePaneOutliner + + this.contentView.replaceChildren(containerPane) + } + + private renderContainerPaneInFullView (selectedResource: NamedNode) { + if (!this.fullView) return + + const containerPane = document.createElement('storage-container-pane') as HTMLElement & { + outliner?: StoragePaneOutliner + browserContext?: DataBrowserContext | null + subject?: NamedNode + } + + containerPane.subject = selectedResource + containerPane.browserContext = this.browserContext + containerPane.outliner = this.browserContext?.getOutliner(this.browserContext?.dom) as StoragePaneOutliner + + this.fullView.replaceChildren(containerPane) + } + + private renderAccessDeniedScreen (targetView: HTMLElement) { + const message = document.createElement('div') + message.className = 'storage-pane-access-denied-message storage-container-pane-empty-message' + + const body = document.createElement('p') + body.className = 'storage-pane-access-denied-message-body storage-container-pane-empty-message-body' + body.textContent = 'You do not have access to the contents of this container.' + + message.appendChild(body) + targetView.replaceChildren(message) + } + + private isAccessDeniedError (error: unknown) { + const status = typeof error === 'object' && error !== null + ? (error as { status?: number, response?: { status?: number } }).status ?? (error as { response?: { status?: number } }).response?.status + : undefined + + return status === 401 || status === 403 + } + + private async showResourceInFullView (selectedResource: NamedNode) { + try { + if (!this.fullView) { + return + } + + const outliner = this.browserContext?.getOutliner(this.browserContext?.dom) as StoragePaneOutliner | undefined + + try { + await this.store.fetcher.load(selectedResource) + } catch (_error) { + if (this.isAccessDeniedError(_error)) { + this.renderAccessDeniedScreen(this.fullView) + } + return + } + + const isContainer = solidLogicSingleton.resource.isContainer(selectedResource) + + if (isContainer) { + if (containerHasIndexDocument(this.store, selectedResource)) { + const indexThing = getContainerIndexThing(this.store, selectedResource) + this.fullView.replaceChildren() + outliner?.GotoSubject(indexThing, true, undefined, false, undefined, this.fullView) + return + } + + this.renderContainerPaneInFullView(selectedResource) + return + } + + this.fullView.replaceChildren() + outliner?.GotoSubject(selectedResource, true, undefined, false, undefined, this.fullView) + } catch (error) { + log.error('Unable to render selected resource in full view: ' + error) + } + } + + private async showSelectedPaneInContentView (selectedResource: NamedNode, selectedPaneName: string) { + try { + if (!this.contentView || !this.browserContext) { + return + } + + const relevantPanes = await getRelevantPanes(selectedResource, this.browserContext) + const requestedPane = byName(selectedPaneName) + const selectedPane = requestedPane ?? getRelevantPane(relevantPanes, selectedResource) + + if (!selectedPane) { + await this.showResourceInFullView(selectedResource) + return + } + + const paneElement = selectedPane.render(selectedResource, this.browserContext) + const provider = document.createElement('file-explorer-provider') as HTMLElement & { + context?: DataBrowserContext | null + subjectUri?: string + relevantPanes?: PaneDefinition[] + pane?: PaneDefinition + paneRenderOptions?: Record + showHeader?: boolean + openPane?: (subject: NamedNode, paneName: string) => void + onBack?: () => void + handleAccessClick?: () => void + } + + provider.context = this.browserContext + provider.subjectUri = selectedResource.uri + provider.relevantPanes = relevantPanes + provider.pane = selectedPane + provider.paneRenderOptions = {} + provider.showHeader = false + provider.onBack = () => { + const parentSubjectUri = this.fileExplorerContext?.subjectUri + if (parentSubjectUri) { + this.storageContext.selectResource(this.store.sym(parentSubjectUri)) + } + } + provider.handleAccessClick = () => { + this.storageContext.selectResource(selectedResource, 'sharing') + } + provider.openPane = (paneSubject: NamedNode, paneName: string) => { + this.storageContext.selectResource(paneSubject, paneName) + } + + paneElement.classList.add('paneDiv') + this.contentView.replaceChildren(provider) + provider.appendChild(paneElement) + } catch (error) { + log.error('Unable to render selected pane: ' + error) + } + } + + private async showSelectedPaneInFullView (selectedResource: NamedNode, selectedPaneName: string) { + try { + if (!this.fullView || !this.browserContext) { + return + } + + const relevantPanes = await getRelevantPanes(selectedResource, this.browserContext) + const requestedPane = byName(selectedPaneName) + const selectedPane = requestedPane ?? getRelevantPane(relevantPanes, selectedResource) + + if (!selectedPane) { + await this.showResourceInFullView(selectedResource) + return + } + + const paneElement = selectedPane.render(selectedResource, this.browserContext) + const provider = document.createElement('file-explorer-provider') as HTMLElement & { + context?: DataBrowserContext | null + subjectUri?: string + relevantPanes?: PaneDefinition[] + pane?: PaneDefinition + paneRenderOptions?: Record + showHeader?: boolean + openPane?: (subject: NamedNode, paneName: string) => void + onBack?: () => void + handleAccessClick?: () => void + } + + provider.context = this.browserContext + provider.subjectUri = selectedResource.uri + provider.relevantPanes = relevantPanes + provider.pane = selectedPane + provider.paneRenderOptions = {} + provider.showHeader = true + provider.onBack = () => { + const parentSubjectUri = this.fileExplorerContext?.subjectUri + if (parentSubjectUri) { + this.storageContext.selectResource(this.store.sym(parentSubjectUri)) + } + } + provider.handleAccessClick = () => { + this.storageContext.selectResource(selectedResource, 'sharing') + } + provider.openPane = (paneSubject: NamedNode, paneName: string) => { + this.storageContext.selectResource(paneSubject, paneName) + } + + paneElement.classList.add('paneDiv') + this.fullView.replaceChildren(provider) + provider.appendChild(paneElement) + } catch (error) { + log.error('Unable to render selected pane in full view: ' + error) + } + } + + private async showResourceInContentView (selectedResource: NamedNode) { + try { + if (this.contentView) { + await renderSelectedResourceInContentView({ + store: this.store, + selectedResource, + contentView: this.contentView, + outliner: this.browserContext?.getOutliner(this.browserContext?.dom) as StoragePaneOutliner, + renderContainerPane: this.renderContainerPane.bind(this), + renderAccessDeniedView: () => this.renderAccessDeniedScreen(this.contentView as HTMLElement), + }) + } + } catch (error) { + log.error('Unable to render selected resource: ' + error) + } + } + + private getStatusArea = () => this.statusArea + + render () { + const fullScreenMode = (this.storageContext.selectedPaneName === 'resource' || this.storageContext.selectedPaneName === 'internal') && this.browserContext + + if (fullScreenMode) { + return html` +
    + ` + } + + return html` + +
    + +
    + +
    + +
    +
    + ` + } +} diff --git a/src/components/storage-pane-view/index.ts b/src/components/storage-pane-view/index.ts new file mode 100644 index 0000000..34fccca --- /dev/null +++ b/src/components/storage-pane-view/index.ts @@ -0,0 +1,2 @@ +export * from '../storage-provider/context' +export { default } from './StoragePaneView' \ No newline at end of file diff --git a/src/components/storage-provider/StorageProvider.ts b/src/components/storage-provider/StorageProvider.ts new file mode 100644 index 0000000..2564c8b --- /dev/null +++ b/src/components/storage-provider/StorageProvider.ts @@ -0,0 +1,250 @@ +import type { PropertyValues } from 'lit' +import { html } from 'lit' +import { provide, consume } from '@lit/context' +import { customElement, property, state } from 'lit/decorators.js' +import { NamedNode } from 'rdflib' +import { WebComponent } from 'solid-ui' +import type { DataBrowserContext } from 'pane-registry' +import { fileExplorerContext, type FileExplorerContext, storeContext, DEFAULT_STORE } from 'solid-ui' +import { solidLogicSingleton } from 'solid-logic' +import type { LiveStore } from 'rdflib' +import { DEFAULT_STORAGE_CONTEXT, storageContext, type StorageContext } from './context' +import { DEFAULT_DISCOVER_CLASS } from 'solid-ui' + +@customElement('storage-provider') +export default class StorageProvider extends WebComponent { + @property({ attribute: false }) + accessor browserContext: DataBrowserContext | null = null + + @property({ attribute: false }) + accessor subject: NamedNode | undefined = undefined + + @consume({ context: storeContext, subscribe: true }) + accessor store: LiveStore = DEFAULT_STORE + + @consume({ context: fileExplorerContext, subscribe: true }) + accessor parentFileExplorerContext: FileExplorerContext = undefined as unknown as FileExplorerContext + + @state() + accessor selectedResource: NamedNode | undefined = undefined + + @state() + accessor selectedPaneName: string | undefined = undefined + + @state() + accessor view: 'grid' | 'list' = 'grid' + + @state() + accessor searchQuery: string = '' + + @state() + accessor history: NamedNode[] = [] + + @state() + accessor resourceRevision = 0 + + @provide({ context: storageContext }) + accessor storageContext: StorageContext = { + ...DEFAULT_STORAGE_CONTEXT + } + + @provide({ context: fileExplorerContext }) + accessor fileExplorerContext: FileExplorerContext = { subjectUri: this.currentSubject?.uri } + + private get currentSubject (): NamedNode | undefined { + if (this.subject) { + return this.subject + } + + const subjectUri = this.parentFileExplorerContext?.subjectUri + return subjectUri ? this.store.sym(subjectUri) : undefined + } + + private selectResource = (resource: NamedNode, paneName?: string) => { + const currentResource = this.selectedResource ?? this.currentSubject + const selectedPaneName = paneName ?? (this.selectedPaneName === 'resource' || this.selectedPaneName === 'internal' + ? this.selectedPaneName + : undefined) + + if (currentResource && !currentResource.sameTerm(resource)) { + this.history = [...this.history, currentResource] + } + + this.selectedResource = resource + this.selectedPaneName = selectedPaneName + this.searchQuery = '' + this.refreshStorageContextValue() + } + + private deleteResource = async (resource: NamedNode) => { + const isInTrash = resource.dir()?.uri.endsWith('/Trash/') ?? false + + if (isInTrash) { + await solidLogicSingleton.resource.deleteResourceAndTypeIndexIfExists(resource) + } else { + await solidLogicSingleton.resource.moveToTrash(resource) + } + + const sourceContainer = resource.dir() ?? this.currentSubject + if (sourceContainer) { + try { + await this.store.fetcher.load(sourceContainer, { force: true, clearPreviousData: true }) + } catch (_error) { + // Keep the UI responsive even if the source container reload fails. + } + } + + const selectedResource = this.selectedResource + const deletedResourceUri = resource.uri.endsWith('/') ? resource.uri : `${resource.uri}/` + const selectedResourceWasDeleted = selectedResource?.sameTerm(resource) || + selectedResource?.uri.startsWith(deletedResourceUri) + + this.history = this.history.filter((historyResource) => + !historyResource.sameTerm(resource) && !historyResource.uri.startsWith(deletedResourceUri) + ) + + if (selectedResourceWasDeleted) { + this.selectedResource = resource.dir() ?? this.currentSubject + this.selectedPaneName = undefined + } + + this.resourceRevision += 1 + this.refreshStorageContextValue() + } + + private moveResource = async (resource: NamedNode, targetContainer: NamedNode) => { + if (!this.store || !targetContainer) { + return + } + + const sourceIsContainer = solidLogicSingleton.resource.isContainer(resource) + const sourceUriWithoutTrailingSlash = resource.uri.endsWith('/') ? resource.uri.slice(0, -1) : resource.uri + const resourceName = sourceUriWithoutTrailingSlash.substring(sourceUriWithoutTrailingSlash.lastIndexOf('/') + 1) + const targetUrl = sourceIsContainer + ? `${targetContainer.uri}${resourceName}/` + : `${targetContainer.uri}${resourceName}` + + if (resource.sameTerm(targetContainer) || resource.dir()?.sameTerm(targetContainer)) { + return + } + + if (sourceIsContainer && targetContainer.uri.startsWith(resource.uri)) { + return + } + + await solidLogicSingleton.resource.moveResource(resource, targetUrl) + + const sourceContainer = resource.dir() + const containersToReload = [sourceContainer, targetContainer].filter( + (container): container is NamedNode => Boolean(container) + ) + + if (containersToReload.length > 0) { + await Promise.all(containersToReload.map(async (container) => { + try { + await this.store.fetcher.load(container, { force: true, clearPreviousData: true }) + } catch (_error) { + // Keep the move path resilient if a container reload fails. + } + })) + } + + if (this.selectedResource?.sameTerm(resource)) { + this.selectedResource = targetContainer + this.selectedPaneName = undefined + } + + this.resourceRevision += 1 + this.refreshStorageContextValue() + } + + private setView = (view: 'grid' | 'list') => { + this.view = view + this.refreshStorageContextValue() + } + + private setSearchQuery = (query: string) => { + this.searchQuery = query + this.refreshStorageContextValue() + } + + goBack = () => { + if (this.history.length > 0) { + const previousHistory = [...this.history] + const previousResource = previousHistory.pop() + + this.history = previousHistory + this.selectedResource = previousResource + this.selectedPaneName = undefined + this.refreshStorageContextValue() + } + } + + private refreshStorageContextValue () { + this.storageContext = { + selectedResource: this.selectedResource ?? this.currentSubject, + selectedPaneName: this.selectedPaneName, + selectResource: this.selectResource, + deleteResource: this.deleteResource, + moveResource: this.moveResource, + resourceRevision: this.resourceRevision, + view: this.view, + setView: this.setView, + searchQuery: this.searchQuery, + setSearchQuery: this.setSearchQuery, + history: this.history + } + } + + private refreshFileExplorerContextValue () { + const currentSubjectDiscoverClass = this.currentSubject ? solidLogicSingleton.resource.getContainerMintClass(this.currentSubject) : undefined + + this.fileExplorerContext = { + subjectUri: this.currentSubject?.uri, + onBack: this.goBack, + deleteResource: this.deleteResource, + resourceRevision: this.resourceRevision, + discoverClass: currentSubjectDiscoverClass ?? this.parentFileExplorerContext?.discoverClass ?? DEFAULT_DISCOVER_CLASS + } + } + + // Legacy panes rendered below this provider are styled by global stylesheets, + // which cannot cross a shadow boundary. + protected createRenderRoot () { + return this + } + + protected willUpdate (changedProperties: PropertyValues) { + super.willUpdate(changedProperties) + + const subjectChanged = changedProperties.has('subject') + const parentFileExplorerContextChanged = changedProperties.has('parentFileExplorerContext') + const selectedResourceChanged = changedProperties.has('selectedResource') + const selectedPaneNameChanged = changedProperties.has('selectedPaneName') + // const resourceRevisionChanged = changedProperties.has('resourceRevision') + const storageContextShouldRefresh = subjectChanged || parentFileExplorerContextChanged || selectedResourceChanged || selectedPaneNameChanged + const fileExplorerContextShouldRefresh = subjectChanged || parentFileExplorerContextChanged || changedProperties.has('resourceRevision') + + if (subjectChanged) { + if (this.selectedPaneName !== undefined) { + this.selectedPaneName = undefined + } + } + + if (storageContextShouldRefresh) { + this.refreshStorageContextValue() + } + + if (fileExplorerContextShouldRefresh) { + this.refreshFileExplorerContextValue() + } + } + + render () { + return html` + + ` + } +} diff --git a/src/components/storage-provider/context.ts b/src/components/storage-provider/context.ts new file mode 100644 index 0000000..308dd55 --- /dev/null +++ b/src/components/storage-provider/context.ts @@ -0,0 +1,34 @@ +import { createContext } from '@lit/context' +import type { NamedNode } from 'rdflib' + +export type ViewMode = 'grid' | 'list' + +export interface StorageContext { + selectedResource: NamedNode | undefined, + selectedPaneName: string | undefined, + selectResource: (resource: NamedNode, paneName?: string) => void, + deleteResource: (resource: NamedNode) => Promise, + moveResource: (resource: NamedNode, targetContainer: NamedNode) => Promise, + resourceRevision: number, + view: ViewMode, + setView: (view: ViewMode) => void, + searchQuery: string, + setSearchQuery: (query: string) => void, + history: NamedNode[] +} + +export const DEFAULT_STORAGE_CONTEXT: StorageContext = { + selectedResource: undefined, + selectedPaneName: undefined, + selectResource: () => {}, + deleteResource: async () => {}, + moveResource: async () => {}, + resourceRevision: 0, + view: 'grid', + setView: (view: ViewMode) => {}, + searchQuery: '', + setSearchQuery: (query: string) => {}, + history: [] +} + +export const storageContext = createContext(Symbol('storage')) \ No newline at end of file diff --git a/src/components/storage-provider/index.ts b/src/components/storage-provider/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/components/storage-resource-sidebar/StorageResourceSidebar.styles.css b/src/components/storage-resource-sidebar/StorageResourceSidebar.styles.css new file mode 100644 index 0000000..98fbdaa --- /dev/null +++ b/src/components/storage-resource-sidebar/StorageResourceSidebar.styles.css @@ -0,0 +1,152 @@ +:host { + display: flex; + flex-direction: column; + height: 100%; + min-height: 0; + overflow: hidden; + + aside { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; + overflow: auto; + box-sizing: border-box; + padding: 17px 10px 0; + border-radius: 5px 0 0 5px; + background: var(--neutral-50, #FAFAFA); + /* Firefox */ + scrollbar-width: thin; + scrollbar-color: transparent transparent; + } + + /* Chrome, Edge, Safari */ + aside::-webkit-scrollbar { + width: 6px; + } + + aside::-webkit-scrollbar-track { + background: transparent; + } + + aside::-webkit-scrollbar-thumb { + background: transparent; + border-radius: 999px; + } + + aside:hover { + scrollbar-color: #c7cbd1 transparent; + } + + aside:hover::-webkit-scrollbar-thumb { + background: #c7cbd1; + } + + .resource-tree { + flex: 1 1 auto; + padding-bottom: 0; + margin: 0; + width: 100%; + } + + .resource-item { + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + align-self: stretch; + } + + .resource-item.drop-target > .resource-row, + .resource-item.drop-target > .resource-row-special { + outline: 2px solid var(--solid-ui-color-lavender-300, #C4B5FD); + outline-offset: -2px; + background: color-mix(in srgb, var(--solid-ui-color-lavender-200, #DDD6FE) 35%, white); + border-radius: 5px; + } + + .resource-group { + position: relative; + display: flex; + padding: 10px 5px 10px 25px; + flex-direction: column; + align-items: flex-start; + gap: 4px; + align-self: stretch; + } + + .resource-group::before { + content: ''; + position: absolute; + left: 20px; + top: 10px; + bottom: 10px; + border-left: 1px solid var(--solid-ui-color-gray-200, #E5E7EB); + } + + storage-creation-area { + display: block; + align-self: stretch; + flex: 0 0 auto; + margin-top: 0; + padding: 10px; + } + + .resource-row { + color: var(--solid-ui-color-gray-500, #6A7282); + display: flex; + align-items: center; + gap: 6px; + font-size: var(--solid-ui-font-size-sm, .875rem); + width: 100%; + box-sizing: border-box; + padding: 5px 15px 5px 10px; + gap: 10px; + cursor: grab; + } + + .resource-item.selected > .resource-row, + .resource-item.selected > .resource-row-special { + background: var(--solid-ui-color-primary-selected); + color: var(--solid-ui-color-primary); + border-radius: 5px; + } + + .resource-item.selected icon-lucide-chevron-right, + .resource-item.selected icon-lucide-circle-small { + color: currentColor; + stroke: currentColor; + } + + .resource-row-special icon-lucide-folder, + .resource-row-special icon-lucide-globe, + .resource-row-special icon-lucide-trash-2 { + width: 16px; + height: 16px; + flex-shrink: 0; + } + + icon-lucide-circle-small { + width: 6px; + height: 6px; + flex-shrink: 0; + aspect-ratio: 1/1; + stroke-width: 1px; + stroke: var(--solid-ui-color-gray-500, #6A7282); + } + + icon-lucide-chevron-right, + icon-lucide-chevron-down { + flex: 0 0 16px; + width: 16px; + height: 16px; + /* The lucide icon strokes with currentColor, so fill has no effect. */ + color: var(--solid-ui-color-gray-300, #D1D5DC); + cursor: pointer; + transition: transform 120ms ease; + } + + li[data-expanded='true'] > .resource-row > icon-lucide-chevron-right { + transform: rotate(90deg); + } +} diff --git a/src/components/storage-resource-sidebar/StorageResourceSidebar.ts b/src/components/storage-resource-sidebar/StorageResourceSidebar.ts new file mode 100644 index 0000000..00132e3 --- /dev/null +++ b/src/components/storage-resource-sidebar/StorageResourceSidebar.ts @@ -0,0 +1,467 @@ +import { customElement, DEFAULT_STORE, FileExplorerContext, fileExplorerContext, storeContext, utils, WebComponent } from 'solid-ui' +import { html, nothing } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { property, state } from 'lit/decorators.js' +import type { PropertyValues } from 'lit' +import styles from './StorageResourceSidebar.styles.css' +import type { NamedNode } from 'rdflib' +import type { Resource, ResourceMap } from '../../types' +import '~icons/lucide/chevron-right' +import '~icons/lucide/folder' +import '~icons/lucide/globe' +import '~icons/lucide/circle-small' +import '~icons/lucide/trash-2' +import '../storage-creation-area' +import { consume } from '@lit/context' +import { DEFAULT_STORAGE_CONTEXT, StorageContext, storageContext } from '../storage-provider/context' +import { LiveStore } from 'rdflib' +import { getDraggedResourceUri, getResourcesForContainer, handleContainerDragOver, handleContainerDrop, handleResourceChevronClick, loadResourcesForContainer, setDraggedResource } from '../../helpers' + +@customElement('storage-resource-sidebar') +export default class StorageResourceSidebar extends WebComponent { + static styles = styles + + @property({ attribute: false }) + accessor dom: HTMLDocument | null = null + + @consume({ context: storeContext, subscribe: true }) + accessor store: LiveStore = DEFAULT_STORE + + @consume({ context: fileExplorerContext, subscribe: true }) + accessor fileExplorerContext: FileExplorerContext = undefined as unknown as FileExplorerContext + + @consume({ context: storageContext, subscribe: true }) + accessor storageContext: StorageContext = DEFAULT_STORAGE_CONTEXT + + @state() + accessor resources: ResourceMap = new Map() + + @state() + accessor expandedContainers: Set = new Set() + + @state() + accessor homeExpanded = true + + @state() + accessor dropTargetResourceUri: string | undefined = undefined + + private get currentSubject (): NamedNode | undefined { + if (!this.fileExplorerContext?.subjectUri || this.store === DEFAULT_STORE) { + return undefined + } + + return this.store.sym(this.fileExplorerContext.subjectUri) + } + + private get currentSelectedResource (): NamedNode | undefined { + return this.storageContext.selectedResource ?? this.currentSubject + } + + private syncResources = async () => { + const subject = this.currentSubject + + if (!this.store || !subject) { + return + } + + const loadedResources = await loadResourcesForContainer(this.store, subject) + this.resources = loadedResources + } + + protected firstUpdated () { + void this.syncResources() + } + + private async expandContainer (resource: Resource) { + if (!resource.isContainer) { + return + } + + if (this.store?.fetcher?.load) { + await this.store.fetcher.load(resource.subject) + } + + const nextExpandedContainers = new Set(this.expandedContainers) + if (nextExpandedContainers.has(resource.id)) { + nextExpandedContainers.delete(resource.id) + } else { + nextExpandedContainers.add(resource.id) + } + + this.expandedContainers = nextExpandedContainers + } + + private isSelectedResource (resource: Resource) { + return this.currentSelectedResource?.sameTerm(resource.subject) ?? false + } + + private isPublicResource (resource: Resource) { + return utils.label(resource.subject).toLowerCase() === 'public' + } + + private isTrashResource (resource: Resource) { + return utils.label(resource.subject).toLowerCase() === 'trash' + } + + private getHomeResource (): Resource | null { + const subject = this.currentSubject + + if (!subject) { + return null + } + + return { + id: subject.value, + subject, + parentId: null, + isContainer: true, + } + } + + private renderSpecialRootItem ( + label: string, + icon: 'folder' | 'globe' | 'trash', + selected: boolean, + expanded: boolean, + selectItem: () => void, + toggleExpanded: () => void, + children: unknown, + resource: Resource | null, + ) { + return html` +
  • this.onContainerDragOver(resource, event) : undefined} + @dragleave=${() => { this.dropTargetResourceUri = undefined }} + @drop=${resource ? (event: DragEvent) => this.onContainerDrop(resource, event) : undefined} + > +
    setDraggedResource(event, resource.subject) : undefined} + @click=${selectItem} + @keydown=${(event: KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + selectItem() + } + }} + > + handleResourceChevronClick(event, resource, this.storageContext.selectResource, toggleExpanded)} + @click=${(event: MouseEvent) => { + event.preventDefault() + event.stopPropagation() + }} + > + ${icon === 'folder' + ? html`` + : icon === 'globe' + ? html`` + : html``} + ${label} +
    + ${expanded ? children : nothing} +
  • + ` + } + + private renderTrashResource (resource: Resource) { + const selected = this.isSelectedResource(resource) + const isExpanded = this.expandedContainers.has(resource.id) + const children = resource.isContainer && isExpanded + ? getResourcesForContainer(this.store, resource.subject) + : null + + return html` +
  • this.onContainerDragOver(resource, event)} + @dragleave=${() => { this.dropTargetResourceUri = undefined }} + @drop=${(event: DragEvent) => this.onContainerDrop(resource, event)} + > +
    setDraggedResource(event, resource.subject)} + @click=${() => this.selectResource(resource)} + @keydown=${(event: KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + this.selectResource(resource) + } + }} + > + handleResourceChevronClick(event, resource, this.storageContext.selectResource, () => { void this.expandContainer(resource) })} + @click=${(event: MouseEvent) => { + event.preventDefault() + event.stopPropagation() + }} + > + + Trash +
    + ${children ? this.renderResourceGroup(children, false) : nothing} +
  • + ` + } + + private renderPublicResource (resource: Resource) { + const selected = this.isSelectedResource(resource) + const isExpanded = this.expandedContainers.has(resource.id) + const children = resource.isContainer && isExpanded + ? getResourcesForContainer(this.store, resource.subject) + : null + + return html` +
  • this.onContainerDragOver(resource, event)} + @dragleave=${() => { this.dropTargetResourceUri = undefined }} + @drop=${(event: DragEvent) => this.onContainerDrop(resource, event)} + > +
    setDraggedResource(event, resource.subject)} + @click=${() => this.selectResource(resource)} + @keydown=${(event: KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + this.selectResource(resource) + } + }} + > + handleResourceChevronClick(event, resource, this.storageContext.selectResource, () => { void this.expandContainer(resource) })} + @click=${(event: MouseEvent) => { + event.preventDefault() + event.stopPropagation() + }} + > + + Public +
    + ${children ? this.renderResourceGroup(children, false) : nothing} +
  • + ` + } + + private selectResource (resource: Resource) { + this.storageContext.selectResource(resource.subject) + } + + private onContainerDragOver (resource: Resource, event: DragEvent) { + if (!resource.isContainer) { + return + } + + const draggedResourceUri = getDraggedResourceUri(event.dataTransfer) + if (draggedResourceUri) { + event.preventDefault() + event.stopPropagation() + event.dataTransfer!.dropEffect = 'move' + this.dropTargetResourceUri = resource.id + return + } + + handleContainerDragOver(event, this.store, resource.subject) + } + + private onContainerDrop (resource: Resource, event: DragEvent) { + if (!resource.isContainer) { + return + } + + const draggedResourceUri = getDraggedResourceUri(event.dataTransfer) + + if (draggedResourceUri && this.isTrashResource(resource)) { + event.preventDefault() + event.stopPropagation() + void (async () => { + await this.storageContext.deleteResource(this.store.sym(draggedResourceUri)) + this.dropTargetResourceUri = undefined + await this.syncResources() + })() + return + } + + if (draggedResourceUri) { + event.preventDefault() + event.stopPropagation() + void (async () => { + await this.storageContext.moveResource(this.store.sym(draggedResourceUri), resource.subject) + this.dropTargetResourceUri = undefined + await this.syncResources() + })() + return + } + + const handled = handleContainerDrop(event, this.store, resource.subject, () => { void this.syncResources() }) + + if (handled) { + this.selectResource(resource) + } + } + + private renderResourceGroup (resources: ResourceMap, isRoot: boolean) { + return this.renderResourceGroupFromList([...resources.values()], isRoot) + } + + private renderResourceGroupFromList (resources: Resource[], isRoot: boolean) { + const orderedResources = [...resources] + const publicResourceIndex = isRoot + ? orderedResources.findIndex((resource) => this.isPublicResource(resource)) + : -1 + const publicResource = publicResourceIndex >= 0 + ? orderedResources.splice(publicResourceIndex, 1)[0] + : null + const trashResourceIndex = isRoot + ? orderedResources.findIndex((resource) => this.isTrashResource(resource)) + : -1 + const trashResource = trashResourceIndex >= 0 + ? orderedResources.splice(trashResourceIndex, 1)[0] + : null + const homeResource = this.getHomeResource() + + return html` +
      + ${isRoot + ? this.renderSpecialRootItem( + 'Home', + 'folder', + homeResource ? this.isSelectedResource(homeResource) : false, + this.homeExpanded, + () => { + if (homeResource) { + this.selectResource(homeResource) + } + }, + () => { this.homeExpanded = !this.homeExpanded }, + this.homeExpanded ? this.renderResourceGroupFromList(orderedResources, false) : nothing, + homeResource + ) + : repeat( + orderedResources, + (resource) => resource.id, + (resource) => this.renderResourceItem(resource) + )} + ${isRoot && publicResource ? this.renderPublicResource(publicResource) : nothing} + ${isRoot && trashResource ? this.renderTrashResource(trashResource) : nothing} +
    + ` + } + + private renderResourceItem (resource: Resource) { + const selected = this.isSelectedResource(resource) + const isExpanded = this.expandedContainers.has(resource.id) + const children = resource.isContainer && isExpanded + ? getResourcesForContainer(this.store, resource.subject) + : null + + return html` +
  • this.onContainerDragOver(resource, event)} + @drop=${(event: DragEvent) => this.onContainerDrop(resource, event)} + > +
    setDraggedResource(event, resource.subject)} + @click=${() => this.selectResource(resource)} + @keydown=${(event: KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + this.selectResource(resource) + } + }} + > + handleResourceChevronClick(event, resource, this.storageContext.selectResource, () => { void this.expandContainer(resource) })} + @click=${(event: MouseEvent) => { + event.preventDefault() + event.stopPropagation() + }} + > + + ${utils.label(resource.subject)} +
    + ${children ? this.renderResourceGroup(children, false) : nothing} +
  • + ` + } + + protected willUpdate (changedProperties: PropertyValues) { + super.willUpdate(changedProperties) + + const previousStorageContext = changedProperties.get('storageContext') as StorageContext | undefined + const resourceRevisionChanged = changedProperties.has('storageContext') && + previousStorageContext?.resourceRevision !== this.storageContext.resourceRevision + + if ( + changedProperties.has('store') || + changedProperties.has('fileExplorerContext') || + resourceRevisionChanged + ) { + void this.syncResources() + } + } + + render () { + return html` + + + ` + } +} diff --git a/src/components/storage-resource-sidebar/index.ts b/src/components/storage-resource-sidebar/index.ts new file mode 100644 index 0000000..4bb4895 --- /dev/null +++ b/src/components/storage-resource-sidebar/index.ts @@ -0,0 +1 @@ +export { default } from './StorageResourceSidebar' diff --git a/src/folderPane.ts b/src/folderPane.ts index ec595be..86c034b 100644 --- a/src/folderPane.ts +++ b/src/folderPane.ts @@ -3,13 +3,14 @@ ** This outline pane lists the members of a folder */ -import { authn } from 'solid-logic' -import * as UI from 'solid-ui' import './styles/folderPane.css' import './styles/utilities.css' +import './components/storage-provider/StorageProvider' +import './components/storage-pane-view/StoragePaneView' +import { icons, ns } from 'solid-ui' export default { - icon: UI.icons.iconBase + 'noun_973694_expanded.svg', + icon: icons.iconBase + 'noun_973694_expanded.svg', name: 'folder', // Create a new folder in a Solid system, @@ -26,151 +27,37 @@ export default { return kb.fetcher .webOperation('PUT', newInstance.uri) .then(function () { - console.log('New container created: ' + newInstance.uri) return newPaneOptions }) }, label: function (subject, context) { const kb = context.session.store - const n = kb.each(subject, UI.ns.ldp('contains')).length + const n = kb.each(subject, ns.ldp('contains')).length if (n > 0) { return 'Contents (' + n + ')' // Show how many in hover text } - if (kb.holds(subject, UI.ns.rdf('type'), UI.ns.ldp('Container'))) { + if (kb.holds(subject, ns.rdf('type'), ns.ldp('Container'))) { // It is declared as being a container return 'Container (0)' } return null // Suppress pane otherwise }, - // Render a file folder in a LDP/solid system render: function (subject, context) { - function noHiddenFiles (obj) { - // @@ This hiddenness should actually be server defined - const pathEnd = obj.uri.slice(obj.dir().uri.length) - return !( - pathEnd.startsWith('.') || - pathEnd.endsWith('.acl') || - pathEnd.endsWith('~') - ) - } - - function refresh () { - let objs = kb.each(subject, UI.ns.ldp('contains')).filter(noHiddenFiles) - objs = objs.map(obj => [UI.utils.label(obj).toLowerCase(), obj]) - objs.sort() // Sort by label case-insensitive - objs = objs.map(pair => pair[1]) - UI.utils.syncTableToArray(mainTable, objs, function (obj) { - const st = kb.statementsMatching(subject, UI.ns.ldp('contains'), obj)[0] - const defaultpropview = outliner.VIEWAS_boring_default - const tr = outliner.propertyTR(dom, st, false) - const predicateCell = tr.firstChild as HTMLElement - predicateCell.textContent = '' // Was initialized to 'Contains' - predicateCell.classList.add('folderPanePredicateCell') - tr.appendChild( - outliner.outlineObjectTD(obj, defaultpropview, undefined, st) - ) - // UI.widgets.makeDraggable(tr, obj) - return tr - }) - } - const dom = context.dom - const outliner = context.getOutliner(dom) - const kb = context.session.store - let mainTable // This is a live synced table const div = dom.createElement('div') - div.classList.add('instancePane', 'folderPaneInstancePane') - - const thisDir = subject.uri.endsWith('/') ? subject.uri : subject.uri + '/' - const indexThing = kb.sym(thisDir + 'index.ttl#this') - if (kb.holds(subject, UI.ns.ldp('contains'), indexThing.doc())) { - console.log( - 'View of folder will be view of indexThing. Loading ' + indexThing - ) - const packageDiv = div.appendChild(dom.createElement('div')) - packageDiv.classList.add('folderPanePackageDiv') - kb.fetcher.load(indexThing.doc()).then(function () { - mainTable = packageDiv.appendChild(dom.createElement('table')) - mainTable.classList.add('folderPaneMainTable') - context - .getOutliner(dom) - .GotoSubject(indexThing, true, undefined, false, undefined, mainTable) - }) - return div - } else { - mainTable = div.appendChild(dom.createElement('table')) - mainTable.classList.add('folderPaneMainTable') - mainTable.refresh = refresh - refresh() - // addDownstreamChangeListener is a high level function which when someone else changes the resource, - // reloads it into the kb, then must call addDownstreamChangeListener to be able to update the folder pane. - kb.updater.addDownstreamChangeListener(subject, refresh) // Update store and call me if folder changes - } - - // Allow user to create new things within the folder - const creationDiv = div.appendChild(dom.createElement('div')) - creationDiv.classList.add('folderPaneCreationDiv') - const me = authn.currentUser() // @@ respond to login events - if (!me) { - return div // Cannot create new things without being logged in - } - const creationContext: UI.createTypes.CreateContext = { - folder: subject, - div: creationDiv, - dom: dom, - statusArea: creationDiv, - me: me - } - creationContext.refreshTarget = mainTable - UI.login - .filterAvailablePanes(context.session.paneRegistry.list) - .then(function (relevantPanes) { - UI.create.newThingUI(creationContext, context, relevantPanes) // Have to pass panes down newUI - - UI.aclControl.preventBrowserDropEvents(dom) - - const explicitDropIcon = false - let target - if (explicitDropIcon) { - target = creationDiv.insertBefore( - dom.createElement('img'), - creationDiv.firstChild - ) - target.classList.add('folderPaneExplicitDropIcon') - target.setAttribute('src', UI.icons.iconBase + 'noun_748003.svg') - } else { - target = creationDiv.firstChild // Overload drop target semantics onto the plus sign - } - - if (target instanceof HTMLElement) { - target.classList.add('folderPaneDropTarget') - } + div.classList.add('instancePane', 'storage-pane') + const storageProvider = div.appendChild(dom.createElement('storage-provider')) + storageProvider.browserContext = context + storageProvider.subject = subject - creationDiv.classList.add('folderPaneDropZone') - - // /////////// Allow new file to be Uploaded - UI.widgets.makeDropTarget(creationDiv, null, droppedFileHandler) - }) + // The pane registry is needed to open the internal pane on Alt-click. + // addDownstreamChangeListener is a high level function which when someone else changes the resource, + // reloads it into the kb, then must call addDownstreamChangeListener to be able to update the folder pane. + // SAM need to figure out how to add this line later kb.updater.addDownstreamChangeListener(subject, refresh) // Update store and call me if folder changes return div - - function droppedFileHandler (files) { - UI.widgets.uploadFiles( - kb.fetcher, - files, - subject.uri, - subject.uri, - function (file, uri) { - // A file has been uploaded - const destination = kb.sym(uri) - console.log(' Upload: put OK: ' + destination) - kb.add(subject, UI.ns.ldp('contains'), destination, subject.doc()) - mainTable.refresh() - } - ) - } } } // ends diff --git a/src/helpers.ts b/src/helpers.ts new file mode 100644 index 0000000..afd295e --- /dev/null +++ b/src/helpers.ts @@ -0,0 +1,424 @@ +import { ns, utils, widgets } from 'solid-ui' +import type { NamedNode, Statement } from 'rdflib' +import { solidLogicSingleton } from 'solid-logic' +import type { ContentViewRenderer, Resource, ResourceMap } from './types' + +const draggedResourceMimeType = 'application/x-solidos-resource-uri' + +const hiddenFileSuffixes = ['.acl', '~'] + +function noHiddenFiles (obj) { + // @@ This hiddenness should actually be server defined + const parentUri = obj?.dir?.()?.uri + const uri = obj?.uri + + if (typeof uri !== 'string' || typeof parentUri !== 'string') { + return true + } + + const pathEnd = uri.slice(parentUri.length) + return !pathEnd.startsWith('.') && !hiddenFileSuffixes.some((suffix) => pathEnd.endsWith(suffix)) +} + +function isNamedNode (term): term is NamedNode { + return Boolean(term && typeof term.uri === 'string') +} + +function getResourcesFromSearchQuery (store, query: string): ResourceMap { + if (!store) { + return new Map() + } + + const normalizedQuery = query.trim().toLowerCase() + if (!normalizedQuery) { + return new Map() + } + + const statements = (store.statements || []) as Statement[] + const candidateResources = new Map() + + for (const statement of statements) { + for (const term of [statement.subject, statement.object]) { + if (!isNamedNode(term)) { + continue + } + + if (!noHiddenFiles(term)) { + continue + } + + const label = utils.label(term).toLowerCase() + if (label.includes(normalizedQuery) || term.uri.toLowerCase().includes(normalizedQuery)) { + candidateResources.set(term.value, term) + } + } + } + + const resourceMap = Array.from(candidateResources.values()) + .map(resource => [utils.label(resource).toLowerCase(), resource]) + .sort() + + return new Map(resourceMap.map(pair => [pair[1].value, { + id: pair[1].value, + subject: pair[1], + parentId: pair[1].dir().value, + isContainer: solidLogicSingleton.resource?.isContainer?.(pair[1]) ?? false + }])) +} + +function getResourcesForContainer (store, container: NamedNode): ResourceMap { + if (!store) { + return new Map() + } + + let containedResources = store.each(container, ns.ldp('contains')).filter(noHiddenFiles) + containedResources = containedResources.filter((containedResource, index, allContainedResources) => { + return allContainedResources.findIndex(other => other.sameTerm(containedResource)) === index + }) + containedResources = containedResources.map(containedResource => [utils.label(containedResource).toLowerCase(), containedResource]) + containedResources.sort() + + return new Map(containedResources.map(pair => [pair[1].value, { + id: pair[1].value, + subject: pair[1], + parentId: container.value, + isContainer: solidLogicSingleton.resource?.isContainer?.(pair[1]) ?? false + }])) +} + +async function loadResourcesForContainer (store, container: NamedNode): Promise { + if (!store) { + return new Map() + } + + let resources = getResourcesForContainer(store, container) + + await Promise.all( + Array.from(resources.values()) + .filter((resource) => resource.isContainer) + .map(async (resource) => { + try { + await store.fetcher.load(resource.subject) + } catch (_error) { + // Keep loading best-effort; a missing child should not stop the rest. + } + }) + ) + + resources = getResourcesForContainer(store, container) + return resources +} + +async function loadResourcesForStorage (store, root: NamedNode, onProgress?: (resource: NamedNode) => void): Promise { + if (!store || !root) { + return new Map() + } + + const discoveredResources = new Map() + const visitedContainers = new Set() + const pendingContainers: NamedNode[] = [root] + + while (pendingContainers.length > 0) { + const container = pendingContainers.shift() + + if (!container || visitedContainers.has(container.value)) { + continue + } + + visitedContainers.add(container.value) + + const resources = getResourcesForContainer(store, container) + + for (const resource of resources.values()) { + discoveredResources.set(resource.id, resource) + + if (resource.isContainer && !visitedContainers.has(resource.subject.value)) { + pendingContainers.push(resource.subject) + } + } + } + + return new Map(discoveredResources) +} + +function getContainerIndexThing (store, container: NamedNode): NamedNode { + const folderUri = container.uri.endsWith('/') ? container.uri : container.uri + '/' + return store.sym(folderUri + 'index.ttl#this') +} + +function containerHasMintClassIndexDocument (store, container: NamedNode): boolean { + if (!store || !containerHasIndexDocument(store, container)) { + return false + } + + const indexThing = getContainerIndexThing(store, container) + const mintClassPredicate = ns.ui('mintClass') + + return Boolean( + store.any(indexThing, mintClassPredicate, undefined, indexThing.doc()) || + store.any(indexThing.doc(), mintClassPredicate, undefined, indexThing.doc()) + ) +} + +function canAcceptUploads (store, container: NamedNode): boolean { + return !containerHasMintClassIndexDocument(store, container) +} + +function uploadFilesIntoContainer ( + store, + container: NamedNode, + files: FileList | File[], + onCreated?: (resource: NamedNode) => void +) { + widgets.uploadFiles( + store.fetcher, + files, + container.uri, + container.uri, + (_file, uri) => { + const destination = store.sym(uri) + store.add(container, ns.ldp('contains'), destination, container.doc()) + onCreated?.(destination) + } + ) +} + +function parseDroppedUris (dataTransfer: DataTransfer | null | undefined): string[] { + const types = dataTransfer?.types + + if (!types) { + return [] + } + + const normalizeDroppedUris = (uriText: string) => uriText + .split('\n') + .map(uri => uri.trim()) + .filter(uri => uri && uri[0] !== '#') + + if (Array.from(types).includes('text/uri-list')) { + return normalizeDroppedUris(dataTransfer?.getData('text/uri-list') ?? '') + } + + const plainText = dataTransfer?.getData('text/plain')?.trim() ?? '' + + if (plainText.startsWith('http')) { + return [plainText] + } + + return [] +} + +function getDraggedResourceUri (dataTransfer: DataTransfer | null | undefined): string | undefined { + if (!dataTransfer) { + return undefined + } + + const draggedResourceUri = dataTransfer.getData(draggedResourceMimeType).trim() + if (draggedResourceUri) { + return draggedResourceUri + } + + const [uri] = parseDroppedUris(dataTransfer) + return uri +} + +function setDraggedResource (event: DragEvent, resource: NamedNode) { + const dataTransfer = event.dataTransfer + + if (!dataTransfer) { + return + } + + dataTransfer.effectAllowed = 'move' + dataTransfer.setData(draggedResourceMimeType, resource.uri) + dataTransfer.setData('text/uri-list', resource.uri) + dataTransfer.setData('text/plain', resource.uri) +} + +function addUrisToContainer ( + store, + container: NamedNode, + uris: string[], + onCreated?: (resource: NamedNode) => void +) { + for (const uri of uris) { + const destination = store.sym(uri) + store.add(container, ns.ldp('contains'), destination, container.doc()) + onCreated?.(destination) + } +} + +function handleContainerDrop ( + event: DragEvent, + store, + container: NamedNode, + onCreated?: (resource: NamedNode) => void +) { + event.preventDefault() + event.stopPropagation() + + if (!canAcceptUploads(store, container)) { + return false + } + + const uris = parseDroppedUris(event.dataTransfer) + if (uris.length > 0) { + addUrisToContainer(store, container, uris, onCreated) + return true + } + + const files = event.dataTransfer?.files ?? [] + if (files.length > 0) { + uploadFilesIntoContainer(store, container, files, onCreated) + return true + } + + return false +} + +function handleContainerDragOver (event: DragEvent, store, container: NamedNode) { + if (event.dataTransfer?.types.includes(draggedResourceMimeType)) { + event.preventDefault() + event.stopPropagation() + event.dataTransfer!.dropEffect = 'move' + return true + } + + if (!canAcceptUploads(store, container)) { + return false + } + + event.preventDefault() + event.stopPropagation() + event.dataTransfer!.dropEffect = 'copy' + return true +} + +function isStorageRoot (store, resource: NamedNode): boolean { + if (!store || typeof store.holds !== 'function') return false + + try { + return store.holds(resource, ns.rdf('type'), ns.space('Storage'), resource.doc()) + } catch (_error) { + return false + } +} + +function containerHasIndexDocument (store, container: NamedNode): boolean { + if (!store || typeof store.holds !== 'function') return false + + try { + const indexThing = getContainerIndexThing(store, container) + return store.holds(container, ns.ldp('contains'), indexThing.doc()) + } catch (_error) { + return false + } +} + +async function renderSelectedResourceInContentView ({ + store, + selectedResource, + contentView, + outliner, + renderContainerPane, + renderAccessDeniedView, +}: ContentViewRenderer): Promise { + const isAccessDeniedError = (error: unknown) => { + const status = typeof error === 'object' && error !== null + ? (error as { status?: number, response?: { status?: number } }).status ?? (error as { response?: { status?: number } }).response?.status + : undefined + + return status === 401 || status === 403 + } + + const isContainer = solidLogicSingleton.resource.isContainer(selectedResource) + + if (isContainer) { + try { + await store.fetcher.load(selectedResource) + await loadResourcesForContainer(store, selectedResource) + } catch (_error) { + if (isAccessDeniedError(_error)) { + renderAccessDeniedView?.() + } + return + } + + const hasIndexDocumentAfterLoad = containerHasIndexDocument(store, selectedResource) + + if (hasIndexDocumentAfterLoad) { + const indexThing = getContainerIndexThing(store, selectedResource) + contentView.replaceChildren() + outliner?.GotoSubject(indexThing, true, undefined, false, undefined, contentView) + return + } + renderContainerPane(selectedResource) + return + } + + try { + await store.fetcher.load(selectedResource) + } catch (_error) { + return + } + + contentView.replaceChildren() + outliner?.GotoSubject(selectedResource, true, undefined, false, undefined, contentView) +} + +// Resource tree chevrons should mirror the legacy outline behavior: +// shift opens the resource in the main view, alt opens the internal pane immediately. +function handleResourceChevronClick ( + event: MouseEvent, + resource: Resource | null, + selectResource: (resource: NamedNode, paneName?: string) => void, + toggleExpanded: () => void +) { + if (!resource) { + return + } + + event.preventDefault() + event.stopPropagation() + + if (event.altKey) { + selectResource(resource.subject, 'internal') + return + } + + if (event.shiftKey) { + selectResource(resource.subject, 'resource') + return + } + + if (resource.isContainer) { + toggleExpanded() + } +} + +export { + addUrisToContainer, + canAcceptUploads, + containerHasIndexDocument, + containerHasMintClassIndexDocument, + getContainerIndexThing, + getResourcesForContainer, + getResourcesFromSearchQuery, + getDraggedResourceUri, + loadResourcesForContainer, + loadResourcesForStorage, + handleContainerDragOver, + handleContainerDrop, + handleResourceChevronClick, + isStorageRoot, + noHiddenFiles, + parseDroppedUris, + setDraggedResource, + uploadFilesIntoContainer, + renderSelectedResourceInContentView +} diff --git a/src/styles/containerPane.css b/src/styles/containerPane.css new file mode 100644 index 0000000..21d2965 --- /dev/null +++ b/src/styles/containerPane.css @@ -0,0 +1,389 @@ +/* + * Light-DOM layout for the storage container pane. + * This is the file to edit while the shell still renders into light DOM. + * The matching shadow-DOM version stays in StorageContainerPane.styles.css + * for later transfer back to static styles. + */ + +storage-container-pane { + display: flex; + flex-direction: column; + width: 100%; + box-sizing: border-box; + height: 100%; + overflow: hidden; + min-height: 0; +} + +storage-container-pane > .storage-container-pane-content { + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-height: 0; + width: 100%; + overflow-y: auto; + overflow-x: hidden; + scrollbar-width: thin; + scrollbar-color: transparent transparent; +} + +storage-container-pane > .storage-container-pane-content::-webkit-scrollbar { + width: 6px; +} + +storage-container-pane > .storage-container-pane-content::-webkit-scrollbar-track { + background: transparent; +} + +storage-container-pane > .storage-container-pane-content::-webkit-scrollbar-thumb { + background: transparent; + border-radius: 999px; +} + +storage-container-pane > .storage-container-pane-content:hover { + scrollbar-color: #c7cbd1 transparent; +} + +storage-container-pane > .storage-container-pane-content:hover::-webkit-scrollbar-thumb { + background: #c7cbd1; +} + +storage-container-pane > .storage-container-pane-content > .storage-container-pane-resource-drop-zone { + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-height: 0; + width: 100%; +} + +storage-container-pane > storage-content-view { + order: 0; + flex: 1 1 0%; + min-height: 0; + overflow: auto; +} + +storage-container-pane > .resource-list, +storage-container-pane > .resource-grid { + order: 1; + flex: 0 0 auto; +} + +storage-container-pane .resource-list-header { + display: flex; + padding: 14px 12px; + align-items: center; + justify-content: space-between; + align-self: stretch; + color: var(--solid-ui-color-gray-600, #4A5565); + font-size: var(--solid-ui-font-size-sm, .875rem); + font-weight: 500; + list-style: none; + border-bottom: 1px solid var(--solid-ui-color-gray-200, #E5E7EB); + background: var(--solid-ui-color-white, #FFF); +} + +storage-container-pane > storage-creation-area { + order: 2; +} + +storage-container-pane .resource-list, +storage-container-pane .resource-grid { + display: flex; + flex: 1 1 auto; + width: 100%; + overflow: visible; + min-height: 0; + margin: 0; + padding: 20px; +} + +storage-container-pane .resource-list { + flex-direction: column; +} + +storage-container-pane .resource-list-item { + display: flex; + padding: 14px 12px; + align-items: center; + gap: 10px; + align-self: stretch; + border-bottom: 1px solid var(--solid-ui-color-gray-200, #E5E7EB); + background: var(--solid-ui-color-white, #FFF); +} + +storage-container-pane .resource-list-main { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; + flex: 1 1 auto; +} + +storage-container-pane .resource-list-label { + color: var(--solid-ui-color-gray-700, #364153); + font-size: var(--solid-ui-font-size-md, 1rem); + font-weight: 500; + min-width: 0; + flex: 0 1 auto; +} + +storage-container-pane .resource-list-icon { + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; +} + +storage-container-pane .resource-grid { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: 20px; +} + +storage-container-pane .resource-grid-item { + position: relative; + width: 206px; + height: 145.824px; + flex-shrink: 0; + display: flex; + flex-direction: column; + border-radius: 5px; + background-color: var(--solid-ui-color-white, #FFF); + border: 1px solid var(--solid-ui-color-gray-200, #E5E7EB); +} + +storage-container-pane .resource-grid-icon { + display: flex; + width: 80px; + height: 80px; + padding: 0; + justify-content: center; + align-items: center; + aspect-ratio: 1/1; + margin: 12px auto 0; +} + +storage-container-pane .resource-grid-icon .resource-pane-icon { + width: 80px; + height: 80px; + object-fit: contain; +} + +storage-container-pane .resource-grid-icon--container { + width: 37.282px; + height: 37.282px; + padding: 0 9.32px; + margin: 12px 0 0 12px; + justify-content: flex-start; +} + +storage-container-pane .resource-grid-top { + display: flex; + align-items: flex-start; + justify-content: center; + width: 100%; + min-height: 92px; + position: relative; +} + +storage-container-pane .resource-grid-label { + color: var(--solid-ui-color-gray-500, #6A7282); + font-size: var(--solid-ui-font-size-xs, .75rem); + font-weight: 500; + padding: 0 12px; + min-width: 0; + flex: 1 1 auto; +} + +storage-container-pane .resource-grid-label--container { + color: var(--solid-ui-color-gray-700, #364153); + font-size: var(--solid-ui-font-size-md, 1rem); + font-weight: 500; +} + +storage-container-pane .resource-grid-item-footer { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: auto; + height: 40px; + min-height: 40px; + padding: 0 15px; + border-top: 1px solid var(--solid-ui-color-gray-200, #E5E7EB); + gap: 12px; +} + +storage-container-pane .resource-actions-menu { + display: flex; + align-items: center; + flex: 0 0 auto; + justify-content: flex-end; +} + +storage-container-pane .resource-actions-menu--grid { + position: absolute; + top: 10px; + right: 10px; +} + +storage-container-pane .resource-actions-menu--list { + margin-left: auto; +} + +storage-container-pane .container-member-count { + color: var(--solid-ui-color-gray-500, #6A7282); + font-size: var(--solid-ui-font-size-xs, .75rem); + font-weight: 500; +} + +storage-container-pane icon-lucide-globe { + width: 0.875rem; + height: 0.875rem; + color: var(--blue-400, #51A2FF); + stroke-width: 1.021px; + stroke: var(--blue-400, #51A2FF); +} + +storage-container-pane icon-lucide-lock-keyhole { + width: 0.875rem; + height: 0.875rem; + color: var(--yellow-600, #D08700); + stroke-width: 1.021px; + stroke: var(--yellow-600, #D08700); +} + +storage-container-pane .storage-container-pane-empty-message--search { + min-height: 100%; +} + +storage-container-pane .storage-container-pane-empty-message--centered { + flex: 1 1 auto; + min-height: 0; +} + +storage-container-pane storage-content-view { + display: flex; + width: 100%; + min-height: 0; + flex: 1 1 0%; + overflow: auto; +} + +storage-container-pane storage-creation-area { + margin-top: auto; + align-self: stretch; + width: 100%; + box-sizing: border-box; + flex: 0 0 auto; + padding: 10px 20px; + --storage-creation-area-message-font-size: var(--solid-ui-font-size-sm, .875rem); + --storage-creation-area-padding: 28px 20px; + --storage-creation-icon-size: 26px; + --storage-creation-gap: 10px; + --storage-creation-area-border-radius: 12px; +} + +storage-container-pane icon-lucide-folder, +storage-container-pane icon-lucide-file, +storage-container-pane icon-lucide-globe, +storage-container-pane icon-lucide-lock-keyhole { + width: 15px; + height: 15px; + flex-shrink: 0; +} + +storage-container-pane icon-lucide-folder, +storage-container-pane icon-lucide-file { + color: var(--solid-ui-color-gray-500, #6A7282); + fill: rgba(224, 231, 255, 0.50); + stroke: var(--solid-ui-color-gray-500, #6A7282); + stroke-width: 1.25px; +} + +storage-container-pane .storage-container-pane-empty-message { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + min-height: 100%; + text-align: center; + color: var(--solid-ui-color-gray-700, #364153); +} + +storage-container-pane .storage-container-pane-empty-message-icon { + display: flex; + align-items: center; + justify-content: center; + width: 64px; + height: 64px; + padding: 0 16.785px; + border-radius: 16px; + border: 1px solid var(--solid-ui-color-slate-200, #E2E8F0); + background: var(--solid-ui-color-white, #FFF); +} + +storage-container-pane .storage-container-pane-empty-message-icon icon-lucide-file-box { + width: 32px; + height: 32px; + color: var(--solid-ui-color-gray-300, #D1D5DC); + stroke-width: 2px; +} + +storage-container-pane .storage-container-pane-empty-message-title { + margin: 0; + color: var(--solid-ui-color-gray-900, #030712); + font-size: 1.25rem; + font-weight: 700; + line-height: 1.2; +} + +storage-container-pane .storage-container-pane-empty-message-body { + max-width: 24rem; + margin: 0; + color: var(--solid-ui-color-gray-600, #4A5565); + font-size: var(--solid-ui-font-size-sm, .875rem); + line-height: 21.125px; +} + +storage-container-pane .storage-container-pane-empty-message-button { + --text-color: var(--solid-ui-color-primary); + --border-color: var(--solid-ui-color-gray-200, #e5e7eb); + --background-color: var(--solid-ui-color-white, #ffffff); + --hover-text-color: var(--solid-ui-color-primary); + --hover-border-color: var(--solid-ui-color-gray-300, #d1d5db); + --hover-background-color: var(--solid-ui-color-white, #ffffff); + align-self: center; +} + +storage-container-pane .storage-container-pane-empty-message--search { + min-height: 100%; +} + +storage-container-pane storage-creation-area { + margin-top: auto; + align-self: stretch; + width: 100%; + box-sizing: border-box; + flex: 0 0 auto; + padding: 10px 20px; + --storage-creation-area-message-font-size: var(--solid-ui-font-size-sm, .875rem); + --storage-creation-area-padding: 28px 20px; + --storage-creation-icon-size: 26px; + --storage-creation-gap: 10px; + --storage-creation-area-border-radius: 12px; +} + +storage-container-pane icon-lucide-globe { + color: var(--blue-400, #51A2FF); + stroke-width: 1.021px; + stroke: var(--blue-400, #51A2FF); +} + +storage-container-pane icon-lucide-lock-keyhole { + color: var(--yellow-600, #D08700); + stroke-width: 1.021px; + stroke: var(--yellow-600, #D08700); +} diff --git a/src/styles/folderPane.css b/src/styles/folderPane.css index 4266fc7..8acebeb 100644 --- a/src/styles/folderPane.css +++ b/src/styles/folderPane.css @@ -1,17 +1,31 @@ -.folderPaneInstancePane { + +/* + * Temporary light-DOM layout for the storage pane shell. + * These row/fill rules live here because storage-pane-view and storage-container-pane + * currently render into light DOM, and the component `*.styles.css` files are compiled + * by litCss for shadow DOM only. When the storage shell can move back to shadow DOM, + * move the corresponding layout rules back into StoragePaneView.styles.css and + * StorageContainerPane.styles.css so they apply via static styles again. + */ + +@import './containerPane.css'; + +.storage-pane { + overflow: hidden; + display: flex; + flex-direction: column; box-sizing: border-box; + height: 100%; + min-height: 0; align-self: stretch; text-align: left; width: 100%; max-width: none; margin-left: 0; margin-right: 0; - padding-top: 5px; - overflow-x: auto; - -webkit-overflow-scrolling: touch; } -.folderPanePackageDiv { +.storage-pane-package-div { display: block; text-align: left; width: 100%; @@ -21,32 +35,96 @@ overflow-x: auto; } -.folderPaneMainTable { - display: table; +/* storage-pane-view and storage-container-pane render into light DOM, so their + layout must live here: *.styles.css files are compiled to Lit CSSResult. */ +storage-provider, +storage-pane-view { + display: flex; + flex-direction: column; width: 100%; - max-width: none; - table-layout: auto; - text-align: left; - margin-left: 0; - margin-right: auto; - margin-bottom: var(--spacing-lg, 1.5625rem); + height: 100%; + min-height: 0; } -.folderPaneMainTable td, -.folderPaneMainTable th { - text-align: left; - vertical-align: top; - overflow-wrap: anywhere; - word-break: break-word; +.storage-pane-main-content { + display: flex; + flex-direction: row; + width: 100%; + flex: 1 1 auto; + min-height: 0; + overflow: hidden; + border-radius: 5px; + border: 1px solid var(--solid-ui-color-gray-200, #E5E7EB); + background: var(--solid-ui-color-white, #fff); +} + +storage-header { + position: sticky; + top: 0; + z-index: 2; + background: var(--solid-ui-color-white, #fff); +} + +storage-resource-sidebar { + display: flex; + flex-direction: column; + flex: 0 0 256px; + min-width: 256px; + height: 100%; + min-height: 0; + overflow: hidden; + box-sizing: border-box; + border-right: 1px solid var(--solid-ui-color-gray-200, #E5E7EB); +} + +.storage-pane-content-column { + display: flex; + flex-direction: column; + flex: 1 1 0%; + min-width: 0; + min-height: 0; + height: 100%; +} + +.storage-pane-status { + flex: 0 0 auto; +} + +.storage-pane-status:empty { + display: none; +} + +storage-content-view { + display: flex; + flex: 1 1 0%; + min-width: 0; + min-height: 0; + overflow: hidden; +} + +.storage-pane-full-view, +.storage-pane-access-denied-message { + display: flex; + width: 100%; + height: 100%; + min-height: 0; } -/* manager.js sets inline padding:0 on object cells; use an important override here */ -.folderPaneMainTable > tr > td.obj, -.folderPaneMainTable > tbody > tr > td.obj { - padding-left: var(--spacing-lg, 1.5625rem) !important; +.storage-pane-full-view { + flex: 1 1 auto; } -.folderPanePredicateCell { +.storage-pane-access-denied-message { + align-items: center; + justify-content: center; + text-align: center; +} + +.storage-pane-access-denied-message-body { + max-width: 24rem; +} + +.storage-pane-predicate-cell { width: 0; min-width: 0; max-width: 0; @@ -56,7 +134,7 @@ overflow: hidden; } -.folderPaneCreationDiv { +.storage-pane-creation-div { display: flex; align-items: flex-start; justify-content: flex-start; @@ -67,13 +145,13 @@ margin-right: 0; } -.folderPaneDropZone { +.storage-pane-drop-zone { padding: 0.5rem 0.25rem; min-height: calc(var(--icon-base, 2em) + 1rem); box-sizing: border-box; } -.folderPaneDropTarget { +.storage-pane-drop-target { display: inline-block; width: var(--icon-base, 2em); min-width: var(--icon-base, 2em); @@ -87,13 +165,20 @@ flex: 0 0 var(--icon-base, 2em); } -.folderPaneExplicitDropIcon { +.storage-pane-explicit-drop-icon { width: var(--icon-base, 2em); height: var(--icon-base, 2em); } @media (max-width: 700px) { - .folderPaneExplicitDropIcon { + .storage-pane { + border-top: solid 1px #777; + border-bottom: solid 1px #777; + margin-top: var(--spacing-2xs, 0.25em); + margin-bottom: var(--spacing-2xs, 0.25em); + } + + .storage-pane-explicit-drop-icon { width: var(--icon-sm, 1.5em); height: var(--icon-sm, 1.5em); } diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..7ceca1e --- /dev/null +++ b/src/types.ts @@ -0,0 +1,30 @@ +import { NamedNode } from 'rdflib' + +export type Resource = { + id: string + subject: NamedNode + parentId: string | null + isContainer: boolean +} + +export type ResourceMap = Map + +export type StoragePaneOutliner = { + GotoSubject: ( + subject: NamedNode, + openPane: boolean, + focus?: unknown, + replace?: boolean, + source?: unknown, + targetView?: HTMLElement + ) => void +} + +export type ContentViewRenderer = { + store: any + selectedResource: NamedNode + contentView: HTMLElement + outliner?: StoragePaneOutliner + renderContainerPane: (selectedResource: NamedNode) => void + renderAccessDeniedView?: () => void +} diff --git a/vite.config.mts b/vite.config.mts index 0a1bc75..2975a96 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -3,7 +3,7 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: solidPane({ - litDecoratorPaths: [], + litDecoratorPaths: ['src/components'], sandbox: { subject: "https://testingsolidos.solidcommunity.net/", },