@hashsome/ui 0.10.0 → 0.12.0
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/dist/.tsbuildinfo +1 -1
- package/dist/entities/media-browser.js +35 -5
- package/dist/entities/media-pending.d.ts +3 -1
- package/dist/entities/media-pending.js +24 -5
- package/dist/entities/media-player-bar.d.ts +5 -1
- package/dist/entities/media-player-bar.js +6 -3
- package/dist/entities/media-player-column.d.ts +5 -1
- package/dist/entities/media-player-column.js +12 -3
- package/dist/entities/media-player-full.d.ts +7 -1
- package/dist/entities/media-player-full.js +14 -4
- package/dist/entities/media-queue.js +1 -0
- package/dist/entities/media-speakers-pill.d.ts +44 -0
- package/dist/entities/media-speakers-pill.js +73 -0
- package/dist/entities/media-speakers.d.ts +20 -0
- package/dist/entities/media-speakers.js +133 -0
- package/dist/entities/now-playing.d.ts +3 -1
- package/dist/entities/now-playing.js +2 -2
- package/dist/gallery/fixtures.js +50 -0
- package/dist/gallery/gallery.js +6 -1
- package/dist/gallery/props-data.js +65 -0
- package/dist/hooks.d.ts +3 -0
- package/dist/hooks.js +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/layout/debug-menu.js +3 -1
- package/dist/use-media-browser.d.ts +9 -1
- package/dist/use-media-browser.js +89 -11
- package/package.json +2 -2
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { useReconnects } from './hooks.js';
|
|
3
3
|
import { useClient } from './provider.js';
|
|
4
|
+
/** The order the kinds of a search result are offered in: what is played most, first. */
|
|
5
|
+
const KIND_ORDER = [
|
|
6
|
+
'track',
|
|
7
|
+
'playlist',
|
|
8
|
+
'album',
|
|
9
|
+
'artist',
|
|
10
|
+
'radio',
|
|
11
|
+
'folder',
|
|
12
|
+
'other',
|
|
13
|
+
];
|
|
14
|
+
/** The kinds among some items, in the order they are offered. */
|
|
15
|
+
const kindsOf = (items) => KIND_ORDER.filter((candidate) => items.some((item) => item.kind === candidate));
|
|
4
16
|
/** A top level that is nothing but folders to look inside is a set of shelves, which read better
|
|
5
17
|
* as tabs than as a list that always looks the same. */
|
|
6
18
|
const isShelves = (items) => items.length >= 2 && items.every((item) => item.expandable && !item.playable);
|
|
@@ -16,6 +28,12 @@ export function useMediaBrowser(ref) {
|
|
|
16
28
|
const [trail, setTrail] = useState([{}]);
|
|
17
29
|
const [text, setText] = useState('');
|
|
18
30
|
const [tab, setTab] = useState();
|
|
31
|
+
// The kind of result chosen to narrow a search to; only counts while that kind is among the results.
|
|
32
|
+
const [kindChoice, setKindChoice] = useState();
|
|
33
|
+
// The search with no kind chosen: it says which kinds there are, and is the list while none is chosen.
|
|
34
|
+
const [overview, setOverview] = useState(null);
|
|
35
|
+
// The kinds the last answered search found, kept while the next one is on its way.
|
|
36
|
+
const [lastKinds, setLastKinds] = useState();
|
|
19
37
|
// Answers are kept with the request they answer, so a stale one is never shown for a new one.
|
|
20
38
|
const [top, setTop] = useState(null);
|
|
21
39
|
const [answer, setAnswer] = useState(null);
|
|
@@ -34,13 +52,49 @@ export function useMediaBrowser(ref) {
|
|
|
34
52
|
const atTop = trail.length === 1;
|
|
35
53
|
const tabs = top && !top.error && isShelves(top.items) ? top.items : undefined;
|
|
36
54
|
const selected = tabs ? (tabs.find((item) => item.id === tab) ?? tabs[0])?.id : undefined;
|
|
37
|
-
//
|
|
38
|
-
|
|
55
|
+
// The kinds the search found, once it has answered, and the one chosen if it is among them.
|
|
56
|
+
const overviewKey = `search:${needle}`;
|
|
57
|
+
const found = searching && overview?.key === overviewKey ? overview : null;
|
|
58
|
+
const foundKinds = found ? kindsOf(found.items) : undefined;
|
|
59
|
+
if (!searching && lastKinds !== undefined) {
|
|
60
|
+
setLastKinds(undefined);
|
|
61
|
+
}
|
|
62
|
+
// What to offer: this search's kinds once they are in, the last search's until then.
|
|
63
|
+
const kinds = searching ? (foundKinds ?? lastKinds) : undefined;
|
|
64
|
+
const kind = kinds && kindChoice !== undefined && kinds.includes(kindChoice) ? kindChoice : undefined;
|
|
65
|
+
// What to ask for, if anything: a folder, the open tab, or one kind of a search's results. A top level
|
|
66
|
+
// that is not shelves is already in hand, and so is the search with no kind chosen.
|
|
39
67
|
const path = atTop ? selected : here.path;
|
|
40
|
-
const key = searching ? `search:${needle}` : `path:${path ?? ''}`;
|
|
68
|
+
const key = searching ? `search:${needle}:${kind ?? ''}` : `path:${path ?? ''}`;
|
|
41
69
|
const result = answer?.key === key ? answer : null;
|
|
70
|
+
// The search with no kind chosen, always: its kinds are the choices, whichever one is showing.
|
|
42
71
|
useEffect(() => {
|
|
43
|
-
|
|
72
|
+
if (!searching) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let current = true;
|
|
76
|
+
const timer = setTimeout(() => {
|
|
77
|
+
client.browse(ref, { search: needle }).then((level) => {
|
|
78
|
+
if (current) {
|
|
79
|
+
setOverview({ key: `search:${needle}`, ...level });
|
|
80
|
+
setLastKinds(kindsOf(level.items));
|
|
81
|
+
}
|
|
82
|
+
}, (error) => current && setOverview({ key: `search:${needle}`, items: [], error: messageOf(error) }));
|
|
83
|
+
}, 300);
|
|
84
|
+
return () => {
|
|
85
|
+
current = false;
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
};
|
|
88
|
+
// oxlint-disable-next-line react/exhaustive-effect-dependencies
|
|
89
|
+
}, [client, ref, searching, needle, reconnects]);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const request = searching && kind
|
|
92
|
+
? { search: needle, kind }
|
|
93
|
+
: searching
|
|
94
|
+
? undefined
|
|
95
|
+
: path === undefined
|
|
96
|
+
? undefined
|
|
97
|
+
: { path };
|
|
44
98
|
if (!request) {
|
|
45
99
|
return;
|
|
46
100
|
}
|
|
@@ -48,18 +102,33 @@ export function useMediaBrowser(ref) {
|
|
|
48
102
|
const timer = setTimeout(() => {
|
|
49
103
|
client.browse(ref, request).then((level) => current && setAnswer({ key, ...level }), (error) => current && setAnswer({ key, items: [], error: messageOf(error) }));
|
|
50
104
|
},
|
|
51
|
-
// Typing in the search box waits for a pause; opening a folder or a tab does not.
|
|
52
|
-
|
|
105
|
+
// Typing in the search box waits for a pause; opening a folder or a tab or choosing a kind does not.
|
|
106
|
+
0);
|
|
53
107
|
return () => {
|
|
54
108
|
current = false;
|
|
55
109
|
clearTimeout(timer);
|
|
56
110
|
};
|
|
57
111
|
// oxlint-disable-next-line react/exhaustive-effect-dependencies
|
|
58
|
-
}, [client, ref, key, searching, needle, path, reconnects]);
|
|
59
|
-
|
|
112
|
+
}, [client, ref, key, searching, needle, kind, path, reconnects]);
|
|
113
|
+
// Opening what a search found (an album, a playlist) shows it in place of the results: the search steps
|
|
114
|
+
// aside, and going back from it returns to the results.
|
|
115
|
+
const open = useCallback((item) => {
|
|
116
|
+
const from = text.trim();
|
|
117
|
+
setTrail((levels) => [
|
|
118
|
+
...levels,
|
|
119
|
+
{
|
|
120
|
+
path: item.id,
|
|
121
|
+
title: item.title,
|
|
122
|
+
item,
|
|
123
|
+
...(from !== '' ? { from: { text: from, kind } } : {}),
|
|
124
|
+
},
|
|
125
|
+
]);
|
|
126
|
+
setText('');
|
|
127
|
+
}, [text, kind]);
|
|
60
128
|
const selectTab = useCallback((id) => {
|
|
61
129
|
setTab(id);
|
|
62
130
|
setText('');
|
|
131
|
+
setKindChoice(undefined);
|
|
63
132
|
setTrail([{}]);
|
|
64
133
|
}, []);
|
|
65
134
|
const back = useCallback(() => {
|
|
@@ -67,13 +136,18 @@ export function useMediaBrowser(ref) {
|
|
|
67
136
|
setText('');
|
|
68
137
|
return;
|
|
69
138
|
}
|
|
139
|
+
const left = trail.at(-1);
|
|
70
140
|
setTrail((levels) => (levels.length > 1 ? levels.slice(0, -1) : levels));
|
|
71
|
-
|
|
141
|
+
if (trail.length > 1 && left?.from !== undefined) {
|
|
142
|
+
setText(left.from.text);
|
|
143
|
+
setKindChoice(left.from.kind);
|
|
144
|
+
}
|
|
145
|
+
}, [text, trail]);
|
|
72
146
|
// The plain top-level list, when there are no shelves to make tabs of.
|
|
73
|
-
const listing = !searching && atTop && !tabs ? top : result;
|
|
147
|
+
const listing = searching && !kind ? found : !searching && atTop && !tabs ? top : result;
|
|
74
148
|
return {
|
|
75
149
|
items: listing?.items ?? [],
|
|
76
|
-
listKey: searching ?
|
|
150
|
+
listKey: searching ? `search:${kind ?? ''}` : `path:${path ?? 'top'}`,
|
|
77
151
|
title: searching
|
|
78
152
|
? (result?.title ?? `Results for “${needle}”`)
|
|
79
153
|
: atTop
|
|
@@ -91,5 +165,9 @@ export function useMediaBrowser(ref) {
|
|
|
91
165
|
back,
|
|
92
166
|
query: text,
|
|
93
167
|
search: setText,
|
|
168
|
+
kinds: kinds && kinds.length >= 1 ? kinds : undefined,
|
|
169
|
+
kindsPending: searching && foundKinds === undefined && !(lastKinds && lastKinds.length >= 1),
|
|
170
|
+
kind,
|
|
171
|
+
filterBy: setKindChoice,
|
|
94
172
|
};
|
|
95
173
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hashsome/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Design system components and hooks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@emotion/react": "11.14.0",
|
|
19
|
-
"@hashsome/core": "0.
|
|
19
|
+
"@hashsome/core": "0.12.0",
|
|
20
20
|
"e-prim": "2.0.1",
|
|
21
21
|
"motion": "13.4.5",
|
|
22
22
|
"react": "19.3.0",
|