@axium/storage 0.15.0 → 0.15.1
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/client/3rd-party.d.ts +11 -0
- package/dist/client/3rd-party.js +2 -0
- package/lib/List.svelte +86 -30
- package/package.json +1 -1
- package/dist/client/previews.d.ts +0 -4
- package/dist/client/previews.js +0 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { StorageItemMetadata } from '../common.js';
|
|
3
|
+
export declare const previews: Map<string, Snippet<[item: StorageItemMetadata<Record<string, unknown>>]>>;
|
|
4
|
+
export interface Opener {
|
|
5
|
+
/** Mime types supported by this opener */
|
|
6
|
+
types: string[];
|
|
7
|
+
name: string;
|
|
8
|
+
/** Get a URL to open the item with another plugin */
|
|
9
|
+
openURL(item: StorageItemMetadata): string;
|
|
10
|
+
}
|
|
11
|
+
export declare const openers: Opener[];
|
package/lib/List.svelte
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { AccessControlDialog, FormDialog, Icon } from '@axium/client/components';
|
|
2
|
+
import { AccessControlDialog, FormDialog, Icon, Popover } from '@axium/client/components';
|
|
3
3
|
import '@axium/client/styles/list';
|
|
4
4
|
import type { AccessControllable, UserPublic } from '@axium/core';
|
|
5
5
|
import { formatBytes } from '@axium/core/format';
|
|
6
6
|
import { forMime as iconForMime } from '@axium/core/icons';
|
|
7
7
|
import { downloadItem, getDirectoryMetadata, updateItemMetadata } from '@axium/storage/client';
|
|
8
|
-
import previews from '@axium/storage/client/
|
|
8
|
+
import { openers, previews } from '@axium/storage/client/3rd-party';
|
|
9
9
|
import type { StorageItemMetadata } from '@axium/storage/common';
|
|
10
10
|
|
|
11
11
|
let {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
{#snippet action(name: string, icon: string, i: number, preview: boolean = false)}
|
|
24
24
|
<span
|
|
25
|
-
class={[!preview
|
|
25
|
+
class={['icon-text', !preview ? 'action' : 'preview-action']}
|
|
26
26
|
onclick={() => {
|
|
27
27
|
activeIndex = i;
|
|
28
28
|
dialogs[name].showModal();
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
<span>Size</span>
|
|
49
49
|
</div>
|
|
50
50
|
{#each items as item, i (item.id)}
|
|
51
|
+
{@const itemOpeners = openers.filter(opener => opener.types.includes(item.type))}
|
|
51
52
|
<div
|
|
52
53
|
class="list-item"
|
|
53
54
|
onclick={async () => {
|
|
@@ -71,7 +72,7 @@
|
|
|
71
72
|
{@render action('rename', 'pencil', i)}
|
|
72
73
|
{@render action('share' + item.id, 'user-group', i)}
|
|
73
74
|
<AccessControlDialog
|
|
74
|
-
bind:dialog={dialogs['share' + item.id]}
|
|
75
|
+
bind:dialog={dialogs['share:' + item.id]}
|
|
75
76
|
{item}
|
|
76
77
|
itemType="storage"
|
|
77
78
|
editable={(item.acl?.find(
|
|
@@ -85,13 +86,33 @@
|
|
|
85
86
|
{@render action('download', 'download', i)}
|
|
86
87
|
{@render action('trash', 'trash', i)}
|
|
87
88
|
<dialog bind:this={dialogs['preview:' + item.id]} class="preview">
|
|
88
|
-
<div class="
|
|
89
|
-
|
|
90
|
-
{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
<div class="preview-top-bar">
|
|
90
|
+
<div class="title">{item.name}</div>
|
|
91
|
+
{#if itemOpeners.length}
|
|
92
|
+
{@const [first, ...others] = itemOpeners}
|
|
93
|
+
<div class="openers">
|
|
94
|
+
<span>Open with <a href={first.openURL(item)} target="_blank">{first.name}</a></span>
|
|
95
|
+
{#if others.length}
|
|
96
|
+
<Popover>
|
|
97
|
+
{#snippet toggle()}
|
|
98
|
+
<span class="popover-toggle"><Icon i="caret-down" /></span>
|
|
99
|
+
{/snippet}
|
|
100
|
+
{#each others as opener}
|
|
101
|
+
<a href={opener.openURL(item)} target="_blank">{opener.name}</a>
|
|
102
|
+
{/each}
|
|
103
|
+
</Popover>
|
|
104
|
+
{/if}
|
|
105
|
+
</div>
|
|
106
|
+
{/if}
|
|
107
|
+
<div class="actions">
|
|
108
|
+
{@render action('rename', 'pencil', i, true)}
|
|
109
|
+
{@render action('share:' + item.id, 'user-group', i, true)}
|
|
110
|
+
{@render action('download', 'download', i, true)}
|
|
111
|
+
{@render action('trash', 'trash', i, true)}
|
|
112
|
+
<span class="mobile-hide" onclick={() => dialogs['preview:' + item.id].close()}>
|
|
113
|
+
<Icon i="xmark" --size="20px" />
|
|
114
|
+
</span>
|
|
115
|
+
</div>
|
|
95
116
|
</div>
|
|
96
117
|
<div class="content">
|
|
97
118
|
{#if item.type.startsWith('image/')}
|
|
@@ -108,12 +129,13 @@
|
|
|
108
129
|
<p>PDF not displayed? <a href={item.dataURL} download={item.name}>Download</a></p>
|
|
109
130
|
</object>
|
|
110
131
|
{:else if item.type.startsWith('text/')}
|
|
111
|
-
<pre
|
|
132
|
+
<pre
|
|
133
|
+
class="full-fill preview-text">{#await downloadItem(item.id).then( b => b.text() ) then content}{content}{/await}</pre>
|
|
112
134
|
{:else if previews.has(item.type)}
|
|
113
135
|
{@render previews.get(item.type)!(item)}
|
|
114
136
|
{:else}
|
|
115
|
-
<div class="no-preview">
|
|
116
|
-
<Icon i="eye-slash" />
|
|
137
|
+
<div class="full-fill no-preview">
|
|
138
|
+
<Icon i="eye-slash" --size="50px" />
|
|
117
139
|
<span>Preview not available</span>
|
|
118
140
|
</div>
|
|
119
141
|
{/if}
|
|
@@ -175,28 +197,45 @@
|
|
|
175
197
|
inset: 0;
|
|
176
198
|
width: 100%;
|
|
177
199
|
height: 100%;
|
|
178
|
-
background-color: #
|
|
200
|
+
background-color: #000a;
|
|
179
201
|
border: none;
|
|
180
202
|
padding: 1em;
|
|
181
203
|
word-wrap: normal;
|
|
204
|
+
anchor-scope: --preview-openers;
|
|
182
205
|
|
|
183
|
-
.
|
|
184
|
-
|
|
185
|
-
|
|
206
|
+
.preview-action:hover {
|
|
207
|
+
cursor: pointer;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.preview-top-bar {
|
|
186
211
|
display: flex;
|
|
187
212
|
align-items: center;
|
|
188
213
|
gap: 1em;
|
|
214
|
+
justify-content: space-between;
|
|
215
|
+
padding: 0;
|
|
189
216
|
position: absolute;
|
|
190
|
-
|
|
217
|
+
inset: 0.5em 1em 0;
|
|
218
|
+
height: fit-content;
|
|
219
|
+
|
|
220
|
+
> div {
|
|
221
|
+
display: flex;
|
|
222
|
+
gap: 1em;
|
|
223
|
+
align-items: center;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.openers {
|
|
191
228
|
padding: 1em;
|
|
192
|
-
|
|
229
|
+
border: 1px solid var(--border-accent);
|
|
230
|
+
border-radius: 1em;
|
|
231
|
+
height: 2em;
|
|
232
|
+
anchor-name: --preview-openers;
|
|
193
233
|
}
|
|
194
234
|
|
|
195
|
-
.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
text-overflow: ellipsis;
|
|
235
|
+
.openers :global([popover]) {
|
|
236
|
+
inset: anchor(bottom) anchor(right) auto anchor(left);
|
|
237
|
+
position-anchor: --preview-openers;
|
|
238
|
+
width: anchor-size(width);
|
|
200
239
|
}
|
|
201
240
|
|
|
202
241
|
.actions {
|
|
@@ -207,11 +246,14 @@
|
|
|
207
246
|
position: absolute;
|
|
208
247
|
inset: 3em 10em 0;
|
|
209
248
|
|
|
210
|
-
.
|
|
249
|
+
.full-fill {
|
|
211
250
|
position: absolute;
|
|
212
251
|
inset: 0;
|
|
213
252
|
width: 100%;
|
|
214
253
|
height: 100%;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.preview-text {
|
|
215
257
|
white-space: pre-wrap;
|
|
216
258
|
overflow-y: scroll;
|
|
217
259
|
line-height: 1.6;
|
|
@@ -229,13 +271,27 @@
|
|
|
229
271
|
}
|
|
230
272
|
|
|
231
273
|
@media (width < 700px) {
|
|
232
|
-
.
|
|
233
|
-
|
|
234
|
-
|
|
274
|
+
.preview-top-bar {
|
|
275
|
+
flex-direction: column;
|
|
276
|
+
|
|
277
|
+
.actions {
|
|
278
|
+
justify-content: space-around;
|
|
279
|
+
width: 100%;
|
|
280
|
+
|
|
281
|
+
.preview-action {
|
|
282
|
+
padding: 1em;
|
|
283
|
+
flex: 1 1 0;
|
|
284
|
+
border-radius: 1em;
|
|
285
|
+
border: 1px solid var(--border-accent);
|
|
286
|
+
padding: 1em;
|
|
287
|
+
justify-content: center;
|
|
288
|
+
display: flex;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
235
291
|
}
|
|
236
292
|
|
|
237
293
|
.content {
|
|
238
|
-
inset:
|
|
294
|
+
inset: 10em 1em 0;
|
|
239
295
|
}
|
|
240
296
|
}
|
|
241
297
|
}
|
package/package.json
CHANGED
package/dist/client/previews.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default new Map();
|