@brftech/filex-core 0.4.2 → 0.6.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/{ArchiveViewer-B_0IQExT.js → ArchiveViewer-EGgNqLPE.js} +2 -2
- package/dist/{ArchiveViewer-B_0IQExT.js.map → ArchiveViewer-EGgNqLPE.js.map} +1 -1
- package/dist/{CsvViewer-maMfUy5i.js → CsvViewer-BvriJ42M.js} +2 -2
- package/dist/{CsvViewer-maMfUy5i.js.map → CsvViewer-BvriJ42M.js.map} +1 -1
- package/dist/{DrawioViewer-DP_pXI00.js → DrawioViewer-B_bSPsWX.js} +2 -2
- package/dist/{DrawioViewer-DP_pXI00.js.map → DrawioViewer-B_bSPsWX.js.map} +1 -1
- package/dist/{EpubViewer-DSaqGnVx.js → EpubViewer-hl68h4gG.js} +2 -2
- package/dist/{EpubViewer-DSaqGnVx.js.map → EpubViewer-hl68h4gG.js.map} +1 -1
- package/dist/{IpynbViewer-BmdBanF1.js → IpynbViewer-lhwOjXK7.js} +2 -2
- package/dist/{IpynbViewer-BmdBanF1.js.map → IpynbViewer-lhwOjXK7.js.map} +1 -1
- package/dist/{MermaidViewer-Drlkl0Z5.js → MermaidViewer-H2Z6AYXg.js} +2 -2
- package/dist/{MermaidViewer-Drlkl0Z5.js.map → MermaidViewer-H2Z6AYXg.js.map} +1 -1
- package/dist/{PsdViewer-gftUJrsm.js → PsdViewer-Bsk-11yH.js} +2 -2
- package/dist/{PsdViewer-gftUJrsm.js.map → PsdViewer-Bsk-11yH.js.map} +1 -1
- package/dist/{TiffViewer-0Uv0-Lxh.js → TiffViewer-D-j9vycG.js} +2 -2
- package/dist/{TiffViewer-0Uv0-Lxh.js.map → TiffViewer-D-j9vycG.js.map} +1 -1
- package/dist/{Viewer3D-Gu3kUpxV.js → Viewer3D-Bcr70rTe.js} +2 -2
- package/dist/{Viewer3D-Gu3kUpxV.js.map → Viewer3D-Bcr70rTe.js.map} +1 -1
- package/dist/filex-core.js +50 -25
- package/dist/filex-core.umd.cjs +59 -58
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index-C_ZSnk_I.js +11109 -0
- package/dist/index-C_ZSnk_I.js.map +1 -0
- package/dist/index.d.ts +597 -4
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +636 -15
- package/src/components/CommandPalette.vue +24 -1
- package/src/components/ContextMenu.vue +114 -8
- package/src/components/GalleryView.vue +214 -0
- package/src/components/GridView.vue +34 -1
- package/src/components/InspectorPanel.vue +170 -0
- package/src/components/ListView.vue +40 -6
- package/src/components/OnboardingTour.vue +332 -0
- package/src/components/OperationsCenter.vue +284 -0
- package/src/components/PendingOpsTray.vue +50 -66
- package/src/components/QuickLook.vue +142 -0
- package/src/components/SecondaryPane.vue +411 -0
- package/src/components/ShortcutSettings.vue +270 -0
- package/src/components/ShortcutsHelp.vue +30 -9
- package/src/components/TabBar.vue +174 -0
- package/src/components/ThemeGallery.vue +253 -0
- package/src/components/Toolbar.vue +75 -11
- package/src/components/UploadProgress.vue +49 -57
- package/src/composables/useFileApi.ts +53 -0
- package/src/composables/useKeyboardShortcuts.ts +340 -137
- package/src/composables/useOperations.ts +291 -0
- package/src/composables/useTabs.ts +222 -0
- package/src/index.ts +51 -0
- package/src/lib/dragGhost.ts +41 -0
- package/src/lib/themes.ts +505 -0
- package/src/locales/en.ts +125 -0
- package/src/locales/tr.ts +125 -0
- package/src/modals/Modal.vue +41 -5
- package/src/styles/base.css +1210 -0
- package/src/types/FileNode.ts +1 -1
- package/dist/index-BLocdbmc.js +0 -7993
- package/dist/index-BLocdbmc.js.map +0 -1
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* ShortcutsHelp — "?" cheat-sheet modal.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* wiring:c2 — renders the live shortcut registry (useShortcutList) so
|
|
6
|
+
* user remaps show up here too, grouped by section. Unbound actions are
|
|
7
|
+
* listed with an "unassigned" chip. The footer carries a "Customize"
|
|
8
|
+
* button that asks the host to open ShortcutSettings. Modal.vue
|
|
9
|
+
* supplies Esc handling, backdrop, focus and the close button.
|
|
8
10
|
*/
|
|
9
11
|
import { computed } from 'vue';
|
|
10
12
|
import type { LocaleCode } from '../types/ExplorerConfig';
|
|
11
13
|
import { useLocale } from '../composables/useLocale';
|
|
12
|
-
import {
|
|
14
|
+
import { useShortcutList, type ShortcutView } from '../composables/useKeyboardShortcuts';
|
|
13
15
|
import Modal from '../modals/Modal.vue';
|
|
14
16
|
|
|
15
17
|
const props = defineProps<{
|
|
@@ -19,14 +21,17 @@ const props = defineProps<{
|
|
|
19
21
|
|
|
20
22
|
const emit = defineEmits<{
|
|
21
23
|
(e: 'close'): void;
|
|
24
|
+
(e: 'customize'): void;
|
|
22
25
|
}>();
|
|
23
26
|
|
|
24
27
|
const { t } = useLocale(() => props.locale);
|
|
25
28
|
|
|
29
|
+
const list = useShortcutList();
|
|
30
|
+
|
|
26
31
|
const groups = computed(() => {
|
|
27
32
|
const order: string[] = [];
|
|
28
|
-
const map = new Map<string,
|
|
29
|
-
for (const s of
|
|
33
|
+
const map = new Map<string, ShortcutView[]>();
|
|
34
|
+
for (const s of list.value) {
|
|
30
35
|
if (!map.has(s.groupKey)) {
|
|
31
36
|
map.set(s.groupKey, []);
|
|
32
37
|
order.push(s.groupKey);
|
|
@@ -43,17 +48,33 @@ const groups = computed(() => {
|
|
|
43
48
|
<section v-for="g in groups" :key="g.key" class="fe-shortcuts__group">
|
|
44
49
|
<h3 class="fe-shortcuts__heading">{{ t(g.key) }}</h3>
|
|
45
50
|
<div class="fe-shortcuts__table">
|
|
46
|
-
<div v-for="s in g.items" :key="s.
|
|
51
|
+
<div v-for="s in g.items" :key="s.id" class="fe-shortcuts__row">
|
|
47
52
|
<span class="fe-shortcuts__keys">
|
|
48
|
-
<template v-
|
|
53
|
+
<template v-if="s.keys.length === 0">
|
|
54
|
+
<span class="fe-shortcuts__unbound">{{ t('shortcuts.settings.unbound') }}</span>
|
|
55
|
+
</template>
|
|
56
|
+
<template v-for="(combo, i) in s.keys" v-else :key="combo">
|
|
49
57
|
<kbd class="fe-kbd">{{ combo }}</kbd>
|
|
50
58
|
<span v-if="i < s.keys.length - 1" class="fe-shortcuts__or" aria-hidden="true">/</span>
|
|
51
59
|
</template>
|
|
52
60
|
</span>
|
|
53
|
-
<span class="fe-shortcuts__desc">
|
|
61
|
+
<span class="fe-shortcuts__desc">
|
|
62
|
+
{{ t(s.labelKey) }}
|
|
63
|
+
<span
|
|
64
|
+
v-if="s.overridden"
|
|
65
|
+
class="fe-shortcuts__custom"
|
|
66
|
+
:title="t('shortcuts.settings.customized')"
|
|
67
|
+
>●</span>
|
|
68
|
+
</span>
|
|
54
69
|
</div>
|
|
55
70
|
</div>
|
|
56
71
|
</section>
|
|
57
72
|
</div>
|
|
73
|
+
<template #actions>
|
|
74
|
+
<button type="button" class="fe-btn fe-btn--primary" @click="emit('customize')">
|
|
75
|
+
⌨ {{ t('shortcuts.customize') }}
|
|
76
|
+
</button>
|
|
77
|
+
<button type="button" class="fe-btn" @click="emit('close')">{{ t('viewer.close') }}</button>
|
|
78
|
+
</template>
|
|
58
79
|
</Modal>
|
|
59
80
|
</template>
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* TabBar — wiring:d1 sekme şeridi.
|
|
4
|
+
*
|
|
5
|
+
* Thin strip that sits ABOVE the toolbar. The host renders it only when
|
|
6
|
+
* 2+ tabs exist (embed pixel-parity: a single tab shows nothing at all).
|
|
7
|
+
* Purely presentational: every mutation is an emit; useTabs owns state.
|
|
8
|
+
*
|
|
9
|
+
* Interactions:
|
|
10
|
+
* click tab → select
|
|
11
|
+
* × / middle-tık → close
|
|
12
|
+
* + → new tab (clone of the current location)
|
|
13
|
+
* drag → reorder (HTML5 DnD; live-swap while dragging)
|
|
14
|
+
* ⫿ (split) → toggle the active tab's secondary pane
|
|
15
|
+
*
|
|
16
|
+
* UNSCOPED styles (`fe-tabs*` in base.css) — the webcomponent build
|
|
17
|
+
* cannot carry scoped data-v hashes (v0.5.0 discovery).
|
|
18
|
+
*/
|
|
19
|
+
import { ref } from 'vue';
|
|
20
|
+
import type { LocaleCode } from '../types/ExplorerConfig';
|
|
21
|
+
import { useLocale } from '../composables/useLocale';
|
|
22
|
+
|
|
23
|
+
export interface TabItem {
|
|
24
|
+
id: string;
|
|
25
|
+
label: string;
|
|
26
|
+
/** true → the tab carries a split pane (small accent dot). */
|
|
27
|
+
split: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const props = defineProps<{
|
|
31
|
+
tabs: TabItem[];
|
|
32
|
+
activeId: string;
|
|
33
|
+
locale: LocaleCode;
|
|
34
|
+
/** false → the split toggle is hidden (narrow embeds). */
|
|
35
|
+
splitEnabled?: boolean;
|
|
36
|
+
/** true → the split toggle renders pressed (active tab has a pane). */
|
|
37
|
+
splitActive?: boolean;
|
|
38
|
+
}>();
|
|
39
|
+
|
|
40
|
+
const emit = defineEmits<{
|
|
41
|
+
(e: 'select', id: string): void;
|
|
42
|
+
(e: 'close', id: string): void;
|
|
43
|
+
(e: 'new'): void;
|
|
44
|
+
(e: 'reorder', from: number, to: number): void;
|
|
45
|
+
(e: 'toggle-split'): void;
|
|
46
|
+
}>();
|
|
47
|
+
|
|
48
|
+
const { t } = useLocale(() => props.locale);
|
|
49
|
+
|
|
50
|
+
// ---- drag-sort (HTML5 DnD, live swap) ------------------------------
|
|
51
|
+
const dragIndex = ref<number | null>(null);
|
|
52
|
+
const overIndex = ref<number | null>(null);
|
|
53
|
+
|
|
54
|
+
function onDragStart(i: number, ev: DragEvent) {
|
|
55
|
+
dragIndex.value = i;
|
|
56
|
+
if (ev.dataTransfer) {
|
|
57
|
+
ev.dataTransfer.effectAllowed = 'move';
|
|
58
|
+
// Firefox requires SOME payload for a drag to start. Deliberately not
|
|
59
|
+
// the internal file-DnD MIME, so the explorer's drop surfaces ignore it.
|
|
60
|
+
ev.dataTransfer.setData('text/plain', props.tabs[i]?.id ?? '');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function onDragOver(i: number, ev: DragEvent) {
|
|
65
|
+
if (dragIndex.value === null) return;
|
|
66
|
+
ev.preventDefault();
|
|
67
|
+
ev.stopPropagation();
|
|
68
|
+
if (ev.dataTransfer) ev.dataTransfer.dropEffect = 'move';
|
|
69
|
+
overIndex.value = i;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function onDragLeave(i: number) {
|
|
73
|
+
if (overIndex.value === i) overIndex.value = null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function onDrop(i: number, ev: DragEvent) {
|
|
77
|
+
if (dragIndex.value === null) return;
|
|
78
|
+
ev.preventDefault();
|
|
79
|
+
ev.stopPropagation();
|
|
80
|
+
if (dragIndex.value !== i) emit('reorder', dragIndex.value, i);
|
|
81
|
+
dragIndex.value = null;
|
|
82
|
+
overIndex.value = null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function onDragEnd() {
|
|
86
|
+
dragIndex.value = null;
|
|
87
|
+
overIndex.value = null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Middle-click closes a tab (browser convention). The mousedown guard
|
|
91
|
+
// cancels Chromium's autoscroll on the scrollable strip — without it the
|
|
92
|
+
// auxclick event is never generated.
|
|
93
|
+
function onTabMiddleDown(ev: MouseEvent) {
|
|
94
|
+
if (ev.button === 1) ev.preventDefault();
|
|
95
|
+
}
|
|
96
|
+
function onAux(id: string, ev: MouseEvent) {
|
|
97
|
+
if (ev.button !== 1) return;
|
|
98
|
+
ev.preventDefault();
|
|
99
|
+
ev.stopPropagation();
|
|
100
|
+
emit('close', id);
|
|
101
|
+
}
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<template>
|
|
105
|
+
<div class="fe-tabs" role="tablist" :aria-label="t('tabs.strip')">
|
|
106
|
+
<div class="fe-tabs__scroll">
|
|
107
|
+
<div
|
|
108
|
+
v-for="(tab, i) in tabs"
|
|
109
|
+
:key="tab.id"
|
|
110
|
+
class="fe-tabs__tab"
|
|
111
|
+
:class="{
|
|
112
|
+
'is-active': tab.id === activeId,
|
|
113
|
+
'is-dragover': overIndex === i && dragIndex !== null && dragIndex !== i,
|
|
114
|
+
}"
|
|
115
|
+
role="tab"
|
|
116
|
+
:aria-selected="tab.id === activeId ? 'true' : 'false'"
|
|
117
|
+
tabindex="0"
|
|
118
|
+
draggable="true"
|
|
119
|
+
:title="tab.label"
|
|
120
|
+
@click="emit('select', tab.id)"
|
|
121
|
+
@keydown.enter.prevent="emit('select', tab.id)"
|
|
122
|
+
@mousedown="onTabMiddleDown($event)"
|
|
123
|
+
@auxclick="onAux(tab.id, $event)"
|
|
124
|
+
@dragstart="onDragStart(i, $event)"
|
|
125
|
+
@dragover="onDragOver(i, $event)"
|
|
126
|
+
@dragleave="onDragLeave(i)"
|
|
127
|
+
@drop="onDrop(i, $event)"
|
|
128
|
+
@dragend="onDragEnd"
|
|
129
|
+
>
|
|
130
|
+
<span v-if="tab.split" class="fe-tabs__splitdot" aria-hidden="true"></span>
|
|
131
|
+
<span class="fe-tabs__label">{{ tab.label }}</span>
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
class="fe-tabs__close"
|
|
135
|
+
:aria-label="t('tabs.close')"
|
|
136
|
+
:title="t('tabs.close')"
|
|
137
|
+
@click.stop="emit('close', tab.id)"
|
|
138
|
+
>×</button>
|
|
139
|
+
</div>
|
|
140
|
+
<button
|
|
141
|
+
type="button"
|
|
142
|
+
class="fe-tabs__new"
|
|
143
|
+
:aria-label="t('tabs.new')"
|
|
144
|
+
:title="t('tabs.new')"
|
|
145
|
+
@click="emit('new')"
|
|
146
|
+
>+</button>
|
|
147
|
+
</div>
|
|
148
|
+
<button
|
|
149
|
+
v-if="splitEnabled !== false"
|
|
150
|
+
type="button"
|
|
151
|
+
class="fe-tabs__split"
|
|
152
|
+
:class="{ 'is-active': splitActive }"
|
|
153
|
+
:aria-label="splitActive ? t('tabs.split_off') : t('tabs.split')"
|
|
154
|
+
:title="splitActive ? t('tabs.split_off') : t('tabs.split')"
|
|
155
|
+
:aria-pressed="splitActive ? 'true' : 'false'"
|
|
156
|
+
@click="emit('toggle-split')"
|
|
157
|
+
>
|
|
158
|
+
<svg
|
|
159
|
+
class="fe-ficon"
|
|
160
|
+
viewBox="0 0 24 24"
|
|
161
|
+
fill="none"
|
|
162
|
+
stroke="currentColor"
|
|
163
|
+
stroke-width="1.8"
|
|
164
|
+
stroke-linecap="round"
|
|
165
|
+
stroke-linejoin="round"
|
|
166
|
+
aria-hidden="true"
|
|
167
|
+
focusable="false"
|
|
168
|
+
>
|
|
169
|
+
<rect x="3" y="4" width="18" height="16" rx="2" />
|
|
170
|
+
<path d="M12 4v16" />
|
|
171
|
+
</svg>
|
|
172
|
+
</button>
|
|
173
|
+
</div>
|
|
174
|
+
</template>
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* ThemeGallery — wiring:c1 theme picker modal.
|
|
4
|
+
*
|
|
5
|
+
* A grid of theme cards, each rendering a mini live preview (surface +
|
|
6
|
+
* toolbar chips + sample rows) painted with that theme's tokens for the
|
|
7
|
+
* CURRENTLY resolved light/dark mode — so the card shows exactly what
|
|
8
|
+
* you'd get. Click applies instantly (live preview behind the modal) and
|
|
9
|
+
* persists; the selected card carries a ✓. "Varsayılana dön" resets.
|
|
10
|
+
*
|
|
11
|
+
* Presentational only — selection state + persistence live in
|
|
12
|
+
* lib/themes.ts (shared across instances); the parent forwards `select`.
|
|
13
|
+
*/
|
|
14
|
+
import type { LocaleCode, ThemeMode } from '../types/ExplorerConfig';
|
|
15
|
+
import { useLocale } from '../composables/useLocale';
|
|
16
|
+
import { THEMES, DEFAULT_THEME_ID, type ThemeDef } from '../lib/themes';
|
|
17
|
+
import Modal from '../modals/Modal.vue';
|
|
18
|
+
|
|
19
|
+
const props = defineProps<{
|
|
20
|
+
open: boolean;
|
|
21
|
+
locale: LocaleCode;
|
|
22
|
+
/** Host mode (forwarded to Modal so the backdrop themes correctly). */
|
|
23
|
+
theme?: ThemeMode;
|
|
24
|
+
/** Resolved mode — decides which variant the preview cards paint. */
|
|
25
|
+
dark: boolean;
|
|
26
|
+
/** Currently selected theme id. */
|
|
27
|
+
current: string;
|
|
28
|
+
}>();
|
|
29
|
+
|
|
30
|
+
const emit = defineEmits<{
|
|
31
|
+
(e: 'close'): void;
|
|
32
|
+
(e: 'select', id: string): void;
|
|
33
|
+
}>();
|
|
34
|
+
|
|
35
|
+
const { t } = useLocale(() => props.locale);
|
|
36
|
+
|
|
37
|
+
/** Inline variables for one card — a self-contained mini `--fe-*` scope,
|
|
38
|
+
* so the preview markup can use the exact same tokens the real UI uses. */
|
|
39
|
+
function cardVars(th: ThemeDef): Record<string, string> {
|
|
40
|
+
return props.dark ? th.dark : th.light;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const DEFAULT_ID = DEFAULT_THEME_ID;
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<Modal :open="open" :title="t('theme.title')" size="lg" :theme="theme" @close="emit('close')">
|
|
48
|
+
<p class="fe-themes__hint">{{ t('theme.hint') }}</p>
|
|
49
|
+
<div class="fe-themes" role="listbox" :aria-label="t('theme.title')">
|
|
50
|
+
<button
|
|
51
|
+
v-for="th in THEMES"
|
|
52
|
+
:key="th.id"
|
|
53
|
+
type="button"
|
|
54
|
+
class="fe-themecard"
|
|
55
|
+
:class="{ 'is-selected': th.id === current }"
|
|
56
|
+
role="option"
|
|
57
|
+
:aria-selected="th.id === current"
|
|
58
|
+
:style="cardVars(th)"
|
|
59
|
+
@click="emit('select', th.id)"
|
|
60
|
+
>
|
|
61
|
+
<span class="fe-themecard__preview" aria-hidden="true">
|
|
62
|
+
<span class="fe-themecard__topbar">
|
|
63
|
+
<span class="fe-themecard__chip fe-themecard__chip--primary"></span>
|
|
64
|
+
<span class="fe-themecard__chip"></span>
|
|
65
|
+
<span class="fe-themecard__chip fe-themecard__chip--ghost"></span>
|
|
66
|
+
</span>
|
|
67
|
+
<span class="fe-themecard__row">
|
|
68
|
+
<span class="fe-themecard__dot"></span>
|
|
69
|
+
<span class="fe-themecard__line" style="width: 62%"></span>
|
|
70
|
+
</span>
|
|
71
|
+
<span class="fe-themecard__row fe-themecard__row--selected">
|
|
72
|
+
<span class="fe-themecard__dot fe-themecard__dot--primary"></span>
|
|
73
|
+
<span class="fe-themecard__line" style="width: 44%"></span>
|
|
74
|
+
</span>
|
|
75
|
+
<span class="fe-themecard__row">
|
|
76
|
+
<span class="fe-themecard__dot fe-themecard__dot--muted"></span>
|
|
77
|
+
<span class="fe-themecard__line fe-themecard__line--muted" style="width: 74%"></span>
|
|
78
|
+
</span>
|
|
79
|
+
</span>
|
|
80
|
+
<span class="fe-themecard__name">
|
|
81
|
+
{{ t(th.nameKey) }}
|
|
82
|
+
<span v-if="th.id === current" class="fe-themecard__check" :title="t('theme.selected')">✓</span>
|
|
83
|
+
</span>
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
<template #actions>
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
class="fe-btn"
|
|
90
|
+
:disabled="current === DEFAULT_ID"
|
|
91
|
+
@click="emit('select', DEFAULT_ID)"
|
|
92
|
+
>
|
|
93
|
+
{{ t('theme.reset') }}
|
|
94
|
+
</button>
|
|
95
|
+
<button type="button" class="fe-btn fe-btn--primary" @click="emit('close')">
|
|
96
|
+
{{ t('theme.close') }}
|
|
97
|
+
</button>
|
|
98
|
+
</template>
|
|
99
|
+
</Modal>
|
|
100
|
+
</template>
|
|
101
|
+
|
|
102
|
+
<!-- Deliberately NOT scoped: the webcomponent injects the CORE package's
|
|
103
|
+
built style.css, whose data-v scope ids don't match the ids the
|
|
104
|
+
webcomponent's own compile assigns (verified: fe-themecard got
|
|
105
|
+
data-v-99a95ed7 in core vs data-v-5d4cc1fd in the wc bundle) — scoped
|
|
106
|
+
rules would silently not apply in embeds. Class names are fe-*
|
|
107
|
+
namespaced, so global is collision-safe (same approach as base.css). -->
|
|
108
|
+
<style>
|
|
109
|
+
.fe-themes__hint {
|
|
110
|
+
margin: 0 0 12px;
|
|
111
|
+
font-size: 12.5px;
|
|
112
|
+
color: var(--fe-text-muted);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.fe-themes {
|
|
116
|
+
display: grid;
|
|
117
|
+
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
118
|
+
gap: 12px;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Each card is its own mini token scope (inline --fe-* vars from the theme
|
|
122
|
+
definition), so every color below refers to the CARD's theme — not the
|
|
123
|
+
active page theme. */
|
|
124
|
+
.fe-themecard {
|
|
125
|
+
display: flex;
|
|
126
|
+
flex-direction: column;
|
|
127
|
+
padding: 0;
|
|
128
|
+
border: 2px solid var(--fe-border-strong);
|
|
129
|
+
border-radius: var(--fe-radius, 8px);
|
|
130
|
+
background: var(--fe-bg);
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
overflow: hidden;
|
|
133
|
+
text-align: left;
|
|
134
|
+
transition: transform 0.12s ease, box-shadow 0.12s ease;
|
|
135
|
+
font: inherit;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.fe-themecard:hover {
|
|
139
|
+
transform: translateY(-2px);
|
|
140
|
+
box-shadow: var(--fe-shadow-sm, 0 2px 6px rgba(15, 23, 42, 0.08));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.fe-themecard:focus-visible {
|
|
144
|
+
outline: 2px solid var(--fe-primary);
|
|
145
|
+
outline-offset: 2px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.fe-themecard.is-selected {
|
|
149
|
+
border-color: var(--fe-primary);
|
|
150
|
+
box-shadow: 0 0 0 2px var(--fe-primary);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.fe-themecard__preview {
|
|
154
|
+
display: flex;
|
|
155
|
+
flex-direction: column;
|
|
156
|
+
gap: 6px;
|
|
157
|
+
padding: 10px;
|
|
158
|
+
background: var(--fe-bg);
|
|
159
|
+
border-bottom: 1px solid var(--fe-border);
|
|
160
|
+
min-height: 84px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.fe-themecard__topbar {
|
|
164
|
+
display: flex;
|
|
165
|
+
gap: 4px;
|
|
166
|
+
margin-bottom: 2px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.fe-themecard__chip {
|
|
170
|
+
height: 8px;
|
|
171
|
+
width: 22px;
|
|
172
|
+
border-radius: 4px;
|
|
173
|
+
background: var(--fe-bg-hover);
|
|
174
|
+
border: 1px solid var(--fe-border);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.fe-themecard__chip--primary {
|
|
178
|
+
background: var(--fe-primary);
|
|
179
|
+
border-color: var(--fe-primary);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.fe-themecard__chip--ghost {
|
|
183
|
+
background: var(--fe-bg-elev);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.fe-themecard__row {
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
gap: 6px;
|
|
190
|
+
padding: 3px 4px;
|
|
191
|
+
border-radius: 4px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.fe-themecard__row--selected {
|
|
195
|
+
background: var(--fe-bg-selected);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.fe-themecard__dot {
|
|
199
|
+
width: 8px;
|
|
200
|
+
height: 8px;
|
|
201
|
+
border-radius: 50%;
|
|
202
|
+
background: var(--fe-text);
|
|
203
|
+
opacity: 0.75;
|
|
204
|
+
flex: none;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.fe-themecard__dot--primary {
|
|
208
|
+
background: var(--fe-primary);
|
|
209
|
+
opacity: 1;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.fe-themecard__dot--muted {
|
|
213
|
+
background: var(--fe-text-muted);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.fe-themecard__line {
|
|
217
|
+
height: 6px;
|
|
218
|
+
border-radius: 3px;
|
|
219
|
+
background: var(--fe-text);
|
|
220
|
+
opacity: 0.55;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.fe-themecard__line--muted {
|
|
224
|
+
background: var(--fe-text-muted);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.fe-themecard__name {
|
|
228
|
+
display: flex;
|
|
229
|
+
align-items: center;
|
|
230
|
+
justify-content: space-between;
|
|
231
|
+
gap: 8px;
|
|
232
|
+
padding: 8px 10px;
|
|
233
|
+
font-size: 12.5px;
|
|
234
|
+
font-weight: 600;
|
|
235
|
+
color: var(--fe-text);
|
|
236
|
+
background: var(--fe-bg-elev);
|
|
237
|
+
font-family: var(--fe-font, inherit);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.fe-themecard__check {
|
|
241
|
+
flex: none;
|
|
242
|
+
width: 18px;
|
|
243
|
+
height: 18px;
|
|
244
|
+
display: inline-flex;
|
|
245
|
+
align-items: center;
|
|
246
|
+
justify-content: center;
|
|
247
|
+
border-radius: 50%;
|
|
248
|
+
background: var(--fe-primary);
|
|
249
|
+
color: var(--fe-text-on-primary);
|
|
250
|
+
font-size: 11px;
|
|
251
|
+
line-height: 1;
|
|
252
|
+
}
|
|
253
|
+
</style>
|
|
@@ -86,6 +86,8 @@ const emit = defineEmits<{
|
|
|
86
86
|
(e: 'open-recents'): void;
|
|
87
87
|
(e: 'update:density', v: Density): void;
|
|
88
88
|
(e: 'toggle-inspector'): void /* koru:k1 */;
|
|
89
|
+
(e: 'open-theme'): void /* wiring:c1 — tema galerisi */;
|
|
90
|
+
(e: 'open-shortcut-settings'): void /* wiring:c2 */;
|
|
89
91
|
}>();
|
|
90
92
|
|
|
91
93
|
// Density toggle — the toolbar owns the persisted preference; the parent
|
|
@@ -219,13 +221,18 @@ const moreActions = computed<ContextAction[]>(() => {
|
|
|
219
221
|
: t('toolbar.density.compact'),
|
|
220
222
|
icon: '⇅',
|
|
221
223
|
});
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
);
|
|
224
|
+
/* wiring:d2 — dar mod ⋯ menüsü: aktif olmayan İKİ görünüm de listelenir
|
|
225
|
+
(list/grid/gallery); eski tekli toggle üç modda eksik kalıyordu. */
|
|
226
|
+
if (props.viewMode !== 'list') list.push({ key: 'view-list', label: t('toolbar.view.list'), icon: '☰' });
|
|
227
|
+
if (props.viewMode !== 'grid') list.push({ key: 'view-grid', label: t('toolbar.view.grid'), icon: '▦' });
|
|
228
|
+
if (props.viewMode !== 'gallery') list.push({ key: 'view-gallery', label: t('toolbar.view.gallery'), icon: '▣' });
|
|
229
|
+
/* /wiring:d2 */
|
|
227
230
|
/* koru:k1 — inspector toggle also reachable from the narrow overflow menu */
|
|
228
231
|
list.push({ key: 'inspector', label: t('toolbar.inspector'), icon: 'ℹ' });
|
|
232
|
+
/* wiring:c1 — tema galerisi de dar modda ⋯ menüsünden açılır */
|
|
233
|
+
list.push({ key: 'theme', label: t('theme.menu'), icon: '🎨' });
|
|
234
|
+
list.push({ key: 'shortcut-settings', label: t('shortcuts.settings.menu'), icon: '⌨' }) /* wiring:c2 */;
|
|
235
|
+
list.push({ key: 'tour', label: t('tour.restart'), icon: '🎓' }); /* wiring:c4 */
|
|
229
236
|
return list;
|
|
230
237
|
});
|
|
231
238
|
|
|
@@ -251,9 +258,21 @@ function onMoreSelect(a: ContextAction) {
|
|
|
251
258
|
case 'view-grid':
|
|
252
259
|
emit('update:viewMode', 'grid');
|
|
253
260
|
break;
|
|
261
|
+
case 'view-gallery' /* wiring:d2 */:
|
|
262
|
+
emit('update:viewMode', 'gallery');
|
|
263
|
+
break;
|
|
254
264
|
case 'inspector' /* koru:k1 */:
|
|
255
265
|
emit('toggle-inspector');
|
|
256
266
|
break;
|
|
267
|
+
case 'theme' /* wiring:c1 */:
|
|
268
|
+
emit('open-theme');
|
|
269
|
+
break;
|
|
270
|
+
case 'shortcut-settings' /* wiring:c2 */:
|
|
271
|
+
emit('open-shortcut-settings');
|
|
272
|
+
break;
|
|
273
|
+
case 'tour' /* wiring:c4 — bubbles to the FileExplorer root listener */:
|
|
274
|
+
moreBtnEl.value?.dispatchEvent(new CustomEvent('fe:tour-restart', { bubbles: true }));
|
|
275
|
+
break;
|
|
257
276
|
default:
|
|
258
277
|
fire(a.key);
|
|
259
278
|
}
|
|
@@ -331,7 +350,7 @@ function onMoreSelect(a: ContextAction) {
|
|
|
331
350
|
class="fe-search__input"
|
|
332
351
|
:placeholder="t('toolbar.search.placeholder')"
|
|
333
352
|
:value="localSearch"
|
|
334
|
-
aria-label="
|
|
353
|
+
:aria-label="t('toolbar.search') /* wiring:c4 — was hardcoded EN */"
|
|
335
354
|
@input="onSearchInput"
|
|
336
355
|
/>
|
|
337
356
|
</div>
|
|
@@ -340,17 +359,19 @@ function onMoreSelect(a: ContextAction) {
|
|
|
340
359
|
type="button"
|
|
341
360
|
class="fe-btn fe-btn--icon-only"
|
|
342
361
|
:title="t('toolbar.upload')"
|
|
362
|
+
:aria-label="t('toolbar.upload') /* wiring:c4 */"
|
|
343
363
|
@click="emit('upload')"
|
|
344
364
|
>
|
|
345
|
-
<span class="fe-icon">⬆</span>
|
|
365
|
+
<span class="fe-icon" aria-hidden="true">⬆</span>
|
|
346
366
|
</button>
|
|
347
367
|
<button
|
|
348
368
|
type="button"
|
|
349
369
|
class="fe-btn fe-btn--icon-only"
|
|
350
370
|
:title="t('toolbar.refresh')"
|
|
371
|
+
:aria-label="t('toolbar.refresh') /* wiring:c4 */"
|
|
351
372
|
@click="emit('refresh')"
|
|
352
373
|
>
|
|
353
|
-
<span class="fe-icon">↻</span>
|
|
374
|
+
<span class="fe-icon" aria-hidden="true">↻</span>
|
|
354
375
|
</button>
|
|
355
376
|
</div>
|
|
356
377
|
|
|
@@ -418,7 +439,34 @@ function onMoreSelect(a: ContextAction) {
|
|
|
418
439
|
</svg>
|
|
419
440
|
</button>
|
|
420
441
|
|
|
421
|
-
|
|
442
|
+
<!-- wiring:c1 — tema galerisi (palet ikonu) -->
|
|
443
|
+
<button
|
|
444
|
+
type="button"
|
|
445
|
+
class="fe-btn fe-btn--icon-only fe-toolbar__theme"
|
|
446
|
+
:title="t('theme.menu')"
|
|
447
|
+
:aria-label="t('theme.menu')"
|
|
448
|
+
@click="emit('open-theme')"
|
|
449
|
+
>
|
|
450
|
+
<svg
|
|
451
|
+
class="fe-ficon"
|
|
452
|
+
viewBox="0 0 24 24"
|
|
453
|
+
fill="none"
|
|
454
|
+
stroke="currentColor"
|
|
455
|
+
stroke-width="1.8"
|
|
456
|
+
stroke-linecap="round"
|
|
457
|
+
stroke-linejoin="round"
|
|
458
|
+
aria-hidden="true"
|
|
459
|
+
focusable="false"
|
|
460
|
+
>
|
|
461
|
+
<path d="M12 3a9 9 0 1 0 0 18h1.4a2.1 2.1 0 0 0 1.5-3.6 2.1 2.1 0 0 1 1.5-3.6H19a2 2 0 0 0 2-2A9 9 0 0 0 12 3z" />
|
|
462
|
+
<circle cx="7.8" cy="10.2" r="1.15" fill="currentColor" stroke="none" />
|
|
463
|
+
<circle cx="11.6" cy="7.2" r="1.15" fill="currentColor" stroke="none" />
|
|
464
|
+
<circle cx="16" cy="8.6" r="1.15" fill="currentColor" stroke="none" />
|
|
465
|
+
</svg>
|
|
466
|
+
</button>
|
|
467
|
+
<!-- /wiring:c1 -->
|
|
468
|
+
|
|
469
|
+
<div class="fe-toolbar__view" role="tablist" :aria-label="t('toolbar.view_label') /* wiring:c4 — was hardcoded EN */">
|
|
422
470
|
<button
|
|
423
471
|
type="button"
|
|
424
472
|
class="fe-btn fe-btn--icon-only"
|
|
@@ -426,9 +474,10 @@ function onMoreSelect(a: ContextAction) {
|
|
|
426
474
|
role="tab"
|
|
427
475
|
:aria-selected="viewMode === 'list'"
|
|
428
476
|
:title="t('toolbar.view.list')"
|
|
477
|
+
:aria-label="t('toolbar.view.list') /* wiring:c4 */"
|
|
429
478
|
@click="emit('update:viewMode', 'list')"
|
|
430
479
|
>
|
|
431
|
-
<span class="fe-icon">☰</span>
|
|
480
|
+
<span class="fe-icon" aria-hidden="true">☰</span>
|
|
432
481
|
</button>
|
|
433
482
|
<button
|
|
434
483
|
type="button"
|
|
@@ -437,10 +486,25 @@ function onMoreSelect(a: ContextAction) {
|
|
|
437
486
|
role="tab"
|
|
438
487
|
:aria-selected="viewMode === 'grid'"
|
|
439
488
|
:title="t('toolbar.view.grid')"
|
|
489
|
+
:aria-label="t('toolbar.view.grid') /* wiring:c4 */"
|
|
440
490
|
@click="emit('update:viewMode', 'grid')"
|
|
441
491
|
>
|
|
442
|
-
<span class="fe-icon">▦</span>
|
|
492
|
+
<span class="fe-icon" aria-hidden="true">▦</span>
|
|
493
|
+
</button>
|
|
494
|
+
<!-- wiring:d2 — üçüncü görünüm: galeri -->
|
|
495
|
+
<button
|
|
496
|
+
type="button"
|
|
497
|
+
class="fe-btn fe-btn--icon-only"
|
|
498
|
+
:class="{ 'is-active': viewMode === 'gallery' }"
|
|
499
|
+
role="tab"
|
|
500
|
+
:aria-selected="viewMode === 'gallery'"
|
|
501
|
+
:title="t('toolbar.view.gallery')"
|
|
502
|
+
:aria-label="t('toolbar.view.gallery')"
|
|
503
|
+
@click="emit('update:viewMode', 'gallery')"
|
|
504
|
+
>
|
|
505
|
+
<span class="fe-icon" aria-hidden="true">▣</span>
|
|
443
506
|
</button>
|
|
507
|
+
<!-- /wiring:d2 -->
|
|
444
508
|
</div>
|
|
445
509
|
</template>
|
|
446
510
|
|