@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.
Files changed (168) hide show
  1. package/README.md +98 -0
  2. package/package.json +17 -11
  3. package/s/cellar/cask.ts +1 -13
  4. package/s/cellar/cellar.test.ts +25 -42
  5. package/s/cellar/cellar.ts +16 -20
  6. package/s/cellar/index.ts +0 -4
  7. package/s/cellar/mammoth.ts +50 -0
  8. package/s/cellar/mammoth.worker.bundle.ts +30 -0
  9. package/s/demo/main.bundle.ts +2 -3
  10. package/s/dom/components/breadcrumb/component.ts +6 -5
  11. package/s/dom/components/browser/component.ts +11 -7
  12. package/s/dom/components/browser/style.css.ts +31 -31
  13. package/s/dom/components/dropzone/component.ts +6 -4
  14. package/s/dom/components/dropzone/style.css.ts +12 -13
  15. package/s/dom/components/filter/component.ts +13 -8
  16. package/s/dom/components/outliner/component.ts +8 -5
  17. package/s/dom/components/searchbar/component.ts +13 -7
  18. package/s/dom/components/sort/component.ts +13 -9
  19. package/s/dom/theme.css.ts +66 -2
  20. package/s/dom/views/tree/component.ts +10 -8
  21. package/s/dom/views/tree/style.css.ts +10 -10
  22. package/s/genius/parts/hy-tree.ts +10 -9
  23. package/s/index.html.ts +68 -73
  24. package/s/index.ts +6 -1
  25. package/s/logic/aspects/codex/codex.ts +3 -3
  26. package/s/logic/aspects/codex/example.ts +1 -4
  27. package/s/logic/aspects/codex/parts/codex-item.ts +4 -4
  28. package/s/logic/aspects/codex/parts/hierarchy.ts +1 -0
  29. package/s/logic/aspects/codex/parts/types.ts +1 -1
  30. package/s/logic/aspects/dropzone.ts +20 -16
  31. package/s/logic/aspects/tree-trail.ts +5 -4
  32. package/s/logic/brain.ts +5 -3
  33. package/s/logic/group.ts +13 -10
  34. package/s/logic/presets/media/group.ts +5 -4
  35. package/s/logic/presets/media/library.test.ts +82 -0
  36. package/s/logic/presets/media/library.ts +249 -0
  37. package/s/logic/presets/media/schema.ts +10 -1
  38. package/s/logic/presets/opfs/schema.ts +1 -1
  39. package/s/logic/presets/plain/group.ts +2 -2
  40. package/s/logic/presets/plain/schema.ts +1 -1
  41. package/s/logic/types.ts +1 -1
  42. package/s/tests.test.ts +2 -0
  43. package/x/cellar/cask.d.ts +2 -4
  44. package/x/cellar/cask.js +3 -12
  45. package/x/cellar/cask.js.map +1 -1
  46. package/x/cellar/cellar.d.ts +9 -7
  47. package/x/cellar/cellar.js +17 -22
  48. package/x/cellar/cellar.js.map +1 -1
  49. package/x/cellar/cellar.test.d.ts +2 -3
  50. package/x/cellar/cellar.test.js +24 -39
  51. package/x/cellar/cellar.test.js.map +1 -1
  52. package/x/cellar/index.d.ts +0 -3
  53. package/x/cellar/index.js +0 -3
  54. package/x/cellar/index.js.map +1 -1
  55. package/x/cellar/mammoth.d.ts +23 -0
  56. package/x/cellar/mammoth.js +34 -0
  57. package/x/cellar/mammoth.js.map +1 -0
  58. package/x/cellar/mammoth.worker.bundle.d.ts +1 -0
  59. package/x/cellar/mammoth.worker.bundle.js +20 -0
  60. package/x/cellar/mammoth.worker.bundle.js.map +1 -0
  61. package/x/cellar/mammoth.worker.bundle.min.js +8 -0
  62. package/x/cellar/mammoth.worker.bundle.min.js.map +7 -0
  63. package/x/demo/main.bundle.js +2 -2
  64. package/x/demo/main.bundle.js.map +1 -1
  65. package/x/demo/main.bundle.min.js +229 -197
  66. package/x/demo/main.bundle.min.js.LEGAL.txt +33 -0
  67. package/x/demo/main.bundle.min.js.map +7 -0
  68. package/x/dom/components/breadcrumb/component.d.ts +1 -1
  69. package/x/dom/components/breadcrumb/component.js +6 -5
  70. package/x/dom/components/breadcrumb/component.js.map +1 -1
  71. package/x/dom/components/browser/component.d.ts +1 -1
  72. package/x/dom/components/browser/component.js +10 -7
  73. package/x/dom/components/browser/component.js.map +1 -1
  74. package/x/dom/components/browser/style.css.d.ts +1 -1
  75. package/x/dom/components/browser/style.css.js +31 -30
  76. package/x/dom/components/browser/style.css.js.map +1 -1
  77. package/x/dom/components/dropzone/component.d.ts +1 -1
  78. package/x/dom/components/dropzone/component.js +6 -4
  79. package/x/dom/components/dropzone/component.js.map +1 -1
  80. package/x/dom/components/dropzone/style.css.d.ts +1 -1
  81. package/x/dom/components/dropzone/style.css.js +12 -12
  82. package/x/dom/components/dropzone/style.css.js.map +1 -1
  83. package/x/dom/components/filter/component.d.ts +1 -1
  84. package/x/dom/components/filter/component.js +10 -8
  85. package/x/dom/components/filter/component.js.map +1 -1
  86. package/x/dom/components/outliner/component.d.ts +1 -1
  87. package/x/dom/components/outliner/component.js +7 -5
  88. package/x/dom/components/outliner/component.js.map +1 -1
  89. package/x/dom/components/searchbar/component.d.ts +1 -1
  90. package/x/dom/components/searchbar/component.js +12 -6
  91. package/x/dom/components/searchbar/component.js.map +1 -1
  92. package/x/dom/components/sort/component.d.ts +1 -1
  93. package/x/dom/components/sort/component.js +11 -9
  94. package/x/dom/components/sort/component.js.map +1 -1
  95. package/x/dom/components.d.ts +7 -7
  96. package/x/dom/theme.css.d.ts +1 -1
  97. package/x/dom/theme.css.js +65 -1
  98. package/x/dom/theme.css.js.map +1 -1
  99. package/x/dom/views/tree/component.d.ts +1 -6
  100. package/x/dom/views/tree/component.js +10 -8
  101. package/x/dom/views/tree/component.js.map +1 -1
  102. package/x/dom/views/tree/style.css.d.ts +1 -1
  103. package/x/dom/views/tree/style.css.js +10 -10
  104. package/x/dom/views/tree/style.css.js.map +1 -1
  105. package/x/genius/parts/hy-tree.js +9 -8
  106. package/x/genius/parts/hy-tree.js.map +1 -1
  107. package/x/index.d.ts +6 -1
  108. package/x/index.html +70 -167
  109. package/x/index.html.d.ts +1 -2
  110. package/x/index.html.js +67 -73
  111. package/x/index.html.js.map +1 -1
  112. package/x/index.js +6 -1
  113. package/x/index.js.map +1 -1
  114. package/x/logic/aspects/codex/codex.js +3 -3
  115. package/x/logic/aspects/codex/codex.js.map +1 -1
  116. package/x/logic/aspects/codex/example.d.ts +0 -1
  117. package/x/logic/aspects/codex/example.js +1 -3
  118. package/x/logic/aspects/codex/example.js.map +1 -1
  119. package/x/logic/aspects/codex/parts/codex-item.d.ts +1 -1
  120. package/x/logic/aspects/codex/parts/codex-item.js +4 -4
  121. package/x/logic/aspects/codex/parts/codex-item.js.map +1 -1
  122. package/x/logic/aspects/codex/parts/hierarchy.js +1 -0
  123. package/x/logic/aspects/codex/parts/hierarchy.js.map +1 -1
  124. package/x/logic/aspects/codex/parts/types.d.ts +1 -1
  125. package/x/logic/aspects/dropzone.d.ts +1 -1
  126. package/x/logic/aspects/dropzone.js +19 -15
  127. package/x/logic/aspects/dropzone.js.map +1 -1
  128. package/x/logic/aspects/tree-trail.d.ts +1 -1
  129. package/x/logic/aspects/tree-trail.js +4 -4
  130. package/x/logic/aspects/tree-trail.js.map +1 -1
  131. package/x/logic/brain.d.ts +4 -2
  132. package/x/logic/brain.js.map +1 -1
  133. package/x/logic/group.d.ts +7 -7
  134. package/x/logic/group.js +10 -8
  135. package/x/logic/group.js.map +1 -1
  136. package/x/logic/presets/media/group.d.ts +1 -1
  137. package/x/logic/presets/media/group.js +3 -3
  138. package/x/logic/presets/media/group.js.map +1 -1
  139. package/x/logic/presets/media/library.d.ts +59 -0
  140. package/x/logic/presets/media/library.js +201 -0
  141. package/x/logic/presets/media/library.js.map +1 -0
  142. package/x/logic/presets/media/library.test.d.ts +9 -0
  143. package/x/logic/presets/media/library.test.js +62 -0
  144. package/x/logic/presets/media/library.test.js.map +1 -0
  145. package/x/logic/presets/media/schema.d.ts +10 -1
  146. package/x/logic/presets/opfs/schema.d.ts +1 -1
  147. package/x/logic/presets/plain/group.js +2 -2
  148. package/x/logic/presets/plain/group.js.map +1 -1
  149. package/x/logic/presets/plain/schema.d.ts +1 -1
  150. package/x/logic/types.d.ts +1 -1
  151. package/x/tests.test.js +2 -0
  152. package/x/tests.test.js.map +1 -1
  153. package/s/cellar/forklift.ts +0 -9
  154. package/s/cellar/memory-forklift.ts +0 -29
  155. package/s/cellar/opfs-forklift.ts +0 -49
  156. package/x/cellar/forklift.d.ts +0 -7
  157. package/x/cellar/forklift.js +0 -2
  158. package/x/cellar/forklift.js.map +0 -1
  159. package/x/cellar/memory-forklift.d.ts +0 -9
  160. package/x/cellar/memory-forklift.js +0 -21
  161. package/x/cellar/memory-forklift.js.map +0 -1
  162. package/x/cellar/opfs-forklift.d.ts +0 -11
  163. package/x/cellar/opfs-forklift.js +0 -44
  164. package/x/cellar/opfs-forklift.js.map +0 -1
  165. package/x/genius/folio.md +0 -29
  166. package/x/genius/quay.md +0 -13
  167. package/x/genius/turbo.md +0 -21
  168. 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-2",
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
- "@benev/slate": "^0.3.5",
15
- "@e280/kv": "^0.0.0-7",
16
- "@e280/stz": "^0.0.0-22"
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
- "@benev/turtle": "^0.6.13",
24
+ "@e280/hottie": "^0.1.1",
20
25
  "@e280/science": "^0.0.5",
21
26
  "@shoelace-style/shoelace": "^2.20.1",
22
- "http-server": "^14.1.1",
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 _turtlewatch",
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": "turtle build --out=x",
33
- "_ssg": "turtle ssg --in=s,x --out=x",
34
- "_http": "http-server x",
35
- "_turtlewatch": "turtle watch --in=s,x --out=x -v",
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
- static async hash(bytes: Uint8Array) {
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
 
@@ -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
- import {MemoryForklift} from "./memory-forklift.js"
6
+
7
+ const blob = (text: string) => new Blob([Txt.toBytes(text)])
8
8
 
9
9
  export default Science.suite({
10
- "save + load roundtrip": test(async() => {
10
+ "write + load roundtrip": test(async() => {
11
11
  const cellar = new Cellar()
12
12
  const text = "hello world"
13
- const bytes = Txt.toBytes(text)
14
- await cellar.save(bytes)
13
+ const file = blob(text)
14
+ const digest = await cellar.write(file.stream())
15
15
 
16
- const hash = await Cask.hash(bytes)
17
- expect(await cellar.has(hash)).is(true)
16
+ expect(await cellar.has(digest)).is(true)
18
17
 
19
- const loaded = await cellar.load(hash)
20
- expect(loaded.hash).is(hash)
21
- expect(Txt.fromBytes(loaded.bytes)).is(text)
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
- "save is idempotent": test(async() => {
23
+ "write is idempotent": test(async() => {
25
24
  const cellar = new Cellar()
26
- const bytes = new TextEncoder().encode("foo bar")
27
- await cellar.save(bytes)
28
- await cellar.save(bytes) // again
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
- await expect(async() => cellar.load(hash)).throwsAsync()
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 bytes = new TextEncoder().encode("temporary data")
49
- await cellar.save(bytes)
34
+ const file = blob("temporary data")
35
+ const digest = await cellar.write(file.stream())
50
36
 
51
- const hash = await Cask.hash(bytes)
52
- expect(await cellar.has(hash)).is(true)
37
+ expect(await cellar.has(digest)).is(true)
53
38
 
54
- await cellar.delete(hash)
55
- expect(await cellar.has(hash)).is(false)
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.save(Txt.toBytes(t))
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 t of texts) {
78
- const h = await Cask.hash(Txt.toBytes(t))
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.save(Txt.toBytes("one"))
86
- await cellar.save(Txt.toBytes("two"))
87
- await cellar.save(Txt.toBytes("three"))
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++
@@ -1,49 +1,45 @@
1
1
 
2
+ import {Mammoth} from "@e280/mammoth"
3
+
2
4
  import {Cask} from "./cask.js"
3
- import {Forklift} from "./forklift.js"
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
- * - pass in a forklift, which is the backend that actually stores the data
13
- * - MemoryForklift is the default
14
- * - OpfsForklift is probably what you really want in the browser
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 OpfsForklift.setup(dirname))
19
+ return new this(await MammothOpfs.setup(dirname))
19
20
  }
20
21
 
21
- constructor(private forklift: Forklift = new MemoryForklift()) {}
22
+ constructor(private mammoth: Mammoth | MammothOpfs = new Mammoth()) {}
22
23
 
23
24
  async *list(): AsyncIterable<string> {
24
- yield* this.forklift.list()
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.forklift.has(hash)
30
+ return this.mammoth.has(hash)
29
31
  }
30
32
 
31
- async save(bytes: Uint8Array) {
32
- const cask = await Cask.make(bytes)
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
- const bytes = await this.forklift.load(hash)
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.forklift.delete(hash)
42
+ await this.mammoth.delete(hash)
47
43
  }
48
44
 
49
45
  async clear() {
package/s/cellar/index.ts CHANGED
@@ -1,7 +1,3 @@
1
1
 
2
2
  export * from "./cask.js"
3
3
  export * from "./cellar.js"
4
- export * from "./forklift.js"
5
- export * from "./memory-forklift.js"
6
- export * from "./opfs-forklift.js"
7
-
@@ -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
+
@@ -1,5 +1,5 @@
1
1
 
2
- import {register} from "@benev/slate"
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, shadowComponent} from "@benev/slate"
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 = shadowComponent(use => {
10
- use.css(themeCss)
10
+ export const QuayBreadcrumb = shadowElement(() => {
11
+ useStyles(themeCss)
11
12
 
12
- const group = findLocalGroup(use.element)
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.value.map(item => html`
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
- import {html, shadowComponent} from "@benev/slate"
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 = shadowComponent(use => {
10
- use.styles(themeCss, styleCss)
11
+ export const QuayBrowser = shadowElement(() => {
12
+ useStyles(themeCss, styleCss)
13
+ const host = useHost()
14
+ const render = useRender()
11
15
 
12
- const group = findLocalGroup(use.element)
16
+ const group = findLocalGroup(host)
13
17
  const {trail, config} = group
14
- const [viewMode, setViewMode] = use.state<'details' | 'tiles'>('tiles')
18
+ const [viewMode, setViewMode] = useState<'details' | 'tiles'>('tiles')
15
19
 
16
- use.mount(() => {
17
- const dispose = group.on.upload.sub(() => use.rerender())
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 "@benev/slate"
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: 0.5rem;
8
+ padding: var(--space-sm);
9
9
 
10
10
  sl-button {
11
- background: #0F0F11;
12
- border-radius: 5px;
11
+ background: var(--surface);
12
+ border-radius: var(--radius-small);
13
13
 
14
14
  &[data-active=true] {
15
- background: var(--sl-color-neutral-0);
15
+ background: var(--surface-active);
16
16
  }
17
17
 
18
18
  &::part(base) {
19
19
  display: flex;
20
20
  align-items: center;
21
- height: 30px;
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: 40px;
27
+ width: var(--control-width);
27
28
  }
28
29
  }
29
30
  }
30
31
 
31
32
  .tiles,
32
33
  .details {
33
- padding: 0.5rem;
34
+ padding: var(--space-sm);
34
35
  }
35
36
 
36
37
  .tiles {
37
38
  display: flex;
38
39
  flex-wrap: wrap;
39
- row-gap: 1em;
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: 2px solid #0D0D0E;
56
- border-radius: 5px;
57
- background: #0F0F11;
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: 0.5em;
68
+ margin-top: var(--space-xs);
68
69
  max-width: 150px;
69
- font-size: 0.85rem;
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(--sl-color-neutral-700);
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: 1rem;
83
- padding: 0.8rem;
84
- border: 2px solid #0D0D0E;
85
- background: #0F0F11;
86
- border-radius: 6px;
87
- margin-bottom: 0.5rem;
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(--sl-color-neutral-800);
94
+ color: var(--text);
94
95
  }
95
96
  .type {
96
- font-size: 0.75rem;
97
- color: var(--sl-color-neutral-500);
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: 2rem;
105
- color: var(--sl-color-neutral-300);
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: #0D0D0E;
113
- color: var(--sl-color-neutral-700);
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, shadowComponent} from "@benev/slate"
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 = shadowComponent(use => {
10
- use.styles(themeCss, styleCss)
10
+ export const QuayDropzone = shadowElement(() => {
11
+ useStyles(themeCss, styleCss)
12
+ const host = useHost()
11
13
 
12
- const group = findLocalGroup(use.element)
14
+ const group = findLocalGroup(host)
13
15
  const {dropzone, config: {root}} = group
14
16
 
15
17
  return html`