@dosgato/dialog 1.3.3 → 1.3.5
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/FieldChooserLink.svelte +142 -21
- package/dist/FieldChooserLink.svelte.d.ts +8 -1
- package/dist/chooser/AssetTabs.svelte +201 -0
- package/dist/chooser/AssetTabs.svelte.d.ts +23 -0
- package/dist/chooser/Chooser.svelte +21 -3
- package/dist/chooser/Chooser.svelte.d.ts +1 -0
- package/dist/chooser/ChooserAPI.d.ts +6 -0
- package/dist/chooser/ChooserPreview.svelte +1 -1
- package/dist/chooser/Details.svelte +8 -2
- package/dist/chooser/Thumbnail.svelte +12 -1
- package/dist/tree/TreeNode.svelte +3 -3
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import arrowClockwiseFill from '@iconify-icons/ph/arrow-clockwise-fill'
|
|
3
|
-
import deleteOutline from '@iconify-icons/mdi/delete
|
|
3
|
+
import deleteOutline from '@iconify-icons/mdi/delete'
|
|
4
4
|
import xCircle from '@iconify-icons/ph/x-circle'
|
|
5
5
|
import { FORM_CONTEXT, FORM_INHERITED_PATH, type FormStore } from '@txstate-mws/svelte-forms'
|
|
6
6
|
import { getContext } from 'svelte'
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import type { AnyItem, Client } from './chooser/ChooserAPI'
|
|
10
10
|
import Details from './chooser/Details.svelte'
|
|
11
11
|
import Thumbnail from './chooser/Thumbnail.svelte'
|
|
12
|
+
import AssetTabs from './chooser/AssetTabs.svelte'
|
|
12
13
|
import { getDescribedBy, FieldStandard, InlineMessage, Icon } from './'
|
|
13
14
|
|
|
14
15
|
export let id: string | undefined = undefined
|
|
@@ -28,6 +29,8 @@
|
|
|
28
29
|
export let extradescid: string | undefined = undefined
|
|
29
30
|
export let helptext: string | undefined = undefined
|
|
30
31
|
export let selectedAsset: AnyItem | RawURL | BrokenURL | undefined = undefined
|
|
32
|
+
export let altTextPath : string | undefined = undefined
|
|
33
|
+
export let altTextRequired: boolean = false
|
|
31
34
|
|
|
32
35
|
// TODO: add a mime type acceptance prop, maybe a regex or function, to prevent users from
|
|
33
36
|
// choosing unacceptable mime types
|
|
@@ -41,6 +44,7 @@
|
|
|
41
44
|
let urlEntryInput: HTMLInputElement | undefined
|
|
42
45
|
|
|
43
46
|
const descid = randomid()
|
|
47
|
+
const assetTabsId = randomid()
|
|
44
48
|
let modalshown = false
|
|
45
49
|
async function show () {
|
|
46
50
|
if (selectedAsset && selectedAsset.type !== 'raw' && selectedAsset.type !== 'broken') store.setPreview(selectedAsset)
|
|
@@ -51,7 +55,15 @@
|
|
|
51
55
|
}
|
|
52
56
|
function onChange (setVal: any) {
|
|
53
57
|
return (e) => {
|
|
54
|
-
selectedAsset = e.detail
|
|
58
|
+
selectedAsset = e.detail.preview
|
|
59
|
+
if (
|
|
60
|
+
altTextPath &&
|
|
61
|
+
e.detail.copyAltText &&
|
|
62
|
+
selectedAsset?.type === 'asset' &&
|
|
63
|
+
selectedAsset?.image?.altText
|
|
64
|
+
) {
|
|
65
|
+
formStore.setField(altTextPath, selectedAsset.image.altText).catch(console.error)
|
|
66
|
+
}
|
|
55
67
|
setVal(selectedAsset?.id)
|
|
56
68
|
hide()
|
|
57
69
|
}
|
|
@@ -159,25 +171,66 @@
|
|
|
159
171
|
}
|
|
160
172
|
}
|
|
161
173
|
$: void updateSelected($value)
|
|
174
|
+
function onClickRemove (setVal: any) {
|
|
175
|
+
return (e) => {
|
|
176
|
+
selectedAsset = undefined
|
|
177
|
+
setVal(undefined)
|
|
178
|
+
if (altTextPath) {
|
|
179
|
+
formStore.setField(altTextPath, undefined).catch(console.error)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
162
183
|
</script>
|
|
163
184
|
|
|
164
185
|
<FieldStandard bind:id {path} {descid} {label} {defaultValue} {conditional} {required} {related} {helptext} let:value let:messagesid let:helptextid let:valid let:invalid let:id let:onBlur let:setVal>
|
|
165
186
|
{#if selectedAsset?.id}
|
|
166
|
-
|
|
167
|
-
<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
187
|
+
{#if selectedAsset?.type === 'asset'}
|
|
188
|
+
<div class="asset-chooser-container">
|
|
189
|
+
<div class="preview-container">
|
|
190
|
+
<Thumbnail item={selectedAsset} />
|
|
191
|
+
{#if !urlEntry}
|
|
192
|
+
<div class="actions">
|
|
193
|
+
<button type="button" on:click={show} aria-describedby={getDescribedBy([descid, messagesid, helptextid, extradescid])}>
|
|
194
|
+
<span>Replace</span> <Icon icon={arrowClockwiseFill} inline />
|
|
195
|
+
</button>
|
|
196
|
+
<button type="button" on:click={onClickRemove(setVal)} aria-describedby={getDescribedBy([descid, messagesid, helptextid, extradescid])}>
|
|
197
|
+
<span>Remove</span> <Icon icon={deleteOutline} inline />
|
|
198
|
+
</button>
|
|
199
|
+
</div>
|
|
200
|
+
{/if}
|
|
201
|
+
</div>
|
|
202
|
+
<div class="detail-container">
|
|
203
|
+
<div class="file-name" aria-live="polite">
|
|
204
|
+
<span class="chooser-label">Name:</span>
|
|
205
|
+
<span class="chooser-data">{selectedAsset.name}</span>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="tabs-container">
|
|
208
|
+
<AssetTabs id={assetTabsId} {selectedAsset} showMetadata={$$slots.metadata != null} {altTextPath} {altTextRequired}>
|
|
209
|
+
<slot name="metadata" slot="metadata" selectedAsset={selectedAsset} />
|
|
210
|
+
</AssetTabs>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
{:else}
|
|
215
|
+
<div class="dialog-chooser-container" class:urlEntry>
|
|
216
|
+
<Thumbnail item={selectedAsset} />
|
|
217
|
+
<div class="dialog-chooser-right">
|
|
218
|
+
<Details item={selectedAsset} singleLine />
|
|
219
|
+
{#if !urlEntry}
|
|
220
|
+
<div class="actions">
|
|
221
|
+
<button type="button" on:click={show} aria-describedby={getDescribedBy([descid, messagesid, helptextid, extradescid])}>
|
|
222
|
+
<Icon icon={arrowClockwiseFill} inline /> Replace
|
|
223
|
+
</button>
|
|
224
|
+
<button type="button" on:click={() => { selectedAsset = undefined; setVal(undefined) }} aria-describedby={getDescribedBy([descid, messagesid, helptextid, extradescid])}>
|
|
225
|
+
<Icon icon={deleteOutline} inline /> Remove
|
|
226
|
+
</button>
|
|
227
|
+
</div>
|
|
228
|
+
{/if}
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
{/if}
|
|
180
232
|
{/if}
|
|
233
|
+
|
|
181
234
|
{#if urlEntry || !selectedAsset?.id}
|
|
182
235
|
<div class="dialog-chooser-entry">
|
|
183
236
|
{#if urlEntry}
|
|
@@ -193,7 +246,7 @@
|
|
|
193
246
|
<InlineMessage message={{ message: 'Entry does not match an internal resource and is not a valid URL. Nothing will be saved.', type: 'warning' }} />
|
|
194
247
|
{/if}
|
|
195
248
|
{#if modalshown}
|
|
196
|
-
<Chooser {store} {label} {pages} {assets} {images} {initialSource} {initialPath} {folders} {required} on:change={onChange(setVal)} on:escape={hide} />
|
|
249
|
+
<Chooser {store} {label} {pages} {assets} {images} {initialSource} {initialPath} {folders} {required} showAltTextOption={!!altTextPath} on:change={onChange(setVal)} on:escape={hide} />
|
|
197
250
|
{/if}
|
|
198
251
|
</FieldStandard>
|
|
199
252
|
|
|
@@ -225,16 +278,24 @@
|
|
|
225
278
|
:global([data-eq~="400px"]) .dialog-chooser-right {
|
|
226
279
|
max-width: unset;
|
|
227
280
|
}
|
|
228
|
-
.dialog-chooser-right
|
|
229
|
-
|
|
281
|
+
.dialog-chooser-right .actions {
|
|
282
|
+
display: flex;
|
|
283
|
+
}
|
|
284
|
+
.dialog-chooser-right button, .asset-chooser-container .actions button {
|
|
230
285
|
border: 0;
|
|
231
|
-
font-weight: bold;
|
|
232
286
|
border-radius: 0.2em;
|
|
233
287
|
cursor: pointer;
|
|
234
288
|
padding: 0.4em;
|
|
235
289
|
background: none;
|
|
290
|
+
display: flex;
|
|
291
|
+
align-items: center;
|
|
292
|
+
gap: 2px;
|
|
293
|
+
}
|
|
294
|
+
.asset-chooser-container .actions button span {
|
|
295
|
+
font-weight: bold;
|
|
296
|
+
font-size: 0.75em;
|
|
236
297
|
}
|
|
237
|
-
.dialog-chooser-right button:hover {
|
|
298
|
+
.dialog-chooser-right button:hover, .asset-chooser-container .actions button:hover {
|
|
238
299
|
background-color: #cccccc;
|
|
239
300
|
}
|
|
240
301
|
.dialog-chooser-entry {
|
|
@@ -281,4 +342,64 @@
|
|
|
281
342
|
:global([data-eq~="400px"] .dialog-chooser-container .dialog-chooser-thumbnail img) {
|
|
282
343
|
object-position: left;
|
|
283
344
|
}
|
|
345
|
+
.asset-chooser-container {
|
|
346
|
+
display: flex;
|
|
347
|
+
align-items: flex-start;
|
|
348
|
+
gap: 1em;
|
|
349
|
+
padding: 1em;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.asset-chooser-container .preview-container {
|
|
353
|
+
width: 20%;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.asset-chooser-container .preview-container :global(.dialog-chooser-thumbnail) {
|
|
357
|
+
justify-content: center;
|
|
358
|
+
display: flex;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.asset-chooser-container .preview-container .actions {
|
|
362
|
+
display: flex;
|
|
363
|
+
justify-content: space-between;
|
|
364
|
+
flex-wrap: wrap;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.asset-chooser-container .detail-container {
|
|
368
|
+
flex: 1;
|
|
369
|
+
border: 1px solid var(--dg-dialog-header-bg, #ddd);
|
|
370
|
+
border-radius: 4px;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.asset-chooser-container .detail-container .file-name {
|
|
374
|
+
background-color: var(--dg-dialog-header-bg, #ddd);
|
|
375
|
+
padding: 0.75em;
|
|
376
|
+
word-break: break-word;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.asset-chooser-container .detail-container .chooser-label {
|
|
380
|
+
font-weight: bold;
|
|
381
|
+
margin-right: 0.5em;
|
|
382
|
+
}
|
|
383
|
+
.asset-chooser-container .detail-container .tabs-container {
|
|
384
|
+
position: relative;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
@media (max-width: 800px) {
|
|
388
|
+
.asset-chooser-container {
|
|
389
|
+
flex-direction: column;
|
|
390
|
+
}
|
|
391
|
+
.asset-chooser-container .preview-container {
|
|
392
|
+
width: 100%;
|
|
393
|
+
}
|
|
394
|
+
.asset-chooser-container .preview-container :global(.dialog-chooser-thumbnail img) {
|
|
395
|
+
max-height: 300px;
|
|
396
|
+
}
|
|
397
|
+
.asset-chooser-container .preview-container .actions {
|
|
398
|
+
justify-content: center;
|
|
399
|
+
gap: 2em;
|
|
400
|
+
}
|
|
401
|
+
.asset-chooser-container .detail-container {
|
|
402
|
+
width: 100%;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
284
405
|
</style>
|
|
@@ -20,11 +20,18 @@ declare const __propDef: {
|
|
|
20
20
|
extradescid?: string | undefined;
|
|
21
21
|
helptext?: string | undefined;
|
|
22
22
|
selectedAsset?: AnyItem | RawURL | BrokenURL | undefined;
|
|
23
|
+
altTextPath?: string | undefined;
|
|
24
|
+
altTextRequired?: boolean;
|
|
23
25
|
};
|
|
24
26
|
events: {
|
|
25
27
|
[evt: string]: CustomEvent<any>;
|
|
26
28
|
};
|
|
27
|
-
slots: {
|
|
29
|
+
slots: {
|
|
30
|
+
metadata: {
|
|
31
|
+
slot: string;
|
|
32
|
+
selectedAsset: AnyItem | RawURL | BrokenURL | undefined;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
28
35
|
};
|
|
29
36
|
export type FieldChooserLinkProps = typeof __propDef.props;
|
|
30
37
|
export type FieldChooserLinkEvents = typeof __propDef.events;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { bytesToHuman } from 'txstate-utils'
|
|
3
|
+
import type { Asset } from './ChooserAPI'
|
|
4
|
+
import { Dialog, FieldTextArea } from '..'
|
|
5
|
+
import Button from '../Button.svelte'
|
|
6
|
+
import Icon from '../Icon.svelte'
|
|
7
|
+
import outIcon from '@iconify-icons/ph/arrow-square-out-bold'
|
|
8
|
+
import warningIcon from '@iconify-icons/ph/warning'
|
|
9
|
+
import { getContext } from 'svelte'
|
|
10
|
+
import type { Client } from './ChooserAPI'
|
|
11
|
+
import { CHOOSER_API_CONTEXT } from './ChooserAPI'
|
|
12
|
+
|
|
13
|
+
export let id: string
|
|
14
|
+
export let selectedAsset: Asset
|
|
15
|
+
export let showMetadata: boolean = false
|
|
16
|
+
export let altTextRequired: boolean = false
|
|
17
|
+
// if they say it's required but don't provide a path, default to 'altText'
|
|
18
|
+
export let altTextPath: string | undefined = altTextRequired ? 'altText' : undefined
|
|
19
|
+
const chooserClient = getContext<Client>(CHOOSER_API_CONTEXT)
|
|
20
|
+
|
|
21
|
+
let tabListEl: HTMLUListElement
|
|
22
|
+
let manageAssetsModalOpen = false
|
|
23
|
+
|
|
24
|
+
$: isImage = selectedAsset?.image !== undefined
|
|
25
|
+
|
|
26
|
+
$: tabs = [
|
|
27
|
+
...(isImage && altTextPath ? [{ id: `${id}-alttext`, label: 'Alternate Text' }] : []),
|
|
28
|
+
{ id: `${id}-details`, label: 'Asset Details' },
|
|
29
|
+
...(showMetadata ? [{ id: `${id}-metadata`, label: 'Metadata' }] : [])
|
|
30
|
+
]
|
|
31
|
+
$: activeTab = tabs ? tabs[0].id : 0
|
|
32
|
+
let focusedTabIdx = 0
|
|
33
|
+
|
|
34
|
+
function selectTab(tabId: string) {
|
|
35
|
+
activeTab = tabId
|
|
36
|
+
focusedTabIdx = tabs.findIndex(t => t.id === tabId)
|
|
37
|
+
// ensure the focused tab is visible
|
|
38
|
+
const tabEls = tabListEl.querySelectorAll(`[role="tab"][data-tab-asset]`)
|
|
39
|
+
tabEls[focusedTabIdx]?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center'})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function focusTab(idx: number) {
|
|
43
|
+
focusedTabIdx = idx
|
|
44
|
+
activeTab = tabs[idx].id
|
|
45
|
+
const tabEls = tabListEl.querySelectorAll(`[role="tab"][data-tab-asset]`)
|
|
46
|
+
if (tabEls[idx]) (tabEls[idx] as HTMLElement).focus()
|
|
47
|
+
tabEls[idx]?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center'})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function onTabKeydown(e: KeyboardEvent, idx: number) {
|
|
51
|
+
if (e.key === 'ArrowRight') {
|
|
52
|
+
e.preventDefault()
|
|
53
|
+
focusTab((idx + 1) % tabs.length)
|
|
54
|
+
} else if (e.key === 'ArrowLeft') {
|
|
55
|
+
e.preventDefault()
|
|
56
|
+
focusTab((idx - 1 + tabs.length) % tabs.length)
|
|
57
|
+
} else if (e.key === 'Enter' || e.key === ' ') {
|
|
58
|
+
e.preventDefault()
|
|
59
|
+
selectTab(tabs[idx].id)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function navigateToManageAsset() {
|
|
64
|
+
manageAssetsModalOpen = false
|
|
65
|
+
const url = chooserClient.idToEditingUrl ? await chooserClient.idToEditingUrl(selectedAsset.id) : undefined
|
|
66
|
+
if (url) {
|
|
67
|
+
window.location.assign(url)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<ul bind:this={tabListEl} class="tabs" role="tablist">
|
|
73
|
+
{#each tabs as tab, idx}
|
|
74
|
+
<li
|
|
75
|
+
id="{tab.id}-tab"
|
|
76
|
+
class="tab {activeTab === tab.id ? 'active' : ''}"
|
|
77
|
+
role="tab"
|
|
78
|
+
aria-selected={activeTab === tab.id}
|
|
79
|
+
aria-controls={tab.id}
|
|
80
|
+
tabindex={focusedTabIdx === idx ? 0 : -1}
|
|
81
|
+
data-tab-asset
|
|
82
|
+
on:click={() => selectTab(tab.id)}
|
|
83
|
+
on:keydown={(e) => onTabKeydown(e, idx)}
|
|
84
|
+
on:focus={() => { focusedTabIdx = idx }}
|
|
85
|
+
>
|
|
86
|
+
<span>{tab.label}</span>
|
|
87
|
+
</li>
|
|
88
|
+
{/each}
|
|
89
|
+
</ul>
|
|
90
|
+
|
|
91
|
+
{#if activeTab === `${id}-alttext`}
|
|
92
|
+
<div id={`${id}-alttext`} class="tab-content" role="tabpanel" aria-labelledby="{id}-alttext-tab">
|
|
93
|
+
{#if altTextPath}<FieldTextArea required={altTextRequired} path={altTextPath} label="Alt. Text" helptext="Describes the asset for visually-impaired users and search engines."/>{/if}
|
|
94
|
+
</div>
|
|
95
|
+
{/if}
|
|
96
|
+
{#if activeTab === `${id}-details`}
|
|
97
|
+
<div id={`${id}-details`} class="tab-content" role="tabpanel" aria-labelledby="{id}-details-tab">
|
|
98
|
+
<div class="details">
|
|
99
|
+
<div class="path">
|
|
100
|
+
<span class="chooser-label">Assets Path:</span>
|
|
101
|
+
<span class="chooser-data">{selectedAsset.path}</span>
|
|
102
|
+
</div>
|
|
103
|
+
{#if selectedAsset.image}
|
|
104
|
+
<div>
|
|
105
|
+
<span class="chooser-label">Dimensions:</span>
|
|
106
|
+
<span class="chooser-data">{selectedAsset.image.width}x{selectedAsset.image.height}</span>
|
|
107
|
+
</div>
|
|
108
|
+
{/if}
|
|
109
|
+
<div>
|
|
110
|
+
<span class="chooser-label">File Type:</span>
|
|
111
|
+
<span class="chooser-data">{selectedAsset.mime}</span>
|
|
112
|
+
</div>
|
|
113
|
+
<div>
|
|
114
|
+
<span class="chooser-label">File Size:</span>
|
|
115
|
+
<span class="chooser-data">{bytesToHuman(selectedAsset.bytes)}</span>
|
|
116
|
+
</div>
|
|
117
|
+
{#if chooserClient.idToEditingUrl}
|
|
118
|
+
<Button on:click={() => { manageAssetsModalOpen = true }}>
|
|
119
|
+
<span class="manage-in-assets">
|
|
120
|
+
Manage in Assets
|
|
121
|
+
<Icon icon={outIcon} />
|
|
122
|
+
</span>
|
|
123
|
+
</Button>
|
|
124
|
+
{/if}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
{/if}
|
|
128
|
+
{#if activeTab === `${id}-metadata`}
|
|
129
|
+
<div id={`${id}-metadata`} class="tab-content" role="tabpanel" aria-labelledby="{id}-metadata-tab">
|
|
130
|
+
<slot name="metadata" let:selectedAsset={selectedAsset} />
|
|
131
|
+
</div>
|
|
132
|
+
{/if}
|
|
133
|
+
{#if manageAssetsModalOpen}
|
|
134
|
+
<Dialog
|
|
135
|
+
title="Leave Dialog?"
|
|
136
|
+
size="small"
|
|
137
|
+
on:escape={() => { manageAssetsModalOpen = false }}
|
|
138
|
+
on:continue={navigateToManageAsset}
|
|
139
|
+
continueText="Manage Asset"
|
|
140
|
+
cancelText="Go Back"
|
|
141
|
+
icon={warningIcon}>
|
|
142
|
+
<p>Any changes made in the previous window may not be saved. Do you wish to continue?</p>
|
|
143
|
+
</Dialog>
|
|
144
|
+
{/if}
|
|
145
|
+
|
|
146
|
+
<style>
|
|
147
|
+
.tabs {
|
|
148
|
+
list-style: none;
|
|
149
|
+
display: flex;
|
|
150
|
+
gap: 10px;
|
|
151
|
+
margin: 0;
|
|
152
|
+
padding-inline: 0.375em;
|
|
153
|
+
background-color: var(--dg-dialog-header-bg, #ddd);
|
|
154
|
+
max-width: 100%;
|
|
155
|
+
overflow-x: auto;
|
|
156
|
+
scrollbar-width: none;
|
|
157
|
+
}
|
|
158
|
+
.tabs .tab {
|
|
159
|
+
background-color: #ebebeb;
|
|
160
|
+
color: #696969;
|
|
161
|
+
font-weight: 600;
|
|
162
|
+
padding: 0.5em 0.75em 0.125em;
|
|
163
|
+
border: 1px solid #ccced1;
|
|
164
|
+
border-top-left-radius: 4px;
|
|
165
|
+
border-top-right-radius: 4px;
|
|
166
|
+
}
|
|
167
|
+
.tabs .tab span{
|
|
168
|
+
white-space: nowrap;
|
|
169
|
+
}
|
|
170
|
+
.tabs .tab[aria-selected="true"] {
|
|
171
|
+
background-color: #f7f7f7;
|
|
172
|
+
color: #000;
|
|
173
|
+
border-bottom-color: #f7f7f7;
|
|
174
|
+
}
|
|
175
|
+
.tab-content {
|
|
176
|
+
padding: 1em 1em 1em 1.125em; /* left padding to align with tab text, which is 0.75em + 0.375em */
|
|
177
|
+
}
|
|
178
|
+
.tab-content .details {
|
|
179
|
+
display: flex;
|
|
180
|
+
flex-wrap: wrap;
|
|
181
|
+
column-gap: 1.5em;
|
|
182
|
+
row-gap: 0.5em;
|
|
183
|
+
}
|
|
184
|
+
.tab-content .details > div {
|
|
185
|
+
display: flex;
|
|
186
|
+
flex-direction: column;
|
|
187
|
+
}
|
|
188
|
+
.tab-content .path {
|
|
189
|
+
flex-basis: 100%;
|
|
190
|
+
}
|
|
191
|
+
.tab-content .chooser-label {
|
|
192
|
+
font-weight: 600;
|
|
193
|
+
margin-right: 0.5em;
|
|
194
|
+
}
|
|
195
|
+
.manage-in-assets {
|
|
196
|
+
display: flex;
|
|
197
|
+
align-items: center;
|
|
198
|
+
justify-content: center;
|
|
199
|
+
gap: 0.4em;
|
|
200
|
+
}
|
|
201
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SvelteComponentTyped } from "svelte";
|
|
2
|
+
import type { Asset } from './ChooserAPI';
|
|
3
|
+
declare const __propDef: {
|
|
4
|
+
props: {
|
|
5
|
+
id: string;
|
|
6
|
+
selectedAsset: Asset;
|
|
7
|
+
showMetadata?: boolean;
|
|
8
|
+
altTextRequired?: boolean;
|
|
9
|
+
altTextPath?: string | undefined;
|
|
10
|
+
};
|
|
11
|
+
events: {
|
|
12
|
+
[evt: string]: CustomEvent<any>;
|
|
13
|
+
};
|
|
14
|
+
slots: {
|
|
15
|
+
metadata: {};
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export type AssetTabsProps = typeof __propDef.props;
|
|
19
|
+
export type AssetTabsEvents = typeof __propDef.events;
|
|
20
|
+
export type AssetTabsSlots = typeof __propDef.slots;
|
|
21
|
+
export default class AssetTabs extends SvelteComponentTyped<AssetTabsProps, AssetTabsEvents, AssetTabsSlots> {
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
export let passthruFilters: F | undefined = undefined
|
|
24
24
|
export let filter: undefined | ((item: AnyItem) => boolean | Promise<boolean>) = undefined
|
|
25
25
|
export let store = new ChooserStore<F>(chooserClient)
|
|
26
|
+
export let showAltTextOption = false
|
|
26
27
|
store.filter = filter
|
|
27
28
|
|
|
28
29
|
setContext(CHOOSER_STORE_CONTEXT, store)
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
$: store.setPreview($selected)
|
|
41
42
|
|
|
42
43
|
function onChoose () {
|
|
43
|
-
dispatch('change', $store.preview)
|
|
44
|
+
dispatch('change', { preview: $store.preview, copyAltText: altTextCheckbox?.checked ?? false } )
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
function onDeselect () {
|
|
@@ -85,6 +86,8 @@
|
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
}
|
|
89
|
+
|
|
90
|
+
let altTextCheckbox: HTMLInputElement
|
|
88
91
|
</script>
|
|
89
92
|
|
|
90
93
|
<Dialog size="xl" ignoreTabs title={label} on:escape continueText="Choose" disabled={!$preview && required} cancelText="Cancel">
|
|
@@ -102,6 +105,14 @@
|
|
|
102
105
|
{/if}
|
|
103
106
|
</section>
|
|
104
107
|
<ChooserPreview {thumbnailExpanded} {previewId} {store} on:thumbnailsizechange={() => { thumbnailExpanded = !thumbnailExpanded }}/>
|
|
108
|
+
{#if showAltTextOption && $preview && $preview.type === 'asset' && $preview.image}
|
|
109
|
+
<section class="alt-text-options">
|
|
110
|
+
<label>
|
|
111
|
+
<input bind:this={altTextCheckbox} type="checkbox" />
|
|
112
|
+
<span>Copy/paste alt. text (if available)</span>
|
|
113
|
+
</label>
|
|
114
|
+
</section>
|
|
115
|
+
{/if}
|
|
105
116
|
</section>
|
|
106
117
|
<svelte:fragment slot="buttons" let:describedby>
|
|
107
118
|
{#if chooserClient.upload && $source?.type === 'asset'}
|
|
@@ -137,7 +148,7 @@
|
|
|
137
148
|
position: relative;
|
|
138
149
|
width: 75%;
|
|
139
150
|
min-width: calc(100% - 21em);
|
|
140
|
-
height: calc(100% -
|
|
151
|
+
height: calc(100% - 4.5em);
|
|
141
152
|
background-color: white;
|
|
142
153
|
overflow: auto;
|
|
143
154
|
}
|
|
@@ -145,6 +156,10 @@
|
|
|
145
156
|
position: relative;
|
|
146
157
|
width: 100%;
|
|
147
158
|
}
|
|
159
|
+
.alt-text-options {
|
|
160
|
+
width: 100%;
|
|
161
|
+
padding-block: 1em;
|
|
162
|
+
}
|
|
148
163
|
:global(footer.actions .upload) {
|
|
149
164
|
margin-right: auto;
|
|
150
165
|
}
|
|
@@ -155,7 +170,10 @@
|
|
|
155
170
|
.dialog-chooser-chooser {
|
|
156
171
|
order: 3;
|
|
157
172
|
width: 100%;
|
|
158
|
-
height:
|
|
173
|
+
height: 50%;
|
|
174
|
+
}
|
|
175
|
+
.alt-text-options {
|
|
176
|
+
order: 4;
|
|
159
177
|
}
|
|
160
178
|
}
|
|
161
179
|
</style>
|
|
@@ -15,6 +15,7 @@ declare class __sveltets_Render<F> {
|
|
|
15
15
|
passthruFilters?: F | undefined;
|
|
16
16
|
filter?: ((item: AnyItem) => boolean | Promise<boolean>) | undefined;
|
|
17
17
|
store?: ChooserStore<F> | undefined;
|
|
18
|
+
showAltTextOption?: boolean;
|
|
18
19
|
};
|
|
19
20
|
events(): {
|
|
20
21
|
escape: CustomEvent<any>;
|
|
@@ -40,6 +40,11 @@ export interface Client<F = any> {
|
|
|
40
40
|
* may optionally return an array of accepted mime types for the folder
|
|
41
41
|
*/
|
|
42
42
|
mayUpload?: (folder: Folder) => boolean | string[];
|
|
43
|
+
/**
|
|
44
|
+
* given an asset ID, return a URL that can be used to edit the asset in the main
|
|
45
|
+
* application. If undefined is returned, no "Edit" button will be shown
|
|
46
|
+
*/
|
|
47
|
+
idToEditingUrl?: (id: string) => Promise<string | undefined>;
|
|
43
48
|
}
|
|
44
49
|
export interface Source {
|
|
45
50
|
type: ChooserType;
|
|
@@ -108,6 +113,7 @@ export interface Asset extends Item {
|
|
|
108
113
|
thumbnailUrl?: string;
|
|
109
114
|
previewUrl?: string;
|
|
110
115
|
tinyUrl?: string;
|
|
116
|
+
altText?: string;
|
|
111
117
|
};
|
|
112
118
|
}
|
|
113
119
|
export {};
|
|
@@ -25,6 +25,12 @@
|
|
|
25
25
|
{/if}
|
|
26
26
|
{#if item.type === 'asset'}
|
|
27
27
|
{#if item.image}
|
|
28
|
+
{#if item.image.altText}
|
|
29
|
+
<div class="alt-text">
|
|
30
|
+
<dt>Alt. Text:</dt>
|
|
31
|
+
<dd>{item.image.altText}</dd>
|
|
32
|
+
</div>
|
|
33
|
+
{/if}
|
|
28
34
|
<div>
|
|
29
35
|
<dt>Dimensions:</dt>
|
|
30
36
|
<dd>{item.image.width}x{item.image.height}</dd>
|
|
@@ -83,8 +89,8 @@
|
|
|
83
89
|
.multiLine .top-row {
|
|
84
90
|
width: 100%;
|
|
85
91
|
}
|
|
86
|
-
.
|
|
87
|
-
|
|
92
|
+
.alt-text {
|
|
93
|
+
width: 100%;
|
|
88
94
|
}
|
|
89
95
|
dl.asset .top-row {
|
|
90
96
|
max-width: 30em;
|
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
{/if}
|
|
33
33
|
</div>
|
|
34
34
|
{:else}
|
|
35
|
-
<
|
|
35
|
+
<div class="file-icon-container">
|
|
36
|
+
<FileIcon mime={item.mime} width='5em' />
|
|
37
|
+
</div>
|
|
36
38
|
{/if}
|
|
37
39
|
{:else if item.type === 'folder'}
|
|
38
40
|
<Icon icon={folderOutline} width='5em' />
|
|
@@ -62,6 +64,15 @@
|
|
|
62
64
|
.dialog-chooser-thumbnail > :global(*) {
|
|
63
65
|
display: block;
|
|
64
66
|
}
|
|
67
|
+
.dialog-chooser-thumbnail .file-icon-container {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
}
|
|
72
|
+
.dialog-chooser-thumbnail .file-icon-container :global(svg) {
|
|
73
|
+
width: 100%;
|
|
74
|
+
height: 100%;
|
|
75
|
+
}
|
|
65
76
|
.dialog-chooser-thumbnail button {
|
|
66
77
|
position: absolute;
|
|
67
78
|
left: 0;
|
|
@@ -164,13 +164,13 @@
|
|
|
164
164
|
if ($store.selected.size <= 1) dispatch('choose', item)
|
|
165
165
|
}
|
|
166
166
|
function onDragStart (e: DragEvent) {
|
|
167
|
-
userWantsCopy = e.
|
|
167
|
+
userWantsCopy = e.ctrlKey || e.metaKey
|
|
168
168
|
e.dataTransfer!.effectAllowed = 'copyMove'
|
|
169
169
|
e.dataTransfer!.setData('text/plain', item.id)
|
|
170
170
|
store.dragStart(item)
|
|
171
171
|
}
|
|
172
172
|
function onDragOver (e: DragEvent) {
|
|
173
|
-
userWantsCopy = e.
|
|
173
|
+
userWantsCopy = e.ctrlKey || e.metaKey
|
|
174
174
|
if (dropZone) {
|
|
175
175
|
e.preventDefault()
|
|
176
176
|
e.dataTransfer!.dropEffect = store.dropEffect(item, false, userWantsCopy)
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
return !dropZone
|
|
179
179
|
}
|
|
180
180
|
function onDragOverAbove (e: DragEvent) {
|
|
181
|
-
userWantsCopy = e.
|
|
181
|
+
userWantsCopy = e.ctrlKey || e.metaKey
|
|
182
182
|
if (dropAbove) {
|
|
183
183
|
e.preventDefault()
|
|
184
184
|
e.dataTransfer!.dropEffect = store.dropEffect(item, true, userWantsCopy)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dosgato/dialog",
|
|
3
3
|
"description": "A component library for building forms that edit a JSON document.",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.5",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"prepublishOnly": "svelte-package",
|
|
7
7
|
"dev": "vite dev --force",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"txstate-utils": "^1.8.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@sveltejs/adapter-auto": "^
|
|
35
|
+
"@sveltejs/adapter-auto": "^7.0.0",
|
|
36
36
|
"@sveltejs/kit": "^2.0.1",
|
|
37
37
|
"@sveltejs/package": "^2.0.1",
|
|
38
38
|
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|