@brftech/filex-core 0.14.0 → 0.15.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/filex-core.js +4558 -4453
- package/dist/filex-core.js.map +1 -1
- package/dist/filex-core.umd.cjs +30 -30
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index.d.ts +32 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +48 -17
- package/src/components/ThemeGallery.vue +64 -1
- package/src/lib/themes.ts +67 -0
- package/src/locales/en.ts +5 -1
- package/src/locales/tr.ts +5 -1
- package/src/styles/base.css +125 -0
- package/src/types/ExplorerConfig.ts +13 -0
package/src/FileExplorer.vue
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* the difference.
|
|
16
16
|
*/
|
|
17
17
|
import { computed, customRef, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
18
|
-
import type { ExplorerConfig } from './types/ExplorerConfig';
|
|
18
|
+
import type { ExplorerConfig, ThemeMode } from './types/ExplorerConfig';
|
|
19
19
|
import type {
|
|
20
20
|
FileNode,
|
|
21
21
|
ShareInfo,
|
|
@@ -53,7 +53,13 @@ import ShortcutsHelp from './components/ShortcutsHelp.vue';
|
|
|
53
53
|
/* /cila:c wiring */
|
|
54
54
|
/* wiring:c1 — tema galerisi */
|
|
55
55
|
import ThemeGallery from './components/ThemeGallery.vue';
|
|
56
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
useThemeState,
|
|
58
|
+
useThemeModeState,
|
|
59
|
+
applyThemeToEl,
|
|
60
|
+
syncThemeStyle,
|
|
61
|
+
type ThemeModePref,
|
|
62
|
+
} from './lib/themes';
|
|
57
63
|
/* /wiring:c1 */
|
|
58
64
|
/* wiring:c2 — shortcut settings modal + Space quick-look overlay */
|
|
59
65
|
import ShortcutSettings from './components/ShortcutSettings.vue';
|
|
@@ -2341,8 +2347,20 @@ const effectiveViewerBaseUrl = computed(() =>
|
|
|
2341
2347
|
* WHICH variant of the theme paints. */
|
|
2342
2348
|
const showThemeGallery = ref(false);
|
|
2343
2349
|
const { themeId: activeThemeId, setTheme: setActiveTheme } = useThemeState();
|
|
2344
|
-
//
|
|
2345
|
-
//
|
|
2350
|
+
// The light/dark MODE the person looking at it picked in the gallery. 'host'
|
|
2351
|
+
// (the default) defers to whatever the embedder passed, so no existing embed
|
|
2352
|
+
// changes appearance until someone actually chooses.
|
|
2353
|
+
//
|
|
2354
|
+
// ⚠ Everything that used to read `config.theme` reads `themeMode` instead — a
|
|
2355
|
+
// choice that only reached the token resolution would leave the root class, the
|
|
2356
|
+
// modals and the teleported menus painting the OLD mode, which is exactly the
|
|
2357
|
+
// half-dark UI that bug looks like.
|
|
2358
|
+
const { themeMode: themeModePref, setThemeMode: setThemeModePref } = useThemeModeState();
|
|
2359
|
+
const themeMode = computed<ThemeMode>(() =>
|
|
2360
|
+
themeModePref.value === 'host' ? props.config.theme || 'auto' : themeModePref.value,
|
|
2361
|
+
);
|
|
2362
|
+
// Resolved mode: an explicit choice wins, otherwise the OS preference — the
|
|
2363
|
+
// same logic variables.css encodes in CSS. The inline root variables beat every
|
|
2346
2364
|
// stylesheet rule, so they must track this resolution at runtime.
|
|
2347
2365
|
const themeMq =
|
|
2348
2366
|
typeof window !== 'undefined' && window.matchMedia
|
|
@@ -2355,7 +2373,7 @@ function onThemeMqChange(e: MediaQueryListEvent) {
|
|
|
2355
2373
|
onMounted(() => themeMq?.addEventListener?.('change', onThemeMqChange));
|
|
2356
2374
|
onBeforeUnmount(() => themeMq?.removeEventListener?.('change', onThemeMqChange));
|
|
2357
2375
|
const themeResolvedDark = computed(
|
|
2358
|
-
() =>
|
|
2376
|
+
() => themeMode.value === 'dark' || (themeMode.value !== 'light' && themeOsDark.value),
|
|
2359
2377
|
);
|
|
2360
2378
|
watch(
|
|
2361
2379
|
[activeThemeId, themeResolvedDark, rootEl],
|
|
@@ -2564,7 +2582,6 @@ const tabsApi = useTabs({ storageKey: tabsStorageKey() });
|
|
|
2564
2582
|
const tabsRestored = tabsApi.restore();
|
|
2565
2583
|
const tabsActiveId = tabsApi.activeId;
|
|
2566
2584
|
|
|
2567
|
-
const tabsVisible = computed(() => tabsApi.hasMultiple.value);
|
|
2568
2585
|
const activeSplit = computed(() => tabsApi.activeTab.value?.split ?? null);
|
|
2569
2586
|
|
|
2570
2587
|
// Sekme adı OTOMATİK = güncel klasör adı (kök = depo adı / kök etiketi).
|
|
@@ -2578,6 +2595,17 @@ const tabItems = computed(() =>
|
|
|
2578
2595
|
tabsApi.tabs.value.map((tb) => ({ id: tb.id, label: tabLabel(tb.path), split: !!tb.split })),
|
|
2579
2596
|
);
|
|
2580
2597
|
|
|
2598
|
+
// A second tab always shows the strip; `tabStrip: 'always'` keeps it there for
|
|
2599
|
+
// the first one too (see ExplorerConfig).
|
|
2600
|
+
// ⚠ Gated on there being a tab at all, not on the flag alone: tabs are seeded
|
|
2601
|
+
// in onMounted, so an 'always' host would otherwise paint one frame of an empty
|
|
2602
|
+
// strip — a lone `+` floating above the toolbar.
|
|
2603
|
+
const tabsVisible = computed(
|
|
2604
|
+
() =>
|
|
2605
|
+
tabsApi.hasMultiple.value ||
|
|
2606
|
+
(props.config.tabStrip === 'always' && tabItems.value.length > 0),
|
|
2607
|
+
);
|
|
2608
|
+
|
|
2581
2609
|
// Aktif tab kullanıcıyı izler: gezinme + görünüm değişimi snapshot'a yazılır.
|
|
2582
2610
|
watch(currentPath, (p) => tabsApi.syncActive({ path: p }));
|
|
2583
2611
|
watch(viewMode, (v) => tabsApi.syncActive({ viewMode: v }));
|
|
@@ -3031,8 +3059,8 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
3031
3059
|
ref="rootEl"
|
|
3032
3060
|
class="fe"
|
|
3033
3061
|
:class="{
|
|
3034
|
-
'fe--theme-light':
|
|
3035
|
-
'fe--theme-dark':
|
|
3062
|
+
'fe--theme-light': themeMode === 'light',
|
|
3063
|
+
'fe--theme-dark': themeMode === 'dark',
|
|
3036
3064
|
'fe--is-dragover': dragOver,
|
|
3037
3065
|
'fe--density-compact': density === 'compact' /* cila:a density */,
|
|
3038
3066
|
'fe--narrow': isNarrow /* bag:b4 */,
|
|
@@ -3073,7 +3101,7 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
3073
3101
|
:can-write="canWriteHere"
|
|
3074
3102
|
:locale="locale"
|
|
3075
3103
|
:narrow="isNarrow /* bag:b4 */"
|
|
3076
|
-
:theme="
|
|
3104
|
+
:theme="themeMode /* bag:b4 */"
|
|
3077
3105
|
:inspector-open="showInspector /* koru:k1 */"
|
|
3078
3106
|
@toggle-inspector="toggleInspector /* koru:k1 */"
|
|
3079
3107
|
@open-theme="showThemeGallery = true /* wiring:c1 */"
|
|
@@ -3511,7 +3539,7 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
3511
3539
|
<ContextMenu
|
|
3512
3540
|
ref="ctxRef"
|
|
3513
3541
|
:locale="locale"
|
|
3514
|
-
:theme="
|
|
3542
|
+
:theme="themeMode"
|
|
3515
3543
|
:sheet="isCoarse /* bag:b4 */"
|
|
3516
3544
|
:actions="contextActions"
|
|
3517
3545
|
@select="onContextAction"
|
|
@@ -3560,7 +3588,7 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
3560
3588
|
:open="showPreview"
|
|
3561
3589
|
:locale="locale"
|
|
3562
3590
|
:file="previewTarget"
|
|
3563
|
-
:theme="
|
|
3591
|
+
:theme="themeMode"
|
|
3564
3592
|
:preview-url="(p) => e2ePreviewSrc(p) /* wiring:e2 — çözülmüş blob > ham URL */"
|
|
3565
3593
|
:download-url="(p) => (e2eUnlocked ? e2ePreviewSrc(p) : api.downloadUrl(p)) /* wiring:e2 */"
|
|
3566
3594
|
:only-office-base="e2eActive ? null : effectiveOnlyOfficeBase /* wiring:e2 — OO ciphertext açamaz */"
|
|
@@ -3605,8 +3633,8 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
3605
3633
|
v-if="showRecents"
|
|
3606
3634
|
class="fe fe-modal__backdrop fe-recents__backdrop"
|
|
3607
3635
|
:class="{
|
|
3608
|
-
'fe--theme-light':
|
|
3609
|
-
'fe--theme-dark':
|
|
3636
|
+
'fe--theme-light': themeMode === 'light',
|
|
3637
|
+
'fe--theme-dark': themeMode === 'dark',
|
|
3610
3638
|
}"
|
|
3611
3639
|
@click="showRecents = false"
|
|
3612
3640
|
>
|
|
@@ -3693,11 +3721,14 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
3693
3721
|
<ThemeGallery
|
|
3694
3722
|
:open="showThemeGallery"
|
|
3695
3723
|
:locale="locale"
|
|
3696
|
-
:theme="
|
|
3724
|
+
:theme="themeMode"
|
|
3697
3725
|
:dark="themeResolvedDark"
|
|
3698
3726
|
:current="activeThemeId"
|
|
3727
|
+
:mode="themeModePref"
|
|
3728
|
+
:host-mode="config.theme || 'auto'"
|
|
3699
3729
|
@close="showThemeGallery = false"
|
|
3700
3730
|
@select="setActiveTheme"
|
|
3731
|
+
@mode="(m: ThemeModePref) => setThemeModePref(m)"
|
|
3701
3732
|
/>
|
|
3702
3733
|
<!-- /wiring:c1 -->
|
|
3703
3734
|
|
|
@@ -3731,14 +3762,14 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
3731
3762
|
<ShortcutSettings
|
|
3732
3763
|
:open="showShortcutSettings"
|
|
3733
3764
|
:locale="locale"
|
|
3734
|
-
:theme="
|
|
3765
|
+
:theme="themeMode"
|
|
3735
3766
|
@close="showShortcutSettings = false"
|
|
3736
3767
|
/>
|
|
3737
3768
|
<QuickLook
|
|
3738
3769
|
:open="quickLookOpen"
|
|
3739
3770
|
:locale="locale"
|
|
3740
3771
|
:file="quickLookTarget"
|
|
3741
|
-
:theme="
|
|
3772
|
+
:theme="themeMode"
|
|
3742
3773
|
:preview-url="(p: string) => e2ePreviewSrc(p) /* wiring:e2 */"
|
|
3743
3774
|
:download-url="(p: string) => (e2eUnlocked ? e2ePreviewSrc(p) : api.downloadUrl(p)) /* wiring:e2 */"
|
|
3744
3775
|
:only-office-base="e2eActive ? null : effectiveOnlyOfficeBase /* wiring:e2 */"
|
|
@@ -3758,7 +3789,7 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
3758
3789
|
:open="showTour"
|
|
3759
3790
|
:locale="locale"
|
|
3760
3791
|
:root="rootEl"
|
|
3761
|
-
:theme="
|
|
3792
|
+
:theme="themeMode"
|
|
3762
3793
|
@close="onTourClose"
|
|
3763
3794
|
/>
|
|
3764
3795
|
<!-- /wiring:c4 -->
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* Presentational only — selection state + persistence live in
|
|
12
12
|
* lib/themes.ts (shared across instances); the parent forwards `select`.
|
|
13
13
|
*/
|
|
14
|
+
import { computed } from 'vue';
|
|
14
15
|
import type { LocaleCode, ThemeMode } from '../types/ExplorerConfig';
|
|
15
16
|
import { useLocale } from '../composables/useLocale';
|
|
16
|
-
import { THEMES, DEFAULT_THEME_ID, type ThemeDef } from '../lib/themes';
|
|
17
|
+
import { THEMES, DEFAULT_THEME_ID, type ThemeDef, type ThemeModePref } from '../lib/themes';
|
|
17
18
|
import Modal from '../modals/Modal.vue';
|
|
18
19
|
|
|
19
20
|
const props = defineProps<{
|
|
@@ -25,15 +26,37 @@ const props = defineProps<{
|
|
|
25
26
|
dark: boolean;
|
|
26
27
|
/** Currently selected theme id. */
|
|
27
28
|
current: string;
|
|
29
|
+
/** The user's own light/dark preference; 'host' = never chosen. */
|
|
30
|
+
mode: ThemeModePref;
|
|
31
|
+
/** What the embedder asked for — what 'host' resolves to. */
|
|
32
|
+
hostMode: ThemeMode;
|
|
28
33
|
}>();
|
|
29
34
|
|
|
30
35
|
const emit = defineEmits<{
|
|
31
36
|
(e: 'close'): void;
|
|
32
37
|
(e: 'select', id: string): void;
|
|
38
|
+
(e: 'mode', mode: ThemeModePref): void;
|
|
33
39
|
}>();
|
|
34
40
|
|
|
35
41
|
const { t } = useLocale(() => props.locale);
|
|
36
42
|
|
|
43
|
+
/* ---- light / dark / auto -------------------------------------------
|
|
44
|
+
* A theme says which palette; this says whether it paints its light or its
|
|
45
|
+
* dark variant. Two separate questions, one place to answer both — the theme
|
|
46
|
+
* button is where people come looking for "night mode".
|
|
47
|
+
*
|
|
48
|
+
* Only three choices are ever offered. 'host' is an internal default, not an
|
|
49
|
+
* option: when it is still in force the control highlights whichever of the
|
|
50
|
+
* three the embedder asked for, so the strip always shows the truth, and the
|
|
51
|
+
* first click pins an explicit choice. */
|
|
52
|
+
const MODES: { id: Exclude<ThemeModePref, 'host'>; labelKey: string }[] = [
|
|
53
|
+
{ id: 'light', labelKey: 'theme.mode.light' },
|
|
54
|
+
{ id: 'dark', labelKey: 'theme.mode.dark' },
|
|
55
|
+
{ id: 'auto', labelKey: 'theme.mode.auto' },
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const shownMode = computed(() => (props.mode === 'host' ? props.hostMode : props.mode));
|
|
59
|
+
|
|
37
60
|
/** Inline variables for one card — a self-contained mini `--fe-*` scope,
|
|
38
61
|
* so the preview markup can use the exact same tokens the real UI uses. */
|
|
39
62
|
function cardVars(th: ThemeDef): Record<string, string> {
|
|
@@ -45,6 +68,46 @@ const DEFAULT_ID = DEFAULT_THEME_ID;
|
|
|
45
68
|
|
|
46
69
|
<template>
|
|
47
70
|
<Modal :open="open" :title="t('theme.title')" size="lg" :theme="theme" @close="emit('close')">
|
|
71
|
+
<div class="fe-thememode" role="group" :aria-label="t('theme.mode.label')">
|
|
72
|
+
<span class="fe-thememode__label">{{ t('theme.mode.label') }}</span>
|
|
73
|
+
<div class="fe-thememode__seg">
|
|
74
|
+
<button
|
|
75
|
+
v-for="m in MODES"
|
|
76
|
+
:key="m.id"
|
|
77
|
+
type="button"
|
|
78
|
+
class="fe-thememode__opt"
|
|
79
|
+
:class="{ 'is-active': shownMode === m.id }"
|
|
80
|
+
:aria-pressed="shownMode === m.id"
|
|
81
|
+
@click="emit('mode', m.id)"
|
|
82
|
+
>
|
|
83
|
+
<svg
|
|
84
|
+
class="fe-ficon"
|
|
85
|
+
viewBox="0 0 24 24"
|
|
86
|
+
fill="none"
|
|
87
|
+
stroke="currentColor"
|
|
88
|
+
stroke-width="1.8"
|
|
89
|
+
stroke-linecap="round"
|
|
90
|
+
stroke-linejoin="round"
|
|
91
|
+
aria-hidden="true"
|
|
92
|
+
focusable="false"
|
|
93
|
+
>
|
|
94
|
+
<template v-if="m.id === 'light'">
|
|
95
|
+
<circle cx="12" cy="12" r="4" />
|
|
96
|
+
<path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
|
|
97
|
+
</template>
|
|
98
|
+
<template v-else-if="m.id === 'dark'">
|
|
99
|
+
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
|
|
100
|
+
</template>
|
|
101
|
+
<template v-else>
|
|
102
|
+
<!-- Half-filled disc: the mode that is neither, decided elsewhere. -->
|
|
103
|
+
<circle cx="12" cy="12" r="9" />
|
|
104
|
+
<path d="M12 3a9 9 0 0 1 0 18z" fill="currentColor" stroke="none" />
|
|
105
|
+
</template>
|
|
106
|
+
</svg>
|
|
107
|
+
<span>{{ t(m.labelKey) }}</span>
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
48
111
|
<p class="fe-themes__hint">{{ t('theme.hint') }}</p>
|
|
49
112
|
<div class="fe-themes" role="listbox" :aria-label="t('theme.title')">
|
|
50
113
|
<button
|
package/src/lib/themes.ts
CHANGED
|
@@ -403,6 +403,73 @@ export function useThemeState(): { themeId: Ref<string>; setTheme: (id: string)
|
|
|
403
403
|
return { themeId, setTheme };
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
+
/* ------------------------------------------------------------------ */
|
|
407
|
+
/* Light / dark MODE preference */
|
|
408
|
+
/* ------------------------------------------------------------------ */
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* The user's own light/dark choice — a different question from WHICH theme
|
|
412
|
+
* paints (that is `themeId` above).
|
|
413
|
+
*
|
|
414
|
+
* `'host'` is the default and means "whatever the embedder passed in
|
|
415
|
+
* `config.theme`". That is what keeps every existing embed unchanged: work and
|
|
416
|
+
* the fishapp hand the explorer the mode their own page is in, and an explorer
|
|
417
|
+
* that silently overrode it would sit as a dark rectangle inside a light page.
|
|
418
|
+
* The other three are an explicit choice by the person looking at it and
|
|
419
|
+
* outrank the host: `'auto'` follows the operating system, `'light'`/`'dark'`
|
|
420
|
+
* are fixed.
|
|
421
|
+
*/
|
|
422
|
+
export type ThemeModePref = 'host' | 'auto' | 'light' | 'dark';
|
|
423
|
+
|
|
424
|
+
export const THEME_MODE_LS_KEY = 'filex.thememode';
|
|
425
|
+
|
|
426
|
+
const MODE_VALUES: ThemeModePref[] = ['host', 'auto', 'light', 'dark'];
|
|
427
|
+
|
|
428
|
+
function readStoredThemeMode(): ThemeModePref {
|
|
429
|
+
try {
|
|
430
|
+
const v = localStorage.getItem(THEME_MODE_LS_KEY) as ThemeModePref | null;
|
|
431
|
+
return v && MODE_VALUES.includes(v) ? v : 'host';
|
|
432
|
+
} catch {
|
|
433
|
+
return 'host';
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const themeMode: Ref<ThemeModePref> = ref(
|
|
438
|
+
typeof window === 'undefined' ? 'host' : readStoredThemeMode(),
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
export function setThemeMode(mode: ThemeModePref): void {
|
|
442
|
+
const valid = MODE_VALUES.includes(mode) ? mode : 'host';
|
|
443
|
+
themeMode.value = valid;
|
|
444
|
+
try {
|
|
445
|
+
if (valid === 'host') localStorage.removeItem(THEME_MODE_LS_KEY);
|
|
446
|
+
else localStorage.setItem(THEME_MODE_LS_KEY, valid);
|
|
447
|
+
} catch {
|
|
448
|
+
/* quota / private mode */
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** Reactive handle for components: `{ themeMode, setThemeMode }`. */
|
|
453
|
+
export function useThemeModeState(): {
|
|
454
|
+
themeMode: Ref<ThemeModePref>;
|
|
455
|
+
setThemeMode: (mode: ThemeModePref) => void;
|
|
456
|
+
} {
|
|
457
|
+
return { themeMode, setThemeMode };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Cross-tab sync, same contract as the theme id above.
|
|
461
|
+
if (typeof window !== 'undefined') {
|
|
462
|
+
try {
|
|
463
|
+
window.addEventListener('storage', (e) => {
|
|
464
|
+
if (e.key !== THEME_MODE_LS_KEY) return;
|
|
465
|
+
const v = e.newValue as ThemeModePref | null;
|
|
466
|
+
themeMode.value = v && MODE_VALUES.includes(v) ? v : 'host';
|
|
467
|
+
});
|
|
468
|
+
} catch {
|
|
469
|
+
/* non-browser env */
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
406
473
|
// Cross-tab sync — another tab changing the preference updates this one live.
|
|
407
474
|
if (typeof window !== 'undefined') {
|
|
408
475
|
try {
|
package/src/locales/en.ts
CHANGED
|
@@ -248,7 +248,11 @@ export const en: Record<string, string> = {
|
|
|
248
248
|
/* wiring:c1 — theme gallery */
|
|
249
249
|
'theme.menu': 'Theme',
|
|
250
250
|
'theme.title': 'Theme Gallery',
|
|
251
|
-
'theme.hint': '
|
|
251
|
+
'theme.hint': 'A theme only changes the colour palette — day or night is the choice above.',
|
|
252
|
+
'theme.mode.label': 'Appearance',
|
|
253
|
+
'theme.mode.light': 'Day',
|
|
254
|
+
'theme.mode.dark': 'Night',
|
|
255
|
+
'theme.mode.auto': 'Automatic',
|
|
252
256
|
'theme.reset': 'Reset to default',
|
|
253
257
|
'theme.close': 'Close',
|
|
254
258
|
'theme.selected': 'Selected',
|
package/src/locales/tr.ts
CHANGED
|
@@ -248,7 +248,11 @@ export const tr: Record<string, string> = {
|
|
|
248
248
|
/* wiring:c1 — tema galerisi */
|
|
249
249
|
'theme.menu': 'Tema',
|
|
250
250
|
'theme.title': 'Tema Galerisi',
|
|
251
|
-
'theme.hint': 'Tema yalnız renk paletini değiştirir;
|
|
251
|
+
'theme.hint': 'Tema yalnız renk paletini değiştirir; gündüz mü gece mi olduğu yukarıdaki seçim.',
|
|
252
|
+
'theme.mode.label': 'Görünüm',
|
|
253
|
+
'theme.mode.light': 'Gündüz',
|
|
254
|
+
'theme.mode.dark': 'Gece',
|
|
255
|
+
'theme.mode.auto': 'Otomatik',
|
|
252
256
|
'theme.reset': 'Varsayılana dön',
|
|
253
257
|
'theme.close': 'Kapat',
|
|
254
258
|
'theme.selected': 'Seçili',
|
package/src/styles/base.css
CHANGED
|
@@ -3760,3 +3760,128 @@ filex-explorer {
|
|
|
3760
3760
|
flex: none;
|
|
3761
3761
|
}
|
|
3762
3762
|
/* === /ui-fix ========================================================= */
|
|
3763
|
+
|
|
3764
|
+
/* === ui-fix — kaydırma çubukları ======================================
|
|
3765
|
+
The platform scrollbar ignores the theme completely: on Terminal Green or
|
|
3766
|
+
any dark variant it paints a wide light-grey slab down the edge of the
|
|
3767
|
+
panel. Chromium/WebKit honour the -webkit pseudo-elements and Firefox the
|
|
3768
|
+
two standard properties, so both are declared — the standard pair first, so
|
|
3769
|
+
the rules survive an engine that drops the prefixed set.
|
|
3770
|
+
|
|
3771
|
+
⚠ SCOPED TO `.fe`, never to `*`. base.css is loaded by embedders (work,
|
|
3772
|
+
fishapp, the admin SPA), and a bare `*` rule would repaint the HOST page's
|
|
3773
|
+
scrollbars from inside an embedded file browser. The two teleported
|
|
3774
|
+
surfaces are listed explicitly because they are children of <body>, not of
|
|
3775
|
+
the explorer root.
|
|
3776
|
+
|
|
3777
|
+
Colours come from the existing tokens rather than from new ones, so every
|
|
3778
|
+
theme — including ones added later — gets a matching bar for free. */
|
|
3779
|
+
.fe,
|
|
3780
|
+
.fe *,
|
|
3781
|
+
.fe-ctx-backdrop,
|
|
3782
|
+
.fe-ctx-backdrop *,
|
|
3783
|
+
.fe-tour,
|
|
3784
|
+
.fe-tour * {
|
|
3785
|
+
scrollbar-width: thin;
|
|
3786
|
+
scrollbar-color: var(--fe-border-strong) transparent;
|
|
3787
|
+
}
|
|
3788
|
+
.fe ::-webkit-scrollbar,
|
|
3789
|
+
.fe-ctx-backdrop ::-webkit-scrollbar,
|
|
3790
|
+
.fe-tour ::-webkit-scrollbar {
|
|
3791
|
+
width: 10px;
|
|
3792
|
+
height: 10px;
|
|
3793
|
+
}
|
|
3794
|
+
.fe ::-webkit-scrollbar-track,
|
|
3795
|
+
.fe-ctx-backdrop ::-webkit-scrollbar-track,
|
|
3796
|
+
.fe-tour ::-webkit-scrollbar-track {
|
|
3797
|
+
background: transparent;
|
|
3798
|
+
}
|
|
3799
|
+
.fe ::-webkit-scrollbar-thumb,
|
|
3800
|
+
.fe-ctx-backdrop ::-webkit-scrollbar-thumb,
|
|
3801
|
+
.fe-tour ::-webkit-scrollbar-thumb {
|
|
3802
|
+
background: var(--fe-border-strong);
|
|
3803
|
+
/* Inset with a transparent border instead of a narrower width: a thin thumb
|
|
3804
|
+
inside a wide gutter drifts away from the content edge. */
|
|
3805
|
+
border: 2px solid transparent;
|
|
3806
|
+
background-clip: padding-box;
|
|
3807
|
+
border-radius: 8px;
|
|
3808
|
+
}
|
|
3809
|
+
.fe ::-webkit-scrollbar-thumb:hover,
|
|
3810
|
+
.fe-ctx-backdrop ::-webkit-scrollbar-thumb:hover,
|
|
3811
|
+
.fe-tour ::-webkit-scrollbar-thumb:hover {
|
|
3812
|
+
background: var(--fe-text-muted);
|
|
3813
|
+
background-clip: padding-box;
|
|
3814
|
+
}
|
|
3815
|
+
/* Unstyled, the meeting point of a vertical and a horizontal bar paints as a
|
|
3816
|
+
white square in the corner of a dark panel. */
|
|
3817
|
+
.fe ::-webkit-scrollbar-corner,
|
|
3818
|
+
.fe-ctx-backdrop ::-webkit-scrollbar-corner,
|
|
3819
|
+
.fe-tour ::-webkit-scrollbar-corner {
|
|
3820
|
+
background: transparent;
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3823
|
+
/* === wiring:c1 — gündüz / gece / otomatik =============================
|
|
3824
|
+
Lives at the top of the theme gallery: "night mode" is what people open the
|
|
3825
|
+
theme button looking for, and a palette picker that cannot answer that
|
|
3826
|
+
question is the wrong place to stop. */
|
|
3827
|
+
.fe-thememode {
|
|
3828
|
+
display: flex;
|
|
3829
|
+
align-items: center;
|
|
3830
|
+
justify-content: space-between;
|
|
3831
|
+
gap: 12px;
|
|
3832
|
+
flex-wrap: wrap;
|
|
3833
|
+
margin-bottom: 14px;
|
|
3834
|
+
}
|
|
3835
|
+
.fe-thememode__label {
|
|
3836
|
+
font-size: 13px;
|
|
3837
|
+
font-weight: 600;
|
|
3838
|
+
color: var(--fe-text-muted);
|
|
3839
|
+
}
|
|
3840
|
+
.fe-thememode__seg {
|
|
3841
|
+
display: inline-flex;
|
|
3842
|
+
padding: 3px;
|
|
3843
|
+
gap: 2px;
|
|
3844
|
+
border: 1px solid var(--fe-border);
|
|
3845
|
+
border-radius: var(--fe-radius-lg);
|
|
3846
|
+
background: var(--fe-bg-elev);
|
|
3847
|
+
}
|
|
3848
|
+
.fe-thememode__opt {
|
|
3849
|
+
display: inline-flex;
|
|
3850
|
+
align-items: center;
|
|
3851
|
+
gap: 6px;
|
|
3852
|
+
padding: 6px 12px;
|
|
3853
|
+
border: 0;
|
|
3854
|
+
border-radius: calc(var(--fe-radius-lg) - 4px);
|
|
3855
|
+
background: transparent;
|
|
3856
|
+
color: var(--fe-text-muted);
|
|
3857
|
+
font: inherit;
|
|
3858
|
+
font-size: 13px;
|
|
3859
|
+
cursor: pointer;
|
|
3860
|
+
transition: background 0.14s ease, color 0.14s ease;
|
|
3861
|
+
}
|
|
3862
|
+
.fe-thememode__opt:hover {
|
|
3863
|
+
background: var(--fe-bg-hover);
|
|
3864
|
+
color: var(--fe-text);
|
|
3865
|
+
}
|
|
3866
|
+
.fe-thememode__opt.is-active {
|
|
3867
|
+
background: var(--fe-bg);
|
|
3868
|
+
color: var(--fe-text);
|
|
3869
|
+
box-shadow: var(--fe-shadow-sm);
|
|
3870
|
+
}
|
|
3871
|
+
.fe-thememode__opt .fe-ficon {
|
|
3872
|
+
width: 15px;
|
|
3873
|
+
height: 15px;
|
|
3874
|
+
flex: none;
|
|
3875
|
+
}
|
|
3876
|
+
/* Under ~360px the label and the three options cannot share a line; the strip
|
|
3877
|
+
takes the width and the options divide it rather than overflowing. */
|
|
3878
|
+
@media (max-width: 420px) {
|
|
3879
|
+
.fe-thememode__seg {
|
|
3880
|
+
flex: 1 1 100%;
|
|
3881
|
+
}
|
|
3882
|
+
.fe-thememode__opt {
|
|
3883
|
+
flex: 1;
|
|
3884
|
+
justify-content: center;
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
/* === /ui-fix ========================================================= */
|
|
@@ -207,6 +207,19 @@ export interface ExplorerConfig {
|
|
|
207
207
|
/** Theme. */
|
|
208
208
|
theme?: ThemeMode;
|
|
209
209
|
|
|
210
|
+
/**
|
|
211
|
+
* When the tab strip is on screen.
|
|
212
|
+
*
|
|
213
|
+
* `'auto'` (default) shows it only once a second tab exists — the historical
|
|
214
|
+
* behaviour, and what a small embed wants: a fixed strip above a short panel
|
|
215
|
+
* is a lot of chrome to spend on a feature nobody has asked for yet.
|
|
216
|
+
*
|
|
217
|
+
* `'always'` keeps it visible with a single tab open. That is what a full
|
|
218
|
+
* window wants, because the strip is also where `+` lives: hide it and tabs
|
|
219
|
+
* are a feature you can only find by guessing the keyboard shortcut.
|
|
220
|
+
*/
|
|
221
|
+
tabStrip?: 'auto' | 'always';
|
|
222
|
+
|
|
210
223
|
/** Initial path (storage-prefix included: `local://`). Default: root. */
|
|
211
224
|
initialPath?: string;
|
|
212
225
|
|