@e280/quay 0.0.0-2 → 0.0.0-9
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/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/schema.ts +4 -1
- package/s/logic/presets/media/store.test.ts +41 -0
- package/s/logic/presets/media/store.ts +163 -0
- 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/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/schema.d.ts +4 -1
- package/x/logic/presets/media/store.d.ts +92 -0
- package/x/logic/presets/media/store.js +129 -0
- package/x/logic/presets/media/store.js.map +1 -0
- package/x/logic/presets/media/store.test.d.ts +7 -0
- package/x/logic/presets/media/store.test.js +31 -0
- package/x/logic/presets/media/store.test.js.map +1 -0
- 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,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
|
|
|
@@ -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
|
}>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
|
|
2
|
+
import {Txt} from "@e280/stz"
|
|
3
|
+
import {expect, Science, test} from "@e280/science"
|
|
4
|
+
|
|
5
|
+
import {MediaStore} from "./store.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 MediaStore()
|
|
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 MediaStore()
|
|
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 MediaStore()
|
|
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
|
+
})
|
|
@@ -0,0 +1,163 @@
|
|
|
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 MediaStore 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
|
+
}
|
|
163
|
+
|
|
@@ -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/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"}
|