refactor(webui): switch to SvelteKit | prettier everything
parent
e52560ae07
commit
8c1dc5388f
|
@ -28,15 +28,21 @@ module.exports = {
|
|||
}
|
||||
}
|
||||
],
|
||||
rules: {
|
||||
"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"]
|
||||
}],
|
||||
},
|
||||
rules: {
|
||||
'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']
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
*Psst — looking for a more complete solution? Check out [SvelteKit](https://kit.svelte.dev), the official framework for building web applications of all sizes, with a beautiful development experience and flexible filesystem-based routing.*
|
||||
_Psst — looking for a more complete solution? Check out [SvelteKit](https://kit.svelte.dev), the official framework for building web applications of all sizes, with a beautiful development experience and flexible filesystem-based routing._
|
||||
|
||||
*Looking for a shareable component template instead? You can [use SvelteKit for that as well](https://kit.svelte.dev/docs#packaging) or the older [sveltejs/component-template](https://github.com/sveltejs/component-template)*
|
||||
_Looking for a shareable component template instead? You can [use SvelteKit for that as well](https://kit.svelte.dev/docs#packaging) or the older [sveltejs/component-template](https://github.com/sveltejs/component-template)_
|
||||
|
||||
---
|
||||
|
||||
|
@ -15,8 +15,7 @@ npx degit sveltejs/template svelte-app
|
|||
cd svelte-app
|
||||
```
|
||||
|
||||
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
|
||||
|
||||
_Note that you will need to have [Node.js](https://nodejs.org) installed._
|
||||
|
||||
## Get started
|
||||
|
||||
|
@ -49,12 +48,11 @@ npm run build
|
|||
|
||||
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
|
||||
|
||||
|
||||
## Single-page app mode
|
||||
|
||||
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
|
||||
|
||||
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
|
||||
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for _any_ path. You can make it so by editing the `"start"` command in package.json:
|
||||
|
||||
```js
|
||||
"start": "sirv public --single"
|
||||
|
|
|
@ -1,68 +1,68 @@
|
|||
{
|
||||
"name": "upend-kestrel",
|
||||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@types/eslint": "8.56.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-svelte": "^3.1.2",
|
||||
"svelte": "^4.2.7",
|
||||
"svelte-check": "^3.6.0",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^5.0.3"
|
||||
},
|
||||
"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",
|
||||
"d3": "^7.8.5",
|
||||
"date-fns": "^2.30.0",
|
||||
"debug": "^4.3.4",
|
||||
"dompurify": "^2.4.7",
|
||||
"filesize": "^8.0.7",
|
||||
"history": "^5.3.0",
|
||||
"i18next": "^22.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lru-cache": "^6.0.0",
|
||||
"marked": "^4.3.0",
|
||||
"match-sorter": "^6.3.1",
|
||||
"mitt": "^3.0.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
"sass": "^1.66.1",
|
||||
"sirv-cli": "^2.0.2",
|
||||
"sswr": "^1.11.0",
|
||||
"svelte-i18next": "^1.2.2",
|
||||
"three": "^0.147.0",
|
||||
"vite-plugin-static-copy": "^0.13.1",
|
||||
"wavesurfer.js": "^6.6.4"
|
||||
}
|
||||
"name": "upend-kestrel",
|
||||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@types/eslint": "8.56.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-svelte": "^3.1.2",
|
||||
"svelte": "^4.2.7",
|
||||
"svelte-check": "^3.6.0",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^5.0.3"
|
||||
},
|
||||
"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",
|
||||
"d3": "^7.8.5",
|
||||
"date-fns": "^2.30.0",
|
||||
"debug": "^4.3.4",
|
||||
"dompurify": "^2.4.7",
|
||||
"filesize": "^8.0.7",
|
||||
"history": "^5.3.0",
|
||||
"i18next": "^22.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lru-cache": "^6.0.0",
|
||||
"marked": "^4.3.0",
|
||||
"match-sorter": "^6.3.1",
|
||||
"mitt": "^3.0.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
"sass": "^1.66.1",
|
||||
"sirv-cli": "^2.0.2",
|
||||
"sswr": "^1.11.0",
|
||||
"svelte-i18next": "^1.2.2",
|
||||
"three": "^0.147.0",
|
||||
"vite-plugin-static-copy": "^0.13.1",
|
||||
"wavesurfer.js": "^6.6.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { UpEndApi } from "@upnd/upend";
|
||||
import { UpEndWasmExtensionsWeb } from "@upnd/upend/wasm/web";
|
||||
import wasmURL from "@upnd/wasm-web/upend_wasm_bg.wasm?url";
|
||||
import { UpEndApi } from '@upnd/upend';
|
||||
import { UpEndWasmExtensionsWeb } from '@upnd/upend/wasm/web';
|
||||
import wasmURL from '@upnd/wasm-web/upend_wasm_bg.wasm?url';
|
||||
|
||||
const wasm = new UpEndWasmExtensionsWeb(wasmURL);
|
||||
export default new UpEndApi({ instanceUrl: "/", wasmExtensions: wasm });
|
||||
export default new UpEndApi({ instanceUrl: '/', wasmExtensions: wasm });
|
||||
|
|
|
@ -1,90 +1,90 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
import Icon from "./utils/Icon.svelte";
|
||||
import Selector from "./utils/Selector.svelte";
|
||||
const dispatch = createEventDispatcher();
|
||||
let selector: Selector;
|
||||
import Icon from './utils/Icon.svelte';
|
||||
import Selector from './utils/Selector.svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
let selector: Selector;
|
||||
|
||||
let editable = false;
|
||||
$: {
|
||||
if (editable) {
|
||||
dispatch("editable");
|
||||
setTimeout(() => dispatch("editable"), 500); // once animation has finished
|
||||
}
|
||||
}
|
||||
$: editable && selector && selector.focus();
|
||||
let editable = false;
|
||||
$: {
|
||||
if (editable) {
|
||||
dispatch('editable');
|
||||
setTimeout(() => dispatch('editable'), 500); // once animation has finished
|
||||
}
|
||||
}
|
||||
$: editable && selector && selector.focus();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="view"
|
||||
class:editable
|
||||
on:click={() => (editable = true)}
|
||||
on:keydown={(ev) => {
|
||||
if (["Space", "Enter"].includes(ev.key)) editable = true;
|
||||
}}
|
||||
class="view"
|
||||
class:editable
|
||||
on:click={() => (editable = true)}
|
||||
on:keydown={(ev) => {
|
||||
if (['Space', 'Enter'].includes(ev.key)) editable = true;
|
||||
}}
|
||||
>
|
||||
<div class="icon">
|
||||
<Icon name="plus-circle" />
|
||||
</div>
|
||||
{#if editable}
|
||||
<div class="controls">
|
||||
<Selector
|
||||
bind:this={selector}
|
||||
types={["Address", "NewAddress", "Attribute"]}
|
||||
on:input={(ev) => {
|
||||
dispatch("input", ev.detail);
|
||||
editable = false;
|
||||
}}
|
||||
on:focus={(ev) => {
|
||||
if (!ev.detail) editable = false;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="icon">
|
||||
<Icon name="plus-circle" />
|
||||
</div>
|
||||
{#if editable}
|
||||
<div class="controls">
|
||||
<Selector
|
||||
bind:this={selector}
|
||||
types={['Address', 'NewAddress', 'Attribute']}
|
||||
on:input={(ev) => {
|
||||
dispatch('input', ev.detail);
|
||||
editable = false;
|
||||
}}
|
||||
on:focus={(ev) => {
|
||||
if (!ev.detail) editable = false;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.view {
|
||||
position: relative;
|
||||
.view {
|
||||
position: relative;
|
||||
|
||||
background: var(--background-lighter);
|
||||
color: var(--foreground-lighter);
|
||||
border: 1px solid var(--foreground-lightest);
|
||||
border-radius: 0.5em;
|
||||
padding: 1rem;
|
||||
background: var(--background-lighter);
|
||||
color: var(--foreground-lighter);
|
||||
border: 1px solid var(--foreground-lightest);
|
||||
border-radius: 0.5em;
|
||||
padding: 1rem;
|
||||
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
|
||||
transition:
|
||||
opacity 0.3s,
|
||||
width 0.5s,
|
||||
min-width 0.5s;
|
||||
transition:
|
||||
opacity 0.3s,
|
||||
width 0.5s,
|
||||
min-width 0.5s;
|
||||
|
||||
opacity: 0.4;
|
||||
width: 48px;
|
||||
min-width: 48px;
|
||||
opacity: 0.4;
|
||||
width: 48px;
|
||||
min-width: 48px;
|
||||
|
||||
&:hover,
|
||||
&.editable {
|
||||
opacity: 1;
|
||||
}
|
||||
&:hover,
|
||||
&.editable {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.editable {
|
||||
width: 18em;
|
||||
min-width: 18em;
|
||||
.icon {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.editable {
|
||||
width: 18em;
|
||||
min-width: 18em;
|
||||
.icon {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 36px;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 36px;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,67 +1,58 @@
|
|||
<script lang="ts">
|
||||
import type { UpEntry } from "@upnd/upend";
|
||||
import { attributeLabels } from "../../util/labels";
|
||||
import UpObject from "./UpObject.svelte";
|
||||
export let resolve = true;
|
||||
import type { UpEntry } from '@upnd/upend';
|
||||
import { attributeLabels } from '../../util/labels';
|
||||
import UpObject from './UpObject.svelte';
|
||||
export let resolve = true;
|
||||
|
||||
export let entry: UpEntry;
|
||||
export let entry: UpEntry;
|
||||
</script>
|
||||
|
||||
<div class="entry">
|
||||
<div class="entity">
|
||||
<UpObject
|
||||
plain
|
||||
link
|
||||
address={entry.entity}
|
||||
labels={resolve ? undefined : []}
|
||||
/>
|
||||
</div>
|
||||
<div class="attribute" title={entry.attribute}>
|
||||
{$attributeLabels[entry.attribute] || entry.attribute}
|
||||
</div>
|
||||
<div class="value value-{entry.value.t.toLowerCase()}">
|
||||
{#if entry.value.t === "Address"}
|
||||
<UpObject
|
||||
link
|
||||
address={entry.value.c}
|
||||
labels={resolve ? undefined : []}
|
||||
/>
|
||||
{:else}
|
||||
{entry.value.c}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="entity">
|
||||
<UpObject plain link address={entry.entity} labels={resolve ? undefined : []} />
|
||||
</div>
|
||||
<div class="attribute" title={entry.attribute}>
|
||||
{$attributeLabels[entry.attribute] || entry.attribute}
|
||||
</div>
|
||||
<div class="value value-{entry.value.t.toLowerCase()}">
|
||||
{#if entry.value.t === 'Address'}
|
||||
<UpObject link address={entry.value.c} labels={resolve ? undefined : []} />
|
||||
{:else}
|
||||
{entry.value.c}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.entry {
|
||||
border: 1px solid var(--foreground);
|
||||
background: var(--background-lighter);
|
||||
border-radius: 4px;
|
||||
padding: 0.1em 0.5em 0.1em 0.25em;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1em;
|
||||
.entry {
|
||||
border: 1px solid var(--foreground);
|
||||
background: var(--background-lighter);
|
||||
border-radius: 4px;
|
||||
padding: 0.1em 0.5em 0.1em 0.25em;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1em;
|
||||
|
||||
& > * {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
& > * {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.attribute {
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
&::before {
|
||||
content: "→\00a0";
|
||||
}
|
||||
&::after {
|
||||
content: "\00a0→";
|
||||
}
|
||||
}
|
||||
.attribute {
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
&::before {
|
||||
content: '→\00a0';
|
||||
}
|
||||
&::after {
|
||||
content: '\00a0→';
|
||||
}
|
||||
}
|
||||
|
||||
:global(.value-value) {
|
||||
font-family: var(--monospace-font);
|
||||
}
|
||||
:global(.value-value) {
|
||||
font-family: var(--monospace-font);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,59 +1,59 @@
|
|||
<script lang="ts">
|
||||
import HashBadge from "./HashBadge.svelte";
|
||||
import UpLink from "./UpLink.svelte";
|
||||
import BlobPreview from "./BlobPreview.svelte";
|
||||
import UpObject from "./UpObject.svelte";
|
||||
import HashBadge from './HashBadge.svelte';
|
||||
import UpLink from './UpLink.svelte';
|
||||
import BlobPreview from './BlobPreview.svelte';
|
||||
import UpObject from './UpObject.svelte';
|
||||
|
||||
export let address: string;
|
||||
export let labels: string[] | undefined = undefined;
|
||||
export let thumbnail = true;
|
||||
export let banner = true;
|
||||
export let select = true;
|
||||
export let address: string;
|
||||
export let labels: string[] | undefined = undefined;
|
||||
export let thumbnail = true;
|
||||
export let banner = true;
|
||||
export let select = true;
|
||||
</script>
|
||||
|
||||
<div class="upobjectcard">
|
||||
<UpLink to={{ entity: address }} passthrough>
|
||||
<div class="inner">
|
||||
{#if thumbnail}
|
||||
<BlobPreview {address} />
|
||||
{:else}
|
||||
<div class="badge">
|
||||
<HashBadge {address} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="label">
|
||||
<UpObject {address} {labels} {banner} {select} on:resolved />
|
||||
</div>
|
||||
</div>
|
||||
</UpLink>
|
||||
<UpLink to={{ entity: address }} passthrough>
|
||||
<div class="inner">
|
||||
{#if thumbnail}
|
||||
<BlobPreview {address} />
|
||||
{:else}
|
||||
<div class="badge">
|
||||
<HashBadge {address} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="label">
|
||||
<UpObject {address} {labels} {banner} {select} on:resolved />
|
||||
</div>
|
||||
</div>
|
||||
</UpLink>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.upobjectcard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
overflow: hidden;
|
||||
.upobjectcard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
overflow: hidden;
|
||||
|
||||
.inner {
|
||||
border: 1px solid var(--foreground-lighter);
|
||||
border-radius: 4px;
|
||||
padding: 0.25rem;
|
||||
max-height: 420px;
|
||||
min-height: 0;
|
||||
.inner {
|
||||
border: 1px solid var(--foreground-lighter);
|
||||
border-radius: 4px;
|
||||
padding: 0.25rem;
|
||||
max-height: 420px;
|
||||
min-height: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
.label {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 3rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.badge {
|
||||
font-size: 3rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,48 +1,48 @@
|
|||
<script lang="ts">
|
||||
import Ellipsis from "../utils/Ellipsis.svelte";
|
||||
import Ellipsis from '../utils/Ellipsis.svelte';
|
||||
|
||||
export let label: string;
|
||||
export let backpath: string[] = [];
|
||||
export let label: string;
|
||||
export let backpath: string[] = [];
|
||||
</script>
|
||||
|
||||
<div class="upobject-label">
|
||||
<Ellipsis value={label}>
|
||||
{#if backpath.length}
|
||||
<span class="backpath">
|
||||
{#each backpath as component}
|
||||
<span class="component">
|
||||
{component}
|
||||
</span>
|
||||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
<span class="label">
|
||||
{label}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<Ellipsis value={label}>
|
||||
{#if backpath.length}
|
||||
<span class="backpath">
|
||||
{#each backpath as component}
|
||||
<span class="component">
|
||||
{component}
|
||||
</span>
|
||||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
<span class="label">
|
||||
{label}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.upobject-label {
|
||||
max-width: 100%;
|
||||
}
|
||||
.upobject-label {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.backpath {
|
||||
opacity: 0.66;
|
||||
margin-right: 0.25em;
|
||||
.backpath {
|
||||
opacity: 0.66;
|
||||
margin-right: 0.25em;
|
||||
|
||||
.component::after {
|
||||
content: "∋";
|
||||
margin-left: 0.2em;
|
||||
margin-right: 0.4em;
|
||||
font-size: 0.66em;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
top: -0.125em;
|
||||
}
|
||||
.component::after {
|
||||
content: '∋';
|
||||
margin-left: 0.2em;
|
||||
margin-right: 0.4em;
|
||||
font-size: 0.66em;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
top: -0.125em;
|
||||
}
|
||||
|
||||
.component:last-child::after {
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
.component:last-child::after {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,106 +1,106 @@
|
|||
<script lang="ts">
|
||||
import Icon from "../utils/Icon.svelte";
|
||||
import Jobs from "./Jobs.svelte";
|
||||
import Notifications from "./Notifications.svelte";
|
||||
import { i18n } from "../../i18n";
|
||||
import Icon from '../utils/Icon.svelte';
|
||||
import Jobs from './Jobs.svelte';
|
||||
import Notifications from './Notifications.svelte';
|
||||
import { i18n } from '../../i18n';
|
||||
|
||||
let hidden = true;
|
||||
let activeJobs: number;
|
||||
$: togglable = activeJobs > 0 || !hidden;
|
||||
let hidden = true;
|
||||
let activeJobs: number;
|
||||
$: togglable = activeJobs > 0 || !hidden;
|
||||
</script>
|
||||
|
||||
<footer id="footer" class:hidden>
|
||||
<div class="notifications">
|
||||
<Notifications />
|
||||
</div>
|
||||
<div
|
||||
class="status"
|
||||
class:togglable
|
||||
on:click={() => (hidden = !hidden)}
|
||||
on:keydown={(ev) => {
|
||||
if (["Space", "Enter"].includes(ev.key)) hidden = !hidden;
|
||||
}}
|
||||
>
|
||||
<div class="info">
|
||||
{#if activeJobs > 0}
|
||||
{$i18n.t("Active jobs:")} {activeJobs}
|
||||
{:else}
|
||||
{$i18n.t("No active jobs.")}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="icons">
|
||||
<Icon name="{hidden ? 'up' : 'down'}-arrow" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="jobs">
|
||||
<Jobs bind:active={activeJobs} />
|
||||
</div>
|
||||
<div class="notifications">
|
||||
<Notifications />
|
||||
</div>
|
||||
<div
|
||||
class="status"
|
||||
class:togglable
|
||||
on:click={() => (hidden = !hidden)}
|
||||
on:keydown={(ev) => {
|
||||
if (['Space', 'Enter'].includes(ev.key)) hidden = !hidden;
|
||||
}}
|
||||
>
|
||||
<div class="info">
|
||||
{#if activeJobs > 0}
|
||||
{$i18n.t('Active jobs:')} {activeJobs}
|
||||
{:else}
|
||||
{$i18n.t('No active jobs.')}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="icons">
|
||||
<Icon name="{hidden ? 'up' : 'down'}-arrow" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="jobs">
|
||||
<Jobs bind:active={activeJobs} />
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style lang="scss">
|
||||
footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > * {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
& > * {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
background: var(--background);
|
||||
border-top: 1px solid var(--foreground-lighter);
|
||||
background: var(--background);
|
||||
border-top: 1px solid var(--foreground-lighter);
|
||||
|
||||
transition: 0.7s bottom ease;
|
||||
transition: 0.7s bottom ease;
|
||||
|
||||
--height: calc(100vh / 6);
|
||||
}
|
||||
--height: calc(100vh / 6);
|
||||
}
|
||||
|
||||
footer.hidden {
|
||||
bottom: calc(var(--height) * -1);
|
||||
}
|
||||
footer.hidden {
|
||||
bottom: calc(var(--height) * -1);
|
||||
}
|
||||
|
||||
.status {
|
||||
height: 2rem;
|
||||
width: 100%;
|
||||
.status {
|
||||
height: 2rem;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
|
||||
&:not(.togglable) {
|
||||
cursor: unset;
|
||||
pointer-events: none;
|
||||
opacity: 0.66;
|
||||
}
|
||||
&:not(.togglable) {
|
||||
cursor: unset;
|
||||
pointer-events: none;
|
||||
opacity: 0.66;
|
||||
}
|
||||
|
||||
transition: opacity 0.7s ease;
|
||||
transition: opacity 0.7s ease;
|
||||
|
||||
.info {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
.info {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.notifications,
|
||||
.jobs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.notifications,
|
||||
.jobs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
:global(.notifications > *:first-child) {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
:global(.notifications > *:first-child) {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
.jobs {
|
||||
overflow-y: scroll;
|
||||
height: var(--height);
|
||||
.jobs {
|
||||
overflow-y: scroll;
|
||||
height: var(--height);
|
||||
|
||||
padding-top: 0.5rem;
|
||||
padding-top: 0.5rem;
|
||||
|
||||
background: var(--background-lighter);
|
||||
}
|
||||
background: var(--background-lighter);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
section.labelborder {
|
||||
margin-top: 0.66rem;
|
||||
margin-top: 0.66rem;
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
header {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
|
||||
border-bottom: 1px solid var(--foreground);
|
||||
padding-bottom: 0.33rem;
|
||||
margin-bottom: 0.33rem;
|
||||
border-bottom: 1px solid var(--foreground);
|
||||
padding-bottom: 0.33rem;
|
||||
margin-bottom: 0.33rem;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<script lang="ts">
|
||||
export let value: string;
|
||||
export let title: string | undefined = undefined;
|
||||
export let value: string;
|
||||
export let title: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<div class="ellipsis" title={title || value}><slot>{value}</slot></div>
|
||||
|
||||
<style lang="scss">
|
||||
.ellipsis {
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ellipsis {
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,88 +1,88 @@
|
|||
<script lang="ts">
|
||||
import Icon from "./Icon.svelte";
|
||||
import Icon from './Icon.svelte';
|
||||
|
||||
export let name: string;
|
||||
export let active = false;
|
||||
export let disabled = false;
|
||||
export let title: string | undefined = undefined;
|
||||
export let outline = false;
|
||||
export let subdued = false;
|
||||
export let small = false;
|
||||
export let plain = false;
|
||||
export let color: string | undefined = "var(--active-color, var(--primary))";
|
||||
export let name: string;
|
||||
export let active = false;
|
||||
export let disabled = false;
|
||||
export let title: string | undefined = undefined;
|
||||
export let outline = false;
|
||||
export let subdued = false;
|
||||
export let small = false;
|
||||
export let plain = false;
|
||||
export let color: string | undefined = 'var(--active-color, var(--primary))';
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click
|
||||
class:active
|
||||
class:outline
|
||||
class:subdued
|
||||
class:small
|
||||
class:plain
|
||||
{disabled}
|
||||
{title}
|
||||
style="--color: {color}"
|
||||
on:click
|
||||
class:active
|
||||
class:outline
|
||||
class:subdued
|
||||
class:small
|
||||
class:plain
|
||||
{disabled}
|
||||
{title}
|
||||
style="--color: {color}"
|
||||
>
|
||||
<Icon {name} />
|
||||
<div class="text">
|
||||
<slot />
|
||||
</div>
|
||||
<Icon {name} />
|
||||
<div class="text">
|
||||
<slot />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<style lang="scss">
|
||||
button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
border: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
opacity: 0.66;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
opacity: 0.66;
|
||||
|
||||
&.plain {
|
||||
padding: 0;
|
||||
}
|
||||
&.plain {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
transition:
|
||||
opacity 0.2s,
|
||||
color 0.2s,
|
||||
border-color 0.2s;
|
||||
}
|
||||
transition:
|
||||
opacity 0.2s,
|
||||
color 0.2s,
|
||||
border-color 0.2s;
|
||||
}
|
||||
|
||||
button.subdued {
|
||||
opacity: 0.4;
|
||||
}
|
||||
button.subdued {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.outline {
|
||||
border: 1px solid var(--foreground);
|
||||
border-radius: 4px;
|
||||
.outline {
|
||||
border: 1px solid var(--foreground);
|
||||
border-radius: 4px;
|
||||
|
||||
padding: 0.25em 1em;
|
||||
&.small {
|
||||
padding: 0.1em 0.8em;
|
||||
}
|
||||
}
|
||||
padding: 0.25em 1em;
|
||||
&.small {
|
||||
padding: 0.1em 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
.active,
|
||||
button:hover {
|
||||
opacity: 1;
|
||||
color: var(--color);
|
||||
border-color: var(--color);
|
||||
}
|
||||
.active,
|
||||
button:hover {
|
||||
opacity: 1;
|
||||
color: var(--color);
|
||||
border-color: var(--color);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
color: gray;
|
||||
pointer-events: none;
|
||||
}
|
||||
button:disabled {
|
||||
color: gray;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 0.5em;
|
||||
text-align: center;
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
.text {
|
||||
font-size: 0.5em;
|
||||
text-align: center;
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,63 +1,63 @@
|
|||
<script lang="ts">
|
||||
export let hide = false;
|
||||
let hidden = true;
|
||||
export let hide = false;
|
||||
let hidden = true;
|
||||
</script>
|
||||
|
||||
<section class="labelborder" class:hide class:hidden>
|
||||
<header
|
||||
on:click={() => {
|
||||
if (hide) {
|
||||
hidden = !hidden;
|
||||
}
|
||||
}}
|
||||
on:keydown={(ev) => {
|
||||
if (["Space", "Enter"].includes(ev.key) && hide) hidden = !hidden;
|
||||
}}
|
||||
>
|
||||
<slot name="header-full">
|
||||
<h3><slot name="header" /></h3>
|
||||
</slot>
|
||||
</header>
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
<header
|
||||
on:click={() => {
|
||||
if (hide) {
|
||||
hidden = !hidden;
|
||||
}
|
||||
}}
|
||||
on:keydown={(ev) => {
|
||||
if (['Space', 'Enter'].includes(ev.key) && hide) hidden = !hidden;
|
||||
}}
|
||||
>
|
||||
<slot name="header-full">
|
||||
<h3><slot name="header" /></h3>
|
||||
</slot>
|
||||
</header>
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
section.labelborder {
|
||||
margin-top: 0.66rem;
|
||||
section.labelborder {
|
||||
margin-top: 0.66rem;
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
header {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
|
||||
border-bottom: 1px solid var(--foreground);
|
||||
padding-bottom: 0.33rem;
|
||||
margin-bottom: 0.33rem;
|
||||
border-bottom: 1px solid var(--foreground);
|
||||
padding-bottom: 0.33rem;
|
||||
margin-bottom: 0.33rem;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.hide {
|
||||
header {
|
||||
cursor: pointer;
|
||||
}
|
||||
&.hide {
|
||||
header {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
&.hidden {
|
||||
opacity: 0.66;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
&.hidden {
|
||||
opacity: 0.66;
|
||||
|
||||
header {
|
||||
border-bottom-width: 0.5px;
|
||||
}
|
||||
header {
|
||||
border-bottom-width: 0.5px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,71 +1,67 @@
|
|||
<script lang="ts">
|
||||
export let value: number | undefined = undefined;
|
||||
export let value: number | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="progress-bar"
|
||||
class:indeterminate={value == undefined}
|
||||
style="--value: {value}%"
|
||||
>
|
||||
<div class="value" />
|
||||
<div class="label">
|
||||
<slot>
|
||||
{value ? Math.round(value) : "?"}%
|
||||
</slot>
|
||||
</div>
|
||||
<div class="progress-bar" class:indeterminate={value == undefined} style="--value: {value}%">
|
||||
<div class="value" />
|
||||
<div class="label">
|
||||
<slot>
|
||||
{value ? Math.round(value) : '?'}%
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 1em;
|
||||
background: white;
|
||||
position: relative;
|
||||
}
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 1em;
|
||||
background: white;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.value {
|
||||
background: var(--primary);
|
||||
height: 100%;
|
||||
width: var(--value, 100%);
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
.value {
|
||||
background: var(--primary);
|
||||
height: 100%;
|
||||
width: var(--value, 100%);
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
|
||||
.progress-bar,
|
||||
.value {
|
||||
border-radius: 2px;
|
||||
}
|
||||
.progress-bar,
|
||||
.value {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.8em;
|
||||
color: white;
|
||||
z-index: 9;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
.label {
|
||||
font-size: 0.8em;
|
||||
color: white;
|
||||
z-index: 9;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
|
||||
.progress-bar.indeterminate {
|
||||
.value {
|
||||
animation-name: indeterminate;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
}
|
||||
.progress-bar.indeterminate {
|
||||
.value {
|
||||
animation-name: indeterminate;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes indeterminate {
|
||||
0% {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
50% {
|
||||
background-color: var(--primary-lighter);
|
||||
}
|
||||
100% {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
}
|
||||
@keyframes indeterminate {
|
||||
0% {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
50% {
|
||||
background-color: var(--primary-lighter);
|
||||
}
|
||||
100% {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
<script lang="ts">
|
||||
export let centered: boolean | string = false;
|
||||
export let centered: boolean | string = false;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="spinner lds-ripple"
|
||||
class:centered={Boolean(centered)}
|
||||
class:absolute-centered={centered == "absolute"}
|
||||
class="spinner lds-ripple"
|
||||
class:centered={Boolean(centered)}
|
||||
class:absolute-centered={centered == 'absolute'}
|
||||
>
|
||||
<i class="bx bx-loader bx-spin" />
|
||||
<i class="bx bx-loader bx-spin" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.spinner {
|
||||
height: 1em;
|
||||
}
|
||||
.spinner.centered {
|
||||
position: relative;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
}
|
||||
.spinner {
|
||||
height: 1em;
|
||||
}
|
||||
.spinner.centered {
|
||||
position: relative;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.spinner.absolute-centered {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.spinner.absolute-centered {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,36 +1,34 @@
|
|||
export const DEBUG = {
|
||||
imageQueueHalt: Boolean(localStorage.getItem("DEBUG:IMAGEHALT")),
|
||||
mockJobs: parseInt(localStorage.getItem("DEBUG:MOCK:JOBS") || "0"),
|
||||
mockNotifications: parseInt(
|
||||
localStorage.getItem("DEBUG:MOCK:NOTIFICATIONS") || "0",
|
||||
),
|
||||
imageQueueHalt: Boolean(localStorage.getItem('DEBUG:IMAGEHALT')),
|
||||
mockJobs: parseInt(localStorage.getItem('DEBUG:MOCK:JOBS') || '0'),
|
||||
mockNotifications: parseInt(localStorage.getItem('DEBUG:MOCK:NOTIFICATIONS') || '0')
|
||||
};
|
||||
|
||||
export function lipsum(length: number): string {
|
||||
const words = [
|
||||
"lorem",
|
||||
"ipsum",
|
||||
"dolor",
|
||||
"sit",
|
||||
"amet",
|
||||
"consectetur",
|
||||
"adipiscing",
|
||||
"elit",
|
||||
"sed",
|
||||
"do",
|
||||
"eiusmod",
|
||||
"tempor",
|
||||
"incididunt",
|
||||
"ut",
|
||||
"labore",
|
||||
"et",
|
||||
"dolore",
|
||||
"magna",
|
||||
"aliqua",
|
||||
];
|
||||
const result = ["lorem", "ipsum"];
|
||||
for (let i = 0; i < length; i++) {
|
||||
result.push(words[Math.floor(Math.random() * words.length)]);
|
||||
}
|
||||
return result.join(" ");
|
||||
const words = [
|
||||
'lorem',
|
||||
'ipsum',
|
||||
'dolor',
|
||||
'sit',
|
||||
'amet',
|
||||
'consectetur',
|
||||
'adipiscing',
|
||||
'elit',
|
||||
'sed',
|
||||
'do',
|
||||
'eiusmod',
|
||||
'tempor',
|
||||
'incididunt',
|
||||
'ut',
|
||||
'labore',
|
||||
'et',
|
||||
'dolore',
|
||||
'magna',
|
||||
'aliqua'
|
||||
];
|
||||
const result = ['lorem', 'ipsum'];
|
||||
for (let i = 0; i < length; i++) {
|
||||
result.push(words[Math.floor(Math.random() * words.length)]);
|
||||
}
|
||||
return result.join(' ');
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"attributes": {
|
||||
"FILE_MIME": "MIME type",
|
||||
"FILE_SIZE": "File size",
|
||||
"ADDED": "Added at",
|
||||
"LAST_VISITED": "Last visited at",
|
||||
"NUM_VISITED": "Times visited",
|
||||
"ATTR_LABEL": "Label",
|
||||
"IS": "Type",
|
||||
"TYPE": "Type ID",
|
||||
"MEDIA_DURATION": "Duration"
|
||||
}
|
||||
"attributes": {
|
||||
"FILE_MIME": "MIME type",
|
||||
"FILE_SIZE": "File size",
|
||||
"ADDED": "Added at",
|
||||
"LAST_VISITED": "Last visited at",
|
||||
"NUM_VISITED": "Times visited",
|
||||
"ATTR_LABEL": "Label",
|
||||
"IS": "Type",
|
||||
"TYPE": "Type ID",
|
||||
"MEDIA_DURATION": "Duration"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import i18next from "i18next";
|
||||
import { createI18nStore } from "svelte-i18next";
|
||||
import en from "./en.json";
|
||||
import i18next from 'i18next';
|
||||
import { createI18nStore } from 'svelte-i18next';
|
||||
import en from './en.json';
|
||||
|
||||
i18next.init({
|
||||
lng: "en",
|
||||
resources: {
|
||||
en,
|
||||
},
|
||||
lng: 'en',
|
||||
resources: {
|
||||
en
|
||||
}
|
||||
});
|
||||
|
||||
export const i18n = createI18nStore(i18next);
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
import mitt from "mitt";
|
||||
import mitt from 'mitt';
|
||||
|
||||
type NotifyEvents = {
|
||||
notification: UpNotification;
|
||||
notification: UpNotification;
|
||||
};
|
||||
|
||||
export type UpNotificationLevel = "info" | "warning" | "error";
|
||||
export type UpNotificationLevel = 'info' | 'warning' | 'error';
|
||||
|
||||
export interface INotification {
|
||||
id: string;
|
||||
content: string;
|
||||
level: UpNotificationLevel;
|
||||
id: string;
|
||||
content: string;
|
||||
level: UpNotificationLevel;
|
||||
}
|
||||
|
||||
export class UpNotification implements INotification {
|
||||
id: string;
|
||||
content: string;
|
||||
level: UpNotificationLevel;
|
||||
id: string;
|
||||
content: string;
|
||||
level: UpNotificationLevel;
|
||||
|
||||
constructor(content: string, level?: UpNotificationLevel) {
|
||||
this.id = String(Math.random());
|
||||
this.content = content;
|
||||
this.level = level || "info";
|
||||
}
|
||||
constructor(content: string, level?: UpNotificationLevel) {
|
||||
this.id = String(Math.random());
|
||||
this.content = content;
|
||||
this.level = level || 'info';
|
||||
}
|
||||
}
|
||||
|
||||
export const notify = mitt<NotifyEvents>();
|
||||
|
|
|
@ -1,92 +1,83 @@
|
|||
@use "colors";
|
||||
@use "sass:color";
|
||||
@use 'colors';
|
||||
@use 'sass:color';
|
||||
|
||||
@mixin rebase(
|
||||
$rebase03,
|
||||
$rebase02,
|
||||
$rebase01,
|
||||
$rebase00,
|
||||
$rebase0,
|
||||
$rebase1,
|
||||
$rebase2,
|
||||
$rebase3
|
||||
) {
|
||||
--foreground: #{$rebase0};
|
||||
--foreground-lighter: #{$rebase1};
|
||||
--foreground-lightest: #{$rebase2};
|
||||
--background: #{$rebase03};
|
||||
--background-lighter: #{$rebase02};
|
||||
--background-lightest: #{color.adjust($rebase02, $lightness: 1.5%)};
|
||||
--primary: #{colors.$blue};
|
||||
--primary-lighter: #{color.adjust(colors.$blue, $lightness: 25%)};
|
||||
@mixin rebase($rebase03, $rebase02, $rebase01, $rebase00, $rebase0, $rebase1, $rebase2, $rebase3) {
|
||||
--foreground: #{$rebase0};
|
||||
--foreground-lighter: #{$rebase1};
|
||||
--foreground-lightest: #{$rebase2};
|
||||
--background: #{$rebase03};
|
||||
--background-lighter: #{$rebase02};
|
||||
--background-lightest: #{color.adjust($rebase02, $lightness: 1.5%)};
|
||||
--primary: #{colors.$blue};
|
||||
--primary-lighter: #{color.adjust(colors.$blue, $lightness: 25%)};
|
||||
|
||||
background-color: $rebase03;
|
||||
color: $rebase0;
|
||||
background-color: $rebase03;
|
||||
color: $rebase0;
|
||||
}
|
||||
|
||||
@mixin accentize($accent) {
|
||||
a,
|
||||
a:active,
|
||||
a:visited,
|
||||
code.url {
|
||||
color: $accent;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
color: $accent;
|
||||
}
|
||||
a,
|
||||
a:active,
|
||||
a:visited,
|
||||
code.url {
|
||||
color: $accent;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
color: $accent;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
@include rebase(
|
||||
colors.$base3,
|
||||
colors.$base2,
|
||||
colors.$base1,
|
||||
colors.$base0,
|
||||
colors.$base00,
|
||||
colors.$base01,
|
||||
colors.$base02,
|
||||
colors.$base03
|
||||
);
|
||||
}
|
||||
html {
|
||||
@include rebase(
|
||||
colors.$base3,
|
||||
colors.$base2,
|
||||
colors.$base1,
|
||||
colors.$base0,
|
||||
colors.$base00,
|
||||
colors.$base01,
|
||||
colors.$base02,
|
||||
colors.$base03
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
@include rebase(
|
||||
colors.$base03,
|
||||
colors.$base02,
|
||||
colors.$base01,
|
||||
colors.$base00,
|
||||
colors.$base0,
|
||||
colors.$base1,
|
||||
colors.$base2,
|
||||
colors.$base3
|
||||
);
|
||||
@include rebase(
|
||||
colors.$base03,
|
||||
colors.$base02,
|
||||
colors.$base01,
|
||||
colors.$base00,
|
||||
colors.$base0,
|
||||
colors.$base1,
|
||||
colors.$base2,
|
||||
colors.$base3
|
||||
);
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
color: var(--foreground-lighter);
|
||||
border-color: var(--foreground);
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
color: var(--foreground-lighter);
|
||||
border-color: var(--foreground);
|
||||
}
|
||||
|
||||
a,
|
||||
a:active,
|
||||
a:visited {
|
||||
color: var(--foreground-lighter);
|
||||
}
|
||||
a,
|
||||
a:active,
|
||||
a:visited {
|
||||
color: var(--foreground-lighter);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,68 +1,68 @@
|
|||
@use "sass:color";
|
||||
@use "colors";
|
||||
@use 'sass:color';
|
||||
@use 'colors';
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: inherit;
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
select {
|
||||
background: var(--background-lighter);
|
||||
color: var(--foreground);
|
||||
font-family: var(--default-font);
|
||||
background: var(--background-lighter);
|
||||
color: var(--foreground);
|
||||
font-family: var(--default-font);
|
||||
|
||||
border: 1px solid var(--foreground-lighter);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--foreground-lighter);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
font-size: 2em;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.button {
|
||||
border: 1px solid var(--foreground);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--foreground);
|
||||
border-radius: 4px;
|
||||
|
||||
background: var(--background-lighter);
|
||||
color: var(--foreground);
|
||||
background: var(--background-lighter);
|
||||
color: var(--foreground);
|
||||
|
||||
padding: 0.25em 1em;
|
||||
line-height: 1;
|
||||
padding: 0.25em 1em;
|
||||
line-height: 1;
|
||||
|
||||
display: block;
|
||||
text-align: center;
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.disabled,
|
||||
&:disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
&.disabled,
|
||||
&:disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
padding: 0.5em;
|
||||
}
|
||||
@media screen and (max-width: 600px) {
|
||||
padding: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.mark-entity::first-letter,
|
||||
.mark-entity *::first-letter {
|
||||
color: color.scale(color.mix(colors.$base1, colors.$red), $saturation: -33%);
|
||||
color: color.scale(color.mix(colors.$base1, colors.$red), $saturation: -33%);
|
||||
}
|
||||
|
||||
.mark-attribute::first-letter,
|
||||
.mark-attribute *::first-letter {
|
||||
color: color.mix(colors.$base1, colors.$green);
|
||||
color: color.mix(colors.$base1, colors.$green);
|
||||
}
|
||||
|
||||
.mark-value::first-letter,
|
||||
.mark-value *::first-letter {
|
||||
color: color.mix(colors.$base1, colors.$blue);
|
||||
color: color.mix(colors.$base1, colors.$blue);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
@import "node_modules/@ibm/plex/scss/ibm-plex.scss";
|
||||
@import 'node_modules/@ibm/plex/scss/ibm-plex.scss';
|
||||
|
||||
html {
|
||||
--default-font: "IBM Plex Sans", "Helvetica Neue", Arial, sans-serif;
|
||||
--monospace-font: "IBM Plex Mono", "Menlo", "DejaVu Sans Mono", "Consolas",
|
||||
"Inconsolata", "Hack", monospace;
|
||||
font-size: 16px;
|
||||
--default-font: 'IBM Plex Sans', 'Helvetica Neue', Arial, sans-serif;
|
||||
--monospace-font: 'IBM Plex Mono', 'Menlo', 'DejaVu Sans Mono', 'Consolas', 'Inconsolata', 'Hack',
|
||||
monospace;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
font-family: var(--default-font);
|
||||
font-family: var(--default-font);
|
||||
}
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
@use "normalize.css/normalize.css";
|
||||
@use "colors-app";
|
||||
@use "fonts";
|
||||
@use "common";
|
||||
@use 'normalize.css/normalize.css';
|
||||
@use 'colors-app';
|
||||
@use 'fonts';
|
||||
@use 'common';
|
||||
|
||||
body {
|
||||
height: calc(100vh - 2rem);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 2rem);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex-grow: 1;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.image-queued,
|
||||
.image-loading {
|
||||
animation: gradient 1.5s ease infinite;
|
||||
animation: gradient 1.5s ease infinite;
|
||||
|
||||
color: transparent; // Hide alt-text
|
||||
color: transparent; // Hide alt-text
|
||||
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
50% {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
100% {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
50% {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
100% {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-queued {
|
||||
opacity: 0.5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
import type { IValue } from "@upnd/upend/types";
|
||||
import type { IValue } from '@upnd/upend/types';
|
||||
|
||||
export type WidgetChange =
|
||||
| AttributeCreate
|
||||
| AttributeUpdate
|
||||
| AttributeDelete
|
||||
| EntryInAdd
|
||||
| EntryInDelete;
|
||||
| AttributeCreate
|
||||
| AttributeUpdate
|
||||
| AttributeDelete
|
||||
| EntryInAdd
|
||||
| EntryInDelete;
|
||||
|
||||
export interface AttributeCreate {
|
||||
type: "create";
|
||||
attribute: string;
|
||||
value: IValue;
|
||||
type: 'create';
|
||||
attribute: string;
|
||||
value: IValue;
|
||||
}
|
||||
|
||||
export interface AttributeUpdate {
|
||||
type: "update";
|
||||
attribute: string;
|
||||
value: IValue;
|
||||
type: 'update';
|
||||
attribute: string;
|
||||
value: IValue;
|
||||
}
|
||||
|
||||
export interface AttributeDelete {
|
||||
type: "delete";
|
||||
address: string;
|
||||
type: 'delete';
|
||||
address: string;
|
||||
}
|
||||
|
||||
export interface EntryInAdd {
|
||||
type: "entry-add";
|
||||
address: string;
|
||||
type: 'entry-add';
|
||||
address: string;
|
||||
}
|
||||
|
||||
export interface EntryInDelete {
|
||||
type: "entry-delete";
|
||||
address: string;
|
||||
type: 'entry-delete';
|
||||
address: string;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { Readable } from "svelte/store";
|
||||
import type { Readable } from 'svelte/store';
|
||||
|
||||
export interface BrowseContext {
|
||||
index: Readable<number>;
|
||||
addresses: Readable<string[]>;
|
||||
index: Readable<number>;
|
||||
addresses: Readable<string[]>;
|
||||
}
|
||||
|
|
|
@ -1,41 +1,37 @@
|
|||
import type { EntityInfo } from "@upnd/upend/types";
|
||||
import type { UpObject } from "@upnd/upend";
|
||||
import { ATTR_IN } from "@upnd/upend/constants";
|
||||
import type { EntityInfo } from '@upnd/upend/types';
|
||||
import type { UpObject } from '@upnd/upend';
|
||||
import { ATTR_IN } from '@upnd/upend/constants';
|
||||
|
||||
export function getTypes(entity: UpObject, entityInfo: EntityInfo) {
|
||||
const mimeType = String(entity?.get("FILE_MIME"));
|
||||
const mimeType = String(entity?.get('FILE_MIME'));
|
||||
|
||||
const video =
|
||||
["video", "application/x-matroska"].some((p) => mimeType.startsWith(p)) ||
|
||||
entity?.identify().some((l) => l.endsWith(".avi"));
|
||||
const audio =
|
||||
(["audio", "application/x-riff"].some((p) => mimeType.startsWith(p)) &&
|
||||
!video) ||
|
||||
[".ogg", ".mp3", ".wav"].some(
|
||||
(suffix) =>
|
||||
entity?.identify().some((l) => l.toLowerCase().endsWith(suffix)),
|
||||
);
|
||||
const image = mimeType.startsWith("image");
|
||||
const text = mimeType.startsWith("text");
|
||||
const pdf = mimeType.startsWith("application/pdf");
|
||||
const model =
|
||||
mimeType?.startsWith("model") ||
|
||||
entity?.identify().some((l) => l.endsWith(".stl"));
|
||||
const web = entityInfo?.t == "Url";
|
||||
const fragment = Boolean(entity?.get("ANNOTATES"));
|
||||
const video =
|
||||
['video', 'application/x-matroska'].some((p) => mimeType.startsWith(p)) ||
|
||||
entity?.identify().some((l) => l.endsWith('.avi'));
|
||||
const audio =
|
||||
(['audio', 'application/x-riff'].some((p) => mimeType.startsWith(p)) && !video) ||
|
||||
['.ogg', '.mp3', '.wav'].some((suffix) =>
|
||||
entity?.identify().some((l) => l.toLowerCase().endsWith(suffix))
|
||||
);
|
||||
const image = mimeType.startsWith('image');
|
||||
const text = mimeType.startsWith('text');
|
||||
const pdf = mimeType.startsWith('application/pdf');
|
||||
const model = mimeType?.startsWith('model') || entity?.identify().some((l) => l.endsWith('.stl'));
|
||||
const web = entityInfo?.t == 'Url';
|
||||
const fragment = Boolean(entity?.get('ANNOTATES'));
|
||||
|
||||
const group = entity?.backlinks.some((e) => e.attribute == ATTR_IN);
|
||||
const group = entity?.backlinks.some((e) => e.attribute == ATTR_IN);
|
||||
|
||||
return {
|
||||
mimeType,
|
||||
audio,
|
||||
video,
|
||||
image,
|
||||
text,
|
||||
pdf,
|
||||
model,
|
||||
web,
|
||||
fragment,
|
||||
group,
|
||||
};
|
||||
return {
|
||||
mimeType,
|
||||
audio,
|
||||
video,
|
||||
image,
|
||||
text,
|
||||
pdf,
|
||||
model,
|
||||
web,
|
||||
fragment,
|
||||
group
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
export function updateTitle(route?: string, title?: string) {
|
||||
let newTitle = "UpEnd";
|
||||
if (route?.length) {
|
||||
newTitle += ` | ${route}`;
|
||||
}
|
||||
if (title?.length) {
|
||||
newTitle += ` - ${title}`;
|
||||
}
|
||||
document.title = newTitle;
|
||||
let newTitle = 'UpEnd';
|
||||
if (route?.length) {
|
||||
newTitle += ` | ${route}`;
|
||||
}
|
||||
if (title?.length) {
|
||||
newTitle += ` - ${title}`;
|
||||
}
|
||||
document.title = newTitle;
|
||||
}
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
import { sveltekit } from "@sveltejs/kit/vite";
|
||||
import { defineConfig } from "vite";
|
||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||
import * as path from "path";
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
||||
import * as path from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
sveltekit(),
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: path.join(__dirname, "node_modules/boxicons", "fonts"),
|
||||
dest: path.resolve(__dirname, "dist/vendor/boxicons"),
|
||||
},
|
||||
{
|
||||
src: path.join(__dirname, "node_modules/boxicons", "css"),
|
||||
dest: path.resolve(__dirname, "dist/vendor/boxicons"),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
server: {
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://localhost:8093/",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
sveltekit(),
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: path.join(__dirname, 'node_modules/boxicons', 'fonts'),
|
||||
dest: path.resolve(__dirname, 'dist/vendor/boxicons')
|
||||
},
|
||||
{
|
||||
src: path.join(__dirname, 'node_modules/boxicons', 'css'),
|
||||
dest: path.resolve(__dirname, 'dist/vendor/boxicons')
|
||||
}
|
||||
]
|
||||
})
|
||||
],
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8093/'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue