import { scopedThreadKey } from "@t3tools/client-runtime/environment";import type { ScopedThreadRef } from "@t3tools/contracts";import { create } from "zustand"; export interface PreviewMiniPlayerPosition { readonly x: number; readonly y: number;} export interface PreviewMiniPlayerSize { readonly width: number; readonly height: number;} export interface PreviewMiniPlayerState { readonly tabId: string; readonly position: PreviewMiniPlayerPosition | null; /** Height always follows the previewed viewport's aspect ratio. */ readonly width: number | null;} interface PreviewMiniPlayerStoreState { readonly byThreadKey: Record<string, PreviewMiniPlayerState>; readonly open: (ref: ScopedThreadRef, tabId: string) => void; readonly close: (ref: ScopedThreadRef) => void; readonly move: (ref: ScopedThreadRef, tabId: string, position: PreviewMiniPlayerPosition) => void; readonly resize: (ref: ScopedThreadRef, tabId: string, width: number) => void; readonly removeThread: (ref: ScopedThreadRef) => void;} export const usePreviewMiniPlayerStore = create<PreviewMiniPlayerStoreState>()((set) => ({ byThreadKey: {}, open: (ref, tabId) => set((state) => { const threadKey = scopedThreadKey(ref); const current = state.byThreadKey[threadKey]; if (current?.tabId === tabId) return state; return { byThreadKey: { ...state.byThreadKey, [threadKey]: { tabId, position: current?.position ?? null, width: current?.width ?? null, }, }, }; }), close: (ref) => set((state) => { const threadKey = scopedThreadKey(ref); if (!(threadKey in state.byThreadKey)) return state; const { [threadKey]: _closed, ...byThreadKey } = state.byThreadKey; return { byThreadKey }; }), move: (ref, tabId, position) => set((state) => { const threadKey = scopedThreadKey(ref); const current = state.byThreadKey[threadKey]; if (!current || current.tabId !== tabId) return state; if (current.position?.x === position.x && current.position.y === position.y) return state; return { byThreadKey: { ...state.byThreadKey, [threadKey]: { ...current, position }, }, }; }), resize: (ref, tabId, width) => set((state) => { const threadKey = scopedThreadKey(ref); const current = state.byThreadKey[threadKey]; if (!current || current.tabId !== tabId || current.width === width) return state; return { byThreadKey: { ...state.byThreadKey, [threadKey]: { ...current, width }, }, }; }), removeThread: (ref) => set((state) => { const threadKey = scopedThreadKey(ref); if (!(threadKey in state.byThreadKey)) return state; const { [threadKey]: _removed, ...byThreadKey } = state.byThreadKey; return { byThreadKey }; }),})); export function selectThreadPreviewMiniPlayer( byThreadKey: Record<string, PreviewMiniPlayerState>, ref: ScopedThreadRef | null | undefined,): PreviewMiniPlayerState | null { if (!ref) return null; return byThreadKey[scopedThreadKey(ref)] ?? null;}
import { scopedThreadKey } from "@t3tools/client-runtime/environment";
import type { ScopedThreadRef } from "@t3tools/contracts";
import { create } from "zustand";
export interface PreviewMiniPlayerPosition {
readonly x: number;
readonly y: number;
}
export interface PreviewMiniPlayerSize {
readonly width: number;
readonly height: number;
}
export interface PreviewMiniPlayerState {
readonly tabId: string;
readonly position: PreviewMiniPlayerPosition | null;
/** Height always follows the previewed viewport's aspect ratio. */
readonly width: number | null;
}
interface PreviewMiniPlayerStoreState {
readonly byThreadKey: Record<string, PreviewMiniPlayerState>;
readonly open: (ref: ScopedThreadRef, tabId: string) => void;
readonly close: (ref: ScopedThreadRef) => void;
readonly move: (ref: ScopedThreadRef, tabId: string, position: PreviewMiniPlayerPosition) => void;
readonly resize: (ref: ScopedThreadRef, tabId: string, width: number) => void;
readonly removeThread: (ref: ScopedThreadRef) => void;
}
export const usePreviewMiniPlayerStore = create<PreviewMiniPlayerStoreState>()((set) => ({
byThreadKey: {},
open: (ref, tabId) =>
set((state) => {
const threadKey = scopedThreadKey(ref);
const current = state.byThreadKey[threadKey];
if (current?.tabId === tabId) return state;
return {
byThreadKey: {
...state.byThreadKey,
[threadKey]: {
tabId,
position: current?.position ?? null,
width: current?.width ?? null,
},
},
};
}),
close: (ref) =>
set((state) => {
const threadKey = scopedThreadKey(ref);
if (!(threadKey in state.byThreadKey)) return state;
const { [threadKey]: _closed, ...byThreadKey } = state.byThreadKey;
return { byThreadKey };
}),
move: (ref, tabId, position) =>
set((state) => {
const threadKey = scopedThreadKey(ref);
const current = state.byThreadKey[threadKey];
if (!current || current.tabId !== tabId) return state;
if (current.position?.x === position.x && current.position.y === position.y) return state;
return {
byThreadKey: {
...state.byThreadKey,
[threadKey]: { ...current, position },
},
};
}),
resize: (ref, tabId, width) =>
set((state) => {
const threadKey = scopedThreadKey(ref);
const current = state.byThreadKey[threadKey];
if (!current || current.tabId !== tabId || current.width === width) return state;
return {
byThreadKey: {
...state.byThreadKey,
[threadKey]: { ...current, width },
},
};
}),
removeThread: (ref) =>
set((state) => {
const threadKey = scopedThreadKey(ref);
if (!(threadKey in state.byThreadKey)) return state;
const { [threadKey]: _removed, ...byThreadKey } = state.byThreadKey;
return { byThreadKey };
}),
}));
export function selectThreadPreviewMiniPlayer(
byThreadKey: Record<string, PreviewMiniPlayerState>,
ref: ScopedThreadRef | null | undefined,
): PreviewMiniPlayerState | null {
if (!ref) return null;
return byThreadKey[scopedThreadKey(ref)] ?? null;
}