@kolkrabbi/kol-component 0.317.4 → 0.317.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -78,6 +78,7 @@ import { GRAB_COLUMN } from '../utilities/motion.js'
|
|
|
78
78
|
* so a consumer hands back what it stored. A key with no column is ignored;
|
|
79
79
|
* any column it does not name falls back to the drag state, then `columnWidth`
|
|
80
80
|
* @param {Function} onColumnResize (index, px) => void — the column's index, or `'preview'`
|
|
81
|
+
* @param {'inline'|'dock'} previewPlacement the preview column after the last column (default), or docked at the right edge, the columns scrolling to its left; empty until something is shown
|
|
81
82
|
* @param {Function} onDropFiles (files, folderPath) => void — OS files dropped on a folder row or column; the consumer uploads
|
|
82
83
|
* @param {string} className extra classes on the browser
|
|
83
84
|
*
|
|
@@ -525,6 +526,10 @@ export default function ColumnBrowser({
|
|
|
525
526
|
* folder's own pane (MediaLibraryInfoKey, kol-website 2026-10-30 — the media page's folder facts,
|
|
526
527
|
* as its rows draw them). Null for a level with nothing to say; absent, the column is empty. */
|
|
527
528
|
renderFolderPreview,
|
|
529
|
+
/* `previewPlacement` — where the preview column sits (ColumnPreviewDocked, kol-website 2026-10-30):
|
|
530
|
+
* `'inline'` the column after the last one, as ever; `'dock'` pinned at the right edge beside a
|
|
531
|
+
* self-scrolling strip of columns, the way the media page's Rows and Grid dock theirs. */
|
|
532
|
+
previewPlacement = 'inline',
|
|
528
533
|
height,
|
|
529
534
|
defaultHeight = 528,
|
|
530
535
|
onHeightChange,
|
|
@@ -844,7 +849,7 @@ export default function ColumnBrowser({
|
|
|
844
849
|
useEffect(() => {
|
|
845
850
|
const box = marquee.ref.current
|
|
846
851
|
if (box && box.scrollWidth > box.clientWidth) box.scrollTo({ left: box.scrollWidth, behavior: 'smooth' })
|
|
847
|
-
}, [prefix, picked?.key, selectedKeys?.size]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
852
|
+
}, [prefix, picked?.key, selectedKeys?.size, previewPlacement]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
848
853
|
|
|
849
854
|
if (stack) {
|
|
850
855
|
/* THE CURRENT LEVEL IS THE LIST (StackModeChromeAndAncestors, kol-r2b2
|
|
@@ -989,19 +994,16 @@ export default function ColumnBrowser({
|
|
|
989
994
|
)
|
|
990
995
|
}
|
|
991
996
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
{/* A CLICK ON THE BACKGROUND DESELECTS (user 2026-09-22) — the band's own closing click is
|
|
1003
|
-
swallowed by the hook, so this only ever fires on a real click into empty space. */}
|
|
1004
|
-
<div ref={marquee.ref} {...marquee.props} className="relative flex h-full overflow-x-auto"
|
|
997
|
+
/* the preview pane: several selected → the stack; a file → its preview; else the open folder's pane */
|
|
998
|
+
const dock = previewPlacement === 'dock'
|
|
999
|
+
const pane = selectedKeys?.size > 1
|
|
1000
|
+
? <SelectionPreview keys={[...selectedKeys]} objects={objects} urlOf={urlOf} kindOf={kindOf} formatSize={formatSize} formatDate={formatDate} width={widthOf('preview')} />
|
|
1001
|
+
: shown
|
|
1002
|
+
? <Preview key={shown.key} o={shown} urlOf={urlOf} kindOf={kindOf} kindLabel={kindLabel} formatSize={formatSize} formatDate={formatDate} renderPreview={renderPreview} renderDetails={renderDetails} width={widthOf('preview')} />
|
|
1003
|
+
: folderPane
|
|
1004
|
+
/* the scrolling strip of columns; inline, the pane rides at its end */
|
|
1005
|
+
const strip = (cls, after) => (
|
|
1006
|
+
<div ref={marquee.ref} {...marquee.props} className={`relative flex h-full overflow-x-auto${cls}`}
|
|
1005
1007
|
onClick={(e) => { if (!e.target.closest('[data-marquee-key]')) { pick(null); onSelectKeys?.([], false) } }}>
|
|
1006
1008
|
{marquee.rect && <div className="kol-marquee" style={marquee.rect} />}
|
|
1007
1009
|
{levels.map((level, k) => {
|
|
@@ -1094,20 +1096,39 @@ export default function ColumnBrowser({
|
|
|
1094
1096
|
</Fragment>
|
|
1095
1097
|
)
|
|
1096
1098
|
})}
|
|
1097
|
-
{
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1099
|
+
{after}
|
|
1100
|
+
</div>
|
|
1101
|
+
)
|
|
1102
|
+
|
|
1103
|
+
return (
|
|
1104
|
+
<div
|
|
1105
|
+
ref={rootRef}
|
|
1106
|
+
tabIndex={0}
|
|
1107
|
+
onKeyDown={onKeyDown}
|
|
1108
|
+
// 12 rows of 44px by default (user ruling 2026-08-27), so the browser never jumps as columns change; each column scrolls.
|
|
1109
|
+
// The scroll box is the INNER flex row: the bottom handle is absolute on this root, so it spans the visible width, not the scrolled one.
|
|
1110
|
+
className={`kol-column-browser relative border rounded outline-none ${className}`.trim()}
|
|
1111
|
+
style={{ borderColor: 'var(--kol-oq-08)', height: h }}
|
|
1112
|
+
>
|
|
1113
|
+
{/* A CLICK ON THE BACKGROUND DESELECTS (user 2026-09-22) — the band's own closing click is
|
|
1114
|
+
swallowed by the hook, so this only ever fires on a real click into empty space. */}
|
|
1115
|
+
{dock ? (
|
|
1116
|
+
/* the strip tucks 1px under the pane's edge, so once the columns overflow the last one's edge
|
|
1117
|
+
* and the divider are one line (`.kol-column-browser-strip--dock`, kol-theme) */
|
|
1118
|
+
<div className="flex h-full">
|
|
1119
|
+
{strip(' kol-column-browser-strip--dock flex-1 min-w-0 -mr-px')}
|
|
1120
|
+
{/* the handle sits on the pane's LEFT edge, so a drag to the left widens it */}
|
|
1121
|
+
<ResizeHandle axis="x" onDrag={(dx) => resizeCol('preview')(-dx)} onEnd={endDrag} />
|
|
1122
|
+
<div className="relative flex shrink-0 border-l" style={{ borderColor: 'var(--kol-oq-08)' }}>
|
|
1123
|
+
{pane || <div className="kol-column-browser-preview shrink-0" style={{ width: widthOf('preview') }} />}
|
|
1124
|
+
</div>
|
|
1125
|
+
</div>
|
|
1126
|
+
) : strip('', pane && (
|
|
1103
1127
|
<>
|
|
1104
|
-
{
|
|
1105
|
-
? <Preview key={shown.key} o={shown} urlOf={urlOf} kindOf={kindOf} kindLabel={kindLabel} formatSize={formatSize} formatDate={formatDate} renderPreview={renderPreview} renderDetails={renderDetails} width={widthOf('preview')} />
|
|
1106
|
-
: folderPane}
|
|
1128
|
+
{pane}
|
|
1107
1129
|
<ResizeHandle axis="x" onDrag={resizeCol('preview')} onEnd={endDrag} />
|
|
1108
1130
|
</>
|
|
1109
|
-
)}
|
|
1110
|
-
</div>
|
|
1131
|
+
))}
|
|
1111
1132
|
{/* A FILL HEIGHT HAS NO GRABBER (user 2026-09-22). A CSS length says "take the room you
|
|
1112
1133
|
* are given"; dragging would fight it. A px height keeps its handle. */}
|
|
1113
1134
|
{typeof h !== 'string' && <ResizeHandle axis="y" onDrag={resizeH} onEnd={endDrag} />}
|
|
@@ -1117,7 +1138,7 @@ export default function ColumnBrowser({
|
|
|
1117
1138
|
|
|
1118
1139
|
/* The preview column, for the media page's ROW view (user 2026-09-22 — Finder's "Show preview"):
|
|
1119
1140
|
* one preview, the same pane beside rows as beside columns. Not in the barrel. */
|
|
1120
|
-
export { Preview, SelectionPreview }
|
|
1141
|
+
export { Preview, SelectionPreview, COL_ICON }
|
|
1121
1142
|
|
|
1122
1143
|
/* SelectionPreview — the preview pane for MORE THAN ONE selected item (user 2026-09-23, Finder's:
|
|
1123
1144
|
* *"preview of 3 overlapping files, 2 and 3 slightly rotated behind 1 … the info fields should say
|
|
@@ -42,7 +42,7 @@ import DocumentEditor from './DocumentEditor.jsx'
|
|
|
42
42
|
import { listDrafts, moveDrafts, DRAFTS_EVENT } from '../utilities/localDrafts.js'
|
|
43
43
|
import { parseFrontmatter } from '@kolkrabbi/kol-markdown'
|
|
44
44
|
import CommandOverlay from './CommandOverlay.jsx'
|
|
45
|
-
import ColumnBrowser, { isFileDrag, Preview as ColumnPreview, SelectionPreview } from './ColumnBrowser.jsx'
|
|
45
|
+
import ColumnBrowser, { isFileDrag, Preview as ColumnPreview, SelectionPreview, COL_ICON } from './ColumnBrowser.jsx'
|
|
46
46
|
import SettingsPanel, { SettingsSwitch, SettingsChoice, SettingsMulti, SettingsFooter } from './SettingsPanel.jsx'
|
|
47
47
|
import SettingsSections from './SettingsSections.jsx'
|
|
48
48
|
import { kindOf, extOf, KIND_LABEL, KINDS, DEFAULT_KINDS, isSystemFile, isSegment, groupSegments, groupVariants, posterFor, partition } from '../utilities/mediaKinds.js'
|
|
@@ -536,6 +536,105 @@ function ContainerPreview({ name, icon, facts, width, details }) {
|
|
|
536
536
|
)
|
|
537
537
|
}
|
|
538
538
|
|
|
539
|
+
/* GET INFO (MediaGetInfoModal, kol-website 2026-10-30) — Finder's ⌘I body, one for a folder and a
|
|
540
|
+
* file, drawn inside Quick Look's window (`QuickLookFrame` in a `FullscreenOverlay`, as
|
|
541
|
+
* `MediaInspector` draws it): the head row (a glyph no taller than its lines, the name, the size,
|
|
542
|
+
* "Modified"), the tags, General as key/value rows, then a file's small preview. Never the pane's
|
|
543
|
+
* full-height plate. `general` is `[label, value]` pairs. */
|
|
544
|
+
function InfoBody({ icon, name, size, modified, tags, general, preview }) {
|
|
545
|
+
return (
|
|
546
|
+
<div className="flex-1 self-start w-[min(var(--kol-ql-max-w),420px)] max-h-[var(--kol-ql-media-h)] overflow-y-auto p-4 flex flex-col gap-4">
|
|
547
|
+
<div className="flex items-start gap-3">
|
|
548
|
+
<IconFrame name={icon} variant="ghost" size="sm" />
|
|
549
|
+
<div className="flex-1 min-w-0 flex flex-col gap-0.5 kol-mono-12">
|
|
550
|
+
<div className="flex items-baseline justify-between gap-4">
|
|
551
|
+
<span className="text-strong break-all">{name}</span>
|
|
552
|
+
{size && <span className="text-fg-default shrink-0">{size}</span>}
|
|
553
|
+
</div>
|
|
554
|
+
{modified && <span className="text-meta">Modified {modified}</span>}
|
|
555
|
+
</div>
|
|
556
|
+
</div>
|
|
557
|
+
{tags}
|
|
558
|
+
<dl className="flex flex-col gap-1">
|
|
559
|
+
{general.map(([k, v]) => (
|
|
560
|
+
<div key={k} className="flex justify-between gap-4 kol-mono-12">
|
|
561
|
+
<dt className="text-meta shrink-0">{k}</dt>
|
|
562
|
+
<dd className="text-fg-default text-right break-all">{v}</dd>
|
|
563
|
+
</div>
|
|
564
|
+
))}
|
|
565
|
+
</dl>
|
|
566
|
+
{preview}
|
|
567
|
+
</div>
|
|
568
|
+
)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/* the size as Finder's Get Info writes it: bytes, then readable, then the count for a folder */
|
|
572
|
+
const infoSize = (bytes, items) => `${bytes.toLocaleString('en-US')} bytes (${formatSize(bytes)})${items != null ? ` for ${items} item${items === 1 ? '' : 's'}` : ''}`
|
|
573
|
+
|
|
574
|
+
/* ⌘I's modal: a folder's or a file's Get Info in Quick Look's window. `path` is a folder (ends `/`) or
|
|
575
|
+
* a file key; `where` is the bucket's label, the head of the Where breadcrumb. */
|
|
576
|
+
function GetInfoModal({ path, objects, where, onClose, formatDate, tags, mediaUrl, downloadUrl, hasUrl = true, keySet }) {
|
|
577
|
+
const [dims, setDims] = useState(null)
|
|
578
|
+
const folder = path.endsWith('/')
|
|
579
|
+
const o = folder ? null : objects.find((x) => x.key === path)
|
|
580
|
+
const parts = path.replace(/\/$/, '').split('/')
|
|
581
|
+
const name = o ? (o.displayKey ?? o.displayName ?? parts[parts.length - 1]) : parts[parts.length - 1]
|
|
582
|
+
const crumb = [where, ...parts.slice(0, -1)].join(' › ')
|
|
583
|
+
let frame, body
|
|
584
|
+
if (folder) {
|
|
585
|
+
const inside = objects.filter((x) => x.key.startsWith(path))
|
|
586
|
+
const bytes = inside.reduce((n, x) => n + (x.size ?? 0), 0)
|
|
587
|
+
const last = inside.reduce((t, x) => (x.uploaded && x.uploaded > t ? x.uploaded : t), '')
|
|
588
|
+
frame = { title: name, meta: [`${inside.length} items`, formatSize(bytes)].join(' · ') }
|
|
589
|
+
body = <InfoBody icon="folder" name={name} size={formatSize(bytes)} modified={last ? formatDate(last) : null} tags={tags}
|
|
590
|
+
general={[['Kind', 'Folder'], ['Size', infoSize(bytes, inside.length)], ['Where', crumb], ...(last ? [['Newest', formatDate(last) || '—']] : [])]} />
|
|
591
|
+
} else {
|
|
592
|
+
if (!o) return null
|
|
593
|
+
const kind = kindOf(o)
|
|
594
|
+
const url = hasUrl ? mediaUrl(o.key) : null
|
|
595
|
+
const sized = isImage(o.contentType) || kind === 'video'
|
|
596
|
+
const timed = kind === 'video' || kind === 'audio'
|
|
597
|
+
const poster = kind === 'video' ? posterFor(o.key, keySet) : null
|
|
598
|
+
const onMeta = (e) => { const t = e.currentTarget; setDims({ w: t.videoWidth, h: t.videoHeight, len: t.duration }) }
|
|
599
|
+
frame = {
|
|
600
|
+
title: name,
|
|
601
|
+
meta: [formatSize(o.size), dims?.w && `${dims.w} × ${dims.h} px`, dims?.len && formatLength(dims.len)].filter(Boolean).join(' · '),
|
|
602
|
+
actions: url && (
|
|
603
|
+
<span className="flex items-center gap-1">
|
|
604
|
+
<CopyAction label="Copy URL" text={url} />
|
|
605
|
+
<Tooltip label="Download">
|
|
606
|
+
<Button variant="nav" size="sm" iconOnly="download" href={downloadUrl(o.key)} download={o.key.split('/').pop()} aria-label="Download" />
|
|
607
|
+
</Tooltip>
|
|
608
|
+
</span>
|
|
609
|
+
),
|
|
610
|
+
}
|
|
611
|
+
body = <InfoBody icon={COL_ICON[kind] || 'file'} name={name} size={formatSize(o.size)} modified={o.uploaded ? formatDate(o.uploaded) : null} tags={tags}
|
|
612
|
+
general={[
|
|
613
|
+
['Kind', KIND_LABEL[kind] || 'file'],
|
|
614
|
+
['Type', o.contentType || '—'],
|
|
615
|
+
['Size', infoSize(o.size ?? 0)],
|
|
616
|
+
['Where', crumb],
|
|
617
|
+
['Modified', formatDate(o.uploaded) || '—'],
|
|
618
|
+
...(sized ? [['Dimensions', dims?.w ? `${dims.w} × ${dims.h} px` : url ? '…' : '—']] : []),
|
|
619
|
+
...(timed ? [['Length', dims?.len ? formatLength(dims.len) : url ? '…' : '—']] : []),
|
|
620
|
+
...(url ? [['URL', <span key="u" className="inline-flex items-center gap-1">{url}<CopyAction label="Copy URL" text={url} /></span>]] : []),
|
|
621
|
+
]}
|
|
622
|
+
preview={url && (isImage(o.contentType) ? (
|
|
623
|
+
<img src={url} alt="" className="w-full h-auto rounded" onLoad={(e) => setDims({ w: e.currentTarget.naturalWidth, h: e.currentTarget.naturalHeight })} />
|
|
624
|
+
) : kind === 'video' ? (
|
|
625
|
+
<video src={url} poster={poster ? mediaUrl(poster) : undefined} preload="metadata" controls className="w-full h-auto rounded" onLoadedMetadata={onMeta} />
|
|
626
|
+
) : kind === 'audio' ? (
|
|
627
|
+
/* no picture to show; loaded only for its length */
|
|
628
|
+
<audio src={url} preload="metadata" className="hidden" onLoadedMetadata={onMeta} />
|
|
629
|
+
) : null)} />
|
|
630
|
+
}
|
|
631
|
+
return (
|
|
632
|
+
<FullscreenOverlay open onClose={onClose} closeButton={false} scrim>
|
|
633
|
+
<QuickLookFrame {...frame} onClose={onClose}>{body}</QuickLookFrame>
|
|
634
|
+
</FullscreenOverlay>
|
|
635
|
+
)
|
|
636
|
+
}
|
|
637
|
+
|
|
539
638
|
/* THE COLUMN HEADER — Name · Date · Size, each one the sort control for its own column (user
|
|
540
639
|
* 2026-09-22). Unlabelled meta at the end of a row is a number nobody can read a column of; a
|
|
541
640
|
* header names them AND gives them the one behaviour a labelled column implies. Clicking the
|
|
@@ -1172,6 +1271,10 @@ export function MediaLibraryBrowse({
|
|
|
1172
1271
|
* draw a picked folder's; `false` leaves the column empty, as before. */
|
|
1173
1272
|
infoKey = 'mod+i',
|
|
1174
1273
|
columnFolderInfo = true,
|
|
1274
|
+
/* `previewPlacement` — Columns: the preview pane docked at the right edge as Rows and Grid dock
|
|
1275
|
+
* theirs (`'dock'`, ColumnPreviewDocked, kol-website 2026-10-30), or the column after the last
|
|
1276
|
+
* one (`'inline'`, as before). Handed to ColumnBrowser. */
|
|
1277
|
+
previewPlacement = 'dock',
|
|
1175
1278
|
}) {
|
|
1176
1279
|
const [ownPrefix, setOwnPrefix] = useState('')
|
|
1177
1280
|
const prefix = prefixProp ?? ownPrefix
|
|
@@ -1691,16 +1794,18 @@ export function MediaLibraryBrowse({
|
|
|
1691
1794
|
setPickedFolder(null); setPickedFile(o)
|
|
1692
1795
|
}
|
|
1693
1796
|
}
|
|
1694
|
-
const spaceQuickLook = () => {
|
|
1797
|
+
const spaceQuickLook = (info) => {
|
|
1695
1798
|
/* in the columns a folder is picked by being OPEN — the deepest level (MediaLibraryInfoKey: ⌘I
|
|
1696
1799
|
* is the only caller there; the columns keep their own Space) */
|
|
1697
1800
|
const key = pickedFile?.key ?? (view === 'columns' ? (prefix || null) : pickedFolder ?? (rowSelection.size === 1 ? [...rowSelection][0] : null))
|
|
1698
1801
|
/* a picked bucket or the title Quick-Looks like any folder (user 2026-09-25) */
|
|
1699
|
-
if (!key && previewRoot) {
|
|
1802
|
+
if (!key && previewRoot) { setInfoFor(ROOT_KEY); return }
|
|
1700
1803
|
if (!key) return
|
|
1701
1804
|
/* A FOLDER QUICK-LOOKS TOO (user 2026-09-23: *"anything can be quick viewed, also folders"*) —
|
|
1702
1805
|
* the same pane the preview column draws, at overlay size. */
|
|
1703
|
-
if (key.endsWith('/')) {
|
|
1806
|
+
if (key.endsWith('/')) { setInfoFor(key); return }
|
|
1807
|
+
/* ⌘I on a file is its Get Info, not the media viewer (MediaGetInfoModal) */
|
|
1808
|
+
if (info === true) { setInfoFor(key); return }
|
|
1704
1809
|
const files = isWall
|
|
1705
1810
|
? wallFiles
|
|
1706
1811
|
: rowLevel(key.slice(0, key.lastIndexOf('/') + 1), sortedObjects).files
|
|
@@ -1729,7 +1834,7 @@ export function MediaLibraryBrowse({
|
|
|
1729
1834
|
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) return false
|
|
1730
1835
|
/* ⇧+ARROWS KEEP SELECTING UNDER QUICK LOOK (user 2026-09-24), Finder's: the selection grows
|
|
1731
1836
|
* underneath and the window pages through it */
|
|
1732
|
-
if (view === 'columns' || (quickLook && !e.shiftKey) ||
|
|
1837
|
+
if (view === 'columns' || (quickLook && !e.shiftKey) || infoFor || searchOpen) return false
|
|
1733
1838
|
if (atTitleRoot && !single) {
|
|
1734
1839
|
if (e.metaKey && e.key === 'ArrowUp') goUp()
|
|
1735
1840
|
else if (e.metaKey && e.key === 'ArrowDown') { if (atTop) goTitleRoot(); else openBucket(bucketList.find((b) => (b.id ?? b.label) === pickedBucket) ?? bucketMeta) }
|
|
@@ -1834,7 +1939,7 @@ export function MediaLibraryBrowse({
|
|
|
1834
1939
|
/* ENTER OPENS THE SELECTED FOLDER, ⇧ENTER GOES TO THE PARENT (user 2026-09-24). The same
|
|
1835
1940
|
* two moves as a double-click and ⌘↑, so the child you leave is still the one selected.
|
|
1836
1941
|
* A focused tile has already opened itself on Enter (it calls preventDefault). */
|
|
1837
|
-
if (e.key === 'Enter' && !e.metaKey && !e.ctrlKey && !e.altKey && !e.defaultPrevented && !quickLook && !
|
|
1942
|
+
if (e.key === 'Enter' && !e.metaKey && !e.ctrlKey && !e.altKey && !e.defaultPrevented && !quickLook && !infoFor && !searchOpen) {
|
|
1838
1943
|
if (e.shiftKey) { e.preventDefault(); goUp(); return }
|
|
1839
1944
|
if (view !== 'columns') {
|
|
1840
1945
|
const root = rootItems.find((it) => it.key === pickedBucket)
|
|
@@ -1847,7 +1952,7 @@ export function MediaLibraryBrowse({
|
|
|
1847
1952
|
if (e.key === ' ' && view !== 'columns' && !searchOpen) {
|
|
1848
1953
|
e.preventDefault()
|
|
1849
1954
|
if (quickLook) closeQuickLook()
|
|
1850
|
-
else if (
|
|
1955
|
+
else if (infoFor) setInfoFor(null)
|
|
1851
1956
|
else spaceQuickLook()
|
|
1852
1957
|
}
|
|
1853
1958
|
}
|
|
@@ -1856,10 +1961,10 @@ export function MediaLibraryBrowse({
|
|
|
1856
1961
|
})
|
|
1857
1962
|
|
|
1858
1963
|
const [quickLook, setQuickLook] = useState(null)
|
|
1859
|
-
const [
|
|
1860
|
-
/* ⌘I — GET INFO (MediaLibraryInfoKey
|
|
1861
|
-
*
|
|
1862
|
-
useHotkey(infoKey, !quickLook && !
|
|
1964
|
+
const [infoFor, setInfoFor] = useState(null)
|
|
1965
|
+
/* ⌘I — GET INFO (MediaLibraryInfoKey; MediaGetInfoModal: the Get Info modal, file or folder),
|
|
1966
|
+
* from every view; ⌘I again or Esc closes it; inert over Quick Look and the search modal */
|
|
1967
|
+
useHotkey(infoKey, !quickLook && !searchOpen, () => (infoFor ? setInfoFor(null) : spaceQuickLook(true)))
|
|
1863
1968
|
/* …and the open window takes the new selection as its set, on the file the cursor reached */
|
|
1864
1969
|
useEffect(() => {
|
|
1865
1970
|
if (!quickLook || rowSelection.size < 2) return
|
|
@@ -2124,6 +2229,7 @@ export function MediaLibraryBrowse({
|
|
|
2124
2229
|
height={colH}
|
|
2125
2230
|
onHeightChange={onColH}
|
|
2126
2231
|
rowSize={rowSize}
|
|
2232
|
+
previewPlacement={previewPlacement}
|
|
2127
2233
|
columnWidths={settings.columnWidths}
|
|
2128
2234
|
onColumnResize={(i, px) => setSettings({ ...settings, columnWidths: { ...(settings.columnWidths ?? {}), [i]: px } })}
|
|
2129
2235
|
objects={treeObjects.filter((o) => !isSystemFile(o.key)).map((o) => ({ ...o, key: `${VROOT}${o.key}` }))}
|
|
@@ -2630,13 +2736,21 @@ export function MediaLibraryBrowse({
|
|
|
2630
2736
|
onOpenResults={(q) => { setSettings({ ...settings, filters: true }); setBarQuery(q) }}
|
|
2631
2737
|
/>
|
|
2632
2738
|
)}
|
|
2633
|
-
{
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2739
|
+
{/* GET INFO (MediaGetInfoModal, kol-website 2026-10-30): ⌘I, and Space on a folder, open Finder's
|
|
2740
|
+
* Get Info in Quick Look's window — never the pane's plate at overlay size */}
|
|
2741
|
+
{infoFor && (infoFor === ROOT_KEY ? (
|
|
2742
|
+
<FullscreenOverlay open onClose={() => setInfoFor(null)} closeButton={false} scrim>
|
|
2743
|
+
{previewRoot && (
|
|
2744
|
+
<QuickLookFrame title={previewRoot.name} onClose={() => setInfoFor(null)}>
|
|
2745
|
+
<InfoBody icon={previewRoot.icon} name={previewRoot.name} general={previewRoot.facts} />
|
|
2746
|
+
</QuickLookFrame>
|
|
2747
|
+
)}
|
|
2638
2748
|
</FullscreenOverlay>
|
|
2639
|
-
)
|
|
2749
|
+
) : (
|
|
2750
|
+
<GetInfoModal path={infoFor} objects={objects} where={bucketMeta.label || 'bucket'} onClose={() => setInfoFor(null)} formatDate={formatDate}
|
|
2751
|
+
tags={(canTag || tagsOf(infoFor).length > 0) && <TagEditor tags={tagsOf(infoFor)} suggestions={allTags} onChange={canTag ? (next) => saveTags(infoFor, next) : undefined} />}
|
|
2752
|
+
mediaUrl={mediaUrl} downloadUrl={downloadUrl} hasUrl={hasUrl} keySet={keySet} />
|
|
2753
|
+
))}
|
|
2640
2754
|
{quickLook && (
|
|
2641
2755
|
<MediaInspector files={quickLook.files} index={quickLook.index} onClose={closeQuickLook} mediaUrl={mediaUrl} downloadUrl={downloadUrl} hasUrl={hasUrl} keySet={keySet} showNav={rowSelection.size > 1}
|
|
2642
2756
|
onEdit={canEdit ? (f) => { closeQuickLook(); openEditor(f) } : undefined}
|