@brftech/filex-core 0.29.0 → 0.30.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/README.md +35 -0
- package/dist/filex-core.js +13511 -12642
- package/dist/filex-core.js.map +1 -1
- package/dist/filex-core.umd.cjs +71 -71
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index.d.ts +78 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +440 -4
- package/src/components/Breadcrumb.vue +14 -1
- package/src/components/RecentlyOpened.vue +9 -1
- package/src/components/SideNav.vue +409 -0
- package/src/components/TokensPanel.vue +109 -1
- package/src/components/Toolbar.vue +98 -3
- package/src/locales/en.ts +34 -0
- package/src/locales/tr.ts +34 -0
- package/src/styles/base.css +360 -0
- package/src/types/ExplorerConfig.ts +68 -0
|
@@ -73,6 +73,23 @@ const props = defineProps<{
|
|
|
73
73
|
theme?: ThemeMode;
|
|
74
74
|
/* koru:k1 — inspector (details panel) toggle state, for the pressed style. */
|
|
75
75
|
inspectorOpen?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* gezinti:g1 — navigation panel state, for the pressed style on the toggle.
|
|
78
|
+
* The panel has its own collapse control, but a narrow explorer renders it
|
|
79
|
+
* as a drawer that is off screen when closed, so the only way back to it is
|
|
80
|
+
* from here.
|
|
81
|
+
*/
|
|
82
|
+
navOpen?: boolean;
|
|
83
|
+
/** gezinti:g1 — false when the deployment has no navigation panel; the
|
|
84
|
+
* toggle then has nothing to toggle and is not rendered. */
|
|
85
|
+
navEnabled?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* gezinti:g1 — view modes to offer. Absent = all three (the historical
|
|
88
|
+
* behaviour). `ExplorerConfig.uiProfile: 'simple'` narrows it to list+grid:
|
|
89
|
+
* a four-way switcher is one of the things the reporter in #14 named as
|
|
90
|
+
* "power user tool", and gallery is the one nobody outside photos asks for.
|
|
91
|
+
*/
|
|
92
|
+
viewModes?: ViewMode[];
|
|
76
93
|
}>();
|
|
77
94
|
|
|
78
95
|
const emit = defineEmits<{
|
|
@@ -86,6 +103,7 @@ const emit = defineEmits<{
|
|
|
86
103
|
(e: 'open-recents'): void;
|
|
87
104
|
(e: 'update:density', v: Density): void;
|
|
88
105
|
(e: 'toggle-inspector'): void /* koru:k1 */;
|
|
106
|
+
(e: 'toggle-nav'): void /* gezinti:g1 */;
|
|
89
107
|
(e: 'open-theme'): void /* wiring:c1 — tema galerisi */;
|
|
90
108
|
(e: 'open-shortcut-settings'): void /* wiring:c2 */;
|
|
91
109
|
}>();
|
|
@@ -270,6 +288,12 @@ const moreRef = ref<InstanceType<typeof ContextMenu> | null>(null);
|
|
|
270
288
|
// Everything the wide toolbar renders as standalone buttons, folded into one
|
|
271
289
|
// action list: folder-level writes (New Folder / Paste), the shared
|
|
272
290
|
// selection actions, then the view utilities (Refresh / density / view mode).
|
|
291
|
+
/* gezinti:g1 — is this view mode on offer? No prop = all of them, so every
|
|
292
|
+
embedder that predates uiProfile keeps the switcher it had. */
|
|
293
|
+
function offersView(v: ViewMode): boolean {
|
|
294
|
+
return !props.viewModes || props.viewModes.includes(v);
|
|
295
|
+
}
|
|
296
|
+
|
|
273
297
|
const moreActions = computed<ContextAction[]>(() => {
|
|
274
298
|
const list: ContextAction[] = [];
|
|
275
299
|
const writable =
|
|
@@ -291,10 +315,15 @@ const moreActions = computed<ContextAction[]>(() => {
|
|
|
291
315
|
});
|
|
292
316
|
/* wiring:d2 — dar mod ⋯ menüsü: aktif olmayan İKİ görünüm de listelenir
|
|
293
317
|
(list/grid/gallery); eski tekli toggle üç modda eksik kalıyordu. */
|
|
294
|
-
if (props.viewMode !== 'list') list.push({ key: 'view-list', label: t('toolbar.view.list'), icon: '☰' });
|
|
295
|
-
if (props.viewMode !== 'grid') list.push({ key: 'view-grid', label: t('toolbar.view.grid'), icon: '▦' });
|
|
296
|
-
if (props.viewMode !== 'gallery') list.push({ key: 'view-gallery', label: t('toolbar.view.gallery'), icon: '▣' });
|
|
318
|
+
if (props.viewMode !== 'list' && offersView('list')) list.push({ key: 'view-list', label: t('toolbar.view.list'), icon: '☰' });
|
|
319
|
+
if (props.viewMode !== 'grid' && offersView('grid')) list.push({ key: 'view-grid', label: t('toolbar.view.grid'), icon: '▦' });
|
|
320
|
+
if (props.viewMode !== 'gallery' && offersView('gallery')) list.push({ key: 'view-gallery', label: t('toolbar.view.gallery'), icon: '▣' });
|
|
297
321
|
/* /wiring:d2 */
|
|
322
|
+
/* gezinti:g1 — reachable from the overflow menu too, because the toolbar's
|
|
323
|
+
own toggle is hidden while the narrow search field is expanded. */
|
|
324
|
+
if (props.navEnabled !== false) {
|
|
325
|
+
list.push({ key: 'nav', label: t('toolbar.nav'), icon: '☰' });
|
|
326
|
+
}
|
|
298
327
|
/* koru:k1 — inspector toggle also reachable from the narrow overflow menu */
|
|
299
328
|
list.push({ key: 'inspector', label: t('toolbar.inspector'), icon: 'ℹ' });
|
|
300
329
|
/* wiring:c1 — tema galerisi de dar modda ⋯ menüsünden açılır */
|
|
@@ -332,6 +361,9 @@ function onMoreSelect(a: ContextAction) {
|
|
|
332
361
|
case 'inspector' /* koru:k1 */:
|
|
333
362
|
emit('toggle-inspector');
|
|
334
363
|
break;
|
|
364
|
+
case 'nav' /* gezinti:g1 */:
|
|
365
|
+
emit('toggle-nav');
|
|
366
|
+
break;
|
|
335
367
|
case 'theme' /* wiring:c1 */:
|
|
336
368
|
emit('open-theme');
|
|
337
369
|
break;
|
|
@@ -360,6 +392,36 @@ function onMoreSelect(a: ContextAction) {
|
|
|
360
392
|
<!-- bag:b4 — wide layout, untouched; renders exactly as before when not narrow -->
|
|
361
393
|
<template v-if="!narrow">
|
|
362
394
|
<div ref="primaryEl" class="fe-toolbar__primary">
|
|
395
|
+
<!-- gezinti:g1 — navigation panel toggle. Leftmost, the position every
|
|
396
|
+
Drive-shaped UI puts it in; the panel's own header carries the same
|
|
397
|
+
action, and in narrow mode where the panel is an off-screen drawer
|
|
398
|
+
this is the ONLY way back to it. -->
|
|
399
|
+
<button
|
|
400
|
+
v-if="navEnabled !== false"
|
|
401
|
+
type="button"
|
|
402
|
+
class="fe-btn fe-btn--icon-only fe-toolbar__nav"
|
|
403
|
+
:class="{ 'is-active': navOpen }"
|
|
404
|
+
:aria-pressed="!!navOpen"
|
|
405
|
+
:title="t('toolbar.nav')"
|
|
406
|
+
:aria-label="t('toolbar.nav')"
|
|
407
|
+
data-testid="toolbar-nav"
|
|
408
|
+
@click="emit('toggle-nav')"
|
|
409
|
+
>
|
|
410
|
+
<svg
|
|
411
|
+
class="fe-ficon"
|
|
412
|
+
viewBox="0 0 24 24"
|
|
413
|
+
fill="none"
|
|
414
|
+
stroke="currentColor"
|
|
415
|
+
stroke-width="1.8"
|
|
416
|
+
stroke-linecap="round"
|
|
417
|
+
aria-hidden="true"
|
|
418
|
+
focusable="false"
|
|
419
|
+
>
|
|
420
|
+
<rect x="3.5" y="4.5" width="17" height="15" rx="2" />
|
|
421
|
+
<path d="M9.5 4.5v15" />
|
|
422
|
+
</svg>
|
|
423
|
+
</button>
|
|
424
|
+
|
|
363
425
|
<button
|
|
364
426
|
v-if="canGoUp"
|
|
365
427
|
type="button"
|
|
@@ -567,6 +629,7 @@ function onMoreSelect(a: ContextAction) {
|
|
|
567
629
|
<div class="fe-toolbar__view" role="tablist" :aria-label="t('toolbar.view_label') /* wiring:c4 — was hardcoded EN */">
|
|
568
630
|
<button
|
|
569
631
|
type="button"
|
|
632
|
+
v-if="offersView('list')"
|
|
570
633
|
class="fe-btn fe-btn--icon-only"
|
|
571
634
|
:class="{ 'is-active': viewMode === 'list' }"
|
|
572
635
|
role="tab"
|
|
@@ -579,6 +642,7 @@ function onMoreSelect(a: ContextAction) {
|
|
|
579
642
|
</button>
|
|
580
643
|
<button
|
|
581
644
|
type="button"
|
|
645
|
+
v-if="offersView('grid')"
|
|
582
646
|
class="fe-btn fe-btn--icon-only"
|
|
583
647
|
:class="{ 'is-active': viewMode === 'grid' }"
|
|
584
648
|
role="tab"
|
|
@@ -592,6 +656,7 @@ function onMoreSelect(a: ContextAction) {
|
|
|
592
656
|
<!-- wiring:d2 — üçüncü görünüm: galeri -->
|
|
593
657
|
<button
|
|
594
658
|
type="button"
|
|
659
|
+
v-if="offersView('gallery')"
|
|
595
660
|
class="fe-btn fe-btn--icon-only"
|
|
596
661
|
:class="{ 'is-active': viewMode === 'gallery' }"
|
|
597
662
|
role="tab"
|
|
@@ -632,6 +697,36 @@ function onMoreSelect(a: ContextAction) {
|
|
|
632
697
|
</button>
|
|
633
698
|
</template>
|
|
634
699
|
<template v-else>
|
|
700
|
+
<!-- gezinti:g1 — navigation panel toggle. Leftmost, the position every
|
|
701
|
+
Drive-shaped UI puts it in; the panel's own header carries the same
|
|
702
|
+
action, and in narrow mode where the panel is an off-screen drawer
|
|
703
|
+
this is the ONLY way back to it. -->
|
|
704
|
+
<button
|
|
705
|
+
v-if="navEnabled !== false"
|
|
706
|
+
type="button"
|
|
707
|
+
class="fe-btn fe-btn--icon-only fe-toolbar__nav"
|
|
708
|
+
:class="{ 'is-active': navOpen }"
|
|
709
|
+
:aria-pressed="!!navOpen"
|
|
710
|
+
:title="t('toolbar.nav')"
|
|
711
|
+
:aria-label="t('toolbar.nav')"
|
|
712
|
+
data-testid="toolbar-nav"
|
|
713
|
+
@click="emit('toggle-nav')"
|
|
714
|
+
>
|
|
715
|
+
<svg
|
|
716
|
+
class="fe-ficon"
|
|
717
|
+
viewBox="0 0 24 24"
|
|
718
|
+
fill="none"
|
|
719
|
+
stroke="currentColor"
|
|
720
|
+
stroke-width="1.8"
|
|
721
|
+
stroke-linecap="round"
|
|
722
|
+
aria-hidden="true"
|
|
723
|
+
focusable="false"
|
|
724
|
+
>
|
|
725
|
+
<rect x="3.5" y="4.5" width="17" height="15" rx="2" />
|
|
726
|
+
<path d="M9.5 4.5v15" />
|
|
727
|
+
</svg>
|
|
728
|
+
</button>
|
|
729
|
+
|
|
635
730
|
<button
|
|
636
731
|
v-if="canGoUp"
|
|
637
732
|
type="button"
|
package/src/locales/en.ts
CHANGED
|
@@ -840,4 +840,38 @@ export const en: Record<string, string> = {
|
|
|
840
840
|
'storages.fields.url': 'Base URL',
|
|
841
841
|
'storages.fields.user': 'User',
|
|
842
842
|
|
|
843
|
+
|
|
844
|
+
/* === gezinti:g1 — navigation panel (SideNav) === */
|
|
845
|
+
'sidenav.title': 'Navigation',
|
|
846
|
+
'sidenav.views': 'Views',
|
|
847
|
+
'sidenav.storages': 'Storages',
|
|
848
|
+
'sidenav.recent': 'Recent',
|
|
849
|
+
'sidenav.starred': 'Starred',
|
|
850
|
+
'sidenav.shared': 'Shared with me',
|
|
851
|
+
'sidenav.trash': 'Trash',
|
|
852
|
+
'sidenav.collapse': 'Collapse navigation',
|
|
853
|
+
'sidenav.expand': 'Expand navigation',
|
|
854
|
+
'sidenav.close': 'Close navigation',
|
|
855
|
+
'sidenav.storage.shared': 'Shared',
|
|
856
|
+
'toolbar.nav': 'Navigation',
|
|
857
|
+
'node.recent': 'Recent',
|
|
858
|
+
'node.starred': 'Starred',
|
|
859
|
+
'node.shared': 'Shared with me',
|
|
860
|
+
'empty.recent.title': 'Nothing opened yet',
|
|
861
|
+
'empty.recent.hint': 'Files you open show up here',
|
|
862
|
+
'empty.starred.title': 'Nothing starred yet',
|
|
863
|
+
'empty.starred.hint': 'Star a file to find it here',
|
|
864
|
+
'empty.shared.title': 'Nothing has been shared with you',
|
|
865
|
+
'empty.shared.hint': 'Folders and files other people share appear here',
|
|
866
|
+
'sidenav.connections': 'Connections',
|
|
867
|
+
'sidenav.connect': 'How to connect',
|
|
868
|
+
'sidenav.apikeys': 'API keys',
|
|
869
|
+
'overlay.close': 'Close',
|
|
870
|
+
'conn.tokens.scopes': 'Permissions',
|
|
871
|
+
'conn.tokens.root': 'Limit to one folder (optional)',
|
|
872
|
+
'conn.tokens.rootPlaceholder': 'storage://folder',
|
|
873
|
+
'conn.tokens.expiry': 'Expires in (days)',
|
|
874
|
+
'conn.tokens.expiryNever': 'never',
|
|
875
|
+
'conn.tokens.capNote': 'Your account and its permissions are the ceiling — asking for more than you have is refused, not granted.',
|
|
876
|
+
/* === /gezinti:g1 === */
|
|
843
877
|
};
|
package/src/locales/tr.ts
CHANGED
|
@@ -843,4 +843,38 @@ export const tr: Record<string, string> = {
|
|
|
843
843
|
'storages.fields.url': 'Temel URL',
|
|
844
844
|
'storages.fields.user': 'Kullanıcı',
|
|
845
845
|
|
|
846
|
+
|
|
847
|
+
/* === gezinti:g1 — gezinti paneli (SideNav) === */
|
|
848
|
+
'sidenav.title': 'Gezinti',
|
|
849
|
+
'sidenav.views': 'Görünümler',
|
|
850
|
+
'sidenav.storages': 'Depolar',
|
|
851
|
+
'sidenav.recent': 'Son kullanılanlar',
|
|
852
|
+
'sidenav.starred': 'Yıldızlılar',
|
|
853
|
+
'sidenav.shared': 'Benimle paylaşılanlar',
|
|
854
|
+
'sidenav.trash': 'Çöp Kutusu',
|
|
855
|
+
'sidenav.collapse': 'Gezintiyi daralt',
|
|
856
|
+
'sidenav.expand': 'Gezintiyi genişlet',
|
|
857
|
+
'sidenav.close': 'Gezintiyi kapat',
|
|
858
|
+
'sidenav.storage.shared': 'Paylaşılan',
|
|
859
|
+
'toolbar.nav': 'Gezinti',
|
|
860
|
+
'node.recent': 'Son kullanılanlar',
|
|
861
|
+
'node.starred': 'Yıldızlılar',
|
|
862
|
+
'node.shared': 'Benimle paylaşılanlar',
|
|
863
|
+
'empty.recent.title': 'Henüz hiçbir şey açılmadı',
|
|
864
|
+
'empty.recent.hint': 'Açtığın dosyalar burada görünür',
|
|
865
|
+
'empty.starred.title': 'Henüz yıldızlı dosya yok',
|
|
866
|
+
'empty.starred.hint': 'Bir dosyayı yıldızla, burada bulasın',
|
|
867
|
+
'empty.shared.title': 'Seninle henüz bir şey paylaşılmadı',
|
|
868
|
+
'empty.shared.hint': 'Başkalarının paylaştığı klasör ve dosyalar burada görünür',
|
|
869
|
+
'sidenav.connections': 'Bağlantılar',
|
|
870
|
+
'sidenav.connect': 'Nasıl bağlanılır',
|
|
871
|
+
'sidenav.apikeys': 'API anahtarları',
|
|
872
|
+
'overlay.close': 'Kapat',
|
|
873
|
+
'conn.tokens.scopes': 'İzinler',
|
|
874
|
+
'conn.tokens.root': 'Tek bir klasörle sınırla (isteğe bağlı)',
|
|
875
|
+
'conn.tokens.rootPlaceholder': 'depo://klasör',
|
|
876
|
+
'conn.tokens.expiry': 'Geçerlilik (gün)',
|
|
877
|
+
'conn.tokens.expiryNever': 'süresiz',
|
|
878
|
+
'conn.tokens.capNote': 'Tavan hesabın ve yetkilerin — sahip olduğundan fazlasını istemek reddedilir, verilmez.',
|
|
879
|
+
/* === /gezinti:g1 === */
|
|
846
880
|
};
|
package/src/styles/base.css
CHANGED
|
@@ -4010,3 +4010,363 @@ filex-explorer {
|
|
|
4010
4010
|
background: var(--fe-primary);
|
|
4011
4011
|
transition: width 0.3s ease;
|
|
4012
4012
|
}
|
|
4013
|
+
|
|
4014
|
+
/* === gezinti:g1 — SideNav (navigation panel) ==========================
|
|
4015
|
+
A left column inside .fe__main, the mirror of .fe-inspector on the right:
|
|
4016
|
+
same flex slot, same overlay trick when the explorer is narrow. .fe__primary
|
|
4017
|
+
already carries `flex: 1 1 auto; min-width: 0`, so it absorbs the width
|
|
4018
|
+
change with no rule of its own. */
|
|
4019
|
+
.fe-sidenav {
|
|
4020
|
+
flex: 0 0 232px;
|
|
4021
|
+
width: 232px;
|
|
4022
|
+
min-height: 0;
|
|
4023
|
+
display: flex;
|
|
4024
|
+
flex-direction: column;
|
|
4025
|
+
border-right: 1px solid var(--fe-border);
|
|
4026
|
+
/* No `background: var(--fe-bg)` here on purpose — see .fe-conn. This one IS
|
|
4027
|
+
inside the full-screen explorer, whose root already paints --fe-bg, so a
|
|
4028
|
+
second identical fill would only be a second chance to disagree with it. */
|
|
4029
|
+
color: var(--fe-text);
|
|
4030
|
+
transition: flex-basis 0.14s ease, width 0.14s ease;
|
|
4031
|
+
}
|
|
4032
|
+
/* Collapsed: an icon rail, not nothing. A panel that disappears takes its own
|
|
4033
|
+
re-open affordance with it. Width is one 32px control plus its padding. */
|
|
4034
|
+
.fe-sidenav--rail {
|
|
4035
|
+
flex: 0 0 56px;
|
|
4036
|
+
width: 56px;
|
|
4037
|
+
}
|
|
4038
|
+
.fe-sidenav__head {
|
|
4039
|
+
flex: 0 0 auto;
|
|
4040
|
+
display: flex;
|
|
4041
|
+
align-items: center;
|
|
4042
|
+
/* Same height as .fe-breadcrumb (37px) so the panel head and the crumb bar
|
|
4043
|
+
sit on one line across the top of .fe__main. */
|
|
4044
|
+
min-height: 37px;
|
|
4045
|
+
padding: 4px 8px;
|
|
4046
|
+
border-bottom: 1px solid var(--fe-border);
|
|
4047
|
+
}
|
|
4048
|
+
.fe-sidenav--rail .fe-sidenav__head {
|
|
4049
|
+
justify-content: center;
|
|
4050
|
+
padding: 4px;
|
|
4051
|
+
}
|
|
4052
|
+
.fe-sidenav__toggle {
|
|
4053
|
+
display: inline-flex;
|
|
4054
|
+
align-items: center;
|
|
4055
|
+
justify-content: center;
|
|
4056
|
+
width: 30px;
|
|
4057
|
+
height: 28px;
|
|
4058
|
+
padding: 0;
|
|
4059
|
+
border: 1px solid transparent;
|
|
4060
|
+
border-radius: var(--fe-radius-sm);
|
|
4061
|
+
background: transparent;
|
|
4062
|
+
color: var(--fe-text-muted);
|
|
4063
|
+
font: inherit;
|
|
4064
|
+
font-size: 17px;
|
|
4065
|
+
cursor: pointer;
|
|
4066
|
+
}
|
|
4067
|
+
.fe-sidenav__toggle:hover {
|
|
4068
|
+
background: var(--fe-bg-hover);
|
|
4069
|
+
color: var(--fe-text);
|
|
4070
|
+
}
|
|
4071
|
+
.fe-sidenav__toggle:focus-visible,
|
|
4072
|
+
.fe-sidenav__item:focus-visible,
|
|
4073
|
+
.fe-sidenav__upload:focus-visible,
|
|
4074
|
+
.fe-sidenav__secondary:focus-visible {
|
|
4075
|
+
outline: 2px solid var(--fe-primary);
|
|
4076
|
+
outline-offset: 1px;
|
|
4077
|
+
}
|
|
4078
|
+
|
|
4079
|
+
/* Primary action block. */
|
|
4080
|
+
.fe-sidenav__primary {
|
|
4081
|
+
flex: 0 0 auto;
|
|
4082
|
+
display: flex;
|
|
4083
|
+
flex-direction: column;
|
|
4084
|
+
gap: 6px;
|
|
4085
|
+
padding: 10px 10px 6px;
|
|
4086
|
+
}
|
|
4087
|
+
.fe-sidenav--rail .fe-sidenav__primary {
|
|
4088
|
+
padding: 10px 8px 6px;
|
|
4089
|
+
align-items: center;
|
|
4090
|
+
}
|
|
4091
|
+
.fe-sidenav__upload,
|
|
4092
|
+
.fe-sidenav__secondary {
|
|
4093
|
+
display: inline-flex;
|
|
4094
|
+
align-items: center;
|
|
4095
|
+
gap: 10px;
|
|
4096
|
+
width: 100%;
|
|
4097
|
+
padding: 9px 12px;
|
|
4098
|
+
border-radius: 999px;
|
|
4099
|
+
border: 1px solid transparent;
|
|
4100
|
+
font: inherit;
|
|
4101
|
+
font-size: 13.5px;
|
|
4102
|
+
cursor: pointer;
|
|
4103
|
+
text-align: left;
|
|
4104
|
+
white-space: nowrap;
|
|
4105
|
+
overflow: hidden;
|
|
4106
|
+
}
|
|
4107
|
+
.fe-sidenav__upload {
|
|
4108
|
+
background: var(--fe-primary);
|
|
4109
|
+
border-color: var(--fe-primary);
|
|
4110
|
+
color: var(--fe-text-on-primary);
|
|
4111
|
+
font-weight: 600;
|
|
4112
|
+
box-shadow: var(--fe-shadow-sm);
|
|
4113
|
+
}
|
|
4114
|
+
.fe-sidenav__upload:hover:not(:disabled) {
|
|
4115
|
+
background: var(--fe-primary-hover);
|
|
4116
|
+
border-color: var(--fe-primary-hover);
|
|
4117
|
+
}
|
|
4118
|
+
.fe-sidenav__secondary {
|
|
4119
|
+
background: transparent;
|
|
4120
|
+
border-color: var(--fe-border);
|
|
4121
|
+
color: var(--fe-text);
|
|
4122
|
+
}
|
|
4123
|
+
.fe-sidenav__secondary:hover:not(:disabled) {
|
|
4124
|
+
background: var(--fe-bg-hover);
|
|
4125
|
+
border-color: var(--fe-border-strong);
|
|
4126
|
+
}
|
|
4127
|
+
.fe-sidenav__upload:disabled,
|
|
4128
|
+
.fe-sidenav__secondary:disabled {
|
|
4129
|
+
opacity: 0.45;
|
|
4130
|
+
cursor: not-allowed;
|
|
4131
|
+
}
|
|
4132
|
+
.fe-sidenav__upload .fe-ficon,
|
|
4133
|
+
.fe-sidenav__secondary .fe-ficon,
|
|
4134
|
+
.fe-sidenav__item .fe-ficon {
|
|
4135
|
+
font-size: 17px;
|
|
4136
|
+
}
|
|
4137
|
+
/* On the rail the two buttons become squares — a pill with no label reads as
|
|
4138
|
+
a stretched blob. */
|
|
4139
|
+
.fe-sidenav--rail .fe-sidenav__upload,
|
|
4140
|
+
.fe-sidenav--rail .fe-sidenav__secondary {
|
|
4141
|
+
width: 38px;
|
|
4142
|
+
height: 38px;
|
|
4143
|
+
padding: 0;
|
|
4144
|
+
justify-content: center;
|
|
4145
|
+
border-radius: var(--fe-radius-md);
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
.fe-sidenav__scroll {
|
|
4149
|
+
flex: 1 1 auto;
|
|
4150
|
+
min-height: 0;
|
|
4151
|
+
overflow-y: auto;
|
|
4152
|
+
padding: 4px 8px 10px;
|
|
4153
|
+
}
|
|
4154
|
+
.fe-sidenav__group {
|
|
4155
|
+
list-style: none;
|
|
4156
|
+
margin: 0;
|
|
4157
|
+
padding: 0;
|
|
4158
|
+
}
|
|
4159
|
+
.fe-sidenav__item {
|
|
4160
|
+
display: flex;
|
|
4161
|
+
align-items: center;
|
|
4162
|
+
gap: 10px;
|
|
4163
|
+
width: 100%;
|
|
4164
|
+
padding: 7px 10px;
|
|
4165
|
+
margin: 1px 0;
|
|
4166
|
+
border: 0;
|
|
4167
|
+
border-radius: 999px;
|
|
4168
|
+
background: transparent;
|
|
4169
|
+
color: var(--fe-text);
|
|
4170
|
+
font: inherit;
|
|
4171
|
+
font-size: 13.5px;
|
|
4172
|
+
cursor: pointer;
|
|
4173
|
+
text-align: left;
|
|
4174
|
+
white-space: nowrap;
|
|
4175
|
+
overflow: hidden;
|
|
4176
|
+
}
|
|
4177
|
+
.fe-sidenav__item:hover {
|
|
4178
|
+
background: var(--fe-bg-hover);
|
|
4179
|
+
}
|
|
4180
|
+
.fe-sidenav__item.is-active {
|
|
4181
|
+
background: var(--fe-bg-selected);
|
|
4182
|
+
font-weight: 600;
|
|
4183
|
+
}
|
|
4184
|
+
.fe-sidenav--rail .fe-sidenav__item {
|
|
4185
|
+
justify-content: center;
|
|
4186
|
+
padding: 8px 0;
|
|
4187
|
+
border-radius: var(--fe-radius-md);
|
|
4188
|
+
}
|
|
4189
|
+
.fe-sidenav__text {
|
|
4190
|
+
overflow: hidden;
|
|
4191
|
+
text-overflow: ellipsis;
|
|
4192
|
+
}
|
|
4193
|
+
.fe-sidenav__section {
|
|
4194
|
+
margin-top: 10px;
|
|
4195
|
+
}
|
|
4196
|
+
.fe-sidenav__heading {
|
|
4197
|
+
margin: 4px 0 2px;
|
|
4198
|
+
padding: 0 10px;
|
|
4199
|
+
font-size: 11px;
|
|
4200
|
+
font-weight: 600;
|
|
4201
|
+
letter-spacing: 0.04em;
|
|
4202
|
+
text-transform: uppercase;
|
|
4203
|
+
color: var(--fe-text-muted);
|
|
4204
|
+
}
|
|
4205
|
+
.fe-sidenav__rule {
|
|
4206
|
+
margin: 8px 6px;
|
|
4207
|
+
border: 0;
|
|
4208
|
+
border-top: 1px solid var(--fe-border);
|
|
4209
|
+
}
|
|
4210
|
+
/* "Shared" marker on a storage the caller only reaches through a grant. */
|
|
4211
|
+
.fe-sidenav__tag {
|
|
4212
|
+
margin-left: auto;
|
|
4213
|
+
flex: 0 0 auto;
|
|
4214
|
+
padding: 1px 6px;
|
|
4215
|
+
border-radius: 999px;
|
|
4216
|
+
background: color-mix(in srgb, var(--fe-primary) 16%, transparent);
|
|
4217
|
+
color: var(--fe-primary);
|
|
4218
|
+
font-size: 10px;
|
|
4219
|
+
font-weight: 600;
|
|
4220
|
+
letter-spacing: 0.02em;
|
|
4221
|
+
}
|
|
4222
|
+
|
|
4223
|
+
/* Narrow (<560px): a drawer over the listing, never a column. At 390px a
|
|
4224
|
+
232px column leaves 158px for the files, and a 56px rail still takes an
|
|
4225
|
+
eighth of the screen from a listing that is already tight. */
|
|
4226
|
+
.fe-sidenav--drawer {
|
|
4227
|
+
position: absolute;
|
|
4228
|
+
top: 0;
|
|
4229
|
+
bottom: 0;
|
|
4230
|
+
left: 0;
|
|
4231
|
+
width: min(280px, 84%);
|
|
4232
|
+
flex: none;
|
|
4233
|
+
/* Same layer as .fe-inspector--overlay: above the listing and the FAB (45),
|
|
4234
|
+
below the context menus (90) and the onboarding tour (96), which are
|
|
4235
|
+
appended to <body> and would paint over anything lower anyway. */
|
|
4236
|
+
z-index: 60;
|
|
4237
|
+
background: var(--fe-bg);
|
|
4238
|
+
border-right: 1px solid var(--fe-border);
|
|
4239
|
+
box-shadow: var(--fe-shadow);
|
|
4240
|
+
}
|
|
4241
|
+
.fe-sidenav__scrim {
|
|
4242
|
+
position: absolute;
|
|
4243
|
+
inset: 0;
|
|
4244
|
+
z-index: 59;
|
|
4245
|
+
border: 0;
|
|
4246
|
+
padding: 0;
|
|
4247
|
+
background: rgba(13, 19, 27, 0.42);
|
|
4248
|
+
cursor: pointer;
|
|
4249
|
+
}
|
|
4250
|
+
/* === /gezinti:g1 === */
|
|
4251
|
+
|
|
4252
|
+
/* --- gezinti:g1 — the panel's Connections / API-keys overlay --- */
|
|
4253
|
+
/* ⚠ z-index 130. The explorer's onboarding tour (96) and its teleported menus
|
|
4254
|
+
(90) are appended to <body> and are `fixed`, so they paint over anything in
|
|
4255
|
+
the normal stacking order — measured in the web app, where the tour card
|
|
4256
|
+
landed on top of this very surface and swallowed its clicks; that page went
|
|
4257
|
+
to z-[120] for the same reason and this has to clear it. */
|
|
4258
|
+
.fe-overlay {
|
|
4259
|
+
position: fixed;
|
|
4260
|
+
inset: 0;
|
|
4261
|
+
z-index: 130;
|
|
4262
|
+
display: flex;
|
|
4263
|
+
align-items: flex-start;
|
|
4264
|
+
justify-content: center;
|
|
4265
|
+
padding: 32px 16px;
|
|
4266
|
+
overflow: auto;
|
|
4267
|
+
background: rgba(13, 19, 27, 0.45);
|
|
4268
|
+
}
|
|
4269
|
+
.fe-overlay__card {
|
|
4270
|
+
width: min(880px, 100%);
|
|
4271
|
+
max-height: calc(100vh - 64px);
|
|
4272
|
+
overflow: auto;
|
|
4273
|
+
padding: 18px;
|
|
4274
|
+
border-radius: var(--fe-radius-lg);
|
|
4275
|
+
border: 1px solid var(--fe-border);
|
|
4276
|
+
background: var(--fe-bg);
|
|
4277
|
+
color: var(--fe-text);
|
|
4278
|
+
box-shadow: var(--fe-shadow);
|
|
4279
|
+
}
|
|
4280
|
+
.fe-overlay__head {
|
|
4281
|
+
display: flex;
|
|
4282
|
+
align-items: center;
|
|
4283
|
+
justify-content: space-between;
|
|
4284
|
+
gap: 8px;
|
|
4285
|
+
margin-bottom: 10px;
|
|
4286
|
+
}
|
|
4287
|
+
.fe-overlay__title {
|
|
4288
|
+
margin: 0;
|
|
4289
|
+
font-size: 15px;
|
|
4290
|
+
font-weight: 600;
|
|
4291
|
+
}
|
|
4292
|
+
.fe-overlay__close {
|
|
4293
|
+
border: 0;
|
|
4294
|
+
background: transparent;
|
|
4295
|
+
color: var(--fe-text-muted);
|
|
4296
|
+
font-size: 20px;
|
|
4297
|
+
line-height: 1;
|
|
4298
|
+
padding: 2px 6px;
|
|
4299
|
+
border-radius: var(--fe-radius-sm);
|
|
4300
|
+
cursor: pointer;
|
|
4301
|
+
}
|
|
4302
|
+
.fe-overlay__close:hover {
|
|
4303
|
+
background: var(--fe-bg-hover);
|
|
4304
|
+
color: var(--fe-text);
|
|
4305
|
+
}
|
|
4306
|
+
.fe-overlay__close:focus-visible {
|
|
4307
|
+
outline: 2px solid var(--fe-primary);
|
|
4308
|
+
outline-offset: 1px;
|
|
4309
|
+
}
|
|
4310
|
+
@media (max-width: 560px) {
|
|
4311
|
+
.fe-overlay {
|
|
4312
|
+
padding: 12px 8px;
|
|
4313
|
+
}
|
|
4314
|
+
.fe-overlay__card {
|
|
4315
|
+
padding: 12px;
|
|
4316
|
+
max-height: calc(100vh - 24px);
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
|
|
4320
|
+
/* The full API-key form (TokensPanel `full`). */
|
|
4321
|
+
.fe-tokform {
|
|
4322
|
+
display: flex;
|
|
4323
|
+
flex-direction: column;
|
|
4324
|
+
gap: 10px;
|
|
4325
|
+
margin: 8px 0 4px;
|
|
4326
|
+
}
|
|
4327
|
+
.fe-tokform__scopes {
|
|
4328
|
+
display: flex;
|
|
4329
|
+
flex-wrap: wrap;
|
|
4330
|
+
align-items: center;
|
|
4331
|
+
gap: 12px;
|
|
4332
|
+
margin: 0;
|
|
4333
|
+
padding: 8px 10px;
|
|
4334
|
+
border: 1px solid var(--fe-border);
|
|
4335
|
+
border-radius: var(--fe-radius-sm);
|
|
4336
|
+
}
|
|
4337
|
+
.fe-tokform__legend {
|
|
4338
|
+
padding: 0 4px;
|
|
4339
|
+
font-size: 12px;
|
|
4340
|
+
color: var(--fe-text-muted);
|
|
4341
|
+
}
|
|
4342
|
+
.fe-tokform__scope {
|
|
4343
|
+
display: inline-flex;
|
|
4344
|
+
align-items: center;
|
|
4345
|
+
gap: 5px;
|
|
4346
|
+
font-size: 13px;
|
|
4347
|
+
font-family: var(--fe-font-mono);
|
|
4348
|
+
cursor: pointer;
|
|
4349
|
+
}
|
|
4350
|
+
.fe-tokform__field {
|
|
4351
|
+
display: flex;
|
|
4352
|
+
flex-direction: column;
|
|
4353
|
+
gap: 4px;
|
|
4354
|
+
min-width: 0;
|
|
4355
|
+
}
|
|
4356
|
+
.fe-tokform__field--narrow {
|
|
4357
|
+
flex: 0 0 180px;
|
|
4358
|
+
}
|
|
4359
|
+
.fe-tokform__label {
|
|
4360
|
+
font-size: 12px;
|
|
4361
|
+
color: var(--fe-text-muted);
|
|
4362
|
+
}
|
|
4363
|
+
.fe-tokform__row {
|
|
4364
|
+
display: flex;
|
|
4365
|
+
align-items: flex-end;
|
|
4366
|
+
gap: 10px;
|
|
4367
|
+
flex-wrap: wrap;
|
|
4368
|
+
}
|
|
4369
|
+
.fe-tokform__row .fe-s3keys__btn {
|
|
4370
|
+
margin-left: auto;
|
|
4371
|
+
}
|
|
4372
|
+
/* --- /gezinti:g1 --- */
|
|
@@ -257,6 +257,74 @@ export interface ExplorerConfig {
|
|
|
257
257
|
/** Whether the info panel toggle is visible. */
|
|
258
258
|
showInfoPanel?: boolean;
|
|
259
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Which set of chrome the explorer presents. A PRESET, not a feature switch:
|
|
262
|
+
* nothing is removed from the code, and every capability stays reachable —
|
|
263
|
+
* `'simple'` only changes what is on screen by default.
|
|
264
|
+
*
|
|
265
|
+
* 'standard' (default) — everything: tab strip, split pane, all three view
|
|
266
|
+
* modes. The tool the explorer has always been.
|
|
267
|
+
* 'simple' — one pane, one folder, list/grid only. The
|
|
268
|
+
* navigation panel starts expanded, the tab strip
|
|
269
|
+
* and split pane are off, the gallery view mode and
|
|
270
|
+
* the host's "How to connect" surface are hidden.
|
|
271
|
+
*
|
|
272
|
+
* Why it exists (GitHub #14): the reporter's users are not in IT and read
|
|
273
|
+
* split panes, tabs and mount instructions as a file manager they would have
|
|
274
|
+
* to relearn. The answer was NOT a second UI — one explorer, configured, so
|
|
275
|
+
* a fix lands in one place for every surface that mounts this package.
|
|
276
|
+
*
|
|
277
|
+
* ⚠ It does not gate the navigation panel. The panel ships in both profiles,
|
|
278
|
+
* for administrators too; only its default expanded/collapsed state and the
|
|
279
|
+
* rest of the chrome differ. A viewer's own collapse choice, once made,
|
|
280
|
+
* outranks the profile — it is a per-viewer preference, not a policy.
|
|
281
|
+
*/
|
|
282
|
+
uiProfile?: 'standard' | 'simple';
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Render the navigation panel (Upload · Recent / Starred / Shared with me /
|
|
286
|
+
* Trash · the storage list). Collapsible to an icon rail by the viewer, whose
|
|
287
|
+
* choice is remembered per browser.
|
|
288
|
+
*
|
|
289
|
+
* Default: ON — everywhere, including the desktop app and every embed. This
|
|
290
|
+
* package is the reason those are one product; a panel that appeared in the
|
|
291
|
+
* web app and nowhere else would turn it back into three (0.16.0 did exactly
|
|
292
|
+
* that with `tabStrip` and it had to be undone).
|
|
293
|
+
*
|
|
294
|
+
* ⚠ ONE exception, and it is about `rootPath`, not about who is embedding: a
|
|
295
|
+
* confined embed has no storage list to show, and its views would list files
|
|
296
|
+
* from OUTSIDE the folder the embed was confined to. So `rootPath` flips the
|
|
297
|
+
* default to off. Setting `sideNav: true` alongside `rootPath` still wins —
|
|
298
|
+
* the panel is then yours, along with what its views will show.
|
|
299
|
+
*/
|
|
300
|
+
sideNav?: boolean;
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Show the navigation panel's **Connections** entries — "How to connect"
|
|
304
|
+
* (the storage list and the per-protocol guides: WebDAV · SFTP · FTPS · S3 ·
|
|
305
|
+
* NFS · `filex mount`) and **API keys** (mint and revoke the tokens three of
|
|
306
|
+
* those protocols sign in with).
|
|
307
|
+
*
|
|
308
|
+
* Default: on, EXCEPT under `uiProfile: 'simple'`, where it is off. Those two
|
|
309
|
+
* defaults answer two different people: #14's reporter called mount
|
|
310
|
+
* instructions power-user noise in front of users who are not in IT, and an
|
|
311
|
+
* embedder's own tenant may still legitimately need an S3 key. Whichever one
|
|
312
|
+
* you are, say so explicitly and the default stops mattering.
|
|
313
|
+
*
|
|
314
|
+
* ⚠ Not gated on role, ever. The backend decides what a caller may see —
|
|
315
|
+
* ConnectionsPanel renders what the API returns, and `/api/tokens` caps every
|
|
316
|
+
* scope against the caller's own role and grants. A UI-side role check here
|
|
317
|
+
* would only hide the surface from the accounts that need it most; that is
|
|
318
|
+
* the bug this became: for a year the sole place to mint the token the FTPS
|
|
319
|
+
* guide told you to use was the admin panel.
|
|
320
|
+
*
|
|
321
|
+
* ⚠ The entries live in the panel, so `sideNav: false` takes them with it.
|
|
322
|
+
* Hosts that want the surface without the panel mount `<filex-connections>`
|
|
323
|
+
* (or `ConnectionsPanel`) on a page of their own.
|
|
324
|
+
*/
|
|
325
|
+
connections?: boolean;
|
|
326
|
+
|
|
327
|
+
|
|
260
328
|
/** Default view. */
|
|
261
329
|
viewMode?: 'list' | 'grid';
|
|
262
330
|
|