@e280/quay 0.0.0-2 → 0.0.0-20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -0
- package/package.json +17 -11
- package/s/cellar/cask.ts +1 -13
- package/s/cellar/cellar.test.ts +25 -42
- package/s/cellar/cellar.ts +16 -20
- package/s/cellar/index.ts +0 -4
- package/s/cellar/mammoth.ts +50 -0
- package/s/cellar/mammoth.worker.bundle.ts +30 -0
- package/s/demo/main.bundle.ts +2 -3
- package/s/dom/components/breadcrumb/component.ts +6 -5
- package/s/dom/components/browser/component.ts +11 -7
- package/s/dom/components/browser/style.css.ts +31 -31
- package/s/dom/components/dropzone/component.ts +6 -4
- package/s/dom/components/dropzone/style.css.ts +12 -13
- package/s/dom/components/filter/component.ts +13 -8
- package/s/dom/components/outliner/component.ts +8 -5
- package/s/dom/components/searchbar/component.ts +13 -7
- package/s/dom/components/sort/component.ts +13 -9
- package/s/dom/theme.css.ts +66 -2
- package/s/dom/views/tree/component.ts +10 -8
- package/s/dom/views/tree/style.css.ts +10 -10
- package/s/genius/parts/hy-tree.ts +10 -9
- package/s/index.html.ts +68 -73
- package/s/index.ts +6 -1
- package/s/logic/aspects/codex/codex.ts +3 -3
- package/s/logic/aspects/codex/example.ts +1 -4
- package/s/logic/aspects/codex/parts/codex-item.ts +4 -4
- package/s/logic/aspects/codex/parts/hierarchy.ts +1 -0
- package/s/logic/aspects/codex/parts/types.ts +1 -1
- package/s/logic/aspects/dropzone.ts +20 -16
- package/s/logic/aspects/tree-trail.ts +5 -4
- package/s/logic/brain.ts +5 -3
- package/s/logic/group.ts +13 -10
- package/s/logic/presets/media/group.ts +5 -4
- package/s/logic/presets/media/library.test.ts +82 -0
- package/s/logic/presets/media/library.ts +249 -0
- package/s/logic/presets/media/schema.ts +10 -1
- package/s/logic/presets/opfs/schema.ts +1 -1
- package/s/logic/presets/plain/group.ts +2 -2
- package/s/logic/presets/plain/schema.ts +1 -1
- package/s/logic/types.ts +1 -1
- package/s/tests.test.ts +2 -0
- package/x/cellar/cask.d.ts +2 -4
- package/x/cellar/cask.js +3 -12
- package/x/cellar/cask.js.map +1 -1
- package/x/cellar/cellar.d.ts +9 -7
- package/x/cellar/cellar.js +17 -22
- package/x/cellar/cellar.js.map +1 -1
- package/x/cellar/cellar.test.d.ts +2 -3
- package/x/cellar/cellar.test.js +24 -39
- package/x/cellar/cellar.test.js.map +1 -1
- package/x/cellar/index.d.ts +0 -3
- package/x/cellar/index.js +0 -3
- package/x/cellar/index.js.map +1 -1
- package/x/cellar/mammoth.d.ts +23 -0
- package/x/cellar/mammoth.js +34 -0
- package/x/cellar/mammoth.js.map +1 -0
- package/x/cellar/mammoth.worker.bundle.d.ts +1 -0
- package/x/cellar/mammoth.worker.bundle.js +20 -0
- package/x/cellar/mammoth.worker.bundle.js.map +1 -0
- package/x/cellar/mammoth.worker.bundle.min.js +8 -0
- package/x/cellar/mammoth.worker.bundle.min.js.map +7 -0
- package/x/demo/main.bundle.js +2 -2
- package/x/demo/main.bundle.js.map +1 -1
- package/x/demo/main.bundle.min.js +229 -197
- package/x/demo/main.bundle.min.js.LEGAL.txt +33 -0
- package/x/demo/main.bundle.min.js.map +7 -0
- package/x/dom/components/breadcrumb/component.d.ts +1 -1
- package/x/dom/components/breadcrumb/component.js +6 -5
- package/x/dom/components/breadcrumb/component.js.map +1 -1
- package/x/dom/components/browser/component.d.ts +1 -1
- package/x/dom/components/browser/component.js +10 -7
- package/x/dom/components/browser/component.js.map +1 -1
- package/x/dom/components/browser/style.css.d.ts +1 -1
- package/x/dom/components/browser/style.css.js +31 -30
- package/x/dom/components/browser/style.css.js.map +1 -1
- package/x/dom/components/dropzone/component.d.ts +1 -1
- package/x/dom/components/dropzone/component.js +6 -4
- package/x/dom/components/dropzone/component.js.map +1 -1
- package/x/dom/components/dropzone/style.css.d.ts +1 -1
- package/x/dom/components/dropzone/style.css.js +12 -12
- package/x/dom/components/dropzone/style.css.js.map +1 -1
- package/x/dom/components/filter/component.d.ts +1 -1
- package/x/dom/components/filter/component.js +10 -8
- package/x/dom/components/filter/component.js.map +1 -1
- package/x/dom/components/outliner/component.d.ts +1 -1
- package/x/dom/components/outliner/component.js +7 -5
- package/x/dom/components/outliner/component.js.map +1 -1
- package/x/dom/components/searchbar/component.d.ts +1 -1
- package/x/dom/components/searchbar/component.js +12 -6
- package/x/dom/components/searchbar/component.js.map +1 -1
- package/x/dom/components/sort/component.d.ts +1 -1
- package/x/dom/components/sort/component.js +11 -9
- package/x/dom/components/sort/component.js.map +1 -1
- package/x/dom/components.d.ts +7 -7
- package/x/dom/theme.css.d.ts +1 -1
- package/x/dom/theme.css.js +65 -1
- package/x/dom/theme.css.js.map +1 -1
- package/x/dom/views/tree/component.d.ts +1 -6
- package/x/dom/views/tree/component.js +10 -8
- package/x/dom/views/tree/component.js.map +1 -1
- package/x/dom/views/tree/style.css.d.ts +1 -1
- package/x/dom/views/tree/style.css.js +10 -10
- package/x/dom/views/tree/style.css.js.map +1 -1
- package/x/genius/parts/hy-tree.js +9 -8
- package/x/genius/parts/hy-tree.js.map +1 -1
- package/x/index.d.ts +6 -1
- package/x/index.html +70 -167
- package/x/index.html.d.ts +1 -2
- package/x/index.html.js +67 -73
- package/x/index.html.js.map +1 -1
- package/x/index.js +6 -1
- package/x/index.js.map +1 -1
- package/x/logic/aspects/codex/codex.js +3 -3
- package/x/logic/aspects/codex/codex.js.map +1 -1
- package/x/logic/aspects/codex/example.d.ts +0 -1
- package/x/logic/aspects/codex/example.js +1 -3
- package/x/logic/aspects/codex/example.js.map +1 -1
- package/x/logic/aspects/codex/parts/codex-item.d.ts +1 -1
- package/x/logic/aspects/codex/parts/codex-item.js +4 -4
- package/x/logic/aspects/codex/parts/codex-item.js.map +1 -1
- package/x/logic/aspects/codex/parts/hierarchy.js +1 -0
- package/x/logic/aspects/codex/parts/hierarchy.js.map +1 -1
- package/x/logic/aspects/codex/parts/types.d.ts +1 -1
- package/x/logic/aspects/dropzone.d.ts +1 -1
- package/x/logic/aspects/dropzone.js +19 -15
- package/x/logic/aspects/dropzone.js.map +1 -1
- package/x/logic/aspects/tree-trail.d.ts +1 -1
- package/x/logic/aspects/tree-trail.js +4 -4
- package/x/logic/aspects/tree-trail.js.map +1 -1
- package/x/logic/brain.d.ts +4 -2
- package/x/logic/brain.js.map +1 -1
- package/x/logic/group.d.ts +7 -7
- package/x/logic/group.js +10 -8
- package/x/logic/group.js.map +1 -1
- package/x/logic/presets/media/group.d.ts +1 -1
- package/x/logic/presets/media/group.js +3 -3
- package/x/logic/presets/media/group.js.map +1 -1
- package/x/logic/presets/media/library.d.ts +59 -0
- package/x/logic/presets/media/library.js +201 -0
- package/x/logic/presets/media/library.js.map +1 -0
- package/x/logic/presets/media/library.test.d.ts +9 -0
- package/x/logic/presets/media/library.test.js +62 -0
- package/x/logic/presets/media/library.test.js.map +1 -0
- package/x/logic/presets/media/schema.d.ts +10 -1
- package/x/logic/presets/opfs/schema.d.ts +1 -1
- package/x/logic/presets/plain/group.js +2 -2
- package/x/logic/presets/plain/group.js.map +1 -1
- package/x/logic/presets/plain/schema.d.ts +1 -1
- package/x/logic/types.d.ts +1 -1
- package/x/tests.test.js +2 -0
- package/x/tests.test.js.map +1 -1
- package/s/cellar/forklift.ts +0 -9
- package/s/cellar/memory-forklift.ts +0 -29
- package/s/cellar/opfs-forklift.ts +0 -49
- package/x/cellar/forklift.d.ts +0 -7
- package/x/cellar/forklift.js +0 -2
- package/x/cellar/forklift.js.map +0 -1
- package/x/cellar/memory-forklift.d.ts +0 -9
- package/x/cellar/memory-forklift.js +0 -21
- package/x/cellar/memory-forklift.js.map +0 -1
- package/x/cellar/opfs-forklift.d.ts +0 -11
- package/x/cellar/opfs-forklift.js +0 -44
- package/x/cellar/opfs-forklift.js.map +0 -1
- package/x/genius/folio.md +0 -29
- package/x/genius/quay.md +0 -13
- package/x/genius/turbo.md +0 -21
- package/x/importmap.json +0 -47
package/README.md
CHANGED
|
@@ -115,3 +115,101 @@ new Quay.Brain({
|
|
|
115
115
|
|
|
116
116
|
In this way, you can setup sophisticated rules about what actions are permitted, and under whatever changing circumstances.
|
|
117
117
|
|
|
118
|
+
<br/>
|
|
119
|
+
|
|
120
|
+
## MediaLibrary
|
|
121
|
+
|
|
122
|
+
`MediaLibrary` is a small persistent media-bin preset.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import {MediaLibrary, brain, register, components} from "@e280/quay"
|
|
126
|
+
|
|
127
|
+
const media = await MediaLibrary.open("my-app")
|
|
128
|
+
brain.setGroup("media", media)
|
|
129
|
+
|
|
130
|
+
register(components)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<div group="media">
|
|
135
|
+
<quay-dropzone></quay-dropzone>
|
|
136
|
+
<quay-browser></quay-browser>
|
|
137
|
+
</div>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Open a library once, then derive nested scopes as needed.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const project = await media.scope("project-a")
|
|
144
|
+
const favorites = await project.scope("favorites")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Media already in the library can be included in another scope without uploading
|
|
148
|
+
it again.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
await project.include(hash)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Adding media to a scope also adds it to every parent scope. Removing media from
|
|
155
|
+
a scope removes it from that scope and its child scopes. Removing it from the
|
|
156
|
+
root removes it everywhere, including its stored file.
|
|
157
|
+
|
|
158
|
+
<br/>
|
|
159
|
+
|
|
160
|
+
## Styling
|
|
161
|
+
|
|
162
|
+
Customize quay through its css vars
|
|
163
|
+
|
|
164
|
+
```css
|
|
165
|
+
:root {
|
|
166
|
+
--quay-surface: #111;
|
|
167
|
+
--quay-border: #222;
|
|
168
|
+
--quay-text: #333;
|
|
169
|
+
--quay-muted: #777;
|
|
170
|
+
--quay-accent: #0ea5e9;
|
|
171
|
+
--quay-radius: 6px;
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
You can scope those variables to one area or one component.
|
|
176
|
+
|
|
177
|
+
```css
|
|
178
|
+
.media-panel {
|
|
179
|
+
--quay-surface: #18181b;
|
|
180
|
+
--quay-border: #27272a;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
quay-browser {
|
|
184
|
+
--quay-browser-thumb-width: 120px;
|
|
185
|
+
--quay-browser-thumb-height: 72px;
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Public tokens:
|
|
190
|
+
|
|
191
|
+
```css
|
|
192
|
+
--quay-surface
|
|
193
|
+
--quay-surface-hover
|
|
194
|
+
--quay-surface-selected
|
|
195
|
+
--quay-border
|
|
196
|
+
--quay-text
|
|
197
|
+
--quay-muted
|
|
198
|
+
--quay-accent
|
|
199
|
+
--quay-radius
|
|
200
|
+
--quay-browser-thumb-width
|
|
201
|
+
--quay-browser-thumb-height
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Quay is built on Shoelace, and its internal theme uses Shoelace tokens as fallbacks.
|
|
205
|
+
For deeper control, set Shoelace tokens in your app or scoped around Quay,
|
|
206
|
+
watch out for conflicts if you already use shoelace components in your app.
|
|
207
|
+
|
|
208
|
+
```css
|
|
209
|
+
.media-panel {
|
|
210
|
+
--sl-color-primary-600: #575757;
|
|
211
|
+
--sl-input-focus-ring-color: rgb(119 119 119 / 35%);
|
|
212
|
+
--sl-border-radius-medium: 3px;
|
|
213
|
+
--sl-spacing-x-small: 0.5em;
|
|
214
|
+
}
|
|
215
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e280/quay",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-20",
|
|
4
4
|
"description": "File-browser and outliner UI for the web",
|
|
5
5
|
"author": "Przemysław Gałęzki",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,28 +11,34 @@
|
|
|
11
11
|
"s"
|
|
12
12
|
],
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@
|
|
15
|
-
"@e280/kv": "^0.
|
|
16
|
-
"@e280/
|
|
14
|
+
"@e280/comrade": "^0.1.0",
|
|
15
|
+
"@e280/kv": "^0.2.0-3",
|
|
16
|
+
"@e280/mammoth": "^0.1.0-19",
|
|
17
|
+
"@e280/scute": "^0.4.0-next.4",
|
|
18
|
+
"@e280/sly": "^0.4.0-next.5",
|
|
19
|
+
"@e280/strata": "^0.4.0-next.8",
|
|
20
|
+
"@e280/stz": "^0.2.8",
|
|
21
|
+
"lit": "^3.3.1"
|
|
17
22
|
},
|
|
18
23
|
"devDependencies": {
|
|
19
|
-
"@
|
|
24
|
+
"@e280/hottie": "^0.1.1",
|
|
20
25
|
"@e280/science": "^0.0.5",
|
|
21
26
|
"@shoelace-style/shoelace": "^2.20.1",
|
|
22
|
-
"
|
|
27
|
+
"@types/wicg-file-system-access": "^2023.10.7",
|
|
23
28
|
"npm-run-all": "^4.1.5",
|
|
24
29
|
"typescript": "^5.8.3"
|
|
25
30
|
},
|
|
26
31
|
"scripts": {
|
|
27
32
|
"build": "rm -rf x && mkdir x && run-s _code _ssg _ln",
|
|
28
|
-
"start": "run-p _http
|
|
33
|
+
"start": "run-p _http _tscwatch _scutewatch",
|
|
29
34
|
"test": "node x/tests.test.js",
|
|
30
35
|
"test-watch": "node --watch x/tests.test.js",
|
|
31
36
|
"test-inspect": "node inspect x/tests.test.js",
|
|
32
|
-
"_code": "
|
|
33
|
-
"_ssg": "
|
|
34
|
-
"_http": "
|
|
35
|
-
"
|
|
37
|
+
"_code": "tsc",
|
|
38
|
+
"_ssg": "scute --in=s,x --out=x",
|
|
39
|
+
"_http": "hottie x",
|
|
40
|
+
"_tscwatch": "tsc -w",
|
|
41
|
+
"_scutewatch": "scute --in=s,x --out=x -vw",
|
|
36
42
|
"_ln": "run-s _ln:s _ln:assets",
|
|
37
43
|
"_ln:s": "ln -s \"$(realpath s)\" x/s",
|
|
38
44
|
"_ln:assets": "ln -s \"$(realpath assets)\" x/assets",
|
package/s/cellar/cask.ts
CHANGED
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import {Hex} from "@e280/stz"
|
|
3
|
-
|
|
4
2
|
/** File with a hash label */
|
|
5
3
|
export class Cask {
|
|
6
|
-
|
|
7
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", bytes)
|
|
8
|
-
return Hex.fromBytes(new Uint8Array(hashBuffer))
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
static async make(bytes: Uint8Array) {
|
|
12
|
-
const hash = await this.hash(bytes)
|
|
13
|
-
return new this(hash, bytes)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
constructor(public hash: string, public bytes: Uint8Array) {}
|
|
4
|
+
constructor(public hash: string, public file: Blob) {}
|
|
17
5
|
}
|
|
18
6
|
|
package/s/cellar/cellar.test.ts
CHANGED
|
@@ -2,57 +2,42 @@
|
|
|
2
2
|
import {Txt} from "@e280/stz"
|
|
3
3
|
import {expect, Science, test} from "@e280/science"
|
|
4
4
|
|
|
5
|
-
import {Cask} from "./cask.js"
|
|
6
5
|
import {Cellar} from "./cellar.js"
|
|
7
|
-
|
|
6
|
+
|
|
7
|
+
const blob = (text: string) => new Blob([Txt.toBytes(text)])
|
|
8
8
|
|
|
9
9
|
export default Science.suite({
|
|
10
|
-
"
|
|
10
|
+
"write + load roundtrip": test(async() => {
|
|
11
11
|
const cellar = new Cellar()
|
|
12
12
|
const text = "hello world"
|
|
13
|
-
const
|
|
14
|
-
await cellar.
|
|
13
|
+
const file = blob(text)
|
|
14
|
+
const digest = await cellar.write(file.stream())
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
expect(await cellar.has(hash)).is(true)
|
|
16
|
+
expect(await cellar.has(digest)).is(true)
|
|
18
17
|
|
|
19
|
-
const loaded = await cellar.load(
|
|
20
|
-
expect(loaded.hash).is(
|
|
21
|
-
expect(
|
|
18
|
+
const loaded = await cellar.load(digest)
|
|
19
|
+
expect(loaded.hash).is(digest)
|
|
20
|
+
expect(await loaded.file.text()).is(text)
|
|
22
21
|
}),
|
|
23
22
|
|
|
24
|
-
"
|
|
23
|
+
"write is idempotent": test(async() => {
|
|
25
24
|
const cellar = new Cellar()
|
|
26
|
-
const
|
|
27
|
-
await cellar.
|
|
28
|
-
await cellar.
|
|
29
|
-
|
|
30
|
-
const hash = await Cask.hash(bytes)
|
|
31
|
-
expect(await cellar.has(hash)).is(true)
|
|
32
|
-
}),
|
|
33
|
-
|
|
34
|
-
"throws on corruption": test(async() => {
|
|
35
|
-
const forklift = new MemoryForklift()
|
|
36
|
-
const cellar = new Cellar(forklift)
|
|
37
|
-
const good = new TextEncoder().encode("valid data")
|
|
38
|
-
const bad = new TextEncoder().encode("corrupt data")
|
|
39
|
-
|
|
40
|
-
const hash = await Cask.hash(good)
|
|
41
|
-
await forklift.save(hash, bad)
|
|
25
|
+
const file = blob("foo bar")
|
|
26
|
+
const digest = await cellar.write(file.stream())
|
|
27
|
+
expect(await cellar.write(file.stream())).is(digest)
|
|
42
28
|
|
|
43
|
-
|
|
29
|
+
expect(await cellar.has(digest)).is(true)
|
|
44
30
|
}),
|
|
45
31
|
|
|
46
32
|
"delete removes file": test(async() => {
|
|
47
33
|
const cellar = new Cellar()
|
|
48
|
-
const
|
|
49
|
-
await cellar.
|
|
34
|
+
const file = blob("temporary data")
|
|
35
|
+
const digest = await cellar.write(file.stream())
|
|
50
36
|
|
|
51
|
-
|
|
52
|
-
expect(await cellar.has(hash)).is(true)
|
|
37
|
+
expect(await cellar.has(digest)).is(true)
|
|
53
38
|
|
|
54
|
-
await cellar.delete(
|
|
55
|
-
expect(await cellar.has(
|
|
39
|
+
await cellar.delete(digest)
|
|
40
|
+
expect(await cellar.has(digest)).is(false)
|
|
56
41
|
}),
|
|
57
42
|
|
|
58
43
|
"load missing throws": test(async() => {
|
|
@@ -67,24 +52,22 @@ export default Science.suite({
|
|
|
67
52
|
const texts = ["a", "b", "c"]
|
|
68
53
|
|
|
69
54
|
for (const t of texts)
|
|
70
|
-
await cellar.
|
|
55
|
+
await cellar.write(blob(t).stream())
|
|
71
56
|
|
|
72
57
|
const hashes = []
|
|
73
58
|
for await (const hash of cellar.list())
|
|
74
59
|
hashes.push(hash)
|
|
75
60
|
|
|
76
61
|
expect(hashes.length).is(3)
|
|
77
|
-
for (const
|
|
78
|
-
|
|
79
|
-
expect(hashes.includes(h)).is(true)
|
|
80
|
-
}
|
|
62
|
+
for (const text of texts)
|
|
63
|
+
expect(hashes.includes(await cellar.write(blob(text).stream()))).is(true)
|
|
81
64
|
}),
|
|
82
65
|
|
|
83
66
|
"clear removes all entries": test(async() => {
|
|
84
67
|
const cellar = new Cellar()
|
|
85
|
-
await cellar.
|
|
86
|
-
await cellar.
|
|
87
|
-
await cellar.
|
|
68
|
+
await cellar.write(blob("one").stream())
|
|
69
|
+
await cellar.write(blob("two").stream())
|
|
70
|
+
await cellar.write(blob("three").stream())
|
|
88
71
|
|
|
89
72
|
let count = 0
|
|
90
73
|
for await (const _ of cellar.list()) count++
|
package/s/cellar/cellar.ts
CHANGED
|
@@ -1,49 +1,45 @@
|
|
|
1
1
|
|
|
2
|
+
import {Mammoth} from "@e280/mammoth"
|
|
3
|
+
|
|
2
4
|
import {Cask} from "./cask.js"
|
|
3
|
-
import {
|
|
4
|
-
import {OpfsForklift} from "./opfs-forklift.js"
|
|
5
|
-
import {MemoryForklift} from "./memory-forklift.js"
|
|
5
|
+
import {MammothOpfs} from "./mammoth.js"
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Cellar is an immutable content-addressable file store
|
|
9
9
|
* - files are identified by their hashes (duplicates are impossible)
|
|
10
10
|
* - files cannot be modified (delete it and save another)
|
|
11
11
|
* - no filenames or metadata (store those somewhere else)
|
|
12
|
-
* -
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* - new Cellar uses Mammoth's in-memory backend by default
|
|
13
|
+
* - Cellar.opfs creates MammothOpfs for persistent browser storage
|
|
14
|
+
* - pass in a Mammoth to use a different backend
|
|
15
|
+
* - most likely you want to use Cellar.opfs for storing files in the browser
|
|
15
16
|
*/
|
|
16
17
|
export class Cellar {
|
|
17
18
|
static async opfs(dirname = "cellar") {
|
|
18
|
-
return new this(await
|
|
19
|
+
return new this(await MammothOpfs.setup(dirname))
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
constructor(private
|
|
22
|
+
constructor(private mammoth: Mammoth | MammothOpfs = new Mammoth()) {}
|
|
22
23
|
|
|
23
24
|
async *list(): AsyncIterable<string> {
|
|
24
|
-
|
|
25
|
+
for await (const [hash] of this.mammoth.entries())
|
|
26
|
+
yield hash
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
async has(hash: string): Promise<boolean> {
|
|
28
|
-
return this.
|
|
30
|
+
return this.mammoth.has(hash)
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
async
|
|
32
|
-
|
|
33
|
-
if (!await this.forklift.has(cask.hash))
|
|
34
|
-
await this.forklift.save(cask.hash, cask.bytes)
|
|
35
|
-
return cask
|
|
33
|
+
async write(readable: ReadableStream<Uint8Array>) {
|
|
34
|
+
return this.mammoth.write(readable)
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
async load(hash: string): Promise<Cask> {
|
|
39
|
-
|
|
40
|
-
const cask = await Cask.make(bytes)
|
|
41
|
-
if (cask.hash !== hash) throw new Error(`corruption error, hash mismatch (requested ${hash}) (got ${cask.hash})`)
|
|
42
|
-
return cask
|
|
38
|
+
return new Cask(hash, await this.mammoth.read(hash))
|
|
43
39
|
}
|
|
44
40
|
|
|
45
41
|
async delete(hash: string) {
|
|
46
|
-
await this.
|
|
42
|
+
await this.mammoth.delete(hash)
|
|
47
43
|
}
|
|
48
44
|
|
|
49
45
|
async clear() {
|
package/s/cellar/index.ts
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
|
|
2
|
+
import {Comrade, Thread, tune, AsSchematic} from "@e280/comrade"
|
|
3
|
+
|
|
4
|
+
export type MammothSchematic = AsSchematic<{
|
|
5
|
+
work: {
|
|
6
|
+
setup(dirname: string): Promise<void>
|
|
7
|
+
write(readable: ReadableStream<Uint8Array>): Promise<string>
|
|
8
|
+
has(hash: string): Promise<boolean>
|
|
9
|
+
read(hash: string): Promise<Blob>
|
|
10
|
+
delete(hash: string): Promise<void>
|
|
11
|
+
hashes(): Promise<string[]>
|
|
12
|
+
}
|
|
13
|
+
host: {}
|
|
14
|
+
}>
|
|
15
|
+
|
|
16
|
+
/** Worker proxy for Mammoth using an OPFS bucket and IndexedDB manifest. */
|
|
17
|
+
export class MammothOpfs {
|
|
18
|
+
static async setup(dirname: string) {
|
|
19
|
+
const thread = await Comrade.thread<MammothSchematic>({
|
|
20
|
+
workerUrl: "/node_modules/@e280/quay/x/cellar/mammoth.worker.bundle.min.js",
|
|
21
|
+
setupHost: () => ({}),
|
|
22
|
+
})
|
|
23
|
+
const mammoth = new this(thread)
|
|
24
|
+
await mammoth.thread.work.setup(dirname)
|
|
25
|
+
return mammoth
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
constructor(private thread: Thread<MammothSchematic>) {}
|
|
29
|
+
|
|
30
|
+
async write(readable: ReadableStream<Uint8Array>) {
|
|
31
|
+
return this.thread.work.write[tune]({transfer: [readable]})(readable)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async has(hash: string) {
|
|
35
|
+
return this.thread.work.has(hash)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async read(hash: string) {
|
|
39
|
+
return this.thread.work.read(hash)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async delete(hash: string) {
|
|
43
|
+
return this.thread.work.delete(hash)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async *entries(): AsyncIterable<[string, unknown]> {
|
|
47
|
+
for (const hash of await this.thread.work.hashes())
|
|
48
|
+
yield [hash, undefined]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
import {collect} from "@e280/stz"
|
|
3
|
+
import {Comrade} from "@e280/comrade"
|
|
4
|
+
import {Mammoth, OpfsBucket} from "@e280/mammoth"
|
|
5
|
+
import {idbOpen, IdbMagazine, Kv} from "@e280/kv"
|
|
6
|
+
|
|
7
|
+
import type {MammothSchematic} from "./mammoth.js"
|
|
8
|
+
|
|
9
|
+
let mammoth: Mammoth
|
|
10
|
+
|
|
11
|
+
await Comrade.worker<MammothSchematic>(() => ({
|
|
12
|
+
async setup(dirname) {
|
|
13
|
+
const root = await navigator.storage.getDirectory()
|
|
14
|
+
const directory = await root.getDirectoryHandle(dirname, {create: true})
|
|
15
|
+
mammoth = new Mammoth(
|
|
16
|
+
new OpfsBucket(directory),
|
|
17
|
+
new Kv(new IdbMagazine(await idbOpen(`quay.mammoth:${dirname}`))),
|
|
18
|
+
)
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
write: readable => mammoth.write(readable),
|
|
22
|
+
has: hash => mammoth.has(hash),
|
|
23
|
+
read: hash => mammoth.read(hash),
|
|
24
|
+
delete: hash => mammoth.delete(hash),
|
|
25
|
+
|
|
26
|
+
async hashes() {
|
|
27
|
+
return (await collect(mammoth.entries())).map(([hash]) => hash)
|
|
28
|
+
},
|
|
29
|
+
}))
|
|
30
|
+
|
package/s/demo/main.bundle.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
2
|
+
import {dom} from "@e280/sly"
|
|
3
3
|
import {brain} from "../dom/context.js"
|
|
4
4
|
import {MediaGroup} from "../logic/presets/media/group.js"
|
|
5
5
|
import {MediaFormat} from "../logic/presets/media/schema.js"
|
|
@@ -35,5 +35,4 @@ group.on.upload.sub(({files, target}) => {
|
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
setShoelaceDarkTheme()
|
|
38
|
-
register(components)
|
|
39
|
-
|
|
38
|
+
dom.register(components)
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
import {html
|
|
2
|
+
import {html} from "lit"
|
|
3
|
+
import {shadowElement, useHost, useStyles} from "@e280/sly"
|
|
3
4
|
|
|
4
5
|
import themeCss from "../../theme.css.js"
|
|
5
6
|
|
|
6
7
|
import {findLocalGroup} from "../../utils/find-local-group.js"
|
|
7
8
|
import {CodexItem} from "../../../logic/aspects/codex/parts/codex-item.js"
|
|
8
9
|
|
|
9
|
-
export const QuayBreadcrumb =
|
|
10
|
-
|
|
10
|
+
export const QuayBreadcrumb = shadowElement(() => {
|
|
11
|
+
useStyles(themeCss)
|
|
11
12
|
|
|
12
|
-
const group = findLocalGroup(
|
|
13
|
+
const group = findLocalGroup(useHost())
|
|
13
14
|
const {trail, config: {codex, renderLabel}} = group
|
|
14
15
|
|
|
15
16
|
const click = (item: CodexItem) => (e: Event) => group.trail.setTrail(e, item)
|
|
16
17
|
|
|
17
18
|
return html`
|
|
18
19
|
<sl-breadcrumb>
|
|
19
|
-
${trail.signal.
|
|
20
|
+
${trail.signal().map(item => html`
|
|
20
21
|
<sl-breadcrumb-item @click="${click(item)}">
|
|
21
22
|
${item === null
|
|
22
23
|
? "root"
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
import {html} from "lit"
|
|
3
|
+
import {shadowElement, useHost, useMount, useRender, useState, useStyles} from "@e280/sly"
|
|
2
4
|
|
|
3
5
|
import styleCss from "./style.css.js"
|
|
4
6
|
import themeCss from "../../theme.css.js"
|
|
@@ -6,15 +8,17 @@ import themeCss from "../../theme.css.js"
|
|
|
6
8
|
import {findLocalGroup} from "../../utils/find-local-group.js"
|
|
7
9
|
import {CodexItem} from "../../../logic/aspects/codex/parts/codex-item.js"
|
|
8
10
|
|
|
9
|
-
export const QuayBrowser =
|
|
10
|
-
|
|
11
|
+
export const QuayBrowser = shadowElement(() => {
|
|
12
|
+
useStyles(themeCss, styleCss)
|
|
13
|
+
const host = useHost()
|
|
14
|
+
const render = useRender()
|
|
11
15
|
|
|
12
|
-
const group = findLocalGroup(
|
|
16
|
+
const group = findLocalGroup(host)
|
|
13
17
|
const {trail, config} = group
|
|
14
|
-
const [viewMode, setViewMode] =
|
|
18
|
+
const [viewMode, setViewMode] = useState<'details' | 'tiles'>('tiles')
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
const dispose = group.on.
|
|
20
|
+
useMount(() => {
|
|
21
|
+
const dispose = group.on.refresh.sub(() => render())
|
|
18
22
|
return () => dispose()
|
|
19
23
|
})
|
|
20
24
|
|
|
@@ -1,42 +1,43 @@
|
|
|
1
1
|
|
|
2
|
-
import {css} from "
|
|
2
|
+
import {css} from "lit"
|
|
3
3
|
export default css`
|
|
4
4
|
|
|
5
5
|
.toolbar {
|
|
6
6
|
display: flex;
|
|
7
7
|
justify-content: flex-end;
|
|
8
|
-
padding:
|
|
8
|
+
padding: var(--space-sm);
|
|
9
9
|
|
|
10
10
|
sl-button {
|
|
11
|
-
background:
|
|
12
|
-
border-radius:
|
|
11
|
+
background: var(--surface);
|
|
12
|
+
border-radius: var(--radius-small);
|
|
13
13
|
|
|
14
14
|
&[data-active=true] {
|
|
15
|
-
background: var(--
|
|
15
|
+
background: var(--surface-active);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
&::part(base) {
|
|
19
19
|
display: flex;
|
|
20
20
|
align-items: center;
|
|
21
|
-
height:
|
|
21
|
+
height: var(--control-height);
|
|
22
|
+
border-radius: var(--radius-small);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
&::part(label) {
|
|
25
26
|
display: flex;
|
|
26
|
-
width:
|
|
27
|
+
width: var(--control-width);
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
.tiles,
|
|
32
33
|
.details {
|
|
33
|
-
padding:
|
|
34
|
+
padding: var(--space-sm);
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
.tiles {
|
|
37
38
|
display: flex;
|
|
38
39
|
flex-wrap: wrap;
|
|
39
|
-
row-gap:
|
|
40
|
+
row-gap: var(--space-md);
|
|
40
41
|
width: 100%;
|
|
41
42
|
|
|
42
43
|
.item {
|
|
@@ -50,11 +51,11 @@ export default css`
|
|
|
50
51
|
display: flex;
|
|
51
52
|
align-items: center;
|
|
52
53
|
justify-content: center;
|
|
53
|
-
width: 100px;
|
|
54
|
-
height: 60px;
|
|
55
|
-
border:
|
|
56
|
-
border-radius:
|
|
57
|
-
background:
|
|
54
|
+
width: var(--quay-browser-thumb-width, 100px);
|
|
55
|
+
height: var(--quay-browser-thumb-height, 60px);
|
|
56
|
+
border: var(--border-width) solid var(--border);
|
|
57
|
+
border-radius: var(--radius-small);
|
|
58
|
+
background: var(--surface);
|
|
58
59
|
pointer-events: none;
|
|
59
60
|
|
|
60
61
|
img {
|
|
@@ -64,14 +65,14 @@ export default css`
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
.label {
|
|
67
|
-
margin-top:
|
|
68
|
+
margin-top: var(--space-xs);
|
|
68
69
|
max-width: 150px;
|
|
69
|
-
font-size:
|
|
70
|
+
font-size: var(--font-size-sm);
|
|
70
71
|
text-align: center;
|
|
71
72
|
white-space: nowrap;
|
|
72
73
|
overflow: hidden;
|
|
73
74
|
text-overflow: ellipsis;
|
|
74
|
-
color: var(--
|
|
75
|
+
color: var(--text-muted);
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
}
|
|
@@ -79,40 +80,39 @@ export default css`
|
|
|
79
80
|
.details {
|
|
80
81
|
.card {
|
|
81
82
|
display: flex;
|
|
82
|
-
gap:
|
|
83
|
-
padding:
|
|
84
|
-
border:
|
|
85
|
-
background:
|
|
86
|
-
border-radius:
|
|
87
|
-
margin-bottom:
|
|
83
|
+
gap: var(--space-md);
|
|
84
|
+
padding: var(--space-sm);
|
|
85
|
+
border: var(--border-width) solid var(--border);
|
|
86
|
+
background: var(--surface);
|
|
87
|
+
border-radius: var(--radius);
|
|
88
|
+
margin-bottom: var(--space-sm);
|
|
88
89
|
align-items: center;
|
|
89
90
|
|
|
90
91
|
.meta {
|
|
91
92
|
.name {
|
|
92
93
|
font-weight: 500;
|
|
93
|
-
color: var(--
|
|
94
|
+
color: var(--text);
|
|
94
95
|
}
|
|
95
96
|
.type {
|
|
96
|
-
font-size:
|
|
97
|
-
color: var(--
|
|
97
|
+
font-size: var(--font-size-xs);
|
|
98
|
+
color: var(--text-subtle);
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
sl-icon {
|
|
104
|
-
font-size:
|
|
105
|
-
color: var(--
|
|
105
|
+
font-size: var(--icon-size);
|
|
106
|
+
color: var(--icon);
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
sl-tooltip {
|
|
109
110
|
--sl-tooltip-arrow-size: 0;
|
|
110
111
|
|
|
111
112
|
&::part(body) {
|
|
112
|
-
background:
|
|
113
|
-
color: var(--
|
|
113
|
+
background: var(--border);
|
|
114
|
+
color: var(--text-muted);
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
`
|
|
118
|
-
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
|
|
2
|
-
import {html
|
|
2
|
+
import {html} from "lit"
|
|
3
|
+
import {shadowElement, useHost, useStyles} from "@e280/sly"
|
|
3
4
|
|
|
4
5
|
import styleCss from "./style.css.js"
|
|
5
6
|
import themeCss from "../../theme.css.js"
|
|
6
7
|
|
|
7
8
|
import {findLocalGroup} from "../../utils/find-local-group.js"
|
|
8
9
|
|
|
9
|
-
export const QuayDropzone =
|
|
10
|
-
|
|
10
|
+
export const QuayDropzone = shadowElement(() => {
|
|
11
|
+
useStyles(themeCss, styleCss)
|
|
12
|
+
const host = useHost()
|
|
11
13
|
|
|
12
|
-
const group = findLocalGroup(
|
|
14
|
+
const group = findLocalGroup(host)
|
|
13
15
|
const {dropzone, config: {root}} = group
|
|
14
16
|
|
|
15
17
|
return html`
|