@e280/quay 0.0.0-1 → 0.0.0-10
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 +28 -0
- package/package.json +13 -10
- package/s/cellar/cellar.ts +1 -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 +1 -1
- package/s/dom/components/dropzone/component.ts +6 -4
- package/s/dom/components/dropzone/style.css.ts +1 -1
- package/s/dom/components/filter/component.ts +11 -6
- package/s/dom/components/outliner/component.ts +8 -5
- package/s/dom/components/searchbar/component.ts +8 -5
- package/s/dom/components/sort/component.ts +10 -6
- package/s/dom/theme.css.ts +2 -1
- package/s/dom/views/tree/component.ts +10 -8
- package/s/dom/views/tree/style.css.ts +1 -1
- 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/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 +7 -7
- package/s/logic/presets/media/group.ts +5 -4
- package/s/logic/presets/media/library.test.ts +42 -0
- package/s/logic/presets/media/library.ts +162 -0
- package/s/logic/presets/media/schema.ts +4 -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/cellar.d.ts +1 -1
- package/x/cellar/cellar.js +1 -0
- package/x/cellar/cellar.js.map +1 -1
- package/x/demo/main.bundle.js +2 -2
- package/x/demo/main.bundle.js.map +1 -1
- package/x/demo/main.bundle.min.js +105 -142
- 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 +1 -1
- 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 +1 -1
- 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 +8 -6
- 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 +7 -5
- 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 +8 -6
- 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 +1 -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 +1 -1
- package/x/dom/views/tree/style.css.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/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 +4 -4
- package/x/logic/group.js +6 -6
- 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 +92 -0
- package/x/logic/presets/media/library.js +129 -0
- package/x/logic/presets/media/library.js.map +1 -0
- package/x/logic/presets/media/library.test.d.ts +7 -0
- package/x/logic/presets/media/library.test.js +31 -0
- package/x/logic/presets/media/library.test.js.map +1 -0
- package/x/logic/presets/media/schema.d.ts +4 -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/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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import {signal} from "@
|
|
2
|
+
import {signal} from "@e280/strata"
|
|
3
3
|
import {Codex} from "../codex.js"
|
|
4
4
|
import {Id, Kind, Schema} from "./types.js"
|
|
5
5
|
|
|
@@ -52,14 +52,14 @@ export class CodexItem<Sc extends Schema = any, K extends Kind<Sc> = Kind<Sc>> {
|
|
|
52
52
|
/** add items as children */
|
|
53
53
|
attach(...items: CodexItem<Sc>[]) {
|
|
54
54
|
this.codex.hierarchy.attach(this.id, ...items.map(i => i.id))
|
|
55
|
-
this.signal
|
|
55
|
+
this.signal(this)
|
|
56
56
|
return this
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/** detach this item from the hierarchy completely */
|
|
60
60
|
detach() {
|
|
61
61
|
this.codex.hierarchy.detach(this.id)
|
|
62
|
-
this.signal
|
|
62
|
+
this.signal(this)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/** create a new item that will be a child */
|
|
@@ -72,7 +72,7 @@ export class CodexItem<Sc extends Schema = any, K extends Kind<Sc> = Kind<Sc>> {
|
|
|
72
72
|
/** create a new item that will be a child */
|
|
73
73
|
destroy() {
|
|
74
74
|
this.codex.hierarchy.destroy(this.id)
|
|
75
|
-
this.signal
|
|
75
|
+
this.signal(this)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/** iterate over this item and all its descendants */
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {loot} from "@e280/sly/loot"
|
|
2
|
+
import {signal} from "@e280/strata"
|
|
2
3
|
|
|
3
4
|
import {Group} from "../group.js"
|
|
4
5
|
import {CodexItem} from "./codex/parts/codex-item.js"
|
|
5
6
|
|
|
6
7
|
export class Dropzone {
|
|
7
|
-
#drag_drop = new
|
|
8
|
-
|
|
8
|
+
#drag_drop = new loot.DragAndDrops<CodexItem, CodexItem>({
|
|
9
|
+
acceptDrop: (_e, grabbed, hovering) => this.group.move(grabbed, hovering)
|
|
9
10
|
})
|
|
10
11
|
|
|
11
12
|
constructor(private group: Group) {}
|
|
@@ -14,37 +15,40 @@ export class Dropzone {
|
|
|
14
15
|
#hovering = signal<CodexItem | undefined>(undefined)
|
|
15
16
|
|
|
16
17
|
get grabbed() {
|
|
17
|
-
return this.#drag_drop.
|
|
18
|
+
return this.#drag_drop.dragging
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
get hovering() {
|
|
21
|
-
return this.#drag_drop.hovering ?? this.#hovering
|
|
22
|
+
return this.#drag_drop.hovering ?? this.#hovering()
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
dragenter = (e: DragEvent, target
|
|
25
|
+
dragenter = (e: DragEvent, target: CodexItem) => {
|
|
25
26
|
e.preventDefault()
|
|
26
|
-
this.#drag_drop.dropzone.dragenter(
|
|
27
|
-
this.#hovering
|
|
27
|
+
this.#drag_drop.dropzone(() => target).dragenter(e)
|
|
28
|
+
this.#hovering(target)
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
dragleave = (e: DragEvent) => {
|
|
31
|
-
this
|
|
32
|
-
|
|
32
|
+
const hovering = this.hovering
|
|
33
|
+
if (hovering)
|
|
34
|
+
this.#drag_drop.dropzone(() => hovering).dragleave(e)
|
|
35
|
+
this.#hovering(undefined)
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
dragstart = (e: DragEvent, n: CodexItem) => {
|
|
36
39
|
e.stopPropagation()
|
|
37
|
-
this.#drag_drop.dragzone
|
|
40
|
+
this.#drag_drop.dragzone(() => n).dragstart(e)
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
dragover = (e: DragEvent, n: CodexItem) => {
|
|
41
44
|
e.preventDefault()
|
|
42
|
-
this.#drag_drop.dropzone
|
|
45
|
+
this.#drag_drop.dropzone(() => n).dragover(e)
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
dragend = (e: DragEvent) => {
|
|
46
|
-
this.#drag_drop
|
|
47
|
-
this.#
|
|
49
|
+
this.#drag_drop.$draggy(undefined)
|
|
50
|
+
this.#drag_drop.$droppy(undefined)
|
|
51
|
+
this.#hovering(undefined)
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
drop = (e: DragEvent, target: CodexItem) => {
|
|
@@ -60,9 +64,9 @@ export class Dropzone {
|
|
|
60
64
|
const parent = this.grabbed?.parent?.id === this.hovering?.id
|
|
61
65
|
|
|
62
66
|
if(!same && !child && !parent)
|
|
63
|
-
this.#drag_drop.dropzone
|
|
67
|
+
this.#drag_drop.dropzone(() => target).drop(e)
|
|
64
68
|
|
|
65
|
-
this.#hovering
|
|
69
|
+
this.#hovering(undefined)
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
change = (e: DragEvent, targetFolder: CodexItem) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
import {signal} from "@e280/strata"
|
|
2
3
|
import {Schema} from "./codex/parts/types.js"
|
|
3
4
|
import {CodexItem} from "./codex/parts/codex-item.js"
|
|
4
5
|
|
|
@@ -6,7 +7,7 @@ export class TreeTrail<Sc extends Schema> {
|
|
|
6
7
|
readonly signal = signal<CodexItem<Sc>[]>([])
|
|
7
8
|
|
|
8
9
|
constructor(root: CodexItem<Sc>) {
|
|
9
|
-
this.signal
|
|
10
|
+
this.signal([root])
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
setTrail(e: Event, item: CodexItem<Sc>) {
|
|
@@ -23,11 +24,11 @@ export class TreeTrail<Sc extends Schema> {
|
|
|
23
24
|
cur = cur.parent
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
this.signal
|
|
27
|
+
this.signal(trail)
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
get currentFolder() {
|
|
30
|
-
return this.signal.
|
|
31
|
+
return this.signal().at(-1)?.children ?? []
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
package/s/logic/brain.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import {MapG} from "@e280/stz"
|
|
3
3
|
import {Group} from "./group.js"
|
|
4
4
|
|
|
5
|
+
type RegisteredGroup<G> = G extends Group<infer _Schema> ? G : never
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Quay's global state
|
|
7
9
|
* - manages groups
|
|
@@ -10,13 +12,13 @@ import {Group} from "./group.js"
|
|
|
10
12
|
export class Brain {
|
|
11
13
|
#groups = new MapG<string, Group>()
|
|
12
14
|
|
|
13
|
-
setGroup<G
|
|
15
|
+
setGroup<G>(name: string, group: RegisteredGroup<G>) {
|
|
14
16
|
this.#groups.set(name, group)
|
|
15
17
|
return group
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
getGroup<G
|
|
19
|
-
return this.#groups.require(name) as G
|
|
20
|
+
getGroup<G = Group>(name: string) {
|
|
21
|
+
return this.#groups.require(name) as RegisteredGroup<G>
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
|
package/s/logic/group.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {sub} from "@e280/stz"
|
|
2
|
-
import {signal} from "@
|
|
2
|
+
import {signal} from "@e280/strata"
|
|
3
3
|
|
|
4
4
|
import {Permission} from "./permissions.js"
|
|
5
5
|
import {Dropzone} from "./aspects/dropzone.js"
|
|
@@ -26,13 +26,13 @@ export class Group<Sc extends Schema = any> {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
constructor(public config: GroupConfig<Sc>) {
|
|
29
|
-
this.selectedFilter
|
|
30
|
-
this.selectedSort
|
|
29
|
+
this.selectedFilter(config.defaultFilter)
|
|
30
|
+
this.selectedSort(config.defaultSort)
|
|
31
31
|
this.trail = new TreeTrail(config.root)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
get permissions() {
|
|
35
|
-
return this.config.permissions as (item: CodexItem) => Permission
|
|
35
|
+
return this.config.permissions as (item: CodexItem<Sc>) => Permission
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
getFilterFn(filterKey: string): SearchFn<Sc> {
|
|
@@ -49,13 +49,13 @@ export class Group<Sc extends Schema = any> {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
sort(items: CodexItem<Sc>[]): CodexItem<Sc>[] {
|
|
52
|
-
const sortFn = this.getSortFn(this.selectedSort
|
|
52
|
+
const sortFn = this.getSortFn(this.selectedSort())
|
|
53
53
|
return [...items].sort(sortFn)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
matches(item: CodexItem<Sc>) {
|
|
57
|
-
const filterFn = this.getFilterFn(this.selectedFilter
|
|
58
|
-
const searchFn = this.getSearchFn(this.searchText
|
|
57
|
+
const filterFn = this.getFilterFn(this.selectedFilter())
|
|
58
|
+
const searchFn = this.getSearchFn(this.searchText())
|
|
59
59
|
return filterFn(item) && searchFn(item)
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
|
|
2
|
+
import {html} from "lit"
|
|
2
3
|
import {MapG} from "@e280/stz"
|
|
3
|
-
import {Content
|
|
4
|
+
import {Content} from "@e280/sly"
|
|
4
5
|
|
|
5
|
-
import Quay from "../../../index.js"
|
|
6
6
|
import {Group} from "../../group.js"
|
|
7
|
+
import {Permissions} from "../../permissions.js"
|
|
7
8
|
import {Codex} from "../../aspects/codex/codex.js"
|
|
8
9
|
import {MediaFormat, MediaSchema} from "./schema.js"
|
|
9
10
|
import {GroupConfig, SearchFn, SortFn} from "../../types.js"
|
|
10
11
|
import {CodexItem} from "../../aspects/codex/parts/codex-item.js"
|
|
11
12
|
|
|
12
|
-
export class MediaGroup extends Group<MediaSchema>
|
|
13
|
+
export class MediaGroup extends Group<MediaSchema> {
|
|
13
14
|
static config = (): GroupConfig<MediaSchema> => {
|
|
14
15
|
const formatIcons = new MapG<MediaFormat, Content>()
|
|
15
16
|
.set("audio", html`<sl-icon name=music-note-beamed></sl-icon>`)
|
|
@@ -83,7 +84,7 @@ export class MediaGroup extends Group<MediaSchema> implements Group {
|
|
|
83
84
|
// // TODO icons
|
|
84
85
|
// : html`<sl-icon name=${item.taxon.icon}></sl-icon>`
|
|
85
86
|
},
|
|
86
|
-
permissions: item =>
|
|
87
|
+
permissions: item => Permissions.all,
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
|
|
2
|
+
import {Txt} from "@e280/stz"
|
|
3
|
+
import {expect, Science, test} from "@e280/science"
|
|
4
|
+
|
|
5
|
+
import {MediaLibrary} from "./library.js"
|
|
6
|
+
|
|
7
|
+
delete (globalThis as any).localStorage
|
|
8
|
+
|
|
9
|
+
export default Science.suite({
|
|
10
|
+
"imports files into cellar and index": test(async() => {
|
|
11
|
+
const group = new MediaLibrary()
|
|
12
|
+
const file = new File([Txt.toBytes("hello")], "hello.txt", {type: "text/plain"})
|
|
13
|
+
|
|
14
|
+
const [record] = await group.importFiles([file])
|
|
15
|
+
|
|
16
|
+
expect(await group.cellar.has(record.hash)).is(true)
|
|
17
|
+
expect(group.findByHash(record.hash)?.specimen.label).is("hello.txt")
|
|
18
|
+
}),
|
|
19
|
+
|
|
20
|
+
"lists media records": test(async() => {
|
|
21
|
+
const store = new MediaLibrary()
|
|
22
|
+
const file = new File([Txt.toBytes("image")], "image.png", {type: "image/png"})
|
|
23
|
+
const record = await store.importFile(file)
|
|
24
|
+
const records = []
|
|
25
|
+
for await (const record of store.records())
|
|
26
|
+
records.push(record)
|
|
27
|
+
|
|
28
|
+
expect(records.some(r => r.hash === record.hash)).is(true)
|
|
29
|
+
}),
|
|
30
|
+
|
|
31
|
+
"removes media records and bytes": test(async() => {
|
|
32
|
+
const store = new MediaLibrary()
|
|
33
|
+
const file = new File([Txt.toBytes("image")], "image.png", {type: "image/png"})
|
|
34
|
+
const record = await store.importFile(file)
|
|
35
|
+
|
|
36
|
+
await store.remove(record.hash)
|
|
37
|
+
|
|
38
|
+
expect(await store.cellar.has(record.hash)).is(false)
|
|
39
|
+
expect(store.findByHash(record.hash)).is(undefined)
|
|
40
|
+
}),
|
|
41
|
+
})
|
|
42
|
+
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
|
|
2
|
+
import {Kv, StorageDriver} from "@e280/kv"
|
|
3
|
+
|
|
4
|
+
import {Cellar} from "../../../cellar/cellar.js"
|
|
5
|
+
import {MediaFormat} from "./schema.js"
|
|
6
|
+
import {MediaGroup} from "./group.js"
|
|
7
|
+
|
|
8
|
+
export type MediaRecord = {
|
|
9
|
+
hash: string
|
|
10
|
+
label: string
|
|
11
|
+
format: MediaFormat
|
|
12
|
+
mime: string
|
|
13
|
+
size: number
|
|
14
|
+
createdAt: number
|
|
15
|
+
updatedAt: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class MediaLibrary extends MediaGroup {
|
|
19
|
+
static async open(scope = "default") {
|
|
20
|
+
const group = new this()
|
|
21
|
+
group.cellar = await Cellar.opfs("media")
|
|
22
|
+
group.#index = mediaIndex(scope)
|
|
23
|
+
await group.#load()
|
|
24
|
+
return group
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#objectUrls = new Map<string, string>()
|
|
28
|
+
#index: Kv<MediaRecord>
|
|
29
|
+
|
|
30
|
+
cellar = new Cellar()
|
|
31
|
+
|
|
32
|
+
constructor() {
|
|
33
|
+
super()
|
|
34
|
+
this.#index = mediaIndex("default")
|
|
35
|
+
this.on.upload.sub(({files, target}) => {
|
|
36
|
+
void this.importFiles(files, target)
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async *records() {
|
|
41
|
+
for await (const [, record] of this.#index.entries())
|
|
42
|
+
yield record
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async #load() {
|
|
46
|
+
for await (const record of this.records()) {
|
|
47
|
+
if (record.format === "image" && !this.#objectUrls.has(record.hash))
|
|
48
|
+
await this.#loadPreview(record)
|
|
49
|
+
this.#attachRecord(record, this.config.root)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async importFiles(files: File[], parent = this.config.root) {
|
|
54
|
+
return Promise.all(files.map(f => this.importFile(f, parent)))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async importFile(file: File, parent = this.config.root) {
|
|
58
|
+
const bytes = new Uint8Array(await file.arrayBuffer())
|
|
59
|
+
const cask = await this.cellar.save(bytes)
|
|
60
|
+
const existing = await this.#index.get(cask.hash)
|
|
61
|
+
const now = Date.now()
|
|
62
|
+
const record: MediaRecord = {
|
|
63
|
+
hash: cask.hash,
|
|
64
|
+
label: existing?.label ?? file.name,
|
|
65
|
+
format: mediaFormat(file.type),
|
|
66
|
+
mime: file.type,
|
|
67
|
+
size: file.size,
|
|
68
|
+
createdAt: existing?.createdAt ?? now,
|
|
69
|
+
updatedAt: now,
|
|
70
|
+
}
|
|
71
|
+
await this.#index.set(record.hash, record)
|
|
72
|
+
if (record.format === "image")
|
|
73
|
+
this.#setPreview(record.hash, URL.createObjectURL(file))
|
|
74
|
+
this.#attachRecord(record, parent)
|
|
75
|
+
return record
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async remove(hash: string) {
|
|
79
|
+
await this.#index.del(hash)
|
|
80
|
+
await this.cellar.delete(hash)
|
|
81
|
+
const item = this.findByHash(hash)
|
|
82
|
+
if (item) {
|
|
83
|
+
item.detach()
|
|
84
|
+
item.destroy()
|
|
85
|
+
}
|
|
86
|
+
this.#revokePreview(hash)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
findByHash(hash: string) {
|
|
90
|
+
for (const [item] of this.config.root.crawl()) {
|
|
91
|
+
if (item.isKind("file") && item.specimen.hash === hash)
|
|
92
|
+
return item
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
dispose() {
|
|
97
|
+
for (const url of this.#objectUrls.values())
|
|
98
|
+
URL.revokeObjectURL(url)
|
|
99
|
+
this.#objectUrls.clear()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#attachRecord(record: MediaRecord, parent = this.config.root) {
|
|
103
|
+
const existing = this.findByHash(record.hash)
|
|
104
|
+
if (existing)
|
|
105
|
+
return existing
|
|
106
|
+
|
|
107
|
+
const item = this.config.codex.create("file", {
|
|
108
|
+
hash: record.hash,
|
|
109
|
+
label: record.label,
|
|
110
|
+
format: record.format,
|
|
111
|
+
mime: record.mime,
|
|
112
|
+
size: record.size,
|
|
113
|
+
previewUrl: this.#objectUrls.get(record.hash) ?? null,
|
|
114
|
+
})
|
|
115
|
+
parent.attach(item)
|
|
116
|
+
return item
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async #loadPreview(record: MediaRecord) {
|
|
120
|
+
const cask = await this.cellar.load(record.hash)
|
|
121
|
+
const blob = new Blob([cask.bytes], {type: record.mime || "image/*"})
|
|
122
|
+
return this.#setPreview(record.hash, URL.createObjectURL(blob))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#setPreview(hash: string, url: string) {
|
|
126
|
+
this.#revokePreview(hash)
|
|
127
|
+
this.#objectUrls.set(hash, url)
|
|
128
|
+
return url
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#revokePreview(hash: string) {
|
|
132
|
+
const url = this.#objectUrls.get(hash)
|
|
133
|
+
if (url) {
|
|
134
|
+
URL.revokeObjectURL(url)
|
|
135
|
+
this.#objectUrls.delete(hash)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const mediaFormats = new Set(["video", "image", "audio"])
|
|
141
|
+
const memoryIndexes = new Map<string, Kv<MediaRecord>>()
|
|
142
|
+
|
|
143
|
+
function mediaFormat(mime: string): MediaFormat {
|
|
144
|
+
const [type] = mime.split("/")
|
|
145
|
+
return mediaFormats.has(type) ? type as MediaFormat : "other"
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function mediaIndex(scope: string) {
|
|
149
|
+
const storage = globalThis.localStorage
|
|
150
|
+
if (storage)
|
|
151
|
+
return new Kv<MediaRecord>(new StorageDriver(storage))
|
|
152
|
+
.namespace("quay.media")
|
|
153
|
+
.namespace(scope)
|
|
154
|
+
|
|
155
|
+
const existing = memoryIndexes.get(scope)
|
|
156
|
+
if (existing)
|
|
157
|
+
return existing
|
|
158
|
+
|
|
159
|
+
const index = new Kv<MediaRecord>()
|
|
160
|
+
memoryIndexes.set(scope, index)
|
|
161
|
+
return index
|
|
162
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import {Content} from "@
|
|
2
|
+
import {Content} from "@e280/sly"
|
|
3
3
|
import {AsSchema} from "../../aspects/codex/parts/types.js"
|
|
4
4
|
|
|
5
5
|
export type MediaFormat = "video" | "image" | "audio" | "other"
|
|
@@ -16,6 +16,9 @@ export type MediaSchema = AsSchema<{
|
|
|
16
16
|
label: string
|
|
17
17
|
format: MediaFormat
|
|
18
18
|
previewUrl: string | null
|
|
19
|
+
hash?: string
|
|
20
|
+
mime?: string
|
|
21
|
+
size?: number
|
|
19
22
|
}
|
|
20
23
|
}
|
|
21
24
|
}>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
|
-
import Quay from "../../../index.js"
|
|
3
2
|
import {Group} from "../../group.js"
|
|
4
3
|
import {PlainSchema} from "./schema.js"
|
|
4
|
+
import {Permissions} from "../../permissions.js"
|
|
5
5
|
import {GroupConfig, SortFn} from "../../types.js"
|
|
6
6
|
import {Codex} from "../../aspects/codex/codex.js"
|
|
7
7
|
|
|
@@ -33,7 +33,7 @@ export class PlainGroup extends Group<PlainSchema> {
|
|
|
33
33
|
renderLabel: item => item.id,
|
|
34
34
|
renderIcon: item => item.taxon.icon,
|
|
35
35
|
renderPreview: () => null,
|
|
36
|
-
permissions: item =>
|
|
36
|
+
permissions: item => Permissions.all,
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
package/s/logic/types.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
+
import {Content} from "@e280/sly"
|
|
2
3
|
import {Permission} from "./permissions.js"
|
|
3
4
|
import {Codex} from "./aspects/codex/codex.js"
|
|
4
5
|
import {Schema} from "./aspects/codex/parts/types.js"
|
|
5
6
|
import {CodexItem} from "./aspects/codex/parts/codex-item.js"
|
|
6
|
-
import { Content } from "@benev/slate"
|
|
7
7
|
|
|
8
8
|
export type SearchFn<Sc extends Schema> = (item: CodexItem<Sc>) => boolean
|
|
9
9
|
export type SortFn<Sc extends Schema> = (a: CodexItem<Sc>, b: CodexItem<Sc>) => number
|
package/s/tests.test.ts
CHANGED
package/x/cellar/cellar.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare class Cellar {
|
|
|
15
15
|
constructor(forklift?: Forklift);
|
|
16
16
|
list(): AsyncIterable<string>;
|
|
17
17
|
has(hash: string): Promise<boolean>;
|
|
18
|
-
save(bytes: Uint8Array): Promise<
|
|
18
|
+
save(bytes: Uint8Array): Promise<Cask>;
|
|
19
19
|
load(hash: string): Promise<Cask>;
|
|
20
20
|
delete(hash: string): Promise<void>;
|
|
21
21
|
clear(): Promise<void>;
|
package/x/cellar/cellar.js
CHANGED
package/x/cellar/cellar.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cellar.js","sourceRoot":"","sources":["../../s/cellar/cellar.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAA;AAE9B,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAA;AAC/C,OAAO,EAAC,cAAc,EAAC,MAAM,sBAAsB,CAAA;AAEnD;;;;;;;;GAQG;AACH,MAAM,OAAO,MAAM;IAKE;IAJpB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,QAAQ;QACnC,OAAO,IAAI,IAAI,CAAC,MAAM,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;IACnD,CAAC;IAED,YAAoB,WAAqB,IAAI,cAAc,EAAE;QAAzC,aAAQ,GAAR,QAAQ,CAAiC;IAAG,CAAC;IAEjE,KAAK,CAAC,CAAC,IAAI;QACV,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;IAC5B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAiB;QAC3B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACnC,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YACtC,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"cellar.js","sourceRoot":"","sources":["../../s/cellar/cellar.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAA;AAE9B,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAA;AAC/C,OAAO,EAAC,cAAc,EAAC,MAAM,sBAAsB,CAAA;AAEnD;;;;;;;;GAQG;AACH,MAAM,OAAO,MAAM;IAKE;IAJpB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,QAAQ;QACnC,OAAO,IAAI,IAAI,CAAC,MAAM,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;IACnD,CAAC;IAED,YAAoB,WAAqB,IAAI,cAAc,EAAE;QAAzC,aAAQ,GAAR,QAAQ,CAAiC;IAAG,CAAC;IAEjE,KAAK,CAAC,CAAC,IAAI;QACV,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;IAC5B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAiB;QAC3B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACnC,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YACtC,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;QAChD,OAAO,IAAI,CAAA;IACZ,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY;QACtB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC5C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACnC,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,IAAI,UAAU,IAAI,CAAC,IAAI,GAAG,CAAC,CAAA;QACjH,OAAO,IAAI,CAAA;IACZ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAY;QACxB,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACjC,CAAC;IAED,KAAK,CAAC,KAAK;QACV,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE;YACnC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;CACD"}
|
package/x/demo/main.bundle.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dom } from "@e280/sly";
|
|
2
2
|
import { brain } from "../dom/context.js";
|
|
3
3
|
import { MediaGroup } from "../logic/presets/media/group.js";
|
|
4
4
|
import { components, setShoelaceDarkTheme } from "../dom/components.js";
|
|
@@ -26,5 +26,5 @@ group.on.upload.sub(({ files, target }) => {
|
|
|
26
26
|
}
|
|
27
27
|
});
|
|
28
28
|
setShoelaceDarkTheme();
|
|
29
|
-
register(components);
|
|
29
|
+
dom.register(components);
|
|
30
30
|
//# sourceMappingURL=main.bundle.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.bundle.js","sourceRoot":"","sources":["../../s/demo/main.bundle.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,
|
|
1
|
+
{"version":3,"file":"main.bundle.js","sourceRoot":"","sources":["../../s/demo/main.bundle.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,GAAG,EAAC,MAAM,WAAW,CAAA;AAC7B,OAAO,EAAC,KAAK,EAAC,MAAM,mBAAmB,CAAA;AACvC,OAAO,EAAC,UAAU,EAAC,MAAM,iCAAiC,CAAA;AAE1D,OAAO,EAAC,UAAU,EAAE,oBAAoB,EAAC,MAAM,sBAAsB,CAAA;AAErE,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,UAAU,EAAE,CAAC,CAAA;AACzD,MAAM,EAAC,KAAK,EAAE,IAAI,EAAC,GAAG,KAAK,CAAC,MAAM,CAAA;AAElC,MAAM,UAAU,GAAG,sBAAsB,CAAA;AACzC,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAC,CAAC,CAAA;AAClG,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAC,CAAC,CAAA;AACxF,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAC,CAAC,CAAA;AAE7F,aAAa;AACb,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAC,KAAK,EAAE,SAAS,EAAC,CAAC,CAAA;AAC1D,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAC,CAAC,CAAA;AACtF,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAC,CAAC,CAAA;AAExF,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;KACd,MAAM,CAAC,QAAQ,CAAC;KAChB,MAAM,CAAC,QAAQ,CAAC;KAChB,MAAM,CAAC,OAAO,CAAC;KACf,MAAM,CAAC,OAAO,CAAC,CAAA;AAEjB,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;AAExC,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAC,KAAK,EAAE,MAAM,EAAC,EAAE,EAAE;IACvC,KAAI,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAgB,CAAA;QACrD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAC,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAC,CAAC,CAAA;QACzE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACpB,CAAC;AACF,CAAC,CAAC,CAAA;AAEF,oBAAoB,EAAE,CAAA;AACtB,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA"}
|