Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2649,9 +2649,28 @@ Filesmith's conventions.
**PROJECT SETTINGS IS ITS OWN PAGE, AND THE TREE ALWAYS FOLLOWS THE OPEN FILE** (#296; owner,
2026-10-06: "project specific settings should be in a tab called project settings not in
explorer. and remove the setting for scroll to open file, it should just be on by default, no
setting"). `ProjectPage.tsx` holds Sidebar position and First view of a new project; Folder for
setting"). `ProjectPage.tsx` holds Sidebar position (the tree's) and First view of a new project; Folder for
new tabs stays on Explorer, since the + and Ctrl+T open an Explorer tab. Scroll to the open file
is gone and a stored `prism.tree.autoscroll` is ignored.
- **SIDEBAR POSITION IS TWO SETTINGS: THE EXPLORER'S PLACES AND THE PROJECT TREE, AND THE PREVIEW
TAKES THE OTHER SIDE** (#304; owner, 2026-10-07: "fix the setting in Explorer for the sidebar where
you can put it on the right side or the left side? I think that's just an empty setting for now,
but actually implement it. And remember that when the sidebar goes on the right, the preview menu
and button to open it would have to go on the left"; then, the same day, after testing one shared
row: "No, it should be two settings, one on the project tab and one on the explorer tab").
Explorer > Layout opens with `explorer-side` (key `prism.explorer.side`, `lib/explorerSidePrefs.ts`,
default Left): the Explorer tab's places panel, and nothing else. Project settings keeps
`tree-side` (key `prism.tree.side`, unchanged): the project tree, and nothing else. Neither moves
the other; App reads the Explorer's for an Explorer tab and the tree's for a project tab
(`workspaceSide`), and each panel peeks from its own. On the Explorer's Right:
`.folder-browser[data-side='right']` is a three-column grid (preview, list, places;
the preview column 0px while shut, so its slide still tweens one column), the places come after
the list in the DOM (Tab walks left to right), the preview toggle LEADS the address row (before
Back, the end nearest the pane), the viewer laid over the slot sits at `left: 0`
(`.browse-workspace[data-sidebar-side='right']`), each grip is its left twin turned round
(`ExplorerResize`'s `edge`: a drag toward the middle widens), and the places peek from the right
edge. Left is the window as it was, box for box. The `explorerSide` e2e holds it (it fails on
main), one coat of a see-through ground included, and that each row moves only its own panel.


- **The viewer lives here for now.** The plan is a shared package, **`prism-core`**, which
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "prism",
"version": "0.92.1",
"version": "0.93.0",
"description": "A fast, universal media viewer for Windows.",
"author": "Max",
"license": "MIT",
Expand Down
19 changes: 16 additions & 3 deletions src/renderer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
import './components/browse/workspace.css'
import { focusTermSession } from 'prism-term-core/renderer/components/TerminalPanel'
import { sortFiles, useSort } from './lib/sortPrefs'
import { useExplorerSide } from './lib/explorerSidePrefs'
import { useTreeSide } from './lib/treePrefs'
import { VideoView } from './components/VideoView'
import { AudioView } from './components/AudioView'
Expand Down Expand Up @@ -714,7 +715,7 @@
/** The four places a split pane can sit, as a submenu (owner, 2026-09-03:
* "a tree structure ... so we don't bloat the options"). Shared by the
* pinned panes and the terminal dock. */
export const SPLIT_DIRS: ReadonlyArray<{ dir: SplitDir; label: string }> = [

Check warning on line 718 in src/renderer/src/App.tsx

View workflow job for this annotation

GitHub Actions / check

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
{ dir: 'left', label: 'Left' },
{ dir: 'right', label: 'Right' },
{ dir: 'top', label: 'Top' },
Expand Down Expand Up @@ -996,7 +997,12 @@
)
// The file tree. Off on a fresh install: the media is the point.
const [sidebar, setSidebar] = useState(() => localStorage.getItem(SIDEBAR_KEY) === '1')
// TWO SIDEBAR POSITIONS (#304; owner, 2026-10-07: "No, it should be two
// settings, one on the project tab and one on the explorer tab"): the
// project tree's (Project settings) and the Explorer's places (Explorer >
// Layout). Neither moves the other.
const treeSide = useTreeSide()
const explorerSide = useExplorerSide()
// A PANEL SLIDES WHEN IT IS OPENED OR CLOSED, AND ONLY THEN (owner,
// 2026-09-23: "when you collapse the explorer sidebar its not animated, it
// should be", and "when you switch between a project and explorer with
Expand Down Expand Up @@ -2608,14 +2614,18 @@
const peekPlaces = !!peekTarget?.startsWith('places:')
const { phase: peekPhase, end: endPeek } = useSidebarPeek({
target: peekTarget,
side: peekPlaces ? 'left' : treeSide,
// Each panel peeks from its own Sidebar position (#304).
side: peekPlaces ? explorerSide : treeSide,
zone: () => document.querySelector<HTMLElement>('.browse-workspace'),
panel: () =>
document.querySelector<HTMLElement>(
peekPlaces ? '[data-testid="folder-browser"] .browse-places' : '[data-project-sidebar]'
)
})
peekingRef.current = peekPhase === 'in'
// The side the open tab's sidebar sits on: an Explorer tab's places, else
// the project tree (#304).
const workspaceSide = active && isExplorerTab(active) ? explorerSide : treeSide
// The peeking panel's own toggle pins it; so does Ctrl+B (togglePanel).
const pinFromPeek = useCallback(() => {
endPeek()
Expand Down Expand Up @@ -3736,7 +3746,7 @@
}
window.addEventListener('keydown', onKey, true)
return () => window.removeEventListener('keydown', onKey, true)
}, [

Check warning on line 3749 in src/renderer/src/App.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook useEffect has a missing dependency: 'setFs'. Either include it or remove the dependency array
active,
closeActiveTab,
browsing,
Expand Down Expand Up @@ -4034,7 +4044,8 @@
: undefined
}
data-preview-sliding={previewSlide.sliding || undefined}
className={`browse-workspace relative flex min-h-0 flex-1 ${browsing.folder ? 'is-browsing' : ''} ${treeSide === 'right' ? 'flex-row-reverse' : ''} ${
data-sidebar-side={workspaceSide === 'right' ? 'right' : undefined}
className={`browse-workspace relative flex min-h-0 flex-1 ${browsing.folder ? 'is-browsing' : ''} ${workspaceSide === 'right' ? 'flex-row-reverse' : ''} ${
settingsOpen || setup ? 'invisible' : ''
}`}
>
Expand All @@ -4046,7 +4057,7 @@
section="places"
bounds={explorerWidths.bounds.places}
onResize={(width) => explorerWidths.resize('places', width)}
right={!browsing.folder && treeSide === 'right'}
edge={explorerSide}
/>
)}
{active && active.kind !== 'settings' && !fullscreen && (
Expand Down Expand Up @@ -4140,6 +4151,7 @@
placesVisible={isExplorerTab(active) ? placesVisible : false}
placesSliding={isExplorerTab(active) && panelSliding}
placesPeek={peekPlaces ? peekPhase : null}
side={explorerSide}
onPinPlaces={pinFromPeek}
onPlacePicked={endPeek}
onOpenProject={isExplorerTab(active) ? openAsProject : undefined}
Expand Down Expand Up @@ -4239,6 +4251,7 @@
section="preview"
bounds={explorerWidths.bounds.preview}
onResize={(width) => explorerWidths.resize('preview', width)}
edge={explorerSide === 'right' ? 'left' : 'right'}
/>
)}
{!browsing.folder &&
Expand Down
5 changes: 5 additions & 0 deletions src/renderer/src/components/browse/BrowseToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ type Props = Pick<
/** What sits after the address field: the Explorer's preview toggle and
* search button (#267). The file viewer's toolbar has none. */
trailing?: ReactNode
/** What sits BEFORE the history buttons: the preview toggle, when the
* sidebar is on the right and the preview pane on the left (#304), so the
* toggle is at the end nearest the pane it opens. */
leading?: ReactNode
/** The archives along the path (#300, `ArchiveMeta.chain`): those crumbs
* wear the archive icon. Before main has answered, a crumb named like an
* archive with more path after it is taken to be one. */
Expand Down Expand Up @@ -71,6 +75,7 @@ export function BrowseToolbar(props: Props): JSX.Element {
}
return (
<div className="browse-toolbar" data-testid="browse-toolbar">
{props.leading}
<div className="browse-history">
<button
className="browse-icon-button"
Expand Down
19 changes: 15 additions & 4 deletions src/renderer/src/components/browse/ExplorerResize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,28 @@ export function ExplorerResize({
section,
bounds,
onResize,
right = false
edge
}: {
section: ExplorerSection
bounds: ExplorerWidthBounds
onResize: (value: number | null) => void
right?: boolean
/** The window edge the panel sits against (#304: Sidebar position moves
* the places to the right and the preview to the left). A drag or an
* arrow toward the middle widens it. */
edge: 'left' | 'right'
}): JSX.Element {
const drag = useRef<{ x: number; value: number } | null>(null)
const [dragging, setDragging] = useState(false)
const [pointerResize, setPointerResize] = useState(false)
const direction = section === 'preview' || right ? -1 : 1
const direction = edge === 'right' ? -1 : 1
// Only a panel off its usual edge says so: the left side's classes are
// the ones the window always had.
const turned =
section === 'places' && edge === 'right'
? ' is-right'
: section === 'preview' && edge === 'left'
? ' is-left'
: ''
return (
<div
role="separator"
Expand All @@ -26,7 +37,7 @@ export function ExplorerResize({
aria-valuenow={bounds.value}
tabIndex={0}
title="Drag to resize. Arrow keys adjust width. Double-click to reset."
className={`explorer-resize explorer-resize-${section}${right ? ' is-right' : ''}`}
className={`explorer-resize explorer-resize-${section}${turned}`}
data-dragging={dragging || undefined}
data-pointer-resize={pointerResize || undefined}
onPointerDown={(event) => {
Expand Down
101 changes: 58 additions & 43 deletions src/renderer/src/components/browse/FolderBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,56 @@ export function FolderBrowser(props: FolderBrowserProps): JSX.Element {
: 'This folder is empty.'
: null)

// SIDEBAR POSITION (#304; owner, 2026-10-07): on the right the places
// panel takes the right edge and the preview the left. The places come
// after the list in the DOM then too, so Tab walks the row left to right.
const right = props.side === 'right'
const previewToggle = (
<button
className="browse-icon-button"
aria-label="Preview pane"
title="Preview pane"
aria-pressed={props.previewEnabled}
onClick={props.onPreviewToggle}
>
<BrowseIcon name="preview" />
</button>
)
const places = (props.placesVisible !== false || sliding || !!props.placesPeek) && (
<BrowsePlaces
places={props.places}
onDropInto={props.onDropInto}
quickAccess={props.quickAccess}
readDrives={props.readDrives}
onQuickAccessFile={
props.placesPeek && props.onQuickAccessFile
? (path, full) => {
props.onPlacePicked?.()
props.onQuickAccessFile?.(path, full)
}
: props.onQuickAccessFile
}
onPin={props.placesPeek === 'in' ? props.onPinPlaces : undefined}
onUnpinQuickAccess={props.onUnpinQuickAccess}
onMoveQuickAccess={props.onMoveQuickAccess}
onPinQuickAccessPaths={props.onPinQuickAccessPaths}
directory={props.directory}
chosenPlace={chosenPlace}
onChoosePlace={setChosenPlace}
onNavigate={
props.placesPeek
? (path) => {
props.onPlacePicked?.()
props.onNavigate(path)
}
: props.onNavigate
}
onNewTerminal={props.onNewTerminal}
onOpenProject={props.onOpenProject}
onOpenNewTab={props.onOpenNewTab}
/>
)

return (
<div
ref={shell}
Expand All @@ -299,6 +349,7 @@ export function FolderBrowser(props: FolderBrowserProps): JSX.Element {
data-places-hidden={props.placesVisible === false || undefined}
data-places-sliding={sliding || undefined}
data-places-peek={props.placesPeek || undefined}
data-side={right ? 'right' : undefined}
data-testid="folder-browser"
// Where the user last acted (useActiveArea): the side not acted in
// draws its mark dimmed, File Explorer's inactive selection.
Expand Down Expand Up @@ -389,20 +440,16 @@ export function FolderBrowser(props: FolderBrowserProps): JSX.Element {
// The address commits at once; the rows follow when they answer.
directory={props.pendingPath ?? props.directory}
archiveChain={props.pendingPath ? undefined : props.listing ? (props.listing.archive?.chain ?? []) : undefined}
// The preview toggle sits at the end of the row nearest the pane it
// opens (#304): after the address with the pane on the right, before
// the history buttons with it on the left.
leading={right ? previewToggle : undefined}
trailing={
<>
{props.terminalControls && (
<div className="browse-terminal-controls">{props.terminalControls}</div>
)}
<button
className="browse-icon-button"
aria-label="Preview pane"
title="Preview pane"
aria-pressed={props.previewEnabled}
onClick={props.onPreviewToggle}
>
<BrowseIcon name="preview" />
</button>
{!right && previewToggle}
<button
className="browse-icon-button browse-search-button"
aria-label="Search this folder and subfolders"
Expand Down Expand Up @@ -438,40 +485,7 @@ export function FolderBrowser(props: FolderBrowserProps): JSX.Element {
}}
/>
)}
{(props.placesVisible !== false || sliding || !!props.placesPeek) && (
<BrowsePlaces
places={props.places}
onDropInto={props.onDropInto}
quickAccess={props.quickAccess}
readDrives={props.readDrives}
onQuickAccessFile={
props.placesPeek && props.onQuickAccessFile
? (path, full) => {
props.onPlacePicked?.()
props.onQuickAccessFile?.(path, full)
}
: props.onQuickAccessFile
}
onPin={props.placesPeek === 'in' ? props.onPinPlaces : undefined}
onUnpinQuickAccess={props.onUnpinQuickAccess}
onMoveQuickAccess={props.onMoveQuickAccess}
onPinQuickAccessPaths={props.onPinQuickAccessPaths}
directory={props.directory}
chosenPlace={chosenPlace}
onChoosePlace={setChosenPlace}
onNavigate={
props.placesPeek
? (path) => {
props.onPlacePicked?.()
props.onNavigate(path)
}
: props.onNavigate
}
onNewTerminal={props.onNewTerminal}
onOpenProject={props.onOpenProject}
onOpenNewTab={props.onOpenNewTab}
/>
)}
{!right && places}
<BrowseList
{...props}
loading={props.pending !== 'none'}
Expand Down Expand Up @@ -513,6 +527,7 @@ export function FolderBrowser(props: FolderBrowserProps): JSX.Element {
message={message}
onVisibleFolders={onVisibleFolders}
/>
{right && places}
{props.previewVisible && (
<aside className="browse-preview-slot" aria-label="File preview">
{props.preview}
Expand Down
46 changes: 46 additions & 0 deletions src/renderer/src/components/browse/browse.css
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,52 @@
pointer-events: none;
animation-name: p-peek-out-left;
}
/* SIDEBAR POSITION, RIGHT (#304; owner, 2026-10-07: "when the sidebar goes on
the right, the preview menu and button to open it would have to go on the
left"). The grid mirrors: preview, list, places. Three columns always, the
preview's at 0px while it is shut, so its slide tweens one column as the
left side's does; the places column keeps its own variable, so hiding and
sliding it are the same rules as on the left. Every edge, peek and freeze
below is the left side's rule turned round. */
.folder-browser[data-side='right'] {
grid-template-columns: 0px minmax(0, 1fr) var(--browse-places-width);
}
.folder-browser[data-side='right'][data-preview] {
grid-template-columns: var(--browse-preview-width, clamp(280px, 40%, 720px)) minmax(0, 1fr) var(
--browse-places-width
);
}
.folder-browser[data-side='right'] > .browse-places {
grid-column: 3;
border-right: 0;
border-left: 1px solid var(--p-divider);
}
.folder-browser[data-side='right'] > .browse-preview-slot {
grid-column: 1;
border-left: 0;
border-right: 1px solid var(--p-divider);
}
.folder-browser[data-side='right'] > .browse-status {
grid-column: 1 / 3;
}
/* Mid-slide the contents keep their open width against the WINDOW's edge, so
the panel is wiped from its inner side, as the left one is. */
.folder-browser[data-side='right'][data-places-sliding] .browse-places {
align-items: flex-end;
}
.folder-browser[data-side='right'][data-places-peek] .browse-places {
/* Out of the grid, as on the left: an absolute box in a grid AREA would
measure its top from that area, not from the browser. */
grid-column: auto;
grid-row: auto;
left: auto;
right: 0;
box-shadow: -10px 0 28px -8px rgb(0 0 0 / 0.45);
animation-name: p-peek-in-right;
}
.folder-browser[data-side='right'][data-places-peek='out'] .browse-places {
animation-name: p-peek-out-right;
}
.browse-places-peekhead {
display: flex;
justify-content: flex-end;
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/src/components/browse/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ export interface FolderBrowserProps {
/** THE COLLAPSED PANEL PEEKS (#250): 'in' while the hidden places panel is
* out OVER the list, 'out' for its slide away. */
placesPeek?: 'in' | 'out' | null
/** SIDEBAR POSITION (#304): the edge the places panel sits on. On the
* right, the preview pane and its toggle take the left. */
side?: 'left' | 'right'
/** The peeking panel's own toggle: keep it open. */
onPinPlaces?: () => void
/** A place or a pin was picked from the peeking panel: the peek is over. */
Expand Down
Loading
Loading