refactor(webui): switch to SvelteKit | great lint fixing
parent
efb2cee7b2
commit
9ae18310da
|
@ -29,13 +29,14 @@ module.exports = {
|
|||
}
|
||||
],
|
||||
rules: {
|
||||
"svelte/valid-compile": ["error", { "ignoreWarnings": false }],
|
||||
"svelte/valid-compile": ["error", { "ignoreWarnings": true }],
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"@typescript-eslint/no-unused-vars": ["warn", {
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_"
|
||||
}],
|
||||
"no-console": ["error", {
|
||||
allow: ["debug", "warn", "error"]
|
||||
}]
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -33,6 +33,14 @@
|
|||
"dependencies": {
|
||||
"@ibm/plex": "^6.3.0",
|
||||
"@recogito/annotorious": "^2.7.11",
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/lodash": "^4.14",
|
||||
"@types/marked": "^4.3.2",
|
||||
"@types/node": "^18.19.8",
|
||||
"@types/three": "^0.160.0",
|
||||
"@types/wavesurfer.js": "^6.0.12",
|
||||
"@upnd/upend": "file:../tools/upend_js",
|
||||
"@upnd/wasm-web": "file:../tools/upend_wasm/pkg-web",
|
||||
"boxicons": "^2.1.4",
|
||||
|
@ -54,9 +62,7 @@
|
|||
"sswr": "^1.11.0",
|
||||
"svelte-i18next": "^1.2.2",
|
||||
"three": "^0.147.0",
|
||||
"wavesurfer.js": "^6.6.4",
|
||||
"vite-plugin-static-copy": "^0.13.1",
|
||||
"@types/node": "^18.19.8",
|
||||
"@types/lodash": "^4.14"
|
||||
"wavesurfer.js": "^6.6.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,30 @@ dependencies:
|
|||
'@recogito/annotorious':
|
||||
specifier: ^2.7.11
|
||||
version: 2.7.12(react-dom@16.14.0)(react@16.14.0)
|
||||
'@types/d3':
|
||||
specifier: ^7.4.3
|
||||
version: 7.4.3
|
||||
'@types/debug':
|
||||
specifier: ^4.1.12
|
||||
version: 4.1.12
|
||||
'@types/dompurify':
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
'@types/lodash':
|
||||
specifier: ^4.14
|
||||
version: 4.14.202
|
||||
'@types/marked':
|
||||
specifier: ^4.3.2
|
||||
version: 4.3.2
|
||||
'@types/node':
|
||||
specifier: ^18.19.8
|
||||
version: 18.19.8
|
||||
'@types/three':
|
||||
specifier: ^0.160.0
|
||||
version: 0.160.0
|
||||
'@types/wavesurfer.js':
|
||||
specifier: ^6.0.12
|
||||
version: 6.0.12
|
||||
'@upnd/upend':
|
||||
specifier: file:../tools/upend_js
|
||||
version: file:../tools/upend_js
|
||||
|
@ -783,6 +801,201 @@ packages:
|
|||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||
dev: true
|
||||
|
||||
/@types/d3-array@3.2.1:
|
||||
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-axis@3.0.6:
|
||||
resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==}
|
||||
dependencies:
|
||||
'@types/d3-selection': 3.0.10
|
||||
dev: false
|
||||
|
||||
/@types/d3-brush@3.0.6:
|
||||
resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==}
|
||||
dependencies:
|
||||
'@types/d3-selection': 3.0.10
|
||||
dev: false
|
||||
|
||||
/@types/d3-chord@3.0.6:
|
||||
resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-color@3.1.3:
|
||||
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-contour@3.0.6:
|
||||
resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}
|
||||
dependencies:
|
||||
'@types/d3-array': 3.2.1
|
||||
'@types/geojson': 7946.0.13
|
||||
dev: false
|
||||
|
||||
/@types/d3-delaunay@6.0.4:
|
||||
resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-dispatch@3.0.6:
|
||||
resolution: {integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-drag@3.0.7:
|
||||
resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
|
||||
dependencies:
|
||||
'@types/d3-selection': 3.0.10
|
||||
dev: false
|
||||
|
||||
/@types/d3-dsv@3.0.7:
|
||||
resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-ease@3.0.2:
|
||||
resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-fetch@3.0.7:
|
||||
resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==}
|
||||
dependencies:
|
||||
'@types/d3-dsv': 3.0.7
|
||||
dev: false
|
||||
|
||||
/@types/d3-force@3.0.9:
|
||||
resolution: {integrity: sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-format@3.0.4:
|
||||
resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-geo@3.1.0:
|
||||
resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==}
|
||||
dependencies:
|
||||
'@types/geojson': 7946.0.13
|
||||
dev: false
|
||||
|
||||
/@types/d3-hierarchy@3.1.6:
|
||||
resolution: {integrity: sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-interpolate@3.0.4:
|
||||
resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
|
||||
dependencies:
|
||||
'@types/d3-color': 3.1.3
|
||||
dev: false
|
||||
|
||||
/@types/d3-path@3.0.2:
|
||||
resolution: {integrity: sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-polygon@3.0.2:
|
||||
resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-quadtree@3.0.6:
|
||||
resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-random@3.0.3:
|
||||
resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-scale-chromatic@3.0.3:
|
||||
resolution: {integrity: sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-scale@4.0.8:
|
||||
resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==}
|
||||
dependencies:
|
||||
'@types/d3-time': 3.0.3
|
||||
dev: false
|
||||
|
||||
/@types/d3-selection@3.0.10:
|
||||
resolution: {integrity: sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-shape@3.1.6:
|
||||
resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==}
|
||||
dependencies:
|
||||
'@types/d3-path': 3.0.2
|
||||
dev: false
|
||||
|
||||
/@types/d3-time-format@4.0.3:
|
||||
resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-time@3.0.3:
|
||||
resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-timer@3.0.2:
|
||||
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-transition@3.0.8:
|
||||
resolution: {integrity: sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==}
|
||||
dependencies:
|
||||
'@types/d3-selection': 3.0.10
|
||||
dev: false
|
||||
|
||||
/@types/d3-zoom@3.0.8:
|
||||
resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
|
||||
dependencies:
|
||||
'@types/d3-interpolate': 3.0.4
|
||||
'@types/d3-selection': 3.0.10
|
||||
dev: false
|
||||
|
||||
/@types/d3@7.4.3:
|
||||
resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
|
||||
dependencies:
|
||||
'@types/d3-array': 3.2.1
|
||||
'@types/d3-axis': 3.0.6
|
||||
'@types/d3-brush': 3.0.6
|
||||
'@types/d3-chord': 3.0.6
|
||||
'@types/d3-color': 3.1.3
|
||||
'@types/d3-contour': 3.0.6
|
||||
'@types/d3-delaunay': 6.0.4
|
||||
'@types/d3-dispatch': 3.0.6
|
||||
'@types/d3-drag': 3.0.7
|
||||
'@types/d3-dsv': 3.0.7
|
||||
'@types/d3-ease': 3.0.2
|
||||
'@types/d3-fetch': 3.0.7
|
||||
'@types/d3-force': 3.0.9
|
||||
'@types/d3-format': 3.0.4
|
||||
'@types/d3-geo': 3.1.0
|
||||
'@types/d3-hierarchy': 3.1.6
|
||||
'@types/d3-interpolate': 3.0.4
|
||||
'@types/d3-path': 3.0.2
|
||||
'@types/d3-polygon': 3.0.2
|
||||
'@types/d3-quadtree': 3.0.6
|
||||
'@types/d3-random': 3.0.3
|
||||
'@types/d3-scale': 4.0.8
|
||||
'@types/d3-scale-chromatic': 3.0.3
|
||||
'@types/d3-selection': 3.0.10
|
||||
'@types/d3-shape': 3.1.6
|
||||
'@types/d3-time': 3.0.3
|
||||
'@types/d3-time-format': 4.0.3
|
||||
'@types/d3-timer': 3.0.2
|
||||
'@types/d3-transition': 3.0.8
|
||||
'@types/d3-zoom': 3.0.8
|
||||
dev: false
|
||||
|
||||
/@types/debounce@1.2.4:
|
||||
resolution: {integrity: sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==}
|
||||
dev: false
|
||||
|
||||
/@types/debug@4.1.12:
|
||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||
dependencies:
|
||||
'@types/ms': 0.7.34
|
||||
dev: false
|
||||
|
||||
/@types/dompurify@3.0.5:
|
||||
resolution: {integrity: sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==}
|
||||
dependencies:
|
||||
'@types/trusted-types': 2.0.7
|
||||
dev: false
|
||||
|
||||
/@types/eslint@8.56.0:
|
||||
resolution: {integrity: sha512-FlsN0p4FhuYRjIxpbdXovvHQhtlG05O1GG/RNWvdAxTboR438IOTwmrY/vLA+Xfgg06BTkP045M3vpFwTMv1dg==}
|
||||
dependencies:
|
||||
|
@ -793,6 +1006,10 @@ packages:
|
|||
/@types/estree@1.0.5:
|
||||
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
||||
|
||||
/@types/geojson@7946.0.13:
|
||||
resolution: {integrity: sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==}
|
||||
dev: false
|
||||
|
||||
/@types/json-schema@7.0.13:
|
||||
resolution: {integrity: sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==}
|
||||
dev: true
|
||||
|
@ -801,6 +1018,14 @@ packages:
|
|||
resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==}
|
||||
dev: false
|
||||
|
||||
/@types/marked@4.3.2:
|
||||
resolution: {integrity: sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==}
|
||||
dev: false
|
||||
|
||||
/@types/ms@0.7.34:
|
||||
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
|
||||
dev: false
|
||||
|
||||
/@types/node@18.19.8:
|
||||
resolution: {integrity: sha512-g1pZtPhsvGVTwmeVoexWZLTQaOvXwoSq//pTL0DHeNzUDrFnir4fgETdhjhIxjVnN+hKOuh98+E1eMLnUXstFg==}
|
||||
dependencies:
|
||||
|
@ -818,6 +1043,33 @@ packages:
|
|||
resolution: {integrity: sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==}
|
||||
dev: true
|
||||
|
||||
/@types/stats.js@0.17.3:
|
||||
resolution: {integrity: sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==}
|
||||
dev: false
|
||||
|
||||
/@types/three@0.160.0:
|
||||
resolution: {integrity: sha512-jWlbUBovicUKaOYxzgkLlhkiEQJkhCVvg4W2IYD2trqD2om3VK4DGLpHH5zQHNr7RweZK/5re/4IVhbhvxbV9w==}
|
||||
dependencies:
|
||||
'@types/stats.js': 0.17.3
|
||||
'@types/webxr': 0.5.10
|
||||
fflate: 0.6.10
|
||||
meshoptimizer: 0.18.1
|
||||
dev: false
|
||||
|
||||
/@types/trusted-types@2.0.7:
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||
dev: false
|
||||
|
||||
/@types/wavesurfer.js@6.0.12:
|
||||
resolution: {integrity: sha512-oM9hYlPIVms4uwwoaGs9d0qp7Xk7IjSGkdwgmhUymVUIIilRfjtSQvoOgv4dpKiW0UozWRSyXfQqTobi0qWyCw==}
|
||||
dependencies:
|
||||
'@types/debounce': 1.2.4
|
||||
dev: false
|
||||
|
||||
/@types/webxr@0.5.10:
|
||||
resolution: {integrity: sha512-n3u5sqXQJhf1CS68mw3Wf16FQ4cRPNBBwdYLFzq3UddiADOim1Pn3Y6PBdDilz1vOJF3ybLxJ8ZEDlLIzrOQZg==}
|
||||
dev: false
|
||||
|
||||
/@typescript-eslint/eslint-plugin@6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3):
|
||||
resolution: {integrity: sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
|
@ -1925,6 +2177,10 @@ packages:
|
|||
dependencies:
|
||||
reusify: 1.0.4
|
||||
|
||||
/fflate@0.6.10:
|
||||
resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==}
|
||||
dev: false
|
||||
|
||||
/file-entry-cache@6.0.1:
|
||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||
engines: {node: ^10.12.0 || >=12.0.0}
|
||||
|
@ -2532,6 +2788,10 @@ packages:
|
|||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
/meshoptimizer@0.18.1:
|
||||
resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==}
|
||||
dev: false
|
||||
|
||||
/micromatch@4.0.5:
|
||||
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import { ATTR_IN } from '@upnd/upend/constants';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { Any } from '@upnd/upend/query';
|
||||
import type { Widget } from '$lib/components/EntryView.svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let spec: string;
|
||||
|
@ -26,7 +27,7 @@
|
|||
dispatch('close');
|
||||
}
|
||||
|
||||
const combinedWidgets = [
|
||||
const combinedWidgets: Widget[] = [
|
||||
{
|
||||
name: 'List',
|
||||
icon: 'list-check',
|
||||
|
@ -55,7 +56,7 @@
|
|||
}
|
||||
];
|
||||
|
||||
let resultEntities = [];
|
||||
let resultEntities: string[] = [];
|
||||
async function updateResultEntities(
|
||||
includedGroups: string[],
|
||||
requiredGroups: string[],
|
||||
|
@ -109,21 +110,21 @@
|
|||
<div class="controls">
|
||||
<EntitySetEditor
|
||||
entities={includedGroups}
|
||||
header={$i18n.t('Include')}
|
||||
header={$i18n.t('Include') || ''}
|
||||
confirmRemoveMessage={null}
|
||||
on:add={(ev) => (includedGroups = [...includedGroups, ev.detail])}
|
||||
on:remove={(ev) => (includedGroups = includedGroups.filter((e) => e !== ev.detail))}
|
||||
/>
|
||||
<EntitySetEditor
|
||||
entities={requiredGroups}
|
||||
header={$i18n.t('Require')}
|
||||
header={$i18n.t('Require') || ''}
|
||||
confirmRemoveMessage={null}
|
||||
on:add={(ev) => (requiredGroups = [...requiredGroups, ev.detail])}
|
||||
on:remove={(ev) => (requiredGroups = requiredGroups.filter((e) => e !== ev.detail))}
|
||||
/>
|
||||
<EntitySetEditor
|
||||
entities={excludedGroups}
|
||||
header={$i18n.t('Exclude')}
|
||||
header={$i18n.t('Exclude') || ''}
|
||||
confirmRemoveMessage={null}
|
||||
on:add={(ev) => (excludedGroups = [...excludedGroups, ev.detail])}
|
||||
on:remove={(ev) => (excludedGroups = excludedGroups.filter((e) => e !== ev.detail))}
|
||||
|
@ -131,7 +132,7 @@
|
|||
</div>
|
||||
<div class="entities">
|
||||
<EntryView
|
||||
title={$i18n.t('Matching entities')}
|
||||
title={$i18n.t('Matching entities') || ''}
|
||||
entities={resultEntities}
|
||||
widgets={combinedWidgets}
|
||||
/>
|
||||
|
|
|
@ -1,87 +1,88 @@
|
|||
<script lang="ts">
|
||||
import { addEmitter } from "./AddModal.svelte";
|
||||
import Icon from "./utils/Icon.svelte";
|
||||
import { addEmitter } from './AddModal.svelte';
|
||||
import Icon from './utils/Icon.svelte';
|
||||
|
||||
let dragging = false;
|
||||
let dragging = false;
|
||||
|
||||
function onDrop(ev: DragEvent) {
|
||||
if (ev.dataTransfer.files.length > 0) {
|
||||
addEmitter.emit("files", Array.from(ev.dataTransfer.files));
|
||||
} // TODO: else check for URLs
|
||||
dragging = false;
|
||||
}
|
||||
function onDrop(ev: DragEvent) {
|
||||
if (ev.dataTransfer?.files.length) {
|
||||
addEmitter.emit('files', Array.from(ev.dataTransfer?.files || []));
|
||||
} // TODO: else check for URLs
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
function onDragEnter() {
|
||||
// noop
|
||||
}
|
||||
function onDragEnter() {
|
||||
// noop
|
||||
}
|
||||
|
||||
function onDragOver(ev: DragEvent) {
|
||||
if (Array.from(ev.dataTransfer.items).some((it) => it.kind === "file")) {
|
||||
dragging = true;
|
||||
}
|
||||
}
|
||||
function onDragOver(ev: DragEvent) {
|
||||
if (Array.from(ev.dataTransfer?.items || []).some((it) => it.kind === 'file')) {
|
||||
dragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
function onDragLeave() {
|
||||
dragging = false;
|
||||
}
|
||||
function onDragLeave() {
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
function onPaste(ev: ClipboardEvent) {
|
||||
if (ev.clipboardData.files.length > 0) {
|
||||
addEmitter.emit("files", Array.from(ev.clipboardData.files));
|
||||
} // TODO: else check for URLs
|
||||
}
|
||||
function onPaste(ev: ClipboardEvent) {
|
||||
if (ev.clipboardData?.files.length) {
|
||||
addEmitter.emit('files', Array.from(ev.clipboardData?.files || []));
|
||||
} // TODO: else check for URLs
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:body
|
||||
on:dragenter|preventDefault={onDragEnter}
|
||||
on:dragover|preventDefault={onDragOver}
|
||||
on:dragleave|preventDefault={onDragLeave}
|
||||
on:drop|preventDefault={onDrop}
|
||||
on:paste={onPaste} />
|
||||
on:dragenter|preventDefault={onDragEnter}
|
||||
on:dragover|preventDefault={onDragOver}
|
||||
on:dragleave|preventDefault={onDragLeave}
|
||||
on:drop|preventDefault={onDrop}
|
||||
on:paste={onPaste}
|
||||
/>
|
||||
|
||||
<div class="dropindicator" class:dragging>
|
||||
<div class="content">
|
||||
<div class="icon">
|
||||
<Icon name="current-location" />
|
||||
</div>
|
||||
<p>Drop an URL, an image or a file here!</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="icon">
|
||||
<Icon name="current-location" />
|
||||
</div>
|
||||
<p>Drop an URL, an image or a file here!</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.dropindicator {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
.dropindicator {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
|
||||
display: none;
|
||||
}
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dragging {
|
||||
display: unset;
|
||||
}
|
||||
.dragging {
|
||||
display: unset;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
.content {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
|
||||
color: var(--foreground);
|
||||
border: solid 0.25em var(--foreground);
|
||||
border-radius: 0.5em;
|
||||
padding: 1.5em;
|
||||
color: var(--foreground);
|
||||
border: solid 0.25em var(--foreground);
|
||||
border-radius: 0.5em;
|
||||
padding: 1.5em;
|
||||
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
}
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 128px;
|
||||
}
|
||||
.icon {
|
||||
font-size: 128px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,167 +1,161 @@
|
|||
<script lang="ts" context="module">
|
||||
import { writable } from "svelte/store";
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const selected = writable<string[]>([]);
|
||||
export const selected = writable<string[]>([]);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { i18n } from "../i18n";
|
||||
import { onMount } from 'svelte';
|
||||
import { i18n } from '../i18n';
|
||||
|
||||
let canvas: HTMLCanvasElement;
|
||||
let canvas: HTMLCanvasElement;
|
||||
|
||||
onMount(() => {
|
||||
const ctx = canvas.getContext("2d");
|
||||
onMount(() => {
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
function resizeCanvas() {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
}
|
||||
window.addEventListener("resize", resizeCanvas);
|
||||
resizeCanvas();
|
||||
function resizeCanvas() {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
}
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
resizeCanvas();
|
||||
|
||||
let selecting = false;
|
||||
let selectAllArea: DOMRect | undefined = undefined;
|
||||
let selectAllAddresses: string[] | undefined = undefined;
|
||||
let addressesToRemove = new Set();
|
||||
document.addEventListener("mousedown", (ev) => {
|
||||
if (ev.ctrlKey || ev.metaKey) {
|
||||
ev.preventDefault();
|
||||
let selecting = false;
|
||||
let selectAllArea: DOMRect | undefined = undefined;
|
||||
let selectAllAddresses: string[] = [];
|
||||
let addressesToRemove = new Set();
|
||||
document.addEventListener('mousedown', (ev) => {
|
||||
if (!ctx) return;
|
||||
|
||||
selecting = true;
|
||||
addressesToRemove = new Set();
|
||||
if (ev.ctrlKey || ev.metaKey) {
|
||||
ev.preventDefault();
|
||||
|
||||
const el = document.elementFromPoint(
|
||||
ev.clientX,
|
||||
ev.clientY,
|
||||
) as HTMLElement;
|
||||
selecting = true;
|
||||
addressesToRemove = new Set();
|
||||
|
||||
const multiElement = el.closest("[data-address-multi]") as
|
||||
| HTMLElement
|
||||
| undefined;
|
||||
const el = document.elementFromPoint(ev.clientX, ev.clientY) as HTMLElement;
|
||||
|
||||
if (multiElement) {
|
||||
const banner = multiElement.querySelector("h2");
|
||||
const multiElement = el.closest('[data-address-multi]') as HTMLElement | undefined;
|
||||
|
||||
if (banner) {
|
||||
const rect = banner.getBoundingClientRect();
|
||||
selectAllArea = rect;
|
||||
selectAllAddresses = multiElement.dataset.addressMulti.split(",");
|
||||
if (multiElement) {
|
||||
const banner = multiElement.querySelector('h2');
|
||||
|
||||
ctx.rect(rect.left, rect.top, rect.width, rect.height);
|
||||
ctx.fillStyle = "#dc322f33";
|
||||
ctx.fill();
|
||||
if (banner) {
|
||||
const rect = banner.getBoundingClientRect();
|
||||
selectAllArea = rect;
|
||||
selectAllAddresses = multiElement.dataset.addressMulti?.split(',') || [];
|
||||
|
||||
ctx.fillStyle = "#dc322f77";
|
||||
ctx.font = `bold ${rect.height / 2}px Inter`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
const fix = ctx.measureText("M").actualBoundingBoxDescent / 2;
|
||||
ctx.fillText(
|
||||
$i18n.t("Select All"),
|
||||
rect.left + rect.width / 2,
|
||||
rect.top + rect.height / 2 + fix,
|
||||
);
|
||||
}
|
||||
}
|
||||
ctx.rect(rect.left, rect.top, rect.width, rect.height);
|
||||
ctx.fillStyle = '#dc322f33';
|
||||
ctx.fill();
|
||||
|
||||
ctx.strokeStyle = "#dc322f77";
|
||||
ctx.lineWidth = 7;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(ev.clientX, ev.clientY);
|
||||
}
|
||||
});
|
||||
ctx.fillStyle = '#dc322f77';
|
||||
ctx.font = `bold ${rect.height / 2}px Inter`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
const fix = ctx.measureText('M').actualBoundingBoxDescent / 2;
|
||||
ctx.fillText(
|
||||
$i18n.t('Select All'),
|
||||
rect.left + rect.width / 2,
|
||||
rect.top + rect.height / 2 + fix
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("mousemove", (ev) => {
|
||||
if (selecting) {
|
||||
ev.preventDefault();
|
||||
ctx.strokeStyle = '#dc322f77';
|
||||
ctx.lineWidth = 7;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(ev.clientX, ev.clientY);
|
||||
}
|
||||
});
|
||||
|
||||
if (selectAllArea) {
|
||||
if (
|
||||
ev.clientX > selectAllArea.left &&
|
||||
ev.clientX < selectAllArea.right &&
|
||||
ev.clientY > selectAllArea.top &&
|
||||
ev.clientY < selectAllArea.bottom
|
||||
) {
|
||||
selected.update((selected) => {
|
||||
return [
|
||||
...selected,
|
||||
...selectAllAddresses.filter((a) => {
|
||||
return !selected.includes(a);
|
||||
}),
|
||||
];
|
||||
});
|
||||
stop();
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousemove', (ev) => {
|
||||
if (!ctx) return;
|
||||
|
||||
const el = document.elementFromPoint(
|
||||
ev.clientX,
|
||||
ev.clientY,
|
||||
) as HTMLElement;
|
||||
if (selecting) {
|
||||
ev.preventDefault();
|
||||
|
||||
const addressElement = el.closest("[data-address]") as
|
||||
| HTMLElement
|
||||
| undefined;
|
||||
if (addressElement) {
|
||||
const address = addressElement.dataset.address;
|
||||
const selectMode = addressElement.dataset.selectMode;
|
||||
if (selectMode === "add" || selectMode === undefined) {
|
||||
selected.update((selected) => {
|
||||
if (!selected.includes(address)) {
|
||||
return [...selected, address];
|
||||
} else {
|
||||
return selected;
|
||||
}
|
||||
});
|
||||
} else if (selectMode === "remove") {
|
||||
addressesToRemove.add(address);
|
||||
}
|
||||
}
|
||||
if (selectAllArea) {
|
||||
if (
|
||||
ev.clientX > selectAllArea.left &&
|
||||
ev.clientX < selectAllArea.right &&
|
||||
ev.clientY > selectAllArea.top &&
|
||||
ev.clientY < selectAllArea.bottom
|
||||
) {
|
||||
selected.update((selected) => {
|
||||
return [
|
||||
...selected,
|
||||
...selectAllAddresses.filter((a) => {
|
||||
return !selected.includes(a);
|
||||
})
|
||||
];
|
||||
});
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
ctx.lineTo(ev.clientX, ev.clientY);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(ev.clientX, ev.clientY);
|
||||
}
|
||||
});
|
||||
const el = document.elementFromPoint(ev.clientX, ev.clientY) as HTMLElement;
|
||||
|
||||
document.addEventListener("mouseup", () => {
|
||||
stop();
|
||||
});
|
||||
const addressElement = el.closest('[data-address]') as HTMLElement | undefined;
|
||||
if (addressElement) {
|
||||
const address = addressElement.dataset.address;
|
||||
const selectMode = addressElement.dataset.selectMode;
|
||||
if (selectMode === 'add' || selectMode === undefined) {
|
||||
selected.update((selected) => {
|
||||
if (address && !selected.includes(address)) {
|
||||
return [...selected, address];
|
||||
} else {
|
||||
return selected;
|
||||
}
|
||||
});
|
||||
} else if (selectMode === 'remove') {
|
||||
addressesToRemove.add(address);
|
||||
}
|
||||
}
|
||||
|
||||
function stop() {
|
||||
selectAllArea = undefined;
|
||||
selectAllAddresses = undefined;
|
||||
selecting = false;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
for (const address of addressesToRemove) {
|
||||
selected.update((selected) => {
|
||||
return selected.filter((a) => a !== address);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
ctx.lineTo(ev.clientX, ev.clientY);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(ev.clientX, ev.clientY);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', () => {
|
||||
stop();
|
||||
});
|
||||
|
||||
function stop() {
|
||||
selectAllArea = undefined;
|
||||
selectAllAddresses = [];
|
||||
selecting = false;
|
||||
ctx?.clearRect(0, 0, canvas.width, canvas.height);
|
||||
for (const address of addressesToRemove) {
|
||||
selected.update((selected) => {
|
||||
return selected.filter((a) => a !== address);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="selectIndicator">
|
||||
<canvas bind:this={canvas}></canvas>
|
||||
<canvas bind:this={canvas}></canvas>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.selectIndicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
.selectIndicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
pointer-events: none;
|
||||
pointer-events: none;
|
||||
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,117 +1,111 @@
|
|||
<script lang="ts">
|
||||
import UpObjectDisplay from "./display/UpObject.svelte";
|
||||
import Selector, { type SelectorValue } from "./utils/Selector.svelte";
|
||||
import IconButton from "./utils/IconButton.svelte";
|
||||
import { i18n } from "../i18n";
|
||||
import LabelBorder from "./utils/LabelBorder.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
const dispatch = createEventDispatcher();
|
||||
import UpObjectDisplay from './display/UpObject.svelte';
|
||||
import Selector, { type SelectorValue } from './utils/Selector.svelte';
|
||||
import IconButton from './utils/IconButton.svelte';
|
||||
import { i18n } from '../i18n';
|
||||
import LabelBorder from './utils/LabelBorder.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let entities: string[];
|
||||
export let hide = false;
|
||||
export let entities: string[];
|
||||
export let hide = false;
|
||||
|
||||
export let header = "";
|
||||
export let confirmRemoveMessage: string | null = $i18n.t(
|
||||
"Are you sure you want to remove this?",
|
||||
);
|
||||
export let emptyMessage = $i18n.t("Nothing to show.");
|
||||
export let header = '';
|
||||
export let confirmRemoveMessage: string | null = $i18n.t('Are you sure you want to remove this?');
|
||||
export let emptyMessage = $i18n.t('Nothing to show.');
|
||||
|
||||
let adding = false;
|
||||
let selector: Selector;
|
||||
let adding = false;
|
||||
let selector: Selector;
|
||||
|
||||
$: if (adding && selector) selector.focus();
|
||||
$: if (adding && selector) selector.focus();
|
||||
|
||||
async function add(ev: CustomEvent<SelectorValue>) {
|
||||
if (ev.detail.t !== "Address") {
|
||||
return;
|
||||
}
|
||||
dispatch("add", ev.detail.c);
|
||||
}
|
||||
async function add(ev: CustomEvent<SelectorValue>) {
|
||||
if (ev.detail.t !== 'Address') {
|
||||
return;
|
||||
}
|
||||
dispatch('add', ev.detail.c);
|
||||
}
|
||||
|
||||
async function remove(address: string) {
|
||||
if (!confirmRemoveMessage || confirm(confirmRemoveMessage)) {
|
||||
dispatch("remove", address);
|
||||
}
|
||||
}
|
||||
async function remove(address: string) {
|
||||
if (!confirmRemoveMessage || confirm(confirmRemoveMessage)) {
|
||||
dispatch('remove', address);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<LabelBorder {hide}>
|
||||
<span slot="header">{header}</span>
|
||||
<span slot="header">{header}</span>
|
||||
|
||||
{#if adding}
|
||||
<div class="selector">
|
||||
<Selector
|
||||
bind:this={selector}
|
||||
types={["Address", "NewAddress"]}
|
||||
on:input={add}
|
||||
on:focus={(ev) => {
|
||||
if (!ev.detail) adding = false;
|
||||
}}
|
||||
placeholder={$i18n.t("Choose an entity...")}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if adding}
|
||||
<div class="selector">
|
||||
<Selector
|
||||
bind:this={selector}
|
||||
types={['Address', 'NewAddress']}
|
||||
on:input={add}
|
||||
on:focus={(ev) => {
|
||||
if (!ev.detail) adding = false;
|
||||
}}
|
||||
placeholder={$i18n.t('Choose an entity...') || ''}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="body">
|
||||
<div class="group-list">
|
||||
{#each entities as entity}
|
||||
<div
|
||||
class="group"
|
||||
on:mouseenter={() => dispatch("highlighted", entity)}
|
||||
on:mouseleave={() => dispatch("highlighted", undefined)}
|
||||
>
|
||||
<UpObjectDisplay address={entity} link />
|
||||
<IconButton subdued name="x-circle" on:click={() => remove(entity)} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="no-groups">
|
||||
{emptyMessage}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if !adding}
|
||||
<div class="add-button">
|
||||
<IconButton
|
||||
outline
|
||||
small
|
||||
name="folder-plus"
|
||||
on:click={() => (adding = true)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="group-list">
|
||||
{#each entities as entity}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="group"
|
||||
on:mouseenter={() => dispatch('highlighted', entity)}
|
||||
on:mouseleave={() => dispatch('highlighted', undefined)}
|
||||
>
|
||||
<UpObjectDisplay address={entity} link />
|
||||
<IconButton subdued name="x-circle" on:click={() => remove(entity)} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="no-groups">
|
||||
{emptyMessage}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if !adding}
|
||||
<div class="add-button">
|
||||
<IconButton outline small name="folder-plus" on:click={() => (adding = true)} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</LabelBorder>
|
||||
|
||||
<style lang="scss">
|
||||
.group-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem 0.2rem;
|
||||
align-items: center;
|
||||
}
|
||||
.group-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem 0.2rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.group {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.group {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.body {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
.body {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
|
||||
.group-list {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.group-list {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
padding-bottom: 0.2rem;
|
||||
}
|
||||
padding-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.selector {
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.selector {
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.no-groups {
|
||||
opacity: 0.66;
|
||||
}
|
||||
.no-groups {
|
||||
opacity: 0.66;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,152 +1,150 @@
|
|||
<script lang="ts" context="module">
|
||||
export interface WidgetComponent {
|
||||
component: ComponentType;
|
||||
props: { [key: string]: unknown };
|
||||
}
|
||||
import type { ComponentType } from 'svelte';
|
||||
export interface WidgetComponent {
|
||||
component: ComponentType;
|
||||
props: { [key: string]: unknown };
|
||||
}
|
||||
|
||||
export interface Widget {
|
||||
name: string;
|
||||
icon?: string;
|
||||
components: (input: {
|
||||
entries: UpEntry[];
|
||||
entities: string[];
|
||||
group?: string;
|
||||
address?: string;
|
||||
}) => Array<WidgetComponent>;
|
||||
}
|
||||
export interface Widget {
|
||||
name: string;
|
||||
icon?: string;
|
||||
components: (input: {
|
||||
entries: UpEntry[];
|
||||
entities: string[];
|
||||
group?: string;
|
||||
address?: string;
|
||||
}) => Array<WidgetComponent>;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import EntryList from "./widgets/EntryList.svelte";
|
||||
import type { UpEntry } from "@upnd/upend";
|
||||
import Icon from "./utils/Icon.svelte";
|
||||
import IconButton from "./utils/IconButton.svelte";
|
||||
import { createEventDispatcher, type ComponentType } from "svelte";
|
||||
import UpObject from "./display/UpObject.svelte";
|
||||
import LabelBorder from "./utils/LabelBorder.svelte";
|
||||
const dispatch = createEventDispatcher();
|
||||
import EntryList from './widgets/EntryList.svelte';
|
||||
import type { UpEntry } from '@upnd/upend';
|
||||
import Icon from './utils/Icon.svelte';
|
||||
import IconButton from './utils/IconButton.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import UpObject from './display/UpObject.svelte';
|
||||
import LabelBorder from './utils/LabelBorder.svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let entries: UpEntry[] = [];
|
||||
export let entities: string[] = [];
|
||||
export let widgets: Widget[] | undefined = undefined;
|
||||
export let initialWidget: string | undefined = undefined;
|
||||
export let title: string | undefined = undefined;
|
||||
export let group: string | undefined = undefined;
|
||||
export let address: string | undefined = undefined;
|
||||
export let icon: string | undefined = undefined;
|
||||
export let highlighted = false;
|
||||
export let entries: UpEntry[] = [];
|
||||
export let entities: string[] = [];
|
||||
export let widgets: Widget[] | undefined = undefined;
|
||||
export let initialWidget: string | undefined = undefined;
|
||||
export let title: string | undefined = undefined;
|
||||
export let group: string | undefined = undefined;
|
||||
export let address: string | undefined = undefined;
|
||||
export let icon: string | undefined = undefined;
|
||||
export let highlighted = false;
|
||||
|
||||
let currentWidget: string | undefined;
|
||||
let currentWidget: string | undefined;
|
||||
|
||||
function switchWidget(widget: string) {
|
||||
currentWidget = widget;
|
||||
dispatch("widgetSwitched", currentWidget);
|
||||
}
|
||||
function switchWidget(widget: string) {
|
||||
currentWidget = widget;
|
||||
dispatch('widgetSwitched', currentWidget);
|
||||
}
|
||||
|
||||
let availableWidgets: Widget[] = [];
|
||||
$: {
|
||||
availableWidgets = [];
|
||||
let availableWidgets: Widget[] = [];
|
||||
$: {
|
||||
availableWidgets = [];
|
||||
|
||||
if (entries.length) {
|
||||
availableWidgets = [
|
||||
...availableWidgets,
|
||||
{
|
||||
name: "Entry List",
|
||||
icon: "table",
|
||||
components: ({ entries }) => [
|
||||
{
|
||||
component: EntryList,
|
||||
props: { entries, columns: "entity, attribute, value" },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
if (entries.length) {
|
||||
availableWidgets = [
|
||||
...availableWidgets,
|
||||
{
|
||||
name: 'Entry List',
|
||||
icon: 'table',
|
||||
components: ({ entries }) => [
|
||||
{
|
||||
component: EntryList,
|
||||
props: { entries, columns: 'entity, attribute, value' }
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
if (widgets?.length) {
|
||||
availableWidgets = [...widgets, ...availableWidgets];
|
||||
}
|
||||
if (widgets?.length) {
|
||||
availableWidgets = [...widgets, ...availableWidgets];
|
||||
}
|
||||
|
||||
if (availableWidgets.map((w) => w.name).includes(initialWidget)) {
|
||||
currentWidget = initialWidget;
|
||||
} else {
|
||||
currentWidget = availableWidgets[0].name;
|
||||
}
|
||||
}
|
||||
if (initialWidget && availableWidgets.map((w) => w.name).includes(initialWidget)) {
|
||||
currentWidget = initialWidget;
|
||||
} else {
|
||||
currentWidget = availableWidgets[0].name;
|
||||
}
|
||||
}
|
||||
|
||||
let components: WidgetComponent[] = [];
|
||||
$: {
|
||||
components = availableWidgets
|
||||
.find((w) => w.name === currentWidget)
|
||||
.components({ entries, entities, group, address });
|
||||
}
|
||||
let components: WidgetComponent[] = [];
|
||||
$: {
|
||||
components =
|
||||
availableWidgets
|
||||
.find((w) => w.name === currentWidget)
|
||||
?.components({ entries, entities, group, address }) || [];
|
||||
}
|
||||
</script>
|
||||
|
||||
<LabelBorder hide={entries.length === 0 && entities.length === 0}>
|
||||
<svelte:fragment slot="header-full">
|
||||
<h3 class:highlighted>
|
||||
{#if group}
|
||||
{#if icon}
|
||||
<div class="icon">
|
||||
<Icon name={icon} />
|
||||
</div>
|
||||
{/if}
|
||||
<UpObject link address={group} labels={title ? [title] : undefined} />
|
||||
{:else}
|
||||
{#if icon}
|
||||
<div class="icon">
|
||||
<Icon name={icon} />
|
||||
</div>
|
||||
{/if}
|
||||
{title || ""}
|
||||
{/if}
|
||||
</h3>
|
||||
<svelte:fragment slot="header-full">
|
||||
<h3 class:highlighted>
|
||||
{#if group}
|
||||
{#if icon}
|
||||
<div class="icon">
|
||||
<Icon name={icon} />
|
||||
</div>
|
||||
{/if}
|
||||
<UpObject link address={group} labels={title ? [title] : undefined} />
|
||||
{:else}
|
||||
{#if icon}
|
||||
<div class="icon">
|
||||
<Icon name={icon} />
|
||||
</div>
|
||||
{/if}
|
||||
{title || ''}
|
||||
{/if}
|
||||
</h3>
|
||||
|
||||
{#if currentWidget && availableWidgets.length > 1}
|
||||
<div class="views">
|
||||
{#each availableWidgets as widget (widget.name)}
|
||||
<IconButton
|
||||
name={widget.icon || "cube"}
|
||||
title={widget.name}
|
||||
active={widget.name === currentWidget}
|
||||
--active-color="var(--foreground)"
|
||||
on:click={() => switchWidget(widget.name)}
|
||||
>
|
||||
{widget.name}
|
||||
</IconButton>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
{#each components as component}
|
||||
<svelte:component
|
||||
this={component.component}
|
||||
{...component.props || {}}
|
||||
on:change
|
||||
/>
|
||||
{/each}
|
||||
{#if currentWidget && availableWidgets.length > 1}
|
||||
<div class="views">
|
||||
{#each availableWidgets as widget (widget.name)}
|
||||
<IconButton
|
||||
name={widget.icon || 'cube'}
|
||||
title={widget.name}
|
||||
active={widget.name === currentWidget}
|
||||
--active-color="var(--foreground)"
|
||||
on:click={() => switchWidget(widget.name)}
|
||||
>
|
||||
{widget.name}
|
||||
</IconButton>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
{#each components as component}
|
||||
<svelte:component this={component.component} {...component.props || {}} on:change />
|
||||
{/each}
|
||||
</LabelBorder>
|
||||
|
||||
<style lang="scss">
|
||||
.icon {
|
||||
display: inline-block;
|
||||
font-size: 1.25em;
|
||||
margin-top: -0.3em;
|
||||
position: relative;
|
||||
bottom: -2px;
|
||||
}
|
||||
.icon {
|
||||
display: inline-block;
|
||||
font-size: 1.25em;
|
||||
margin-top: -0.3em;
|
||||
position: relative;
|
||||
bottom: -2px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
transition: text-shadow 0.2s;
|
||||
h3 {
|
||||
margin: 0;
|
||||
transition: text-shadow 0.2s;
|
||||
|
||||
&.highlighted {
|
||||
text-shadow: #cb4b16 0 0 0.5em;
|
||||
}
|
||||
}
|
||||
&.highlighted {
|
||||
text-shadow: #cb4b16 0 0 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.views {
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
}
|
||||
.views {
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
import LabelBorder from './utils/LabelBorder.svelte';
|
||||
import { debug } from 'debug';
|
||||
import { Any } from '@upnd/upend/query';
|
||||
import { isDefined } from '$lib/util/werk';
|
||||
|
||||
const dbg = debug('kestrel:Inspect');
|
||||
|
||||
|
@ -38,7 +39,7 @@
|
|||
$: allTypes = derived(
|
||||
entityInfo,
|
||||
($entityInfo, set) => {
|
||||
getAllTypes($entityInfo).then((allTypes) => {
|
||||
getAllTypes($entityInfo!).then((allTypes) => {
|
||||
set(allTypes);
|
||||
});
|
||||
},
|
||||
|
@ -54,7 +55,7 @@
|
|||
.sort(([_, a], [__, b]) => a.attributes.length - b.attributes.length);
|
||||
|
||||
async function getAllTypes(entityInfo: EntityInfo) {
|
||||
const allTypes = {};
|
||||
const allTypes: Record<Address, { labels: string[]; attributes: string[] }> = {};
|
||||
|
||||
if (!entityInfo) {
|
||||
return {};
|
||||
|
@ -95,15 +96,15 @@
|
|||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
})
|
||||
)
|
||||
).filter(Boolean);
|
||||
).filter(isDefined);
|
||||
})
|
||||
);
|
||||
|
||||
const result = {};
|
||||
const result: Record<Address, { labels: string[]; attributes: string[] }> = {};
|
||||
Object.keys(allTypes).forEach((addr) => {
|
||||
if (allTypes[addr].attributes.length > 0) {
|
||||
result[addr] = allTypes[addr];
|
||||
|
@ -182,10 +183,13 @@
|
|||
|
||||
async function fetchCorrectlyTagged() {
|
||||
const attributes = (
|
||||
await Promise.all($entity?.attr[`~${ATTR_OF}`].map((e) => api.addressToComponents(e.entity)))
|
||||
await Promise.all(
|
||||
($entity?.attr[`~${ATTR_OF}`] ?? []).map((e) => api.addressToComponents(e.entity))
|
||||
)
|
||||
)
|
||||
.filter((ac) => ac.t == 'Attribute')
|
||||
.map((ac) => ac.c);
|
||||
.map((ac) => ac.c)
|
||||
.filter(isDefined);
|
||||
|
||||
const attributeQuery = await api.query(
|
||||
Query.matches(
|
||||
|
@ -288,7 +292,7 @@
|
|||
props: {
|
||||
entries,
|
||||
columns: 'attribute, value',
|
||||
attributes: $allTypes[group]?.attributes || []
|
||||
attributes: group ? $allTypes[group]?.attributes : [] || []
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -341,7 +345,7 @@
|
|||
];
|
||||
|
||||
$: entity.subscribe(async (object) => {
|
||||
if (object && object.listing.entries.length) {
|
||||
if (object && object.listing?.entries.length) {
|
||||
dbg('Updating visit stats for %o', object);
|
||||
await api.putEntityAttribute(
|
||||
object.address,
|
||||
|
@ -387,7 +391,7 @@
|
|||
<div class="blob-viewer">
|
||||
<BlobViewer {address} {detail} on:handled={(ev) => (blobHandled = ev.detail)} />
|
||||
</div>
|
||||
{#if !$error}
|
||||
{#if !$error && $entity}
|
||||
<InspectGroups
|
||||
{entity}
|
||||
on:highlighted={(ev) => (highlightedType = ev.detail)}
|
||||
|
@ -412,7 +416,7 @@
|
|||
|
||||
{#if currentUntypedProperties.length > 0}
|
||||
<EntryView
|
||||
title={$i18n.t('Other Properties')}
|
||||
title={$i18n.t('Other Properties') || ''}
|
||||
widgets={attributeWidgets}
|
||||
entries={currentUntypedProperties}
|
||||
on:change={onChange}
|
||||
|
@ -422,7 +426,7 @@
|
|||
|
||||
{#if currentUntypedLinks.length > 0}
|
||||
<EntryView
|
||||
title={$i18n.t('Links')}
|
||||
title={$i18n.t('Links') || ''}
|
||||
widgets={linkWidgets}
|
||||
entries={currentUntypedLinks}
|
||||
on:change={onChange}
|
||||
|
@ -442,14 +446,14 @@
|
|||
<EntryView
|
||||
title={`${$i18n.t('Typed Members')} (${correctlyTagged.length})`}
|
||||
widgets={taggedWidgets}
|
||||
entries={tagged.filter((e) => correctlyTagged.includes(e.entity))}
|
||||
entries={tagged.filter((e) => correctlyTagged?.includes(e.entity))}
|
||||
on:change={onChange}
|
||||
{address}
|
||||
/>
|
||||
<EntryView
|
||||
title={`${$i18n.t('Untyped members')} (${incorrectlyTagged.length})`}
|
||||
widgets={taggedWidgets}
|
||||
entries={tagged.filter((e) => incorrectlyTagged.includes(e.entity))}
|
||||
entries={tagged.filter((e) => incorrectlyTagged?.includes(e.entity))}
|
||||
on:change={onChange}
|
||||
{address}
|
||||
/>
|
||||
|
@ -482,13 +486,13 @@
|
|||
<div class="entries">
|
||||
<h2>{$i18n.t('Attributes')}</h2>
|
||||
<EntryList
|
||||
entries={$entity.attributes}
|
||||
entries={$entity?.attributes || []}
|
||||
columns={detail ? 'timestamp, provenance, attribute, value' : 'attribute, value'}
|
||||
on:change={onChange}
|
||||
/>
|
||||
<h2>{$i18n.t('Backlinks')}</h2>
|
||||
<EntryList
|
||||
entries={$entity.backlinks}
|
||||
entries={$entity?.backlinks || []}
|
||||
columns={detail ? 'timestamp, provenance, entity, attribute' : 'entity, attribute'}
|
||||
on:change={onChange}
|
||||
/>
|
||||
|
@ -497,7 +501,7 @@
|
|||
<div class="footer">
|
||||
<IconButton
|
||||
name="detail"
|
||||
title={$i18n.t('Show as entries')}
|
||||
title={$i18n.t('Show as entries') || ''}
|
||||
active={showAsEntries}
|
||||
on:click={() => (showAsEntries = !showAsEntries)}
|
||||
/>
|
||||
|
@ -508,7 +512,7 @@
|
|||
subdued
|
||||
color="#dc322f"
|
||||
on:click={deleteObject}
|
||||
title={$i18n.t('Delete object')}
|
||||
title={$i18n.t('Delete object') || ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -575,10 +579,6 @@
|
|||
justify-content: end;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
|
|
|
@ -8,13 +8,15 @@
|
|||
import { i18n } from '../i18n';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let entity: Readable<UpObject>;
|
||||
export let entity: Readable<UpObject | undefined>;
|
||||
|
||||
$: groups = Object.fromEntries(
|
||||
($entity?.attr[ATTR_IN] || []).map((e) => [e.value.c as string, e.address])
|
||||
);
|
||||
|
||||
async function addGroup(address: string) {
|
||||
if (!$entity) return;
|
||||
|
||||
await api.putEntry([
|
||||
{
|
||||
entity: $entity.address,
|
||||
|
@ -36,7 +38,7 @@
|
|||
|
||||
<EntitySetEditor
|
||||
entities={Object.keys(groups)}
|
||||
header={$i18n.t('Groups')}
|
||||
header={$i18n.t('Groups') || ''}
|
||||
hide={Object.keys(groups).length === 0}
|
||||
on:add={(e) => addGroup(e.detail)}
|
||||
on:remove={(e) => removeGroup(e.detail)}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
import LabelBorder from './utils/LabelBorder.svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let entity: Readable<UpObject>;
|
||||
export let entity: Readable<UpObject | undefined>;
|
||||
|
||||
let adding = false;
|
||||
let typeSelector: Selector;
|
||||
|
@ -21,7 +21,7 @@
|
|||
$: typeEntries = $entity?.attr[`~${ATTR_OF}`] || [];
|
||||
|
||||
async function add(ev: CustomEvent<SelectorValue>) {
|
||||
if (ev.detail.t !== 'Attribute') {
|
||||
if (!$entity || ev.detail.t !== 'Attribute') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -37,11 +37,13 @@
|
|||
}
|
||||
|
||||
async function remove(entry: UpEntry) {
|
||||
if (!$entity) return;
|
||||
|
||||
let really = confirm(
|
||||
$i18n.t('Really remove "{{attributeName}}" from "{{typeName}}"?', {
|
||||
attributeName: (await api.addressToComponents(entry.entity)).c,
|
||||
typeName: $entity.identify().join('/')
|
||||
})
|
||||
}) || ''
|
||||
);
|
||||
|
||||
if (really) {
|
||||
|
@ -60,7 +62,7 @@
|
|||
bind:this={typeSelector}
|
||||
types={['Attribute', 'NewAttribute']}
|
||||
on:input={add}
|
||||
placeholder={$i18n.t('Assign an attribute to this type...')}
|
||||
placeholder={$i18n.t('Assign an attribute to this type...') || ''}
|
||||
on:focus={(ev) => {
|
||||
if (!ev.detail) adding = false;
|
||||
}}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
export let entities: string[];
|
||||
|
||||
let groups = [];
|
||||
let groups: string[] = [];
|
||||
let groupListing: UpListing | undefined = undefined;
|
||||
async function updateGroups() {
|
||||
const currentEntities = entities.concat();
|
||||
|
@ -23,7 +23,7 @@
|
|||
const commonGroups = new Set(
|
||||
allGroups.values
|
||||
.filter((v) => v.t == 'Address')
|
||||
.map((v) => v.c)
|
||||
.map((v) => v.c as string)
|
||||
.filter((groupAddr) => {
|
||||
return Object.values(allGroups.objects).every((obj) => {
|
||||
return obj.attr[ATTR_IN].some((v) => v.value.c === groupAddr);
|
||||
|
@ -54,11 +54,14 @@
|
|||
|
||||
async function removeGroup(address: string) {
|
||||
await Promise.all(
|
||||
entities.map((entity) =>
|
||||
api.deleteEntry(
|
||||
groupListing.objects[entity].attr[ATTR_IN].find((v) => v.value.c === address).address
|
||||
)
|
||||
)
|
||||
entities.map((entity) => {
|
||||
const group = groupListing?.objects[entity].attr[ATTR_IN].find(
|
||||
(v) => v.value.c === address
|
||||
);
|
||||
if (group) {
|
||||
return api.deleteEntry(group.address);
|
||||
}
|
||||
})
|
||||
);
|
||||
await updateGroups();
|
||||
}
|
||||
|
@ -66,7 +69,7 @@
|
|||
|
||||
<EntitySetEditor
|
||||
entities={groups}
|
||||
header={$i18n.t('Common groups')}
|
||||
header={$i18n.t('Common groups') || ''}
|
||||
on:add={(ev) => addGroup(ev.detail)}
|
||||
on:remove={(ev) => removeGroup(ev.detail)}
|
||||
/>
|
||||
|
|
|
@ -1,76 +1,77 @@
|
|||
<script lang="ts">
|
||||
import { i18n } from "../i18n";
|
||||
import { selected } from "./EntitySelect.svelte";
|
||||
import EntryView from "./EntryView.svelte";
|
||||
import MultiGroupEditor from "./MultiGroupEditor.svelte";
|
||||
import Icon from "./utils/Icon.svelte";
|
||||
import EntityList from "./widgets/EntityList.svelte";
|
||||
import { i18n } from '../i18n';
|
||||
import { selected } from './EntitySelect.svelte';
|
||||
import EntryView from './EntryView.svelte';
|
||||
import MultiGroupEditor from './MultiGroupEditor.svelte';
|
||||
import Icon from './utils/Icon.svelte';
|
||||
import EntityList from './widgets/EntityList.svelte';
|
||||
import type { Widget } from '$lib/components/EntryView.svelte';
|
||||
|
||||
const selectedWidgets = [
|
||||
{
|
||||
name: "List",
|
||||
icon: "list-check",
|
||||
components: ({ entities }) => [
|
||||
{
|
||||
component: EntityList,
|
||||
props: {
|
||||
entities,
|
||||
thumbnails: false,
|
||||
select: "remove",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "EntityList",
|
||||
icon: "image",
|
||||
components: ({ entities }) => [
|
||||
{
|
||||
component: EntityList,
|
||||
props: {
|
||||
entities,
|
||||
thumbnails: true,
|
||||
select: "remove",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const selectedWidgets: Widget[] = [
|
||||
{
|
||||
name: 'List',
|
||||
icon: 'list-check',
|
||||
components: ({ entities }) => [
|
||||
{
|
||||
component: EntityList,
|
||||
props: {
|
||||
entities,
|
||||
thumbnails: false,
|
||||
select: 'remove'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'EntityList',
|
||||
icon: 'image',
|
||||
components: ({ entities }) => [
|
||||
{
|
||||
component: EntityList,
|
||||
props: {
|
||||
entities,
|
||||
thumbnails: true,
|
||||
select: 'remove'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="view">
|
||||
<h2>
|
||||
<Icon plain name="select-multiple" />
|
||||
{$i18n.t("Selected")}: {$selected.length}
|
||||
</h2>
|
||||
<div class="actions">
|
||||
<MultiGroupEditor entities={$selected} />
|
||||
</div>
|
||||
<div class="entities">
|
||||
<EntryView
|
||||
title={$i18n.t("Selected entities")}
|
||||
entities={$selected}
|
||||
widgets={selectedWidgets}
|
||||
/>
|
||||
</div>
|
||||
<h2>
|
||||
<Icon plain name="select-multiple" />
|
||||
{$i18n.t('Selected')}: {$selected.length}
|
||||
</h2>
|
||||
<div class="actions">
|
||||
<MultiGroupEditor entities={$selected} />
|
||||
</div>
|
||||
<div class="entities">
|
||||
<EntryView
|
||||
title={$i18n.t('Selected entities') || ''}
|
||||
entities={$selected}
|
||||
widgets={selectedWidgets}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
.view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
margin-top: -0.66em;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
margin-top: -0.66em;
|
||||
}
|
||||
|
||||
.entities {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
height: 0;
|
||||
}
|
||||
.entities {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
height: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
import debug from 'debug';
|
||||
import { Query } from '@upnd/upend';
|
||||
import { Any } from '@upnd/upend/query';
|
||||
import { isDefined } from '$lib/util/werk';
|
||||
const dbg = debug('kestrel:surface');
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
|
@ -78,13 +79,15 @@
|
|||
}
|
||||
let points: IPoint[] = [];
|
||||
async function loadPoints() {
|
||||
if (!x || !y) return;
|
||||
|
||||
points = [];
|
||||
const result = await api.query(`(matches ? (in "${x}" "${y}") ?)`);
|
||||
|
||||
points = Object.entries(result.objects)
|
||||
.map(([address, obj]) => {
|
||||
let objX = parseInt(String(obj.get(x)));
|
||||
let objY = parseInt(String(obj.get(y)));
|
||||
let objX = parseInt(String(obj.get(x!)));
|
||||
let objY = parseInt(String(obj.get(y!)));
|
||||
|
||||
if (objX && objY) {
|
||||
return {
|
||||
|
@ -94,7 +97,7 @@
|
|||
};
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
.filter(isDefined);
|
||||
|
||||
tick().then(() => {
|
||||
autofit();
|
||||
|
@ -113,6 +116,10 @@
|
|||
const d3 = await import('d3');
|
||||
|
||||
function init() {
|
||||
if (!viewEl) {
|
||||
dbg("Couldn't find view element");
|
||||
return;
|
||||
}
|
||||
viewWidth = viewEl.clientWidth;
|
||||
viewHeight = viewEl.clientHeight;
|
||||
|
||||
|
@ -164,11 +171,13 @@
|
|||
}
|
||||
|
||||
autofit = () => {
|
||||
zoom.translateTo(view, 0, viewHeight);
|
||||
if (!zoom) return;
|
||||
|
||||
zoom.translateTo(view as any, 0, viewHeight);
|
||||
|
||||
if (points.length) {
|
||||
zoom.scaleTo(
|
||||
view,
|
||||
view as any,
|
||||
Math.min(
|
||||
viewWidth / 2 / Math.max(...points.map((p) => Math.abs(p.x))) - 0.3,
|
||||
viewHeight / 2 / Math.max(...points.map((p) => Math.abs(p.y))) - 0.3
|
||||
|
@ -180,10 +189,10 @@
|
|||
function updateStyles() {
|
||||
svg
|
||||
.selectAll('.tick line')
|
||||
.attr('stroke-width', (d: number) => {
|
||||
.attr('stroke-width', (d) => {
|
||||
return d === 0 ? 2 : 1;
|
||||
})
|
||||
.attr('stroke', function (d: number) {
|
||||
.attr('stroke', (d) => {
|
||||
return d === 0 ? 'var(--foreground-lightest)' : 'var(--foreground-lighter)';
|
||||
});
|
||||
}
|
||||
|
@ -204,7 +213,7 @@
|
|||
});
|
||||
|
||||
d3.select(viewEl)
|
||||
.call(zoom)
|
||||
.call(zoom as any)
|
||||
.on('dblclick.zoom', (_ev: MouseEvent) => {
|
||||
selectorCoords = [currentX, currentY];
|
||||
});
|
||||
|
@ -217,7 +226,7 @@
|
|||
const resizeObserver = new ResizeObserver(() => {
|
||||
tick().then(() => init());
|
||||
});
|
||||
resizeObserver.observe(viewEl);
|
||||
resizeObserver.observe(viewEl as any);
|
||||
});
|
||||
|
||||
async function onSelectorInput(ev: CustomEvent<SelectorValue>) {
|
||||
|
@ -225,13 +234,15 @@
|
|||
if (value.t !== 'Address') return;
|
||||
const address = value.c;
|
||||
|
||||
const [xValue, yValue] = selectorCoords;
|
||||
const [xValue, yValue] = selectorCoords as any;
|
||||
selectorCoords = null;
|
||||
await Promise.all(
|
||||
[
|
||||
[x, xValue],
|
||||
[y, yValue]
|
||||
].map(([axis, value]: [string, number]) =>
|
||||
(
|
||||
[
|
||||
[x, xValue],
|
||||
[y, yValue]
|
||||
] as any[]
|
||||
).map(([axis, value]: [string, number]) =>
|
||||
api.putEntityAttribute(address, axis, {
|
||||
t: 'Number',
|
||||
c: value
|
||||
|
|
|
@ -17,39 +17,40 @@
|
|||
export let recurse = 3;
|
||||
|
||||
$: ({ entity, entityInfo } = useEntity(address));
|
||||
$: types = $entity && getTypes($entity, $entityInfo);
|
||||
$: types = $entity && $entityInfo && getTypes($entity, $entityInfo);
|
||||
|
||||
$: handled =
|
||||
types &&
|
||||
(!$entity ||
|
||||
types.audio ||
|
||||
types.video ||
|
||||
types.image ||
|
||||
types.text ||
|
||||
types.model ||
|
||||
types.web ||
|
||||
types.fragment ||
|
||||
(types.group && recurse > 0));
|
||||
types?.audio ||
|
||||
types?.video ||
|
||||
types?.image ||
|
||||
types?.text ||
|
||||
types?.model ||
|
||||
types?.web ||
|
||||
types?.fragment ||
|
||||
(types?.group && recurse > 0));
|
||||
|
||||
$: dispatch('handled', handled);
|
||||
|
||||
let loaded = null;
|
||||
let loaded: string | boolean = false;
|
||||
$: dispatch('loaded', Boolean(loaded));
|
||||
|
||||
let failedChildren: string[] = [];
|
||||
let loadedChildren: string[] = [];
|
||||
$: groupChildren = $entity?.backlinks
|
||||
.filter((e) => e.attribute === ATTR_IN)
|
||||
.map((e) => String(e.entity))
|
||||
.filter(
|
||||
(addr) =>
|
||||
!failedChildren
|
||||
.slice(0, $entity?.backlinks.filter((e) => e.attribute === ATTR_IN).length - 4)
|
||||
.includes(addr)
|
||||
)
|
||||
.slice(0, 4);
|
||||
$: groupChildren =
|
||||
$entity?.backlinks
|
||||
.filter((e) => e.attribute === ATTR_IN)
|
||||
.map((e) => String(e.entity))
|
||||
.filter(
|
||||
(addr) =>
|
||||
!failedChildren
|
||||
.slice(0, $entity?.backlinks.filter((e) => e.attribute === ATTR_IN).length || 0 - 4)
|
||||
.includes(addr)
|
||||
)
|
||||
.slice(0, 4) || [];
|
||||
|
||||
$: if (groupChildren)
|
||||
$: if (groupChildren.length)
|
||||
loaded = groupChildren.every(
|
||||
(addr) => loadedChildren.includes(addr) || failedChildren.includes(addr)
|
||||
);
|
||||
|
@ -61,7 +62,7 @@
|
|||
{#if !loaded}
|
||||
<Spinner centered="absolute" />
|
||||
{/if}
|
||||
{#if types.group}
|
||||
{#if types?.group}
|
||||
<ul class="group">
|
||||
{#each groupChildren as address (address)}
|
||||
<li>
|
||||
|
@ -80,28 +81,28 @@
|
|||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else if types.model}
|
||||
{:else if types?.model}
|
||||
<ModelViewer
|
||||
lookonly
|
||||
src="{api.apiUrl}/raw/{address}"
|
||||
on:loaded={() => (loaded = address)}
|
||||
/>
|
||||
{:else if types.web}
|
||||
{:else if types?.web}
|
||||
<img
|
||||
alt="OpenGraph image for {$entityInfo?.t == 'Url' && $entityInfo?.c}"
|
||||
use:concurrentImage={String($entity?.get('OG_IMAGE'))}
|
||||
on:load={() => (loaded = address)}
|
||||
on:error={() => (handled = false)}
|
||||
/>
|
||||
{:else if types.fragment}
|
||||
{:else if types?.fragment}
|
||||
<FragmentViewer {address} detail={false} on:loaded={() => (loaded = address)} />
|
||||
{:else if types.audio}
|
||||
{:else if types?.audio}
|
||||
<AudioPreview
|
||||
{address}
|
||||
on:loaded={() => (loaded = address)}
|
||||
on:error={() => (handled = false)}
|
||||
/>
|
||||
{:else if types.video}
|
||||
{:else if types?.video}
|
||||
<VideoViewer {address} detail={false} on:loaded={() => (loaded = address)} />
|
||||
{:else}
|
||||
<div class="image" class:loaded={loaded == address || !handled}>
|
||||
|
@ -109,7 +110,7 @@
|
|||
class:loaded={loaded == address}
|
||||
alt="Thumbnail for {address}..."
|
||||
use:concurrentImage={`${api.apiUrl}/${
|
||||
types.mimeType?.includes('svg+xml') ? 'raw' : 'thumb'
|
||||
types?.mimeType?.includes('svg+xml') ? 'raw' : 'thumb'
|
||||
}/${address}?size=512&quality=75`}
|
||||
on:load={() => (loaded = address)}
|
||||
on:error={() => (handled = false)}
|
||||
|
@ -168,8 +169,6 @@
|
|||
}
|
||||
|
||||
.group {
|
||||
padding: 0;
|
||||
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
|
|
|
@ -1,61 +1,64 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const BADGE_HEIGHT = 3;
|
||||
export let address: string;
|
||||
const BADGE_HEIGHT = 3;
|
||||
export let address: string;
|
||||
|
||||
let canvas: HTMLCanvasElement | undefined;
|
||||
let width = 0;
|
||||
let canvas: HTMLCanvasElement | undefined;
|
||||
let width = 0;
|
||||
|
||||
const bytes = [...address].map((c) => c.charCodeAt(0));
|
||||
while (bytes.length % (3 * BADGE_HEIGHT) !== 0) {
|
||||
bytes.push(bytes[bytes.length - 1]);
|
||||
}
|
||||
const bytes = [...address].map((c) => c.charCodeAt(0));
|
||||
while (bytes.length % (3 * BADGE_HEIGHT) !== 0) {
|
||||
bytes.push(bytes[bytes.length - 1]);
|
||||
}
|
||||
|
||||
width = Math.ceil(bytes.length / 3 / BADGE_HEIGHT);
|
||||
width = Math.ceil(bytes.length / 3 / BADGE_HEIGHT);
|
||||
|
||||
onMount(() => {
|
||||
const ctx = canvas?.getContext("2d");
|
||||
if (!ctx) {
|
||||
console.warn("Couldn't initialize canvas!");
|
||||
return;
|
||||
}
|
||||
onMount(() => {
|
||||
const ctx = canvas?.getContext('2d');
|
||||
if (!ctx) {
|
||||
console.warn("Couldn't initialize canvas!");
|
||||
return;
|
||||
}
|
||||
|
||||
const hueRange = 120;
|
||||
const hueCenter = 90 + bytes.length * 2.5;
|
||||
const hueRange = 120;
|
||||
const hueCenter = 90 + bytes.length * 2.5;
|
||||
|
||||
let idx = 0;
|
||||
function draw() {
|
||||
const tmp = [];
|
||||
while (bytes.length > 0 && tmp.length < 3) {
|
||||
tmp.push(bytes.shift());
|
||||
}
|
||||
while (tmp.length < 3) {
|
||||
tmp.push(tmp[tmp.length - 1]);
|
||||
}
|
||||
let idx = 0;
|
||||
function draw() {
|
||||
if (!ctx) return;
|
||||
|
||||
const h = (tmp[0] / 128) * hueRange + hueCenter - hueRange / 2;
|
||||
const s = (tmp[1] / 128) * 100;
|
||||
const l = (tmp[2] / 128) * 100;
|
||||
ctx.fillStyle = `hsl(${h},${s}%,${l}%)`;
|
||||
ctx.fillRect(Math.floor(idx / BADGE_HEIGHT), idx % BADGE_HEIGHT, 1, 1);
|
||||
idx++;
|
||||
if (bytes.length > 0) {
|
||||
requestAnimationFrame(draw);
|
||||
}
|
||||
}
|
||||
const tmp = [];
|
||||
while (bytes.length > 0 && tmp.length < 3) {
|
||||
tmp.push(bytes.shift());
|
||||
}
|
||||
while (tmp.length < 3) {
|
||||
tmp.push(tmp[tmp.length - 1]);
|
||||
}
|
||||
|
||||
requestAnimationFrame(draw);
|
||||
});
|
||||
const h = (tmp[0]! / 128) * hueRange + hueCenter - hueRange / 2;
|
||||
const s = (tmp[1]! / 128) * 100;
|
||||
const l = (tmp[2]! / 128) * 100;
|
||||
ctx.fillStyle = `hsl(${h},${s}%,${l}%)`;
|
||||
ctx.fillRect(Math.floor(idx / BADGE_HEIGHT), idx % BADGE_HEIGHT, 1, 1);
|
||||
idx++;
|
||||
if (bytes.length > 0) {
|
||||
requestAnimationFrame(draw);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(draw);
|
||||
});
|
||||
</script>
|
||||
|
||||
<canvas bind:this={canvas} {width} height="3" title={address} />
|
||||
|
||||
<!--suppress CssOverwrittenProperties -->
|
||||
<style>
|
||||
canvas {
|
||||
display: inline-block;
|
||||
height: 1em;
|
||||
image-rendering: optimizeSpeed;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
canvas {
|
||||
display: inline-block;
|
||||
height: 1em;
|
||||
image-rendering: optimizeSpeed;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,50 +1,50 @@
|
|||
<script lang="ts">
|
||||
import type { Address } from "@upnd/upend/types";
|
||||
import UpObject from "./UpObject.svelte";
|
||||
import UpLink from "./UpLink.svelte";
|
||||
import type { Address } from '@upnd/upend/types';
|
||||
import UpObject from './UpObject.svelte';
|
||||
import UpLink from './UpLink.svelte';
|
||||
|
||||
export let address: Address;
|
||||
let popup = false;
|
||||
export let address: Address;
|
||||
let popup = false;
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<UpLink passthrough to={{ entity: address }}>
|
||||
<div
|
||||
class="surface-point"
|
||||
on:mouseover={() => (popup = true)}
|
||||
on:mouseleave={() => (popup = false)}
|
||||
>
|
||||
{#if popup}
|
||||
<div class="popup-inner">
|
||||
<UpObject {address} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="surface-point"
|
||||
on:mouseover={() => (popup = true)}
|
||||
on:mouseleave={() => (popup = false)}
|
||||
>
|
||||
{#if popup}
|
||||
<div class="popup-inner">
|
||||
<UpObject {address} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</UpLink>
|
||||
|
||||
<style lang="scss">
|
||||
@use "../../styles/colors.scss";
|
||||
@use '../../styles/colors.scss';
|
||||
|
||||
.surface-point {
|
||||
display: relative;
|
||||
.surface-point {
|
||||
display: relative;
|
||||
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
border-radius: 25%;
|
||||
background: colors.$red;
|
||||
box-shadow: 0 0 0 1px darken(colors.$red, 20%);
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
border-radius: 25%;
|
||||
background: colors.$red;
|
||||
box-shadow: 0 0 0 1px darken(colors.$red, 20%);
|
||||
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: lighten(colors.$red, 20%);
|
||||
}
|
||||
}
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: lighten(colors.$red, 20%);
|
||||
}
|
||||
}
|
||||
|
||||
.popup-inner {
|
||||
position: relative;
|
||||
top: 1rem;
|
||||
display: inline-block;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.popup-inner {
|
||||
position: relative;
|
||||
top: 1rem;
|
||||
display: inline-block;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { useEntity } from '$lib/entity';
|
||||
import api from '$lib/api';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { formatDuration } from '../../../util/fragments/time';
|
||||
import { formatDuration } from '$lib/util/fragments/time';
|
||||
import { concurrentImage } from '../../imageQueue';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
|||
|
||||
$: ({ entity } = useEntity(address));
|
||||
|
||||
let loaded = null;
|
||||
let loaded: string | null = null;
|
||||
let handled = true;
|
||||
$: dispatch('handled', handled);
|
||||
$: dispatch('loaded', Boolean(loaded));
|
||||
|
@ -70,6 +70,6 @@
|
|||
font-size: var(--font-size);
|
||||
font-weight: bold;
|
||||
color: var(--foreground-lightest);
|
||||
text-shadow: 0px 0px 0.2em var(--background-lighter);
|
||||
text-shadow: 0 0 0.2em var(--background-lighter);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
import type WaveSurfer from 'wavesurfer.js';
|
||||
import type { Region, RegionParams } from 'wavesurfer.js/src/plugin/regions';
|
||||
import api from '$lib/api';
|
||||
import { TimeFragment } from '../../../util/fragments/time';
|
||||
import { TimeFragment } from '$lib/util/fragments/time';
|
||||
import Icon from '../../utils/Icon.svelte';
|
||||
import Selector from '../../utils/Selector.svelte';
|
||||
import UpObject from '../../display/UpObject.svelte';
|
||||
import Spinner from '../../utils/Spinner.svelte';
|
||||
import IconButton from '../../../components/utils/IconButton.svelte';
|
||||
import LabelBorder from '../../../components/utils/LabelBorder.svelte';
|
||||
import { i18n } from '../../../i18n';
|
||||
import { i18n } from '$lib/i18n';
|
||||
import { ATTR_LABEL } from '@upnd/upend/constants';
|
||||
import debug from 'debug';
|
||||
const dbg = debug('kestrel:AudioViewer');
|
||||
|
@ -209,7 +209,7 @@
|
|||
wavesurfer.on('region-removed', (region: UpRegion) => {
|
||||
dbg('wavesurfer region-removed', region);
|
||||
|
||||
currentAnnotation = null;
|
||||
currentAnnotation = undefined;
|
||||
deleteAnnotation(region);
|
||||
});
|
||||
|
||||
|
@ -252,7 +252,7 @@
|
|||
confirm(
|
||||
$i18n.t(
|
||||
'File is large (>20 MiB) and UpEnd failed to load waveform from server. Generating the waveform locally may slow down your browser. Do you wish to proceed anyway?'
|
||||
)
|
||||
) || ''
|
||||
)
|
||||
) {
|
||||
console.warn(`Failed to load peaks, falling back to client-side render...`);
|
||||
|
@ -277,7 +277,7 @@
|
|||
<header>
|
||||
<IconButton
|
||||
name="edit"
|
||||
title={$i18n.t('Toggle Edit Mode')}
|
||||
title={$i18n.t('Toggle Edit Mode') || ''}
|
||||
on:click={() => (editable = !editable)}
|
||||
active={editable}
|
||||
>
|
||||
|
@ -306,6 +306,7 @@
|
|||
value={Math.round(currentAnnotation.start * 100) / 100}
|
||||
disabled={!editable}
|
||||
on:input={(ev) => {
|
||||
if (!currentAnnotation) return;
|
||||
currentAnnotation.update({
|
||||
start: parseInt(ev.currentTarget.value)
|
||||
});
|
||||
|
@ -319,6 +320,7 @@
|
|||
value={Math.round(currentAnnotation.end * 100) / 100}
|
||||
disabled={!editable}
|
||||
on:input={(ev) => {
|
||||
if (!currentAnnotation) return;
|
||||
currentAnnotation.update({
|
||||
end: parseInt(ev.currentTarget.value)
|
||||
});
|
||||
|
@ -332,6 +334,7 @@
|
|||
value={currentAnnotation.color || DEFAULT_ANNOTATION_COLOR}
|
||||
disabled={!editable}
|
||||
on:input={(ev) => {
|
||||
if (!currentAnnotation) return;
|
||||
currentAnnotation.update({ color: ev.currentTarget.value });
|
||||
updateAnnotation(currentAnnotation);
|
||||
}}
|
||||
|
@ -340,7 +343,7 @@
|
|||
</div>
|
||||
{#if editable}
|
||||
<div class="existControls">
|
||||
<IconButton outline name="trash" on:click={() => currentAnnotation.remove()} />
|
||||
<IconButton outline name="trash" on:click={() => currentAnnotation?.remove()} />
|
||||
<!-- <div class="button">
|
||||
<Icon name="check" />
|
||||
</div> -->
|
||||
|
@ -354,6 +357,7 @@
|
|||
initial={currentAnnotation.data}
|
||||
disabled={!editable}
|
||||
on:input={(ev) => {
|
||||
if (!currentAnnotation) return;
|
||||
currentAnnotation.update({ data: ev.detail });
|
||||
updateAnnotation(currentAnnotation);
|
||||
}}
|
||||
|
|
|
@ -81,7 +81,8 @@
|
|||
let a8sLinkAddress: string;
|
||||
|
||||
async function loaded() {
|
||||
const { Annotorious } = await import('@recogito/annotorious');
|
||||
// noinspection TypeScriptCheckImport
|
||||
const { Annotorious } = (await import('@recogito/annotorious')) as any;
|
||||
|
||||
if (anno) {
|
||||
anno.destroy();
|
||||
|
|
|
@ -1,86 +1,81 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
const dispatch = createEventDispatcher();
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let src: string;
|
||||
export let lookonly = false;
|
||||
export let src: string;
|
||||
export let lookonly = false;
|
||||
|
||||
let root: HTMLElement;
|
||||
let root: HTMLElement;
|
||||
|
||||
onMount(async () => {
|
||||
root.style.height = `${root.clientWidth}px`;
|
||||
onMount(async () => {
|
||||
root.style.height = `${root.clientWidth}px`;
|
||||
|
||||
const THREE = await import("three");
|
||||
const THREE_OC = await import("three/examples/jsm/controls/OrbitControls");
|
||||
const THREE_STL = await import("three/examples/jsm/loaders/STLLoader");
|
||||
const THREE = await import('three');
|
||||
const THREE_OC = await import('three/examples/jsm/controls/OrbitControls');
|
||||
const THREE_STL = await import('three/examples/jsm/loaders/STLLoader');
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
70,
|
||||
root.clientWidth / root.clientHeight,
|
||||
);
|
||||
const camera = new THREE.PerspectiveCamera(70, root.clientWidth / root.clientHeight);
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||
renderer.setSize(root.clientWidth, root.clientHeight);
|
||||
root.appendChild(renderer.domElement);
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||
renderer.setSize(root.clientWidth, root.clientHeight);
|
||||
root.appendChild(renderer.domElement);
|
||||
|
||||
const controls = new THREE_OC.OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.1;
|
||||
controls.enableZoom = true;
|
||||
controls.autoRotate = true;
|
||||
controls.autoRotateSpeed = 3;
|
||||
const controls = new THREE_OC.OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.1;
|
||||
controls.enableZoom = true;
|
||||
controls.autoRotate = true;
|
||||
controls.autoRotateSpeed = 3;
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
scene.add(new THREE.HemisphereLight(0xffffff, 1.5));
|
||||
const scene = new THREE.Scene();
|
||||
scene.add(new THREE.HemisphereLight(0xffffff, 1.5));
|
||||
|
||||
const loader = new THREE_STL.STLLoader();
|
||||
loader.load(src, (geometry) => {
|
||||
const material = new THREE.MeshPhongMaterial({
|
||||
color: 0xdc322f,
|
||||
specular: 100,
|
||||
shininess: 70,
|
||||
});
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
scene.add(mesh);
|
||||
const loader = new THREE_STL.STLLoader();
|
||||
loader.load(src, (geometry: any) => {
|
||||
const material = new THREE.MeshPhongMaterial({
|
||||
color: 0xdc322f,
|
||||
specular: 100,
|
||||
shininess: 70
|
||||
});
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
scene.add(mesh);
|
||||
|
||||
const middle = new THREE.Vector3();
|
||||
geometry.computeBoundingBox();
|
||||
geometry.boundingBox.getCenter(middle);
|
||||
mesh.geometry.applyMatrix4(
|
||||
new THREE.Matrix4().makeTranslation(-middle.x, -middle.y, -middle.z),
|
||||
);
|
||||
mesh.geometry.applyMatrix4(
|
||||
new THREE.Matrix4().makeRotationX(-Math.PI / 2),
|
||||
);
|
||||
const middle = new THREE.Vector3();
|
||||
geometry.computeBoundingBox();
|
||||
geometry.boundingBox.getCenter(middle);
|
||||
mesh.geometry.applyMatrix4(
|
||||
new THREE.Matrix4().makeTranslation(-middle.x, -middle.y, -middle.z)
|
||||
);
|
||||
mesh.geometry.applyMatrix4(new THREE.Matrix4().makeRotationX(-Math.PI / 2));
|
||||
|
||||
const largestDimension = Math.max(
|
||||
geometry.boundingBox.max.x,
|
||||
geometry.boundingBox.max.y,
|
||||
geometry.boundingBox.max.z,
|
||||
);
|
||||
camera.position.z = largestDimension * 2;
|
||||
});
|
||||
const largestDimension = Math.max(
|
||||
geometry.boundingBox.max.x,
|
||||
geometry.boundingBox.max.y,
|
||||
geometry.boundingBox.max.z
|
||||
);
|
||||
camera.position.z = largestDimension * 2;
|
||||
});
|
||||
|
||||
function animate() {
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
animate();
|
||||
function animate() {
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
animate();
|
||||
|
||||
dispatch("loaded");
|
||||
});
|
||||
dispatch('loaded');
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="modelviewer" class:lookonly bind:this={root} />
|
||||
|
||||
<style>
|
||||
.modelviewer {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
.modelviewer {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.modelviewer.lookonly {
|
||||
pointer-events: none;
|
||||
}
|
||||
.modelviewer.lookonly {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import api from '$lib/api';
|
||||
import IconButton from '../../utils/IconButton.svelte';
|
||||
import Spinner from '../../utils/Spinner.svelte';
|
||||
|
||||
export let address: string;
|
||||
|
||||
let mode: 'preview' | 'full' | 'markdown' = 'preview';
|
||||
|
@ -37,6 +38,8 @@
|
|||
mode = targetMode;
|
||||
}
|
||||
}}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<IconButton name={icon} active={mode == targetMode} on:click={() => (mode = targetMode)} />
|
||||
<div class="label">{label}</div>
|
||||
|
@ -48,6 +51,7 @@
|
|||
<Spinner centered />
|
||||
{:then text}
|
||||
{#if mode === 'markdown'}
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html text}
|
||||
{:else}
|
||||
{text}{#if mode === 'preview'}…{/if}
|
||||
|
|
|
@ -41,7 +41,7 @@ class ImageQueue {
|
|||
});
|
||||
|
||||
while (this.active < this.concurrency && this.queue.length) {
|
||||
const nextIdx = this.queue.findIndex((e) => e.check()) || 0;
|
||||
const nextIdx = this.queue.findIndex((e) => e.check && e.check()) || 0;
|
||||
const next = this.queue.splice(nextIdx, 1)[0];
|
||||
dbg(`Getting ${next.id}...`);
|
||||
this.active += 1;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
export let active = 0;
|
||||
$: active = activeJobs.length;
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
let timeout: NodeJS.Timeout;
|
||||
async function updateJobs() {
|
||||
clearTimeout(timeout);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { UpNotification, UpNotificationLevel } from '../../notifications';
|
||||
import { notify } from '../../notifications';
|
||||
import type { UpNotification, UpNotificationLevel } from '$lib/notifications';
|
||||
import { notify } from '$lib/notifications';
|
||||
import { fade } from 'svelte/transition';
|
||||
import Icon from '../utils/Icon.svelte';
|
||||
import { DEBUG, lipsum } from '$lib/debug';
|
||||
|
@ -55,9 +55,10 @@
|
|||
}, 5000);
|
||||
});
|
||||
|
||||
const icons = {
|
||||
const icons: Record<UpNotificationLevel, string | undefined> = {
|
||||
error: 'error-alt',
|
||||
warning: 'error'
|
||||
warning: 'error',
|
||||
info: undefined
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,96 +1,94 @@
|
|||
<script lang="ts">
|
||||
import Selector, {
|
||||
type SELECTOR_TYPE,
|
||||
type SelectorValue,
|
||||
} from "./Selector.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { IValue } from "@upnd/upend/types";
|
||||
import IconButton from "./IconButton.svelte";
|
||||
import Selector, { type SELECTOR_TYPE, type SelectorValue } from './Selector.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type { IValue } from '@upnd/upend/types';
|
||||
import IconButton from './IconButton.svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let value: IValue | undefined = undefined;
|
||||
export let types: SELECTOR_TYPE[] | undefined = undefined;
|
||||
let newValue: SelectorValue = value;
|
||||
export let value: IValue | undefined = undefined;
|
||||
export let types: SELECTOR_TYPE[] | undefined = undefined;
|
||||
let newValue: SelectorValue | undefined = value;
|
||||
|
||||
let editing = false;
|
||||
let editing = false;
|
||||
|
||||
let selector: Selector;
|
||||
let hover = false;
|
||||
let focus = false;
|
||||
let selector: Selector;
|
||||
let hover = false;
|
||||
let focus = false;
|
||||
|
||||
$: if (editing && selector) selector.focus();
|
||||
$: if (!focus && !hover) editing = false;
|
||||
$: if (editing && selector) selector.focus();
|
||||
$: if (!focus && !hover) editing = false;
|
||||
|
||||
function onInput(ev: CustomEvent<SelectorValue>) {
|
||||
newValue = ev.detail;
|
||||
selector.focus();
|
||||
}
|
||||
function onInput(ev: CustomEvent<SelectorValue>) {
|
||||
newValue = ev.detail;
|
||||
selector.focus();
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="editable"
|
||||
class:editing
|
||||
on:mouseenter={() => (hover = true)}
|
||||
on:mouseleave={() => (hover = false)}
|
||||
class="editable"
|
||||
class:editing
|
||||
on:mouseenter={() => (hover = true)}
|
||||
on:mouseleave={() => (hover = false)}
|
||||
>
|
||||
<div class="inner">
|
||||
{#if editing}
|
||||
<div
|
||||
class="selector"
|
||||
on:keydown={(ev) => {
|
||||
if (ev.key === "Escape") {
|
||||
editing = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Selector
|
||||
{types}
|
||||
bind:this={selector}
|
||||
on:focus={(ev) => (focus = ev.detail)}
|
||||
on:input={onInput}
|
||||
/>
|
||||
</div>
|
||||
<IconButton
|
||||
name="save"
|
||||
on:click={() => {
|
||||
dispatch("edit", newValue);
|
||||
editing = false;
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="edit-icon">
|
||||
<IconButton name="edit" on:click={() => (editing = true)} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="inner">
|
||||
{#if editing}
|
||||
<div
|
||||
class="selector"
|
||||
on:keydown={(ev) => {
|
||||
if (ev.key === 'Escape') {
|
||||
editing = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Selector
|
||||
{types}
|
||||
bind:this={selector}
|
||||
on:focus={(ev) => (focus = ev.detail)}
|
||||
on:input={onInput}
|
||||
/>
|
||||
</div>
|
||||
<IconButton
|
||||
name="save"
|
||||
on:click={() => {
|
||||
dispatch('edit', newValue);
|
||||
editing = false;
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="edit-icon">
|
||||
<IconButton name="edit" on:click={() => (editing = true)} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.edit-icon {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
.edit-icon {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.editable:hover .edit-icon {
|
||||
opacity: 0.8;
|
||||
}
|
||||
.editable:hover .edit-icon {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.inner {
|
||||
display: flex;
|
||||
gap: 0.25em;
|
||||
align-items: center;
|
||||
}
|
||||
.inner {
|
||||
display: flex;
|
||||
gap: 0.25em;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
min-width: 0;
|
||||
}
|
||||
.content {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.selector {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.selector {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,68 +1,68 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
let input: HTMLInputElement;
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
let input: HTMLInputElement;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let placeholder = "";
|
||||
export let value = "";
|
||||
export let disabled = false;
|
||||
export let size: number | undefined = 7;
|
||||
export let placeholder = '';
|
||||
export let value = '';
|
||||
export let disabled = false;
|
||||
export let size: number | undefined = 7;
|
||||
|
||||
let focused = false;
|
||||
$: dispatch("focusChange", focused);
|
||||
let focused = false;
|
||||
$: dispatch('focusChange', focused);
|
||||
|
||||
function onInput() {
|
||||
dispatch("input", value);
|
||||
}
|
||||
function onInput() {
|
||||
dispatch('input', value);
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
input.focus();
|
||||
}
|
||||
export function focus() {
|
||||
input.focus();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="input" class:focused>
|
||||
<slot name="prefix" />
|
||||
<input
|
||||
bind:this={input}
|
||||
{placeholder}
|
||||
bind:value
|
||||
on:input={onInput}
|
||||
on:focus={() => (focused = true)}
|
||||
on:blur={() => (focused = false)}
|
||||
size={Math.max(value.length, size)}
|
||||
on:keydown
|
||||
{disabled}
|
||||
/>
|
||||
<slot name="prefix" />
|
||||
<input
|
||||
bind:this={input}
|
||||
{placeholder}
|
||||
bind:value
|
||||
on:input={onInput}
|
||||
on:focus={() => (focused = true)}
|
||||
on:blur={() => (focused = false)}
|
||||
size={Math.max(value.length, size || 0)}
|
||||
on:keydown
|
||||
{disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25em;
|
||||
padding: 0.25em;
|
||||
.input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25em;
|
||||
padding: 0.25em;
|
||||
|
||||
border: 1px solid var(--foreground-lighter);
|
||||
border-radius: 4px;
|
||||
background: var(--background);
|
||||
border: 1px solid var(--foreground-lighter);
|
||||
border-radius: 4px;
|
||||
background: var(--background);
|
||||
|
||||
transition: box-shadow 0.25s;
|
||||
&.focused {
|
||||
box-shadow: 0 0 2px 3px var(--primary);
|
||||
}
|
||||
}
|
||||
transition: box-shadow 0.25s;
|
||||
&.focused {
|
||||
box-shadow: 0 0 2px 3px var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
input {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
|
||||
color: var(--foreground);
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--foreground);
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -82,14 +82,14 @@
|
|||
import { createEventDispatcher } from 'svelte';
|
||||
import type { UpListing } from '@upnd/upend';
|
||||
import type { Address } from '@upnd/upend/types';
|
||||
import { baseSearchOnce, createLabelled } from '../../util/search';
|
||||
import { baseSearchOnce, createLabelled } from '$lib/util/search';
|
||||
import UpObject from '../display/UpObject.svelte';
|
||||
import IconButton from './IconButton.svelte';
|
||||
import Input from './Input.svelte';
|
||||
import { matchSorter } from 'match-sorter';
|
||||
import api from '$lib/api';
|
||||
import { ATTR_LABEL } from '@upnd/upend/constants';
|
||||
import { i18n } from '../../i18n';
|
||||
import { i18n } from '$lib/i18n';
|
||||
import debug from 'debug';
|
||||
import Spinner from './Spinner.svelte';
|
||||
|
||||
|
@ -187,7 +187,7 @@
|
|||
t: 'Address',
|
||||
c: addr,
|
||||
labels: addressToLabels[addr],
|
||||
entry: null
|
||||
entry: undefined
|
||||
})
|
||||
);
|
||||
} else if (query.length && types.includes('NewAddress')) {
|
||||
|
@ -212,7 +212,7 @@
|
|||
t: 'Address' as const,
|
||||
c: entry.entity
|
||||
};
|
||||
if (entry.attribute == ATTR_LABEL) {
|
||||
if (entry.attribute == ATTR_LABEL && entry.value.c) {
|
||||
result.push({
|
||||
...common,
|
||||
labels: [entry.value.c.toString()]
|
||||
|
@ -226,7 +226,7 @@
|
|||
if (types.includes('Attribute')) {
|
||||
const allAttributes = await api.fetchAllAttributes();
|
||||
const attributes = attributeOptions
|
||||
? allAttributes.filter((attr) => attributeOptions.includes(attr.name))
|
||||
? allAttributes.filter((attr) => attributeOptions!.includes(attr.name))
|
||||
: allAttributes;
|
||||
if (emptyOptions === undefined || query.length > 0) {
|
||||
result.push(
|
||||
|
@ -434,7 +434,7 @@
|
|||
<li><Spinner centered /></li>
|
||||
{/if}
|
||||
{#each options.slice(0, MAX_OPTIONS) as option, idx}
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex a11y-no-noninteractive-element-interactions -->
|
||||
<li
|
||||
tabindex="0"
|
||||
on:click={() => set(option)}
|
||||
|
@ -463,7 +463,7 @@
|
|||
<div class="content new">{option.c}</div>
|
||||
<div class="type">{$i18n.t('Create object')}</div>
|
||||
{:else if option.t === 'Attribute'}
|
||||
{#if option.labels.length}
|
||||
{#if option.labels?.length}
|
||||
<div class="content">
|
||||
{#each option.labels as label}
|
||||
<div class="label">{label}</div>
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
import UpObject from '../display/UpObject.svelte';
|
||||
import UpObjectCard from '../display/UpObjectCard.svelte';
|
||||
import { ATTR_LABEL } from '@upnd/upend/constants';
|
||||
import { i18n } from '../../i18n';
|
||||
import { i18n } from '$lib/i18n';
|
||||
import IconButton from '../utils/IconButton.svelte';
|
||||
import Selector, { type SelectorValue } from '../utils/Selector.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type { WidgetChange } from 'src/types/base';
|
||||
import type { WidgetChange } from '$lib/types/base';
|
||||
import debug from 'debug';
|
||||
const dispatch = createEventDispatcher();
|
||||
const dbg = debug(`kestrel:EntityList`);
|
||||
|
@ -70,7 +70,7 @@
|
|||
}
|
||||
|
||||
// Labelling
|
||||
let labelListing: Readable<UpListing> = readable(undefined);
|
||||
let labelListing: Readable<UpListing | undefined> = readable(undefined);
|
||||
$: {
|
||||
const addressesString = deduplicatedEntities.map((addr) => `@${addr}`).join(' ');
|
||||
|
||||
|
@ -80,7 +80,7 @@
|
|||
$: {
|
||||
if ($labelListing) {
|
||||
deduplicatedEntities.forEach((address) => {
|
||||
addSortKeys(address, $labelListing.getObject(address).identify(), false);
|
||||
addSortKeys(address, $labelListing?.getObject(address).identify() || [], false);
|
||||
});
|
||||
sortEntities();
|
||||
}
|
||||
|
@ -135,7 +135,7 @@
|
|||
}
|
||||
|
||||
function removeEntity(address: string) {
|
||||
if (confirm($i18n.t('Are you sure you want to remove this entry from members?'))) {
|
||||
if (confirm($i18n.t('Are you sure you want to remove this entry from members?') || '')) {
|
||||
dbg('Removing entity', address);
|
||||
dispatch('change', {
|
||||
type: 'entry-delete',
|
||||
|
@ -194,7 +194,7 @@
|
|||
{#if adding}
|
||||
<Selector
|
||||
bind:this={addSelector}
|
||||
placeholder={$i18n.t('Search database or paste an URL')}
|
||||
placeholder={$i18n.t('Search database or paste an URL') || ''}
|
||||
types={['Address', 'NewAddress']}
|
||||
on:input={addEntity}
|
||||
on:focus={(ev) => {
|
||||
|
|
|
@ -4,17 +4,17 @@
|
|||
import Ellipsis from '../utils/Ellipsis.svelte';
|
||||
import UpObject from '../display/UpObject.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type { AttributeUpdate, WidgetChange } from '../../types/base';
|
||||
import type { AttributeUpdate, WidgetChange } from '$lib/types/base';
|
||||
import type { UpEntry, UpListing } from '@upnd/upend';
|
||||
import IconButton from '../utils/IconButton.svelte';
|
||||
import Selector, { type SelectorValue, selectorValueAsValue } from '../utils/Selector.svelte';
|
||||
import Editable from '../utils/Editable.svelte';
|
||||
import { query } from '$lib/entity';
|
||||
import { type Readable, readable } from 'svelte/store';
|
||||
import { defaultEntitySort, entityValueSort } from '../../util/sort';
|
||||
import { attributeLabels } from '../../util/labels';
|
||||
import { formatDuration } from '../../util/fragments/time';
|
||||
import { i18n } from '../../i18n';
|
||||
import { defaultEntitySort, entityValueSort } from '$lib/util/sort';
|
||||
import { attributeLabels } from '$lib/util/labels';
|
||||
import { formatDuration } from '$lib/util/fragments/time';
|
||||
import { i18n } from '$lib/i18n';
|
||||
import UpLink from '../display/UpLink.svelte';
|
||||
import { ATTR_ADDED, ATTR_LABEL } from '@upnd/upend/constants';
|
||||
|
||||
|
@ -68,7 +68,7 @@
|
|||
newEntryValue = undefined;
|
||||
}
|
||||
async function removeEntry(address: string) {
|
||||
if (confirm($i18n.t('Are you sure you want to remove the property?'))) {
|
||||
if (confirm($i18n.t('Are you sure you want to remove the property?') || '')) {
|
||||
dispatch('change', { type: 'delete', address } as WidgetChange);
|
||||
}
|
||||
}
|
||||
|
@ -82,9 +82,9 @@
|
|||
}
|
||||
|
||||
// Labelling
|
||||
let labelListing: Readable<UpListing> = readable(undefined);
|
||||
let labelListing: Readable<UpListing | undefined> = readable(undefined);
|
||||
$: {
|
||||
const addresses = [];
|
||||
const addresses: string[] = [];
|
||||
entries
|
||||
.flatMap((e) => (e.value.t === 'Address' ? [e.entity, e.value.c] : [e.entity]))
|
||||
.forEach((addr) => {
|
||||
|
@ -126,12 +126,12 @@
|
|||
$: {
|
||||
if ($labelListing) {
|
||||
entries.forEach((entry) => {
|
||||
addSortKeys(entry.entity, $labelListing.getObject(entry.entity).identify(), false);
|
||||
addSortKeys(entry.entity, $labelListing!.getObject(entry.entity).identify(), false);
|
||||
|
||||
if (entry.value.t === 'Address') {
|
||||
addSortKeys(
|
||||
entry.value.c,
|
||||
$labelListing.getObject(String(entry.value.c)).identify(),
|
||||
$labelListing!.getObject(String(entry.value.c)).identify(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
@ -141,9 +141,13 @@
|
|||
}
|
||||
|
||||
entries.forEach((entry) => {
|
||||
addSortKeys(entry.entity, entry.listing.getObject(entry.entity).identify(), false);
|
||||
addSortKeys(entry.entity, entry.listing?.getObject(entry.entity).identify() || [], false);
|
||||
if (entry.value.t === 'Address') {
|
||||
addSortKeys(entry.value.c, entry.listing.getObject(String(entry.value.c)).identify(), false);
|
||||
addSortKeys(
|
||||
entry.value.c,
|
||||
entry.listing?.getObject(String(entry.value.c)).identify() || [],
|
||||
false
|
||||
);
|
||||
}
|
||||
});
|
||||
sortEntries();
|
||||
|
@ -184,7 +188,7 @@
|
|||
value: $i18n.t('Value')
|
||||
};
|
||||
|
||||
function formatValue(value: string | number, attribute: string): string {
|
||||
function formatValue(value: string | number | null, attribute: string): string {
|
||||
try {
|
||||
switch (attribute) {
|
||||
case 'FILE_SIZE':
|
||||
|
@ -204,7 +208,7 @@
|
|||
}
|
||||
|
||||
// Unused attributes
|
||||
let unusedAttributes = [];
|
||||
let unusedAttributes: string[] = [];
|
||||
|
||||
$: (async () => {
|
||||
unusedAttributes = await Promise.all(
|
||||
|
@ -332,6 +336,7 @@
|
|||
|
||||
{#if !attributes?.length}
|
||||
{#if adding}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="add-row"
|
||||
on:mouseenter={() => (addHover = true)}
|
||||
|
@ -361,7 +366,10 @@
|
|||
{/if}
|
||||
{/each}
|
||||
<div class="attr-action">
|
||||
<IconButton name="save" on:click={() => addEntry(newEntryAttribute, newEntryValue)} />
|
||||
<IconButton
|
||||
name="save"
|
||||
on:click={() => newEntryValue && addEntry(newEntryAttribute, newEntryValue)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
|
|
|
@ -1,40 +1,38 @@
|
|||
import { writable } from "svelte/store";
|
||||
import { debug } from "debug";
|
||||
const dbg = debug("kestrel:swrshim");
|
||||
import { writable } from 'svelte/store';
|
||||
import { debug } from 'debug';
|
||||
|
||||
const dbg = debug('kestrel:swrshim');
|
||||
|
||||
// stale shim until https://github.com/ConsoleTVs/sswr/issues/24 is resolved
|
||||
export type SWRKey = string;
|
||||
export function useSWR<D = unknown, E = Error>(
|
||||
key: SWRKey,
|
||||
options?: RequestInit,
|
||||
) {
|
||||
const data = writable<D | undefined>();
|
||||
const error = writable<E | undefined>();
|
||||
export function useSWR<D = unknown, E = Error>(key: SWRKey, options?: RequestInit) {
|
||||
const data = writable<D | undefined>();
|
||||
const error = writable<E | undefined>();
|
||||
|
||||
async function doFetch() {
|
||||
dbg("Fetching: %s", key);
|
||||
try {
|
||||
const response = await fetch(key, options);
|
||||
if (response.ok) {
|
||||
data.set(await response.json());
|
||||
} else {
|
||||
let errorText = `${response.status} ${response.statusText}`;
|
||||
const responseText = await response.text();
|
||||
if (responseText) {
|
||||
errorText += ` - ${responseText}`;
|
||||
}
|
||||
throw new Error(errorText);
|
||||
}
|
||||
} catch (err) {
|
||||
error.set(err);
|
||||
}
|
||||
}
|
||||
async function doFetch() {
|
||||
dbg('Fetching: %s', key);
|
||||
try {
|
||||
const response = await fetch(key, options);
|
||||
if (response.ok) {
|
||||
data.set(await response.json());
|
||||
} else {
|
||||
let errorText = `${response.status} ${response.statusText}`;
|
||||
const responseText = await response.text();
|
||||
if (responseText) {
|
||||
errorText += ` - ${responseText}`;
|
||||
}
|
||||
throw new Error(errorText);
|
||||
}
|
||||
} catch (err) {
|
||||
error.set(err as any);
|
||||
}
|
||||
}
|
||||
|
||||
doFetch();
|
||||
doFetch();
|
||||
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
revalidate: doFetch,
|
||||
};
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
revalidate: doFetch
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,45 +2,43 @@
|
|||
* Both `start` and `end` are in seconds.
|
||||
*/
|
||||
export class TimeFragment {
|
||||
start: number | null;
|
||||
end: number | null;
|
||||
start: number;
|
||||
end: number | null;
|
||||
|
||||
constructor(start: number, end: number) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
constructor(start: number, end: number | null) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public static parse(fragment: string): TimeFragment {
|
||||
if (!fragment.startsWith("t=")) {
|
||||
return undefined;
|
||||
}
|
||||
const data = fragment.substring("t=".length);
|
||||
try {
|
||||
const [start, end] = data.split(",").map((str) => parseFloat(str));
|
||||
return new TimeFragment(start || null, end || null);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
public static parse(fragment: string): TimeFragment | undefined {
|
||||
if (!fragment.startsWith('t=')) {
|
||||
return undefined;
|
||||
}
|
||||
const data = fragment.substring('t='.length);
|
||||
try {
|
||||
const [start, end] = data.split(',').map((str) => parseFloat(str));
|
||||
return new TimeFragment(start, end || null);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `t=${this.start || ""},${this.end || ""}`;
|
||||
}
|
||||
public toString(): string {
|
||||
return `t=${this.start || ''},${this.end || ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatDuration(duration: number): string {
|
||||
let result = "";
|
||||
const hours = Math.floor(duration / 3600);
|
||||
const minutes = Math.floor((duration % 3600) / 60);
|
||||
const seconds = Math.floor(duration - hours * 3600 - minutes * 60);
|
||||
|
||||
const hours = Math.floor(duration / 3600);
|
||||
const minutes = Math.floor((duration % 3600) / 60);
|
||||
const seconds = Math.floor(duration - hours * 3600 - minutes * 60);
|
||||
let result = '';
|
||||
if (hours > 0) {
|
||||
result += `${hours}h`;
|
||||
}
|
||||
result += `${minutes}m`.padStart(hours > 0 ? 3 : 2, '0');
|
||||
result += `${seconds}s`.padStart(3, '0');
|
||||
|
||||
result = "";
|
||||
if (hours > 0) {
|
||||
result += `${hours}h`;
|
||||
}
|
||||
result += `${minutes}m`.padStart(hours > 0 ? 3 : 2, "0");
|
||||
result += `${seconds}s`.padStart(3, "0");
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -2,62 +2,61 @@
|
|||
// https://github.com/tomayac/xywh.js
|
||||
|
||||
export type MediaFragment = (
|
||||
| {
|
||||
mediaItem: HTMLImageElement;
|
||||
mediaType: "img";
|
||||
}
|
||||
| {
|
||||
mediaItem: HTMLVideoElement;
|
||||
mediaType: "video";
|
||||
}
|
||||
| {
|
||||
mediaItem: HTMLImageElement;
|
||||
mediaType: 'img';
|
||||
}
|
||||
| {
|
||||
mediaItem: HTMLVideoElement;
|
||||
mediaType: 'video';
|
||||
}
|
||||
) & {
|
||||
unit: string;
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
unit: string;
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
};
|
||||
|
||||
export function xywh(mediaItem: HTMLImageElement | HTMLVideoElement) {
|
||||
const source = mediaItem.src || mediaItem.currentSrc;
|
||||
// See http://www.w3.org/TR/media-frags/#naming-space
|
||||
const xywhRegEx =
|
||||
/[#&?]xywh=(pixel:|percent:)?([\d.]+),([\d.]+),([\d.]+),([\d.]+)/;
|
||||
const match = xywhRegEx.exec(source);
|
||||
if (match) {
|
||||
const mediaFragment = {
|
||||
mediaItem: mediaItem,
|
||||
mediaType: mediaItem.nodeName.toLowerCase(),
|
||||
unit: match[1] ? match[1] : "pixel:",
|
||||
x: parseFloat(match[2]),
|
||||
y: parseFloat(match[3]),
|
||||
w: parseFloat(match[4]),
|
||||
h: parseFloat(match[5]),
|
||||
} as MediaFragment;
|
||||
if (mediaFragment.mediaType === "img") {
|
||||
addImageLoadListener(mediaFragment);
|
||||
} else {
|
||||
addVideoLoadListener(mediaFragment);
|
||||
}
|
||||
}
|
||||
const source = mediaItem.src || mediaItem.currentSrc;
|
||||
// See http://www.w3.org/TR/media-frags/#naming-space
|
||||
const xywhRegEx = /[#&?]xywh=(pixel:|percent:)?([\d.]+),([\d.]+),([\d.]+),([\d.]+)/;
|
||||
const match = xywhRegEx.exec(source);
|
||||
if (match) {
|
||||
const mediaFragment = {
|
||||
mediaItem: mediaItem,
|
||||
mediaType: mediaItem.nodeName.toLowerCase(),
|
||||
unit: match[1] ? match[1] : 'pixel:',
|
||||
x: parseFloat(match[2]),
|
||||
y: parseFloat(match[3]),
|
||||
w: parseFloat(match[4]),
|
||||
h: parseFloat(match[5])
|
||||
} as MediaFragment;
|
||||
if (mediaFragment.mediaType === 'img') {
|
||||
addImageLoadListener(mediaFragment);
|
||||
} else {
|
||||
addVideoLoadListener(mediaFragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the media fragment when the image has loaded.
|
||||
*/
|
||||
function addImageLoadListener(mediaFragment: MediaFragment) {
|
||||
const mediaItem = mediaFragment.mediaItem;
|
||||
// Prevent onload firing when the fragment loads; but still react when `src`
|
||||
// is changed programatically.
|
||||
let lastSrc: string;
|
||||
function onload() {
|
||||
if (mediaItem.src !== lastSrc) {
|
||||
// Required on reloads because of size calculations.
|
||||
applyFragment(mediaFragment);
|
||||
lastSrc = mediaItem.src;
|
||||
}
|
||||
}
|
||||
mediaItem.addEventListener("load", onload);
|
||||
const mediaItem = mediaFragment.mediaItem;
|
||||
// Prevent onload firing when the fragment loads; but still react when `src`
|
||||
// is changed programatically.
|
||||
let lastSrc: string;
|
||||
function onload() {
|
||||
if (mediaItem.src !== lastSrc) {
|
||||
// Required on reloads because of size calculations.
|
||||
applyFragment(mediaFragment);
|
||||
lastSrc = mediaItem.src;
|
||||
}
|
||||
}
|
||||
mediaItem.addEventListener('load', onload);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,9 +64,9 @@ function addImageLoadListener(mediaFragment: MediaFragment) {
|
|||
* need the video's original width and height.
|
||||
*/
|
||||
function addVideoLoadListener(mediaFragment: MediaFragment) {
|
||||
mediaFragment.mediaItem.addEventListener("loadedmetadata", function () {
|
||||
applyFragment(mediaFragment);
|
||||
});
|
||||
mediaFragment.mediaItem.addEventListener('loadedmetadata', function () {
|
||||
applyFragment(mediaFragment);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,77 +81,77 @@ function addVideoLoadListener(mediaFragment: MediaFragment) {
|
|||
* 2D transformation according to the fragment's x and y values.
|
||||
*/
|
||||
function applyFragment(fragment: MediaFragment) {
|
||||
// Media item is a video
|
||||
if (fragment.mediaType === "video") {
|
||||
let x: string, y: string, w: string, h: string;
|
||||
const originalWidth = fragment.mediaItem.videoWidth;
|
||||
const originalHeight = fragment.mediaItem.videoHeight;
|
||||
// Unit is pixel:
|
||||
if (fragment.unit === "pixel:") {
|
||||
const scale = originalWidth / fragment.mediaItem.clientWidth;
|
||||
w = fragment.w * scale + "px";
|
||||
h = fragment.h * scale + "px";
|
||||
x = "-" + fragment.x * scale + "px";
|
||||
y = "-" + fragment.y * scale + "px";
|
||||
// Unit is percent:
|
||||
} else {
|
||||
w = (originalWidth * fragment.w) / 100 + "px";
|
||||
h = (originalHeight * fragment.h) / 100 + "px";
|
||||
x = "-" + (originalWidth * fragment.x) / 100 + "px";
|
||||
y = "-" + (originalHeight * fragment.y) / 100 + "px";
|
||||
}
|
||||
// Media item is a video
|
||||
if (fragment.mediaType === 'video') {
|
||||
let x: string, y: string, w: string, h: string;
|
||||
const originalWidth = fragment.mediaItem.videoWidth;
|
||||
const originalHeight = fragment.mediaItem.videoHeight;
|
||||
// Unit is pixel:
|
||||
if (fragment.unit === 'pixel:') {
|
||||
const scale = originalWidth / fragment.mediaItem.clientWidth;
|
||||
w = fragment.w * scale + 'px';
|
||||
h = fragment.h * scale + 'px';
|
||||
x = '-' + fragment.x * scale + 'px';
|
||||
y = '-' + fragment.y * scale + 'px';
|
||||
// Unit is percent:
|
||||
} else {
|
||||
w = (originalWidth * fragment.w) / 100 + 'px';
|
||||
h = (originalHeight * fragment.h) / 100 + 'px';
|
||||
x = '-' + (originalWidth * fragment.x) / 100 + 'px';
|
||||
y = '-' + (originalHeight * fragment.y) / 100 + 'px';
|
||||
}
|
||||
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.style.cssText +=
|
||||
"overflow:hidden;" +
|
||||
"width:" +
|
||||
w +
|
||||
";" +
|
||||
"height:" +
|
||||
h +
|
||||
";" +
|
||||
"padding:0;" +
|
||||
"margin:0;" +
|
||||
"border-radius:0;" +
|
||||
"border:none;";
|
||||
fragment.mediaItem.style.cssText +=
|
||||
"transform:translate(" +
|
||||
x +
|
||||
"," +
|
||||
y +
|
||||
");" +
|
||||
"-webkit-transform:translate(" +
|
||||
x +
|
||||
"," +
|
||||
y +
|
||||
");";
|
||||
// Evil DOM operations
|
||||
fragment.mediaItem.parentNode.insertBefore(wrapper, fragment.mediaItem);
|
||||
wrapper.appendChild(fragment.mediaItem);
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.style.cssText +=
|
||||
'overflow:hidden;' +
|
||||
'width:' +
|
||||
w +
|
||||
';' +
|
||||
'height:' +
|
||||
h +
|
||||
';' +
|
||||
'padding:0;' +
|
||||
'margin:0;' +
|
||||
'border-radius:0;' +
|
||||
'border:none;';
|
||||
fragment.mediaItem.style.cssText +=
|
||||
'transform:translate(' +
|
||||
x +
|
||||
',' +
|
||||
y +
|
||||
');' +
|
||||
'-webkit-transform:translate(' +
|
||||
x +
|
||||
',' +
|
||||
y +
|
||||
');';
|
||||
// Evil DOM operations
|
||||
fragment.mediaItem.parentNode?.insertBefore(wrapper, fragment.mediaItem);
|
||||
wrapper.appendChild(fragment.mediaItem);
|
||||
|
||||
// We need to manually trigger @autoplay, as DOM access seems to kill it
|
||||
if (fragment.mediaItem.hasAttribute("autoplay")) {
|
||||
fragment.mediaItem.play();
|
||||
}
|
||||
// Media item is an image
|
||||
} else {
|
||||
let x: number, y: number, w: number, h: number;
|
||||
if (fragment.unit === "pixel:") {
|
||||
x = fragment.x;
|
||||
y = fragment.y;
|
||||
w = fragment.w;
|
||||
h = fragment.h;
|
||||
} else {
|
||||
x = (fragment.x / 100) * fragment.mediaItem.naturalWidth;
|
||||
y = (fragment.y / 100) * fragment.mediaItem.naturalHeight;
|
||||
w = (fragment.w / 100) * fragment.mediaItem.naturalWidth;
|
||||
h = (fragment.h / 100) * fragment.mediaItem.naturalHeight;
|
||||
}
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
const context = canvas.getContext("2d");
|
||||
context.drawImage(fragment.mediaItem, x, y, w, h, 0, 0, w, h);
|
||||
fragment.mediaItem.src = canvas.toDataURL();
|
||||
}
|
||||
// We need to manually trigger @autoplay, as DOM access seems to kill it
|
||||
if (fragment.mediaItem.hasAttribute('autoplay')) {
|
||||
fragment.mediaItem.play();
|
||||
}
|
||||
// Media item is an image
|
||||
} else {
|
||||
let x: number, y: number, w: number, h: number;
|
||||
if (fragment.unit === 'pixel:') {
|
||||
x = fragment.x;
|
||||
y = fragment.y;
|
||||
w = fragment.w;
|
||||
h = fragment.h;
|
||||
} else {
|
||||
x = (fragment.x / 100) * fragment.mediaItem.naturalWidth;
|
||||
y = (fragment.y / 100) * fragment.mediaItem.naturalHeight;
|
||||
w = (fragment.w / 100) * fragment.mediaItem.naturalWidth;
|
||||
h = (fragment.h / 100) * fragment.mediaItem.naturalHeight;
|
||||
}
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
const context = canvas.getContext('2d');
|
||||
context?.drawImage(fragment.mediaItem, x, y, w, h, 0, 0, w, h);
|
||||
fragment.mediaItem.src = canvas.toDataURL();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import api from '$lib/api';
|
||||
import { readable, type Readable } from 'svelte/store';
|
||||
import { readable } from 'svelte/store';
|
||||
import type { VaultInfo } from '@upnd/upend/types';
|
||||
|
||||
export const vaultInfo: Readable<VaultInfo> = readable(undefined, (set) => {
|
||||
api.fetchInfo().then(async (info) => {
|
||||
export const vaultInfo = readable(undefined as VaultInfo | undefined, (set) => {
|
||||
api.fetchInfo().then(async (info: VaultInfo) => {
|
||||
set(info);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import api from '$lib/api';
|
||||
import { i18n } from '../i18n';
|
||||
import { i18n } from '$lib/i18n';
|
||||
import { derived, readable, type Readable } from 'svelte/store';
|
||||
import type { AttributeListingResult } from '@upnd/upend/types';
|
||||
|
||||
const databaseAttributeLabels: Readable<{ [key: string]: string }> = readable({}, (set) => {
|
||||
const result = {};
|
||||
api.fetchAllAttributes().then((attributes) => {
|
||||
const result: Record<string, string> = {};
|
||||
api.fetchAllAttributes().then((attributes: AttributeListingResult) => {
|
||||
attributes.forEach((attribute) => {
|
||||
if (attribute.labels.length) {
|
||||
result[attribute.name] = attribute.labels.sort()[0];
|
||||
|
|
|
@ -1,102 +1,82 @@
|
|||
import type { UpEntry } from "@upnd/upend";
|
||||
import type { UpEntry } from '@upnd/upend';
|
||||
|
||||
export type SortKeys = { [key: string]: string[] };
|
||||
|
||||
export function sortByValue(entries: UpEntry[], sortKeys: SortKeys): void {
|
||||
entries.sort((aEntry, bEntry) => {
|
||||
if (aEntry.value.t === "Number" && bEntry.value.t === "Number") {
|
||||
return bEntry.value.c - aEntry.value.c;
|
||||
}
|
||||
entries.sort((aEntry, bEntry) => {
|
||||
if (aEntry.value.c === null || bEntry.value.c === null) {
|
||||
// sort non-null first
|
||||
return aEntry.value.c === null ? 1 : -1;
|
||||
}
|
||||
|
||||
if (
|
||||
!sortKeys[aEntry.value.c]?.length ||
|
||||
!sortKeys[bEntry.value.c]?.length
|
||||
) {
|
||||
if (
|
||||
Boolean(sortKeys[aEntry.value.c]?.length) &&
|
||||
!sortKeys[bEntry.value.c]?.length
|
||||
) {
|
||||
return -1;
|
||||
} else if (
|
||||
!sortKeys[aEntry.value.c]?.length &&
|
||||
Boolean(sortKeys[bEntry.value.c]?.length)
|
||||
) {
|
||||
return 1;
|
||||
} else {
|
||||
return String(aEntry.value.c).localeCompare(
|
||||
String(bEntry.value.c),
|
||||
undefined,
|
||||
{ numeric: true, sensitivity: "base" },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return sortKeys[aEntry.value.c][0].localeCompare(
|
||||
sortKeys[bEntry.value.c][0],
|
||||
undefined,
|
||||
{ numeric: true, sensitivity: "base" },
|
||||
);
|
||||
}
|
||||
});
|
||||
if (aEntry.value.t === 'Number' && bEntry.value.t === 'Number') {
|
||||
return bEntry.value.c - aEntry.value.c;
|
||||
}
|
||||
|
||||
if (!sortKeys[aEntry.value.c]?.length || !sortKeys[bEntry.value.c]?.length) {
|
||||
if (Boolean(sortKeys[aEntry.value.c]?.length) && !sortKeys[bEntry.value.c]?.length) {
|
||||
return -1;
|
||||
} else if (!sortKeys[aEntry.value.c]?.length && Boolean(sortKeys[bEntry.value.c]?.length)) {
|
||||
return 1;
|
||||
} else {
|
||||
return String(aEntry.value.c).localeCompare(String(bEntry.value.c), undefined, {
|
||||
numeric: true,
|
||||
sensitivity: 'base'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return sortKeys[aEntry.value.c][0].localeCompare(sortKeys[bEntry.value.c][0], undefined, {
|
||||
numeric: true,
|
||||
sensitivity: 'base'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function sortByValueLength(entries: UpEntry[]) {
|
||||
entries.sort((aEntry, bEntry) => {
|
||||
return String(aEntry.value.c).length - String(bEntry.value.c).length;
|
||||
});
|
||||
entries.sort((aEntry, bEntry) => {
|
||||
return String(aEntry.value.c).length - String(bEntry.value.c).length;
|
||||
});
|
||||
}
|
||||
|
||||
export function sortByAttribute(entries: UpEntry[], sortKeys: SortKeys): void {
|
||||
entries.sort((aEntry, bEntry) => {
|
||||
const aResolved = (sortKeys[aEntry.attribute] || [])[0] || aEntry.attribute;
|
||||
const bResolved = (sortKeys[bEntry.attribute] || [])[0] || bEntry.attribute;
|
||||
return aResolved.localeCompare(bResolved);
|
||||
});
|
||||
entries.sort((aEntry, bEntry) => {
|
||||
const aResolved = (sortKeys[aEntry.attribute] || [])[0] || aEntry.attribute;
|
||||
const bResolved = (sortKeys[bEntry.attribute] || [])[0] || bEntry.attribute;
|
||||
return aResolved.localeCompare(bResolved);
|
||||
});
|
||||
}
|
||||
|
||||
export function sortByEntity(entries: UpEntry[], sortKeys: SortKeys): void {
|
||||
entries.sort((aEntry, bEntry) => {
|
||||
if (!sortKeys[aEntry.entity]?.length || !sortKeys[bEntry.entity]?.length) {
|
||||
if (
|
||||
Boolean(sortKeys[aEntry.entity]?.length) &&
|
||||
!sortKeys[bEntry.entity]?.length
|
||||
) {
|
||||
return -1;
|
||||
} else if (
|
||||
!sortKeys[aEntry.entity]?.length &&
|
||||
Boolean(sortKeys[bEntry.entity]?.length)
|
||||
) {
|
||||
return 1;
|
||||
} else {
|
||||
return aEntry.entity.localeCompare(bEntry.entity);
|
||||
}
|
||||
} else {
|
||||
return sortKeys[aEntry.entity][0].localeCompare(
|
||||
sortKeys[bEntry.entity][0],
|
||||
);
|
||||
}
|
||||
});
|
||||
entries.sort((aEntry, bEntry) => {
|
||||
if (!sortKeys[aEntry.entity]?.length || !sortKeys[bEntry.entity]?.length) {
|
||||
if (Boolean(sortKeys[aEntry.entity]?.length) && !sortKeys[bEntry.entity]?.length) {
|
||||
return -1;
|
||||
} else if (!sortKeys[aEntry.entity]?.length && Boolean(sortKeys[bEntry.entity]?.length)) {
|
||||
return 1;
|
||||
} else {
|
||||
return aEntry.entity.localeCompare(bEntry.entity);
|
||||
}
|
||||
} else {
|
||||
return sortKeys[aEntry.entity][0].localeCompare(sortKeys[bEntry.entity][0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function defaultEntitySort(
|
||||
entries: UpEntry[],
|
||||
sortKeys: SortKeys,
|
||||
): UpEntry[] {
|
||||
const result = entries.concat();
|
||||
sortByValue(result, sortKeys);
|
||||
sortByValueLength(result);
|
||||
sortByAttribute(result, sortKeys);
|
||||
sortByEntity(result, sortKeys);
|
||||
return result;
|
||||
export function defaultEntitySort(entries: UpEntry[], sortKeys: SortKeys): UpEntry[] {
|
||||
const result = entries.concat();
|
||||
sortByValue(result, sortKeys);
|
||||
sortByValueLength(result);
|
||||
sortByAttribute(result, sortKeys);
|
||||
sortByEntity(result, sortKeys);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function entityValueSort(
|
||||
entries: UpEntry[],
|
||||
sortKeys: SortKeys,
|
||||
): UpEntry[] {
|
||||
const result = entries.concat();
|
||||
sortByEntity(result, sortKeys);
|
||||
sortByAttribute(result, sortKeys);
|
||||
sortByValueLength(result);
|
||||
sortByValue(result, sortKeys);
|
||||
return result;
|
||||
export function entityValueSort(entries: UpEntry[], sortKeys: SortKeys): UpEntry[] {
|
||||
const result = entries.concat();
|
||||
sortByEntity(result, sortKeys);
|
||||
sortByAttribute(result, sortKeys);
|
||||
sortByValueLength(result);
|
||||
sortByValue(result, sortKeys);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export function isDefined<T>(value: T | undefined | null): value is T {
|
||||
return value !== undefined;
|
||||
}
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
$: looksLikeQuery = debouncedQuery.startsWith('(') && debouncedQuery.endsWith(')');
|
||||
|
||||
let result: Readable<UpListing> = readable();
|
||||
let result: Readable<UpListing | undefined> = readable();
|
||||
let error: Readable<unknown> = readable();
|
||||
$: if (debouncedQuery.length) {
|
||||
({ result, error } = looksLikeQuery ? queryFn(debouncedQuery) : baseSearch(debouncedQuery));
|
||||
|
|
|
@ -1,109 +1,107 @@
|
|||
<script lang="ts">
|
||||
import filesize from "filesize";
|
||||
import UpObject from "../components/display/UpObject.svelte";
|
||||
import Icon from "../components/utils/Icon.svelte";
|
||||
import Spinner from "../components/utils/Spinner.svelte";
|
||||
import api from "../lib/api";
|
||||
import filesize from 'filesize';
|
||||
import UpObject from '$lib/components/display/UpObject.svelte';
|
||||
import Icon from '$lib/components/utils/Icon.svelte';
|
||||
import Spinner from '$lib/components/utils/Spinner.svelte';
|
||||
import api from '$lib/api';
|
||||
|
||||
const stores = api.fetchStoreInfo();
|
||||
const stores = api.fetchStoreInfo();
|
||||
</script>
|
||||
|
||||
<div class="store">
|
||||
<h1>Stores</h1>
|
||||
{#await stores}
|
||||
<Spinner />
|
||||
{:then stores}
|
||||
{#each Object.entries(stores) as [key, store] (key)}
|
||||
<h2>{key}</h2>
|
||||
<div class="totals">
|
||||
<strong>{store.totals.count}</strong> blobs,
|
||||
<strong>{filesize(store.totals.size)}</strong>
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Hash</th>
|
||||
<th>Size</th>
|
||||
<th>Path</th>
|
||||
<th>Added</th>
|
||||
<th>Valid</th>
|
||||
</tr>
|
||||
{#each store.blobs as blob}
|
||||
<tbody>
|
||||
<tr class:invalid={!blob.paths[0].valid}>
|
||||
<td class="hash"
|
||||
><UpObject link address={blob.hash} resolve={false} /></td
|
||||
>
|
||||
<td class="size">{filesize(blob.size)}</td>
|
||||
<td class="path">{blob.paths[0].path}</td>
|
||||
<td class="added">{blob.paths[0].added}</td>
|
||||
<td class="valid">
|
||||
{#if blob.paths[0].valid}
|
||||
<Icon name="check" />
|
||||
{:else}
|
||||
<Icon name="x" />
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{#each blob.paths.slice(1) as path}
|
||||
<tr class:invalid={!path.valid}>
|
||||
<td />
|
||||
<td />
|
||||
<td class="path">{path.path}</td>
|
||||
<td class="added">{path.added}</td>
|
||||
<td class="valid">
|
||||
{#if path.valid}
|
||||
<Icon name="check" />
|
||||
{:else}
|
||||
<Icon name="x" />
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
{/each}
|
||||
</table>
|
||||
{/each}
|
||||
{:catch error}
|
||||
<div class="error">
|
||||
{error}
|
||||
</div>
|
||||
{/await}
|
||||
<h1>Stores</h1>
|
||||
{#await stores}
|
||||
<Spinner />
|
||||
{:then stores}
|
||||
{#each Object.entries(stores) as [key, store] (key)}
|
||||
<h2>{key}</h2>
|
||||
<div class="totals">
|
||||
<strong>{store.totals.count}</strong> blobs,
|
||||
<strong>{filesize(store.totals.size)}</strong>
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Hash</th>
|
||||
<th>Size</th>
|
||||
<th>Path</th>
|
||||
<th>Added</th>
|
||||
<th>Valid</th>
|
||||
</tr>
|
||||
{#each store.blobs as blob}
|
||||
<tbody>
|
||||
<tr class:invalid={!blob.paths[0].valid}>
|
||||
<td class="hash"><UpObject link address={blob.hash} resolve={false} /></td>
|
||||
<td class="size">{filesize(blob.size)}</td>
|
||||
<td class="path">{blob.paths[0].path}</td>
|
||||
<td class="added">{blob.paths[0].added}</td>
|
||||
<td class="valid">
|
||||
{#if blob.paths[0].valid}
|
||||
<Icon name="check" />
|
||||
{:else}
|
||||
<Icon name="x" />
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{#each blob.paths.slice(1) as path}
|
||||
<tr class:invalid={!path.valid}>
|
||||
<td />
|
||||
<td />
|
||||
<td class="path">{path.path}</td>
|
||||
<td class="added">{path.added}</td>
|
||||
<td class="valid">
|
||||
{#if path.valid}
|
||||
<Icon name="check" />
|
||||
{:else}
|
||||
<Icon name="x" />
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
{/each}
|
||||
</table>
|
||||
{/each}
|
||||
{:catch error}
|
||||
<div class="error">
|
||||
{error}
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.store {
|
||||
text-align: center;
|
||||
}
|
||||
.store {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.totals,
|
||||
th {
|
||||
font-size: larger;
|
||||
}
|
||||
.totals,
|
||||
th {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 1em 0.25em;
|
||||
margin: 1em;
|
||||
text-align: initial;
|
||||
table {
|
||||
border-spacing: 1em 0.25em;
|
||||
margin: 1em;
|
||||
text-align: initial;
|
||||
|
||||
.size,
|
||||
.valid {
|
||||
text-align: center;
|
||||
}
|
||||
.size,
|
||||
.valid {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.size {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.size {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
font-weight: 200;
|
||||
}
|
||||
.invalid {
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
tbody:nth-child(odd) {
|
||||
font-weight: 350;
|
||||
}
|
||||
tbody:nth-child(odd) {
|
||||
font-weight: 350;
|
||||
}
|
||||
|
||||
tbody:nth-child(even) {
|
||||
font-weight: 450;
|
||||
}
|
||||
}
|
||||
tbody:nth-child(even) {
|
||||
font-weight: 450;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
declare module '@recogito/annotorious';
|
||||
|
||||
declare module 'three/examples/jsm/controls/OrbitControls';
|
||||
declare module 'three/examples/jsm/loaders/STLLoader';
|
Loading…
Reference in New Issue