mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 17:08:13 +00:00
pagedraw: replace hardcoded chrome colors with semantic tokens (#1651)
Implements the P0 color item from the PageDraw design review. Swap raw hex/palette chrome colors for the semantic tokens shared by both lib hosts (artcraft desktop + artcraft-webapp): - ContextMenu: #1A1A1A/#333333/#ECECEC -> bg-ui-panel, white/10 hairlines, text-base-fg/90 - SideToolbar: zinc swatch + #404040/#303030 popovers -> ui-controls / bg-ui-panel + white/10 borders - SliderWithIndicator: #5F5F68 tooltip -> bg-ui-controls - SplitPane: gray-500/600 -> white/20-30 - HistoryStack: text-gray-400 -> text-base-fg/50 - PageDraw + base-image-selector: blue-500/600 accents -> primary token (so the editor accent matches the product's brand-primary) - pagedraw.css pegboard: #1b1b1b -> var(--st-canvas, #1b1b1b) Only tokens defined in both host themes are used. Konva canvas-paint colors (node strokes, brush defaults, selection fills) are intentionally left as literals — they're canvas content, not CSS chrome. Oversized modules deferred per scope. --------- Co-authored-by: bflatastic <bombayapirat@gmail.com>
This commit is contained in:
1 parent
f6c5ea94eb
commit
dc33a99d75
16 files changed
+172
-46
No files matched your search
@@ -90,15 +90,16 @@ function ProtectedContent() {
|
||||
? "var(--sidebar-width)"
|
||||
: "calc(var(--sidebar-width-icon) + 1.5rem)";
|
||||
|
||||
// The Edit 3D and video editors host the header's actions
|
||||
// The Edit 3D, Edit Image, and video editors host the header's actions
|
||||
// (pricing/credits/task queue/profile) inside their own toolbar/header to
|
||||
// reclaim vertical space, so the global header is hidden there — desktop
|
||||
// only, since the mobile route shows a gate that still needs the header's
|
||||
// nav chrome.
|
||||
// only, since the mobile routes show the global chrome (Edit Image keeps the
|
||||
// bar on mobile; Edit 3D shows a gate that still needs the header's nav).
|
||||
const hideTopBar =
|
||||
!isMobile &&
|
||||
(pathname === "/edit-3d" ||
|
||||
pathname.startsWith("/edit-3d/") ||
|
||||
pathname === "/edit-image" ||
|
||||
pathname === "/video-editor" ||
|
||||
pathname.startsWith("/video-editor/"));
|
||||
|
||||
@@ -108,7 +109,14 @@ function ProtectedContent() {
|
||||
style={{ "--ac-sidebar-offset": sidebarOffset } as React.CSSProperties}
|
||||
>
|
||||
{!hideTopBar && <TopBar />}
|
||||
<SidebarInset className="flex-1 min-h-0 overflow-y-auto bg-[#121212]">
|
||||
{/* The immersive editors (hideTopBar routes) paint their own #101014
|
||||
canvas backdrop; match the inset to it so no lighter strip shows at
|
||||
the edges. Other pages keep the standard #121212 surface. */}
|
||||
<SidebarInset
|
||||
className={`flex-1 min-h-0 overflow-y-auto ${
|
||||
hideTopBar ? "bg-[#101014]" : "bg-[#121212]"
|
||||
}`}
|
||||
>
|
||||
<Outlet />
|
||||
</SidebarInset>
|
||||
{isMobile && <MobileBottomNav />}
|
||||
|
||||
@@ -19,7 +19,10 @@ if (import.meta.env.DEV) {
|
||||
window.location.origin,
|
||||
);
|
||||
// NB: This is for Brandon to test with storyteller-web locally:
|
||||
StorytellerApiHostStore.getInstance().setDevelopment();
|
||||
// (disabled — it overrides the origin above and points at localhost:12345,
|
||||
// which breaks dev unless a local backend is running. Re-enable only when
|
||||
// testing against a local storyteller-web.)
|
||||
// StorytellerApiHostStore.getInstance().setDevelopment();
|
||||
} catch (e) {
|
||||
console.warn("Failed to set dev API host override", e);
|
||||
}
|
||||
|
||||
@@ -148,8 +148,8 @@ export function BaseImageSelector({
|
||||
onDrop={onDrop}
|
||||
className={`flex h-full w-full cursor-pointer flex-col items-center justify-center gap-4 rounded-2xl border-2 border-dashed p-8 text-center transition-colors ${
|
||||
isDragging
|
||||
? "border-blue-400 bg-blue-500/10"
|
||||
: "border-ui-panel-border bg-ui-background hover:border-blue-400/50"
|
||||
? "border-primary-400 bg-primary/10"
|
||||
: "border-ui-panel-border bg-ui-background hover:border-primary-400/50"
|
||||
} ${busy ? "pointer-events-none opacity-60" : ""}`}
|
||||
>
|
||||
<input
|
||||
@@ -159,7 +159,7 @@ export function BaseImageSelector({
|
||||
onChange={onFileInput}
|
||||
disabled={busy}
|
||||
/>
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-2xl border border-blue-400/30 bg-blue-500/30 text-blue-300">
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-2xl border border-primary-400/30 bg-primary/30 text-primary-300">
|
||||
<FontAwesomeIcon
|
||||
icon={busy ? faSpinnerThird : faPencil}
|
||||
className={`text-xl ${busy ? "animate-spin" : ""}`}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import type { CSSProperties } from "react";
|
||||
import { PageDraw as PageDrawLib } from "@storyteller/ui-pagedraw";
|
||||
import Seo from "../../components/seo";
|
||||
import { useSidebar } from "../../components/ui/sidebar";
|
||||
import { TopBarActions } from "../../components/topbar/TopBarActions";
|
||||
import { useWebPageDrawAdapter } from "./web-adapter";
|
||||
|
||||
export default function PageDraw() {
|
||||
const adapter = useWebPageDrawAdapter();
|
||||
// The global top bar is hidden on this route (desktop) so the editor
|
||||
// reclaims the full vertical strip — mirror Edit 3D / Video Editor. On
|
||||
// mobile the global bar stays, so don't re-host the actions here.
|
||||
const { isMobile } = useSidebar();
|
||||
return (
|
||||
<>
|
||||
<Seo
|
||||
@@ -16,9 +23,35 @@ export default function PageDraw() {
|
||||
same technique the Edit 3D page uses for its lib overlays. */}
|
||||
<div
|
||||
className="relative h-full w-full overflow-hidden"
|
||||
style={{ transform: "translateZ(0)" }}
|
||||
style={
|
||||
{
|
||||
transform: "translateZ(0)",
|
||||
// Recolors the lib's `.pegboard-bg` canvas backdrop (the surface
|
||||
// behind the artboard) to the webapp's page color.
|
||||
"--st-canvas": "#101014",
|
||||
// With the global top bar hidden but the bottom prompt bar still
|
||||
// present, the usable canvas area's center sits above the geometric
|
||||
// center. Pull the side panels (toolbar + history) up by half the
|
||||
// prompt-bar footprint so they read as centered in that area.
|
||||
"--pd-bottom-reserve": "4rem",
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
<PageDrawLib adapter={adapter} />
|
||||
<PageDrawLib
|
||||
adapter={adapter}
|
||||
backgroundClassName="bg-[#101014]"
|
||||
fillParentHeight={!isMobile}
|
||||
// Cost/help live in the webapp's own chrome, not over the canvas.
|
||||
showBottomRightControls={false}
|
||||
/>
|
||||
{/* With the global top bar hidden on desktop, float the same
|
||||
credits / pricing / task-queue / profile cluster in the top-right
|
||||
corner so those actions stay reachable while editing. */}
|
||||
{!isMobile && (
|
||||
<div className="pointer-events-auto fixed right-4 top-3 z-30">
|
||||
<TopBarActions />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
type BaseSelectorImage,
|
||||
useSceneStore,
|
||||
} from "@storyteller/ui-pagedraw";
|
||||
import { CommonAspectRatio, CommonResolution } from "@storyteller/model-list";
|
||||
import {
|
||||
OmniGenApi,
|
||||
StorytellerApiHostStore,
|
||||
@@ -141,6 +142,29 @@ const uploadBlobAsImage = async (
|
||||
return mediaToken;
|
||||
};
|
||||
|
||||
// The PromptBox UI emits resolution/aspect-ratio as display labels ("1k",
|
||||
// "wide", …); the omni-gen REST body wants the CommonResolution /
|
||||
// CommonAspectRatio enum values ("one_k", "wide", …). Mirror the desktop
|
||||
// PageDraw adapter's mappers so an unmapped "1k" doesn't 400 the request.
|
||||
const mapAspectRatio = (ratio?: string): CommonAspectRatio | undefined => {
|
||||
switch (ratio) {
|
||||
case "auto": return CommonAspectRatio.Auto;
|
||||
case "wide": return CommonAspectRatio.Wide;
|
||||
case "tall": return CommonAspectRatio.Tall;
|
||||
case "square": return CommonAspectRatio.Square;
|
||||
default: return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const mapResolution = (res?: string): CommonResolution | undefined => {
|
||||
switch (res) {
|
||||
case "1k": return CommonResolution.OneK;
|
||||
case "2k": return CommonResolution.TwoK;
|
||||
case "4k": return CommonResolution.FourK;
|
||||
default: return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// PageDrawEditRequest.model is an ImageModel object (or possibly a raw id);
|
||||
// the omni-gen REST body wants the string id.
|
||||
const modelIdOf = (model: unknown): string => {
|
||||
@@ -177,8 +201,8 @@ export const useWebPageDrawAdapter = (): PageDrawAdapter => {
|
||||
model: modelIdOf(req.model),
|
||||
prompt: req.prompt ?? null,
|
||||
idempotency_token: crypto.randomUUID(),
|
||||
aspect_ratio: req.aspectRatio ?? null,
|
||||
resolution: req.imageResolution ?? null,
|
||||
aspect_ratio: mapAspectRatio(req.aspectRatio) ?? null,
|
||||
resolution: mapResolution(req.imageResolution) ?? null,
|
||||
image_batch_count: req.imageCount ?? 1,
|
||||
image_media_tokens: refTokens.length ? refTokens : null,
|
||||
};
|
||||
|
||||
@@ -34,6 +34,14 @@ interface ClassyModelSelectorProps {
|
||||
* provider-selection rows.
|
||||
*/
|
||||
variant?: "floating" | "embedded";
|
||||
/**
|
||||
* Whether models that support multiple providers expose the provider-picker
|
||||
* submenu (hover panel) and the per-row provider icon chips. Defaults to
|
||||
* true. Pages locked to a single provider (e.g. PageDraw) pass false to show
|
||||
* only the model name — the default provider is still resolved into the
|
||||
* store so downstream generation calls keep working.
|
||||
*/
|
||||
showProviderSelection?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_PROVIDER_OPTIONS: GenerationProvider[] = [GenerationProvider.Artcraft];
|
||||
@@ -113,6 +121,7 @@ export function ClassyModelSelector({
|
||||
providerTooltipDelayMs = 300,
|
||||
maxListHeight = "60vh",
|
||||
variant = "floating",
|
||||
showProviderSelection = true,
|
||||
...popoverProps
|
||||
}: ClassyModelSelectorProps) {
|
||||
const { selectedModels, setSelectedModel, setSelectedProvider } =
|
||||
@@ -159,7 +168,10 @@ export function ClassyModelSelector({
|
||||
const modelId = item.model?.id;
|
||||
const allowedProviders = item.model?.getProviders() || DEFAULT_PROVIDER_OPTIONS;
|
||||
|
||||
const hasMultipleProviders = allowedProviders.length >= 2;
|
||||
// When provider selection is hidden, treat every model as
|
||||
// single-provider so the submenu and provider chips never render.
|
||||
const hasMultipleProviders =
|
||||
showProviderSelection && allowedProviders.length >= 2;
|
||||
|
||||
return {
|
||||
...item,
|
||||
@@ -213,6 +225,7 @@ export function ClassyModelSelector({
|
||||
page,
|
||||
providersByModel,
|
||||
providerTooltipDelayMs,
|
||||
showProviderSelection,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ export const HistoryStack = ({
|
||||
<div className="mb-2 flex w-full items-center justify-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faClockRotateLeft}
|
||||
className="p-1 text-gray-400"
|
||||
className="p-1 text-base-fg/50"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -154,7 +154,7 @@ const Edit3DButton = memo(function Edit3DButton({
|
||||
|
||||
return (
|
||||
<button
|
||||
className="pointer-events-auto fixed z-40 -translate-x-1/2 -translate-y-1/2 rounded-full bg-blue-600 px-3 py-1 text-xs font-semibold text-white shadow-lg hover:bg-blue-500"
|
||||
className="pointer-events-auto fixed z-40 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary px-3 py-1 text-xs font-semibold text-white shadow-lg hover:bg-primary-400"
|
||||
style={{ left: btnLeft, top: btnTop }}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={() => onEdit(nodeId)}
|
||||
@@ -185,7 +185,7 @@ function DragScrubButton({
|
||||
return (
|
||||
<button
|
||||
title={title}
|
||||
className="flex h-10 w-10 cursor-move items-center justify-center rounded-full bg-black/70 text-white shadow-lg hover:bg-black/90 active:bg-blue-600"
|
||||
className="flex h-10 w-10 cursor-move items-center justify-center rounded-full bg-black/70 text-white shadow-lg hover:bg-black/90 active:bg-primary"
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
>
|
||||
@@ -254,7 +254,7 @@ const Edit3DScrubControls = memo(function Edit3DScrubControls({
|
||||
/>
|
||||
<button
|
||||
onClick={() => overlayHandle.current?.commit()}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full bg-blue-600 text-sm font-bold text-white shadow-lg hover:bg-blue-500"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full bg-primary text-sm font-bold text-white shadow-lg hover:bg-primary-400"
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
✓
|
||||
@@ -267,9 +267,32 @@ const Edit3DScrubControls = memo(function Edit3DScrubControls({
|
||||
|
||||
interface PageDrawProps {
|
||||
adapter: PageDrawAdapter;
|
||||
/**
|
||||
* Tailwind class for the full-viewport canvas backdrop (and the base-image
|
||||
* selector surface). Defaults to the app's `bg-ui-background`; hosts that
|
||||
* embed PageDraw under their own page chrome can pass a matching color (e.g.
|
||||
* the webapp's `bg-[#101014]`).
|
||||
*/
|
||||
backgroundClassName?: string;
|
||||
/**
|
||||
* When true the base-image selector fills its parent box instead of
|
||||
* reserving 56px for a global top bar. Hosts that hide their top bar on this
|
||||
* route (so the editor reclaims the full vertical strip) should set this.
|
||||
*/
|
||||
fillParentHeight?: boolean;
|
||||
/**
|
||||
* Show the bottom-right cost-calculator + help cluster. Defaults to true;
|
||||
* hosts that surface those elsewhere (e.g. the webapp) pass false.
|
||||
*/
|
||||
showBottomRightControls?: boolean;
|
||||
}
|
||||
|
||||
const PageDraw = ({ adapter }: PageDrawProps) => {
|
||||
const PageDraw = ({
|
||||
adapter,
|
||||
backgroundClassName = "bg-ui-background",
|
||||
fillParentHeight = false,
|
||||
showBottomRightControls = true,
|
||||
}: PageDrawProps) => {
|
||||
const canvasWidth = useRef<number>(1024);
|
||||
const canvasHeight = useRef<number>(1024);
|
||||
const [isSelecting, setIsSelecting] = useState<boolean>(false);
|
||||
@@ -1111,9 +1134,9 @@ const PageDraw = ({ adapter }: PageDrawProps) => {
|
||||
if (!baseImageInfo || (!baseImageBitmap && !baseImageInfo.isBlankCanvas)) {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"bg-ui-panel-gradient flex h-[calc(100vh-56px)] w-full items-center justify-center p-8"
|
||||
}
|
||||
className={`${backgroundClassName} flex ${
|
||||
fillParentHeight ? "h-full" : "h-[calc(100vh-56px)]"
|
||||
} w-full items-center justify-center p-8`}
|
||||
>
|
||||
<div className="w-full max-w-5xl">
|
||||
<div className="aspect-video overflow-hidden rounded-2xl border border-ui-panel-border bg-ui-background shadow-lg">
|
||||
@@ -1135,9 +1158,9 @@ const PageDraw = ({ adapter }: PageDrawProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="fixed inset-0 -z-10 bg-ui-background" />
|
||||
<div className={`fixed inset-0 -z-10 ${backgroundClassName}`} />
|
||||
<div
|
||||
className={`preserve-aspect-ratio fixed right-4 top-1/2 z-10 -translate-y-1/2 transform ${
|
||||
className={`preserve-aspect-ratio fixed right-4 top-[calc(50%_-_var(--pd-bottom-reserve,0px))] z-10 -translate-y-1/2 transform ${
|
||||
isSelecting ? "pointer-events-none" : "pointer-events-auto"
|
||||
}`}
|
||||
>
|
||||
@@ -1167,8 +1190,12 @@ const PageDraw = ({ adapter }: PageDrawProps) => {
|
||||
selectedImageToken={baseImageInfo?.mediaToken}
|
||||
/>
|
||||
</div>
|
||||
{/* The page wrapper's `translateZ(0)` already scopes this fixed box to
|
||||
the content area (right of the sidebar). It spans 0..right so the
|
||||
promptbox's own `left-1/2 -translate-x-1/2` centers within the content
|
||||
area — do NOT re-apply the sidebar offset or it double-shifts right. */}
|
||||
<div
|
||||
className={`preserve-aspect-ratio fixed bottom-0 left-1/2 z-10 -translate-x-1/2 transform ${
|
||||
className={`preserve-aspect-ratio fixed inset-x-0 bottom-0 z-10 ${
|
||||
isSelecting ? "pointer-events-none" : "pointer-events-auto"
|
||||
}`}
|
||||
>
|
||||
@@ -1190,12 +1217,13 @@ const PageDraw = ({ adapter }: PageDrawProps) => {
|
||||
variant="embedded"
|
||||
items={CANVAS_2D_PAGE_MODEL_LIST}
|
||||
page={PAGE_ID}
|
||||
showProviderSelection={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<SideToolbar
|
||||
className="fixed left-0 top-1/2 z-10 -translate-y-1/2 transform"
|
||||
className="fixed left-0 top-[calc(50%_-_var(--pd-bottom-reserve,0px))] z-10 -translate-y-1/2 transform"
|
||||
onSelect={handleSelectTool}
|
||||
onActivateShapeTool={handleActivateShapeTool}
|
||||
onPaintBrush={handlePaintBrush}
|
||||
@@ -1247,13 +1275,16 @@ const PageDraw = ({ adapter }: PageDrawProps) => {
|
||||
transformerRefs={transformerRefs}
|
||||
baseImageRef={baseImageKonvaRef}
|
||||
showMaskLayer={supportsMaskedInpainting}
|
||||
fillParentHeight={fillParentHeight}
|
||||
/>
|
||||
</ContextMenuContainer>
|
||||
</div>
|
||||
<div className="absolute bottom-4 right-4 z-20 flex items-center gap-2">
|
||||
<CostCalculatorButton modelPage={PAGE_ID} />
|
||||
<HelpMenuButton />
|
||||
</div>
|
||||
{showBottomRightControls && (
|
||||
<div className="absolute bottom-4 right-4 z-20 flex items-center gap-2">
|
||||
<CostCalculatorButton modelPage={PAGE_ID} />
|
||||
<HelpMenuButton />
|
||||
</div>
|
||||
)}
|
||||
{!editing3DNodeId && selectedNodeWithModel?.modelUrl && (
|
||||
<Edit3DButton
|
||||
nodeId={selectedNodeIds[0]}
|
||||
|
||||
@@ -50,6 +50,8 @@ export type MiraiProps = {
|
||||
transformerRefs: React.RefObject<{ [key: string]: Konva.Transformer }>;
|
||||
baseImageRef: React.RefObject<Konva.Image>;
|
||||
showMaskLayer: boolean;
|
||||
/** Forwarded to SplitPane so the backdrop fills the parent (no 56px top-bar reserve). */
|
||||
fillParentHeight?: boolean;
|
||||
};
|
||||
|
||||
const InpaintingColor = "rgba(39, 187, 245, 0.54)";
|
||||
@@ -74,6 +76,7 @@ export const PaintSurface = ({
|
||||
transformerRefs,
|
||||
baseImageRef,
|
||||
showMaskLayer = false,
|
||||
fillParentHeight = false,
|
||||
}: MiraiProps) => {
|
||||
const singlePaneMode = true;
|
||||
|
||||
@@ -1616,6 +1619,7 @@ export const PaintSurface = ({
|
||||
return (
|
||||
<SplitPane
|
||||
singlePaneMode={singlePaneMode}
|
||||
fillParentHeight={fillParentHeight}
|
||||
initialPercent={singlePaneMode ? 100 : 50}
|
||||
onChange={setLeftPct}
|
||||
left={
|
||||
|
||||
@@ -41,7 +41,7 @@ export const ContextMenu: React.FC<ContextMenuProps> = ({ items, onAction, onClo
|
||||
left: `${position.x}px`,
|
||||
top: `${position.y}px`,
|
||||
}}
|
||||
className="fixed z-[9999999] min-w-[220px] bg-[#1A1A1A] rounded-lg shadow-xl border border-[#333333] py-1 select-none"
|
||||
className="fixed z-[9999999] min-w-[220px] bg-ui-panel rounded-lg shadow-xl border border-white/10 py-1 select-none"
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<React.Fragment key={index}>
|
||||
@@ -50,14 +50,14 @@ export const ContextMenu: React.FC<ContextMenuProps> = ({ items, onAction, onClo
|
||||
onAction(item.action); // Pass the action identifier up
|
||||
onClose();
|
||||
}}
|
||||
className="w-full px-4 py-2.5 flex items-center gap-3 hover:bg-[#333333] text-[#ECECEC] text-[13px] transition-colors duration-75 ease-in-out"
|
||||
className="w-full px-4 py-2.5 flex items-center gap-3 hover:bg-white/10 text-base-fg/90 text-[13px] transition-colors duration-75 ease-in-out"
|
||||
>
|
||||
<span className="w-5 h-5 flex items-center justify-center text-[#ECECEC]">
|
||||
<span className="w-5 h-5 flex items-center justify-center text-base-fg/90">
|
||||
{item.icon}
|
||||
</span>
|
||||
<span className="font-normal">{item.label}</span>
|
||||
</button>
|
||||
{item.divider && <div className="h-[1px] bg-[#333333] mx-2 my-1" />}
|
||||
{item.divider && <div className="h-[1px] bg-white/10 mx-2 my-1" />}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>,
|
||||
|
||||
@@ -169,7 +169,7 @@ const SideToolbar: React.FC<SideToolbarProps> = ({
|
||||
extra?: React.ReactNode,
|
||||
) => (
|
||||
<div className={`glass relative w-fit rounded-2xl p-4 shadow-lg`}>
|
||||
<button className="bg-zinc-700 text-zinc-300 hover:bg-zinc-600 absolute left-4 top-4 flex h-8 w-8 items-center justify-center rounded-full">
|
||||
<button className="bg-ui-controls/60 text-base-fg hover:bg-ui-controls/80 absolute left-4 top-4 flex h-8 w-8 items-center justify-center rounded-full">
|
||||
<FontAwesomeIcon icon={faEyeDropper} size="sm" />
|
||||
</button>
|
||||
|
||||
@@ -377,7 +377,7 @@ const SideToolbar: React.FC<SideToolbarProps> = ({
|
||||
{open === "shape-color" && (
|
||||
<div
|
||||
onMouseLeave={() => setOpen(null)}
|
||||
className="absolute left-14 top-1/2 -translate-y-1/2 rounded-xl border border-[#404040] bg-[#303030] transition-all duration-200 ease-in-out"
|
||||
className="absolute left-14 top-1/2 -translate-y-1/2 rounded-xl border border-white/10 bg-ui-panel transition-all duration-200 ease-in-out"
|
||||
>
|
||||
{ShapePopout}
|
||||
</div>
|
||||
@@ -546,7 +546,7 @@ const SideToolbar: React.FC<SideToolbarProps> = ({
|
||||
onMouseLeave={() => {
|
||||
setOpen(null);
|
||||
}}
|
||||
className="absolute left-14 top-1/2 -translate-y-1/2 rounded-xl border border-[#404040] bg-[#303030] transition-all duration-200 ease-in-out"
|
||||
className="absolute left-14 top-1/2 -translate-y-1/2 rounded-xl border border-white/10 bg-ui-panel transition-all duration-200 ease-in-out"
|
||||
>
|
||||
{popout}
|
||||
</div>
|
||||
|
||||
@@ -58,7 +58,7 @@ const SliderWithIndicator: React.FC<SliderWithIndicatorProps> = ({
|
||||
absolute
|
||||
top-6 -translate-x-1/2
|
||||
transform rounded
|
||||
bg-[#5F5F68]
|
||||
bg-ui-controls
|
||||
px-2 py-1
|
||||
text-xs font-medium
|
||||
text-white shadow-lg
|
||||
|
||||
@@ -16,6 +16,12 @@ export type SplitPaneProps = {
|
||||
/** Extra classes for the outer wrapper */
|
||||
className?: string;
|
||||
singlePaneMode: boolean;
|
||||
/**
|
||||
* Fill the parent box instead of reserving 56px for a global top bar. Hosts
|
||||
* that hide their top bar on this route pass true so the backdrop reaches
|
||||
* every edge.
|
||||
*/
|
||||
fillParentHeight?: boolean;
|
||||
};
|
||||
|
||||
export default function SplitPane({
|
||||
@@ -27,7 +33,9 @@ export default function SplitPane({
|
||||
onChange,
|
||||
className = "",
|
||||
singlePaneMode = true,
|
||||
fillParentHeight = false,
|
||||
}: SplitPaneProps) {
|
||||
const heightClass = fillParentHeight ? "h-full" : "h-[calc(100vh-56px)]";
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const [leftPct, setLeftPct] = useState(initialPercent);
|
||||
|
||||
@@ -65,7 +73,7 @@ export default function SplitPane({
|
||||
return singlePaneMode ? (
|
||||
<div
|
||||
ref={rootRef}
|
||||
className={`pegboard-bg relative flex h-[calc(100vh-56px)] w-full items-center justify-center overflow-hidden ${className}`}
|
||||
className={`pegboard-bg relative flex ${heightClass} w-full items-center justify-center overflow-hidden ${className}`}
|
||||
>
|
||||
<div style={leftStyle} className="h-full min-w-0 overflow-hidden">
|
||||
{left}
|
||||
@@ -74,15 +82,15 @@ export default function SplitPane({
|
||||
) : (
|
||||
<div
|
||||
ref={rootRef}
|
||||
className={`pegboard-bg relative flex h-[calc(100vh-56px)] w-full overflow-hidden ${className}`}
|
||||
className={`pegboard-bg relative flex ${heightClass} w-full overflow-hidden ${className}`}
|
||||
>
|
||||
<div style={leftStyle} className="h-full min-w-0 overflow-hidden">
|
||||
{left}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="w-1 cursor-col-resize select-none bg-gray-500/80
|
||||
transition hover:bg-gray-600"
|
||||
className="w-1 cursor-col-resize select-none bg-white/20
|
||||
transition hover:bg-white/30"
|
||||
onMouseDown={beginDrag}
|
||||
onTouchStart={beginDrag}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
.pegboard-bg {
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
background-color: #1b1b1b;
|
||||
/* Themeable canvas backdrop; hosts may set --st-canvas, otherwise the
|
||||
original dark grey is used. */
|
||||
background-color: var(--st-canvas, #101014);
|
||||
background-size: 50px 50px;
|
||||
background-position:
|
||||
0 0,
|
||||
|
||||
@@ -358,7 +358,7 @@ export const PromptBox2D = ({
|
||||
>
|
||||
{content}
|
||||
</Modal>
|
||||
<div className="absolute bottom-4 left-1/2 flex -translate-x-1/2 flex-col gap-3">
|
||||
<div className="absolute bottom-4 left-1/2 flex w-[90vw] max-w-5xl -translate-x-1/2 flex-col gap-3">
|
||||
{selectedImageModel?.canUseImagePrompt && isImageRowVisible && (
|
||||
<ImagePromptRow
|
||||
visible={true}
|
||||
@@ -384,7 +384,7 @@ export const PromptBox2D = ({
|
||||
)}
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass relative w-[860px] rounded-2xl p-4",
|
||||
"glass relative w-full rounded-2xl p-4",
|
||||
selectedImageModel?.canUseImagePrompt &&
|
||||
isImageRowVisible &&
|
||||
"rounded-t-none",
|
||||
|
||||
@@ -606,7 +606,7 @@ export const PromptBox3D = ({
|
||||
>
|
||||
{content}
|
||||
</Modal>
|
||||
<div className="absolute bottom-4 left-1/2 flex -translate-x-1/2 flex-col gap-3">
|
||||
<div className="absolute bottom-4 left-1/2 flex w-[90vw] max-w-5xl -translate-x-1/2 flex-col gap-3">
|
||||
{aboveStackSlot}
|
||||
{selectedImageModel?.canUseImagePrompt && isImageRowVisible && (
|
||||
<ImagePromptRow
|
||||
@@ -633,7 +633,7 @@ export const PromptBox3D = ({
|
||||
)}
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass relative w-[860px] rounded-2xl p-4",
|
||||
"glass relative w-full rounded-2xl p-4",
|
||||
isPromptBoxFocused ? "!border !border-primary" : "",
|
||||
selectedImageModel?.canUseImagePrompt &&
|
||||
isImageRowVisible &&
|
||||
|
||||
Reference in new issue
Block a user