@brftech/filex-core 0.40.0 → 0.41.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/README.md +32 -25
- package/dist/ArchiveViewer-DxxjhYZD.js +124 -0
- package/dist/ArchiveViewer-DxxjhYZD.js.map +1 -0
- package/dist/CsvViewer-CyuwENI_.js +141 -0
- package/dist/CsvViewer-CyuwENI_.js.map +1 -0
- package/dist/{DrawioViewer-BNALOB04.js → DrawioViewer-BIYsTX97.js} +48 -42
- package/dist/DrawioViewer-BIYsTX97.js.map +1 -0
- package/dist/EpubViewer-K9PjCMmS.js +163 -0
- package/dist/EpubViewer-K9PjCMmS.js.map +1 -0
- package/dist/IpynbViewer-DYU6Zgz2.js +184 -0
- package/dist/IpynbViewer-DYU6Zgz2.js.map +1 -0
- package/dist/MermaidViewer-Bp73aId8.js +137 -0
- package/dist/MermaidViewer-Bp73aId8.js.map +1 -0
- package/dist/PsdViewer-C2eLivw1.js +122 -0
- package/dist/PsdViewer-C2eLivw1.js.map +1 -0
- package/dist/TiffViewer-DsHg5gcz.js +142 -0
- package/dist/TiffViewer-DsHg5gcz.js.map +1 -0
- package/dist/Viewer3D-C_dc2HN9.js +75 -0
- package/dist/Viewer3D-C_dc2HN9.js.map +1 -0
- package/dist/filex-core.js +218 -20361
- package/dist/filex-core.js.map +1 -1
- package/dist/filex-core.umd.cjs +61 -62
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index-vxMZpYb-.js +26700 -0
- package/dist/index-vxMZpYb-.js.map +1 -0
- package/dist/index.d.ts +4212 -862
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +2154 -506
- package/src/components/AdvancedSearch.vue +870 -0
- package/src/components/Breadcrumb.vue +191 -19
- package/src/components/CommandPalette.vue +66 -22
- package/src/components/ConnectionsPanel.vue +6 -2
- package/src/components/ContextMenu.vue +39 -2
- package/src/components/EncryptedFolderModal.vue +31 -25
- package/src/components/FilePane.vue +951 -0
- package/src/components/FilterBar.vue +568 -72
- package/src/components/GalleryView.vue +172 -56
- package/src/components/GridView.vue +331 -82
- package/src/components/HomeView.vue +331 -0
- package/src/components/InspectorPanel.vue +296 -53
- package/src/components/ListView.vue +1173 -194
- package/src/components/NFSExportsPanel.vue +7 -4
- package/src/components/OnboardingTour.vue +156 -33
- package/src/components/QuickLook.vue +26 -1
- package/src/components/RecentlyOpened.vue +17 -2
- package/src/components/S3KeysPanel.vue +7 -4
- package/src/components/SSHKeysPanel.vue +7 -4
- package/src/components/ShortcutSettings.vue +6 -2
- package/src/components/ShortcutsHelp.vue +4 -1
- package/src/components/SideNav.vue +206 -140
- package/src/components/StorageFields.vue +19 -1
- package/src/components/TabBar.vue +87 -1
- package/src/components/TagPicker.vue +77 -6
- package/src/components/ThemeGallery.vue +13 -198
- package/src/components/ThemePalette.vue +244 -0
- package/src/components/TimeZoneDialog.vue +135 -0
- package/src/components/TimeZonePicker.vue +696 -0
- package/src/components/TokensPanel.vue +35 -14
- package/src/components/Toolbar.vue +953 -465
- package/src/components/ViewSwitcher.vue +9 -4
- package/src/composables/useConnections.ts +35 -4
- package/src/composables/useExplorerTimeZone.ts +89 -0
- package/src/composables/useFileApi.ts +53 -4
- package/src/composables/useLocale.ts +382 -26
- package/src/composables/useRowTouch.ts +95 -0
- package/src/index.ts +177 -3
- package/src/lib/actionIcons.ts +330 -0
- package/src/lib/advSearch.ts +162 -0
- package/src/lib/dateGroups.ts +197 -0
- package/src/lib/destinationTree.ts +235 -0
- package/src/lib/downloadSelection.ts +154 -0
- package/src/lib/fileFilters.ts +319 -12
- package/src/lib/fileIcons.ts +299 -5
- package/src/lib/filePreview.ts +605 -0
- package/src/lib/listing.ts +72 -0
- package/src/lib/shareTtl.ts +31 -2
- package/src/lib/sortOrder.ts +459 -0
- package/src/lib/themes.ts +151 -116
- package/src/lib/timezone.ts +501 -0
- package/src/lib/uiProfile.ts +82 -0
- package/src/lib/viewPrefs.ts +1036 -0
- package/src/locales/en.ts +416 -26
- package/src/locales/tr.ts +409 -26
- package/src/modals/ConvertModal.vue +23 -3
- package/src/modals/DestinationPickerModal.vue +341 -0
- package/src/modals/Modal.vue +18 -4
- package/src/modals/NewDocumentModal.vue +525 -0
- package/src/modals/NewFolderModal.vue +15 -9
- package/src/modals/PermissionsModal.vue +557 -476
- package/src/modals/PreviewModal.vue +516 -156
- package/src/styles/base.css +5409 -8
- package/src/styles/variables.css +139 -65
- package/src/types/Connections.ts +9 -0
- package/src/types/ExplorerConfig.ts +144 -39
- package/src/types/FileNode.ts +28 -0
- package/src/viewers/ArchiveViewer.vue +46 -8
- package/src/viewers/CsvViewer.vue +19 -2
- package/src/viewers/DrawioViewer.vue +12 -1
- package/src/viewers/EpubViewer.vue +50 -6
- package/src/viewers/IpynbViewer.vue +19 -2
- package/src/viewers/MermaidViewer.vue +20 -3
- package/src/viewers/PdfViewer.vue +24 -4
- package/src/viewers/PsdViewer.vue +28 -4
- package/src/viewers/TiffViewer.vue +19 -2
- package/src/viewers/Viewer3D.vue +36 -2
- package/dist/ArchiveViewer-Cdfv9LFC.js +0 -99
- package/dist/ArchiveViewer-Cdfv9LFC.js.map +0 -1
- package/dist/CsvViewer-CqWeV8VO.js +0 -131
- package/dist/CsvViewer-CqWeV8VO.js.map +0 -1
- package/dist/DrawioViewer-BNALOB04.js.map +0 -1
- package/dist/EpubViewer-BPmlImG7.js +0 -145
- package/dist/EpubViewer-BPmlImG7.js.map +0 -1
- package/dist/IpynbViewer-CDlQmuCV.js +0 -175
- package/dist/IpynbViewer-CDlQmuCV.js.map +0 -1
- package/dist/MermaidViewer-sfZWdR-d.js +0 -127
- package/dist/MermaidViewer-sfZWdR-d.js.map +0 -1
- package/dist/PsdViewer-DwuySZsb.js +0 -112
- package/dist/PsdViewer-DwuySZsb.js.map +0 -1
- package/dist/TiffViewer-BDQ3d3CQ.js +0 -132
- package/dist/TiffViewer-BDQ3d3CQ.js.map +0 -1
- package/dist/Viewer3D-CJJJgkuv.js +0 -60
- package/dist/Viewer3D-CJJJgkuv.js.map +0 -1
- package/src/components/SecondaryPane.vue +0 -447
- package/src/modals/ShareModal.vue +0 -139
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* ThemePalette — the grid of palette cards, and nothing else.
|
|
4
|
+
*
|
|
5
|
+
* Each card is a mini live preview (surface + toolbar chips + sample rows)
|
|
6
|
+
* painted with that palette's own tokens for the currently resolved light/dark
|
|
7
|
+
* mode, so the card shows exactly what you would get. Click emits `select`;
|
|
8
|
+
* the selected card carries a ✓.
|
|
9
|
+
*
|
|
10
|
+
* ⚠ Split out of `ThemeGallery.vue` rather than copied: the appearance
|
|
11
|
+
* controls now have TWO hosts — the explorer's own gallery modal, and the web
|
|
12
|
+
* app's user-settings modal, which cannot mount the gallery because the
|
|
13
|
+
* gallery IS a modal (`modals/Modal.vue`, a fixed full-viewport backdrop) and
|
|
14
|
+
* a dialog inside a dialog is not a settings pane. Duplicating thirty lines of
|
|
15
|
+
* card markup into `web/` would have meant the two pickers drift apart the
|
|
16
|
+
* first time a token is added to a preview — the same split this package
|
|
17
|
+
* exists to prevent. One component, two hosts.
|
|
18
|
+
*
|
|
19
|
+
* Presentational only. Selection state + persistence live in lib/themes.ts,
|
|
20
|
+
* shared across every instance on the page; the host forwards `select` to it.
|
|
21
|
+
*
|
|
22
|
+
* `default` is the first card and IS the reset: applying it clears every
|
|
23
|
+
* override. A host therefore needs no separate "reset" control (the gallery
|
|
24
|
+
* keeps one in its footer because a modal has a footer to keep it in).
|
|
25
|
+
*/
|
|
26
|
+
import type { LocaleCode } from '../types/ExplorerConfig';
|
|
27
|
+
import { useLocale } from '../composables/useLocale';
|
|
28
|
+
import { THEMES, type ThemeDef } from '../lib/themes';
|
|
29
|
+
|
|
30
|
+
const props = defineProps<{
|
|
31
|
+
locale: LocaleCode;
|
|
32
|
+
/** Resolved mode — decides which variant the preview cards paint. */
|
|
33
|
+
dark: boolean;
|
|
34
|
+
/** Currently selected theme id. */
|
|
35
|
+
current: string;
|
|
36
|
+
/** Draw the explanatory line above the grid. Default true. */
|
|
37
|
+
hint?: boolean;
|
|
38
|
+
}>();
|
|
39
|
+
|
|
40
|
+
const emit = defineEmits<{ (e: 'select', id: string): void }>();
|
|
41
|
+
|
|
42
|
+
const { t } = useLocale(() => props.locale);
|
|
43
|
+
|
|
44
|
+
/** Inline variables for one card — a self-contained mini `--fe-*` scope,
|
|
45
|
+
* so the preview markup can use the exact same tokens the real UI uses. */
|
|
46
|
+
function cardVars(th: ThemeDef): Record<string, string> {
|
|
47
|
+
return props.dark ? th.dark : th.light;
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<p v-if="hint !== false" class="fe-themes__hint">{{ t('theme.hint') }}</p>
|
|
53
|
+
<div class="fe-themes" role="listbox" :aria-label="t('theme.title')">
|
|
54
|
+
<button
|
|
55
|
+
v-for="th in THEMES"
|
|
56
|
+
:key="th.id"
|
|
57
|
+
type="button"
|
|
58
|
+
class="fe-themecard"
|
|
59
|
+
:class="{ 'is-selected': th.id === current }"
|
|
60
|
+
role="option"
|
|
61
|
+
:aria-selected="th.id === current"
|
|
62
|
+
:data-theme-id="th.id"
|
|
63
|
+
:style="cardVars(th)"
|
|
64
|
+
@click="emit('select', th.id)"
|
|
65
|
+
>
|
|
66
|
+
<span class="fe-themecard__preview" aria-hidden="true">
|
|
67
|
+
<span class="fe-themecard__topbar">
|
|
68
|
+
<span class="fe-themecard__chip fe-themecard__chip--primary"></span>
|
|
69
|
+
<span class="fe-themecard__chip"></span>
|
|
70
|
+
<span class="fe-themecard__chip fe-themecard__chip--ghost"></span>
|
|
71
|
+
</span>
|
|
72
|
+
<span class="fe-themecard__row">
|
|
73
|
+
<span class="fe-themecard__dot"></span>
|
|
74
|
+
<span class="fe-themecard__line" style="width: 62%"></span>
|
|
75
|
+
</span>
|
|
76
|
+
<span class="fe-themecard__row fe-themecard__row--selected">
|
|
77
|
+
<span class="fe-themecard__dot fe-themecard__dot--primary"></span>
|
|
78
|
+
<span class="fe-themecard__line" style="width: 44%"></span>
|
|
79
|
+
</span>
|
|
80
|
+
<span class="fe-themecard__row">
|
|
81
|
+
<span class="fe-themecard__dot fe-themecard__dot--muted"></span>
|
|
82
|
+
<span class="fe-themecard__line fe-themecard__line--muted" style="width: 74%"></span>
|
|
83
|
+
</span>
|
|
84
|
+
</span>
|
|
85
|
+
<span class="fe-themecard__name">
|
|
86
|
+
{{ t(th.nameKey) }}
|
|
87
|
+
<span v-if="th.id === current" class="fe-themecard__check" :title="t('theme.selected')">✓</span>
|
|
88
|
+
</span>
|
|
89
|
+
</button>
|
|
90
|
+
</div>
|
|
91
|
+
</template>
|
|
92
|
+
|
|
93
|
+
<!-- Deliberately NOT scoped: the webcomponent injects the CORE package's
|
|
94
|
+
built style.css, whose data-v scope ids don't match the ids the
|
|
95
|
+
webcomponent's own compile assigns (verified: fe-themecard got
|
|
96
|
+
data-v-99a95ed7 in core vs data-v-5d4cc1fd in the wc bundle) — scoped
|
|
97
|
+
rules would silently not apply in embeds. Class names are fe-*
|
|
98
|
+
namespaced, so global is collision-safe (same approach as base.css). -->
|
|
99
|
+
<style>
|
|
100
|
+
.fe-themes__hint {
|
|
101
|
+
margin: 0 0 12px;
|
|
102
|
+
font-size: 12.5px;
|
|
103
|
+
color: var(--fe-text-muted);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.fe-themes {
|
|
107
|
+
display: grid;
|
|
108
|
+
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
109
|
+
gap: 12px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Each card is its own mini token scope (inline --fe-* vars from the theme
|
|
113
|
+
definition), so every color below refers to the CARD's theme — not the
|
|
114
|
+
active page theme. */
|
|
115
|
+
.fe-themecard {
|
|
116
|
+
display: flex;
|
|
117
|
+
flex-direction: column;
|
|
118
|
+
padding: 0;
|
|
119
|
+
border: 2px solid var(--fe-border-strong);
|
|
120
|
+
border-radius: var(--fe-radius, 8px);
|
|
121
|
+
background: var(--fe-bg);
|
|
122
|
+
cursor: pointer;
|
|
123
|
+
overflow: hidden;
|
|
124
|
+
text-align: left;
|
|
125
|
+
transition: transform 0.12s ease, box-shadow 0.12s ease;
|
|
126
|
+
font: inherit;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.fe-themecard:hover {
|
|
130
|
+
transform: translateY(-2px);
|
|
131
|
+
box-shadow: var(--fe-shadow-sm, 0 2px 6px rgba(15, 23, 42, 0.08));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.fe-themecard:focus-visible {
|
|
135
|
+
outline: 2px solid var(--fe-primary);
|
|
136
|
+
outline-offset: 2px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.fe-themecard.is-selected {
|
|
140
|
+
border-color: var(--fe-primary);
|
|
141
|
+
box-shadow: 0 0 0 2px var(--fe-primary);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.fe-themecard__preview {
|
|
145
|
+
display: flex;
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
gap: 6px;
|
|
148
|
+
padding: 10px;
|
|
149
|
+
background: var(--fe-bg);
|
|
150
|
+
border-bottom: 1px solid var(--fe-border);
|
|
151
|
+
min-height: 84px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.fe-themecard__topbar {
|
|
155
|
+
display: flex;
|
|
156
|
+
gap: 4px;
|
|
157
|
+
margin-bottom: 2px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.fe-themecard__chip {
|
|
161
|
+
height: 8px;
|
|
162
|
+
width: 22px;
|
|
163
|
+
border-radius: 4px;
|
|
164
|
+
background: var(--fe-bg-hover);
|
|
165
|
+
border: 1px solid var(--fe-border);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.fe-themecard__chip--primary {
|
|
169
|
+
background: var(--fe-primary);
|
|
170
|
+
border-color: var(--fe-primary);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.fe-themecard__chip--ghost {
|
|
174
|
+
background: var(--fe-bg-elev);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.fe-themecard__row {
|
|
178
|
+
display: flex;
|
|
179
|
+
align-items: center;
|
|
180
|
+
gap: 6px;
|
|
181
|
+
padding: 3px 4px;
|
|
182
|
+
border-radius: 4px;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.fe-themecard__row--selected {
|
|
186
|
+
background: var(--fe-bg-selected);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.fe-themecard__dot {
|
|
190
|
+
width: 8px;
|
|
191
|
+
height: 8px;
|
|
192
|
+
border-radius: 50%;
|
|
193
|
+
background: var(--fe-text);
|
|
194
|
+
opacity: 0.75;
|
|
195
|
+
flex: none;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.fe-themecard__dot--primary {
|
|
199
|
+
background: var(--fe-primary);
|
|
200
|
+
opacity: 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.fe-themecard__dot--muted {
|
|
204
|
+
background: var(--fe-text-muted);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.fe-themecard__line {
|
|
208
|
+
height: 6px;
|
|
209
|
+
border-radius: 3px;
|
|
210
|
+
background: var(--fe-text);
|
|
211
|
+
opacity: 0.55;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.fe-themecard__line--muted {
|
|
215
|
+
background: var(--fe-text-muted);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.fe-themecard__name {
|
|
219
|
+
display: flex;
|
|
220
|
+
align-items: center;
|
|
221
|
+
justify-content: space-between;
|
|
222
|
+
gap: 8px;
|
|
223
|
+
padding: 8px 10px;
|
|
224
|
+
font-size: 12.5px;
|
|
225
|
+
font-weight: 600;
|
|
226
|
+
color: var(--fe-text);
|
|
227
|
+
background: var(--fe-bg-elev);
|
|
228
|
+
font-family: var(--fe-font, inherit);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.fe-themecard__check {
|
|
232
|
+
flex: none;
|
|
233
|
+
width: 18px;
|
|
234
|
+
height: 18px;
|
|
235
|
+
display: inline-flex;
|
|
236
|
+
align-items: center;
|
|
237
|
+
justify-content: center;
|
|
238
|
+
border-radius: 50%;
|
|
239
|
+
background: var(--fe-primary);
|
|
240
|
+
color: var(--fe-text-on-primary);
|
|
241
|
+
font-size: 11px;
|
|
242
|
+
line-height: 1;
|
|
243
|
+
}
|
|
244
|
+
</style>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* TimeZoneDialog — zaman:z3, the embed's own time-zone setting.
|
|
4
|
+
*
|
|
5
|
+
* The owner, 2026-09-14: *"gömme için ayar bırakmamız lazım; basitçe tarayıcıda
|
|
6
|
+
* tutulan bir ayar olması lazım."* An embed has no user-settings dialog — its
|
|
7
|
+
* "⋯" menu (theme, density, shortcuts) is where its settings live — so the menu
|
|
8
|
+
* opens this, and a pick is stored in THIS browser (`lib/timezone`, tier
|
|
9
|
+
* `viewer`). Nothing is sent anywhere: an embed's credential may be shared by
|
|
10
|
+
* every visitor, and there is no account of the visitor's to write to.
|
|
11
|
+
*
|
|
12
|
+
* ⚠ The picker is `TimeZonePicker`, the same component the web app's settings
|
|
13
|
+
* modal mounts. Not a second one.
|
|
14
|
+
*
|
|
15
|
+
* ⚠ The first row is "use the default", and it names what the default IS on
|
|
16
|
+
* this page — the zone the embedder configured, the account's zone, or this
|
|
17
|
+
* device's — by asking the resolver with the viewer tier blanked. A reset
|
|
18
|
+
* whose outcome you have to guess is not a reset.
|
|
19
|
+
*/
|
|
20
|
+
import { computed, inject, onBeforeUnmount, ref, watch } from 'vue';
|
|
21
|
+
import type { LocaleCode, ThemeMode } from '../types/ExplorerConfig';
|
|
22
|
+
import { formatInstant, useLocale } from '../composables/useLocale';
|
|
23
|
+
import {
|
|
24
|
+
EXPLORER_CLOCK,
|
|
25
|
+
deviceTimeZone,
|
|
26
|
+
resolveTimeZone,
|
|
27
|
+
resolvedTimeZoneFor,
|
|
28
|
+
setViewerTimeZone,
|
|
29
|
+
timeZoneSourcesFor,
|
|
30
|
+
viewerTimeZone,
|
|
31
|
+
} from '../lib/timezone';
|
|
32
|
+
import Modal from '../modals/Modal.vue';
|
|
33
|
+
import TimeZonePicker from './TimeZonePicker.vue';
|
|
34
|
+
|
|
35
|
+
const props = defineProps<{
|
|
36
|
+
open: boolean;
|
|
37
|
+
locale: LocaleCode;
|
|
38
|
+
theme?: ThemeMode;
|
|
39
|
+
}>();
|
|
40
|
+
|
|
41
|
+
const emit = defineEmits<{ (e: 'close'): void }>();
|
|
42
|
+
|
|
43
|
+
const { t } = useLocale(() => props.locale);
|
|
44
|
+
|
|
45
|
+
const inputId = `fe-tzdialog-${Math.random().toString(36).slice(2, 9)}`;
|
|
46
|
+
|
|
47
|
+
// This explorer's clock, not the page's newest (lib/timezone, timeZoneSourcesFor).
|
|
48
|
+
const clock = inject(EXPLORER_CLOCK, undefined);
|
|
49
|
+
const fallback = computed(() => resolveTimeZone({ ...timeZoneSourcesFor(clock), viewer: '' }));
|
|
50
|
+
const inForce = computed(() => resolvedTimeZoneFor(clock));
|
|
51
|
+
const inForceZone = computed(() => inForce.value.zone ?? deviceTimeZone());
|
|
52
|
+
|
|
53
|
+
/** "Right now in Asia/Tokyo: 14:05:09" — the fastest way to see a wrong pick. */
|
|
54
|
+
const now = ref('');
|
|
55
|
+
let timer: ReturnType<typeof setInterval> | undefined;
|
|
56
|
+
function refresh(): void {
|
|
57
|
+
// Through the shared formatter, which reads the RESOLVED zone — the readout
|
|
58
|
+
// must say what every date on the page is actually drawn in, not what this
|
|
59
|
+
// dialog thinks it chose.
|
|
60
|
+
now.value = formatInstant(
|
|
61
|
+
new Date(),
|
|
62
|
+
props.locale,
|
|
63
|
+
{
|
|
64
|
+
hour: '2-digit',
|
|
65
|
+
minute: '2-digit',
|
|
66
|
+
second: '2-digit',
|
|
67
|
+
},
|
|
68
|
+
clock,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
watch(
|
|
72
|
+
() => props.open,
|
|
73
|
+
(open) => {
|
|
74
|
+
if (timer) clearInterval(timer);
|
|
75
|
+
timer = undefined;
|
|
76
|
+
if (!open) return;
|
|
77
|
+
refresh();
|
|
78
|
+
timer = setInterval(refresh, 1000);
|
|
79
|
+
},
|
|
80
|
+
{ immediate: true },
|
|
81
|
+
);
|
|
82
|
+
watch(inForce, refresh);
|
|
83
|
+
onBeforeUnmount(() => {
|
|
84
|
+
if (timer) clearInterval(timer);
|
|
85
|
+
});
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<template>
|
|
89
|
+
<Modal :open="open" :title="t('tz.title')" size="md" :theme="theme" @close="emit('close')">
|
|
90
|
+
<div class="fe-tzdialog" data-testid="fe-tz-dialog">
|
|
91
|
+
<label class="fe-tzdialog__label" :for="inputId">{{ t('tz.label') }}</label>
|
|
92
|
+
<TimeZonePicker
|
|
93
|
+
:input-id="inputId"
|
|
94
|
+
:locale="locale"
|
|
95
|
+
:model-value="viewerTimeZone()"
|
|
96
|
+
:fallback="fallback"
|
|
97
|
+
:label="t('tz.label')"
|
|
98
|
+
testid="fe-tz"
|
|
99
|
+
@update:model-value="setViewerTimeZone"
|
|
100
|
+
/>
|
|
101
|
+
<p class="fe-tzdialog__hint">{{ t('tz.hint') }}</p>
|
|
102
|
+
<p class="fe-tzdialog__now" data-testid="fe-tz-now" :data-tier="inForce.tier">
|
|
103
|
+
{{ t('tz.now', { zone: inForceZone, time: now }) }}
|
|
104
|
+
</p>
|
|
105
|
+
</div>
|
|
106
|
+
</Modal>
|
|
107
|
+
</template>
|
|
108
|
+
|
|
109
|
+
<!-- Not scoped — see TimeZonePicker.vue / ThemePalette.vue. -->
|
|
110
|
+
<style>
|
|
111
|
+
/* Tall enough that the zone list opens downward inside the dialog instead of
|
|
112
|
+
being clipped by the body's own scroll box. */
|
|
113
|
+
.fe-tzdialog {
|
|
114
|
+
display: flex;
|
|
115
|
+
flex-direction: column;
|
|
116
|
+
gap: var(--fe-gap-sm);
|
|
117
|
+
min-height: 340px;
|
|
118
|
+
}
|
|
119
|
+
.fe-tzdialog__label {
|
|
120
|
+
font-size: var(--fe-text-sm);
|
|
121
|
+
font-weight: 500;
|
|
122
|
+
color: var(--fe-text);
|
|
123
|
+
}
|
|
124
|
+
.fe-tzdialog__hint,
|
|
125
|
+
.fe-tzdialog__now {
|
|
126
|
+
margin: 0;
|
|
127
|
+
font-size: var(--fe-text-xs);
|
|
128
|
+
line-height: 1.45;
|
|
129
|
+
color: var(--fe-text-muted);
|
|
130
|
+
}
|
|
131
|
+
.fe-tzdialog__now {
|
|
132
|
+
font-size: var(--fe-text-sm);
|
|
133
|
+
font-variant-numeric: tabular-nums;
|
|
134
|
+
}
|
|
135
|
+
</style>
|