mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-22 16:48:30 +02:00
Environment colors (#225)
This commit is contained in:
11
package-lock.json
generated
11
package-lock.json
generated
@@ -12946,6 +12946,16 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-colorful": {
|
||||||
|
"version": "5.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz",
|
||||||
|
"integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dnd": {
|
"node_modules/react-dnd": {
|
||||||
"version": "16.0.1",
|
"version": "16.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
|
||||||
@@ -17626,6 +17636,7 @@
|
|||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"parse-color": "^1.0.0",
|
"parse-color": "^1.0.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
"react-colorful": "^5.6.1",
|
||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE environments ADD COLUMN color TEXT;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, base: boolean, variables: Array<EnvironmentVariable>, };
|
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, base: boolean, variables: Array<EnvironmentVariable>, color: string | null, };
|
||||||
|
|
||||||
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export type EditorKeymap = "default" | "vim" | "vscode" | "emacs";
|
|||||||
|
|
||||||
export type EncryptedKey = { encryptedKey: string, };
|
export type EncryptedKey = { encryptedKey: string, };
|
||||||
|
|
||||||
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, base: boolean, variables: Array<EnvironmentVariable>, };
|
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, base: boolean, variables: Array<EnvironmentVariable>, color: string | null, };
|
||||||
|
|
||||||
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { Environment } from "./gen_models.js";
|
import type { Environment, Folder, GrpcRequest, HttpRequest, WebsocketRequest, Workspace } from "./gen_models.js";
|
||||||
import type { Folder } from "./gen_models.js";
|
|
||||||
import type { GrpcRequest } from "./gen_models.js";
|
|
||||||
import type { HttpRequest } from "./gen_models.js";
|
|
||||||
import type { WebsocketRequest } from "./gen_models.js";
|
|
||||||
import type { Workspace } from "./gen_models.js";
|
|
||||||
|
|
||||||
export type BatchUpsertResult = { workspaces: Array<Workspace>, environments: Array<Environment>, folders: Array<Folder>, httpRequests: Array<HttpRequest>, grpcRequests: Array<GrpcRequest>, websocketRequests: Array<WebsocketRequest>, };
|
export type BatchUpsertResult = { workspaces: Array<Workspace>, environments: Array<Environment>, folders: Array<Folder>, httpRequests: Array<HttpRequest>, grpcRequests: Array<GrpcRequest>, websocketRequests: Array<WebsocketRequest>, };
|
||||||
|
|||||||
@@ -520,6 +520,7 @@ pub struct Environment {
|
|||||||
pub public: bool,
|
pub public: bool,
|
||||||
pub base: bool,
|
pub base: bool,
|
||||||
pub variables: Vec<EnvironmentVariable>,
|
pub variables: Vec<EnvironmentVariable>,
|
||||||
|
pub color: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpsertModelInfo for Environment {
|
impl UpsertModelInfo for Environment {
|
||||||
@@ -553,6 +554,7 @@ impl UpsertModelInfo for Environment {
|
|||||||
(UpdatedAt, upsert_date(source, self.updated_at)),
|
(UpdatedAt, upsert_date(source, self.updated_at)),
|
||||||
(WorkspaceId, self.workspace_id.into()),
|
(WorkspaceId, self.workspace_id.into()),
|
||||||
(Base, self.base.into()),
|
(Base, self.base.into()),
|
||||||
|
(Color, self.color.into()),
|
||||||
(Name, self.name.trim().into()),
|
(Name, self.name.trim().into()),
|
||||||
(Public, self.public.into()),
|
(Public, self.public.into()),
|
||||||
(Variables, serde_json::to_string(&self.variables)?.into()),
|
(Variables, serde_json::to_string(&self.variables)?.into()),
|
||||||
@@ -563,6 +565,7 @@ impl UpsertModelInfo for Environment {
|
|||||||
vec. Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { Environment } from "./gen_models.js";
|
import type { Environment, Folder, GrpcRequest, HttpRequest, HttpResponse, WebsocketRequest, Workspace } from "./gen_models.js";
|
||||||
import type { Folder } from "./gen_models.js";
|
|
||||||
import type { GrpcRequest } from "./gen_models.js";
|
|
||||||
import type { HttpRequest } from "./gen_models.js";
|
|
||||||
import type { HttpResponse } from "./gen_models.js";
|
|
||||||
import type { JsonValue } from "./serde_json/JsonValue.js";
|
import type { JsonValue } from "./serde_json/JsonValue.js";
|
||||||
import type { WebsocketRequest } from "./gen_models.js";
|
|
||||||
import type { Workspace } from "./gen_models.js";
|
|
||||||
|
|
||||||
export type BootRequest = { dir: string, watch: boolean, };
|
export type BootRequest = { dir: string, watch: boolean, };
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, base: boolean, variables: Array<EnvironmentVariable>, };
|
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, base: boolean, variables: Array<EnvironmentVariable>, color: string | null, };
|
||||||
|
|
||||||
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, base: boolean, variables: Array<EnvironmentVariable>, };
|
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, base: boolean, variables: Array<EnvironmentVariable>, color: string | null, };
|
||||||
|
|
||||||
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { SyncModel } from "./gen_models.js";
|
import type { SyncModel, SyncState } from "./gen_models.js";
|
||||||
import type { SyncState } from "./gen_models.js";
|
|
||||||
|
|
||||||
export type FsCandidate = { "type": "FsCandidate", model: SyncModel, relPath: string, checksum: string, };
|
export type FsCandidate = { "type": "FsCandidate", model: SyncModel, relPath: string, checksum: string, };
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
|||||||
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
|
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
|
||||||
import { toggleDialog } from '../lib/dialog';
|
import { toggleDialog } from '../lib/dialog';
|
||||||
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
|
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
|
||||||
import type { ButtonProps } from './core/Button';
|
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
|
import type { ButtonProps } from './core/Button';
|
||||||
import type { DropdownItem } from './core/Dropdown';
|
import type { DropdownItem } from './core/Dropdown';
|
||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
|
import { EnvironmentColorCircle } from './EnvironmentColorCircle';
|
||||||
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
|
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -38,6 +39,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
|||||||
(e) => ({
|
(e) => ({
|
||||||
key: e.id,
|
key: e.id,
|
||||||
label: e.name,
|
label: e.name,
|
||||||
|
rightSlot: <EnvironmentColorCircle environment={e} />,
|
||||||
leftSlot: e.id === activeEnvironment?.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
leftSlot: e.id === activeEnvironment?.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
if (e.id !== activeEnvironment?.id) {
|
if (e.id !== activeEnvironment?.id) {
|
||||||
@@ -80,6 +82,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
|||||||
onClick={subEnvironments.length === 0 ? showEnvironmentDialog : undefined}
|
onClick={subEnvironments.length === 0 ? showEnvironmentDialog : undefined}
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
>
|
>
|
||||||
|
<EnvironmentColorCircle environment={activeEnvironment ?? null} />
|
||||||
{activeEnvironment?.name ?? (hasBaseVars ? 'Environment' : 'No Environment')}
|
{activeEnvironment?.name ?? (hasBaseVars ? 'Environment' : 'No Environment')}
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|||||||
29
src-web/components/EnvironmentColorCircle.tsx
Normal file
29
src-web/components/EnvironmentColorCircle.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import type { Environment } from '@yaakapp-internal/models';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { showColorPicker } from '../lib/showColorPicker';
|
||||||
|
|
||||||
|
export function EnvironmentColorCircle({
|
||||||
|
environment,
|
||||||
|
clickToEdit,
|
||||||
|
}: {
|
||||||
|
environment: Environment | null;
|
||||||
|
clickToEdit?: boolean;
|
||||||
|
}) {
|
||||||
|
if (environment?.color == null) return null;
|
||||||
|
|
||||||
|
const style = { backgroundColor: environment.color };
|
||||||
|
const className =
|
||||||
|
'inline-block w-[0.75em] h-[0.75em] rounded-full mr-1.5 border border-transparent';
|
||||||
|
|
||||||
|
if (clickToEdit) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => showColorPicker(environment)}
|
||||||
|
style={style}
|
||||||
|
className={classNames(className, 'hover:border-text')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <span style={style} className={className} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src-web/components/EnvironmentColorPicker.tsx
Normal file
26
src-web/components/EnvironmentColorPicker.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Button } from './core/Button';
|
||||||
|
import { ColorPicker } from './core/ColorPicker';
|
||||||
|
|
||||||
|
export function EnvironmentColorPicker({
|
||||||
|
color: defaultColor,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
color: string | null;
|
||||||
|
onChange: (color: string | null) => void;
|
||||||
|
}) {
|
||||||
|
const [color, setColor] = useState<string | null>(defaultColor);
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-stretch gap-3 pb-2 w-full">
|
||||||
|
<ColorPicker color={color} onChange={setColor} />
|
||||||
|
<div className="grid grid-cols-[1fr_1fr] gap-1.5">
|
||||||
|
<Button variant="border" color="secondary" onClick={() => onChange(null)}>
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
<Button color="primary" onClick={() => onChange(color)}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
setupOrConfigureEncryption,
|
setupOrConfigureEncryption,
|
||||||
withEncryptionEnabled,
|
withEncryptionEnabled,
|
||||||
} from '../lib/setupOrConfigureEncryption';
|
} from '../lib/setupOrConfigureEncryption';
|
||||||
|
import { showColorPicker } from '../lib/showColorPicker';
|
||||||
import { BadgeButton } from './core/BadgeButton';
|
import { BadgeButton } from './core/BadgeButton';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
@@ -35,6 +36,7 @@ import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
|||||||
import { Separator } from './core/Separator';
|
import { Separator } from './core/Separator';
|
||||||
import { SplitLayout } from './core/SplitLayout';
|
import { SplitLayout } from './core/SplitLayout';
|
||||||
import { VStack } from './core/Stacks';
|
import { VStack } from './core/Stacks';
|
||||||
|
import { EnvironmentColorCircle } from './EnvironmentColorCircle';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
initialEnvironment: Environment | null;
|
initialEnvironment: Environment | null;
|
||||||
@@ -123,7 +125,7 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
|||||||
))}
|
))}
|
||||||
{subEnvironments.length > 0 && (
|
{subEnvironments.length > 0 && (
|
||||||
<div className="px-2">
|
<div className="px-2">
|
||||||
<Separator className="my-3"></Separator>
|
<Separator className="my-3" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{subEnvironments.map((e) => (
|
{subEnvironments.map((e) => (
|
||||||
@@ -237,6 +239,7 @@ const EnvironmentEditor = function ({
|
|||||||
return (
|
return (
|
||||||
<VStack space={4} className={classNames(className, 'pl-4')}>
|
<VStack space={4} className={classNames(className, 'pl-4')}>
|
||||||
<Heading className="w-full flex items-center gap-0.5">
|
<Heading className="w-full flex items-center gap-0.5">
|
||||||
|
<EnvironmentColorCircle clickToEdit environment={selectedEnvironment ?? null} />
|
||||||
<div className="mr-2">{selectedEnvironment?.name}</div>
|
<div className="mr-2">{selectedEnvironment?.name}</div>
|
||||||
{isEncryptionEnabled ? (
|
{isEncryptionEnabled ? (
|
||||||
promptToEncrypt ? (
|
promptToEncrypt ? (
|
||||||
@@ -344,6 +347,7 @@ function SidebarButton({
|
|||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
rightSlot={rightSlot}
|
rightSlot={rightSlot}
|
||||||
>
|
>
|
||||||
|
<EnvironmentColorCircle environment={environment} />
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
{outerRightSlot}
|
{outerRightSlot}
|
||||||
@@ -385,6 +389,12 @@ function SidebarButton({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []) as DropdownItem[]),
|
: []) as DropdownItem[]),
|
||||||
|
{
|
||||||
|
label: 'Set Color',
|
||||||
|
leftSlot: <Icon icon="palette" />,
|
||||||
|
hidden: environment.base,
|
||||||
|
onSelect: async () => showColorPicker(environment),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: `Make ${environment.public ? 'Private' : 'Sharable'}`,
|
label: `Make ${environment.public ? 'Private' : 'Sharable'}`,
|
||||||
leftSlot: <Icon icon={environment.public ? 'eye_closed' : 'eye'} />,
|
leftSlot: <Icon icon={environment.public ? 'eye_closed' : 'eye'} />,
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import {
|
|||||||
useEnsureActiveCookieJar,
|
useEnsureActiveCookieJar,
|
||||||
useSubscribeActiveCookieJarId,
|
useSubscribeActiveCookieJarId,
|
||||||
} from '../hooks/useActiveCookieJar';
|
} from '../hooks/useActiveCookieJar';
|
||||||
import { useSubscribeActiveEnvironmentId } from '../hooks/useActiveEnvironment';
|
import {
|
||||||
|
activeEnvironmentAtom,
|
||||||
|
useSubscribeActiveEnvironmentId,
|
||||||
|
} from '../hooks/useActiveEnvironment';
|
||||||
import { activeRequestAtom } from '../hooks/useActiveRequest';
|
import { activeRequestAtom } from '../hooks/useActiveRequest';
|
||||||
import { useSubscribeActiveRequestId } from '../hooks/useActiveRequestId';
|
import { useSubscribeActiveRequestId } from '../hooks/useActiveRequestId';
|
||||||
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
|
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
|
||||||
@@ -32,6 +35,7 @@ import { HotKeyList } from './core/HotKeyList';
|
|||||||
import { FeedbackLink } from './core/Link';
|
import { FeedbackLink } from './core/Link';
|
||||||
import { HStack } from './core/Stacks';
|
import { HStack } from './core/Stacks';
|
||||||
import { CreateDropdown } from './CreateDropdown';
|
import { CreateDropdown } from './CreateDropdown';
|
||||||
|
import { ErrorBoundary } from './ErrorBoundary';
|
||||||
import { GrpcConnectionLayout } from './GrpcConnectionLayout';
|
import { GrpcConnectionLayout } from './GrpcConnectionLayout';
|
||||||
import { HeaderSize } from './HeaderSize';
|
import { HeaderSize } from './HeaderSize';
|
||||||
import { HttpRequestLayout } from './HttpRequestLayout';
|
import { HttpRequestLayout } from './HttpRequestLayout';
|
||||||
@@ -41,7 +45,6 @@ import { Sidebar } from './sidebar/Sidebar';
|
|||||||
import { SidebarActions } from './sidebar/SidebarActions';
|
import { SidebarActions } from './sidebar/SidebarActions';
|
||||||
import { WebsocketRequestLayout } from './WebsocketRequestLayout';
|
import { WebsocketRequestLayout } from './WebsocketRequestLayout';
|
||||||
import { WorkspaceHeader } from './WorkspaceHeader';
|
import { WorkspaceHeader } from './WorkspaceHeader';
|
||||||
import { ErrorBoundary } from './ErrorBoundary';
|
|
||||||
|
|
||||||
const side = { gridArea: 'side' };
|
const side = { gridArea: 'side' };
|
||||||
const head = { gridArea: 'head' };
|
const head = { gridArea: 'head' };
|
||||||
@@ -56,6 +59,7 @@ export function Workspace() {
|
|||||||
const { setWidth, width, resetWidth } = useSidebarWidth();
|
const { setWidth, width, resetWidth } = useSidebarWidth();
|
||||||
const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
|
const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
|
||||||
const [floatingSidebarHidden, setFloatingSidebarHidden] = useFloatingSidebarHidden();
|
const [floatingSidebarHidden, setFloatingSidebarHidden] = useFloatingSidebarHidden();
|
||||||
|
const activeEnvironment = useAtomValue(activeEnvironmentAtom);
|
||||||
const floating = useShouldFloatSidebar();
|
const floating = useShouldFloatSidebar();
|
||||||
const [isResizing, setIsResizing] = useState<boolean>(false);
|
const [isResizing, setIsResizing] = useState<boolean>(false);
|
||||||
const moveState = useRef<{ move: (e: MouseEvent) => void; up: (e: MouseEvent) => void } | null>(
|
const moveState = useRef<{ move: (e: MouseEvent) => void; up: (e: MouseEvent) => void } | null>(
|
||||||
@@ -117,6 +121,12 @@ export function Workspace() {
|
|||||||
[sideWidth, floating],
|
[sideWidth, floating],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const environmentBg = useMemo(() => {
|
||||||
|
if (activeEnvironment?.color == null) return undefined;
|
||||||
|
const background = `linear-gradient(to right, ${activeEnvironment.color} 15%, transparent 40%)`;
|
||||||
|
return { background };
|
||||||
|
}, [activeEnvironment?.color ?? 'n/a']);
|
||||||
|
|
||||||
// We're loading still
|
// We're loading still
|
||||||
if (workspaces.length === 0) {
|
if (workspaces.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -175,9 +185,19 @@ export function Workspace() {
|
|||||||
<HeaderSize
|
<HeaderSize
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
size="lg"
|
size="lg"
|
||||||
className="x-theme-appHeader bg-surface"
|
className="relative x-theme-appHeader bg-surface"
|
||||||
style={head}
|
style={head}
|
||||||
>
|
>
|
||||||
|
<div className="absolute inset-0 pointer-events-none">
|
||||||
|
<div // Add subtle background
|
||||||
|
style={environmentBg}
|
||||||
|
className="absolute inset-0 opacity-5"
|
||||||
|
/>
|
||||||
|
<div // Add subtle border bottom
|
||||||
|
style={environmentBg}
|
||||||
|
className="absolute left-0 right-0 bottom-0 h-[0.5px] opacity-20"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<WorkspaceHeader className="pointer-events-none" />
|
<WorkspaceHeader className="pointer-events-none" />
|
||||||
</HeaderSize>
|
</HeaderSize>
|
||||||
<ErrorBoundary name="Workspace Body">
|
<ErrorBoundary name="Workspace Body">
|
||||||
|
|||||||
40
src-web/components/core/ColorPicker.tsx
Normal file
40
src-web/components/core/ColorPicker.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { HexColorPicker } from 'react-colorful';
|
||||||
|
import { useRandomKey } from '../../hooks/useRandomKey';
|
||||||
|
import { PlainInput } from './PlainInput';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onChange: (value: string | null) => void;
|
||||||
|
color: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ColorPicker({ onChange, color: defaultColor }: Props) {
|
||||||
|
const [updateKey, regenerateKey] = useRandomKey();
|
||||||
|
const [color, setColor] = useState<string | null>(defaultColor);
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className="flex flex-col gap-3 items-stretch w-full"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onChange(color);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HexColorPicker
|
||||||
|
color={color ?? undefined}
|
||||||
|
className="!w-full"
|
||||||
|
onChange={(color) => {
|
||||||
|
setColor(color.toUpperCase());
|
||||||
|
regenerateKey();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PlainInput
|
||||||
|
hideLabel
|
||||||
|
label="Plain Color"
|
||||||
|
forceUpdateKey={updateKey}
|
||||||
|
defaultValue={color ?? ''}
|
||||||
|
onChange={(c) => setColor(c.toUpperCase())}
|
||||||
|
validate={(color) => color.match(/#[0-9a-fA-F]{6}$/) !== null}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -26,9 +26,9 @@ const icons = {
|
|||||||
cake: lucide.CakeIcon,
|
cake: lucide.CakeIcon,
|
||||||
chat: lucide.MessageSquare,
|
chat: lucide.MessageSquare,
|
||||||
check: lucide.CheckIcon,
|
check: lucide.CheckIcon,
|
||||||
|
check_circle: lucide.CheckCircleIcon,
|
||||||
check_square_checked: lucide.SquareCheckIcon,
|
check_square_checked: lucide.SquareCheckIcon,
|
||||||
check_square_unchecked: lucide.SquareIcon,
|
check_square_unchecked: lucide.SquareIcon,
|
||||||
check_circle: lucide.CheckCircleIcon,
|
|
||||||
chevron_down: lucide.ChevronDownIcon,
|
chevron_down: lucide.ChevronDownIcon,
|
||||||
chevron_right: lucide.ChevronRightIcon,
|
chevron_right: lucide.ChevronRightIcon,
|
||||||
circle_alert: lucide.CircleAlertIcon,
|
circle_alert: lucide.CircleAlertIcon,
|
||||||
@@ -78,6 +78,7 @@ const icons = {
|
|||||||
minus_circle: lucide.MinusCircleIcon,
|
minus_circle: lucide.MinusCircleIcon,
|
||||||
moon: lucide.MoonIcon,
|
moon: lucide.MoonIcon,
|
||||||
more_vertical: lucide.MoreVerticalIcon,
|
more_vertical: lucide.MoreVerticalIcon,
|
||||||
|
palette: lucide.PaletteIcon,
|
||||||
paste: lucide.ClipboardPasteIcon,
|
paste: lucide.ClipboardPasteIcon,
|
||||||
pencil: lucide.PencilIcon,
|
pencil: lucide.PencilIcon,
|
||||||
pin: lucide.PinIcon,
|
pin: lucide.PinIcon,
|
||||||
|
|||||||
23
src-web/lib/showColorPicker.tsx
Normal file
23
src-web/lib/showColorPicker.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { Environment } from '@yaakapp-internal/models';
|
||||||
|
import { patchModel } from '@yaakapp-internal/models';
|
||||||
|
import { showDialog } from './dialog';
|
||||||
|
import { EnvironmentColorPicker } from '../components/EnvironmentColorPicker';
|
||||||
|
|
||||||
|
export function showColorPicker(environment: Environment) {
|
||||||
|
showDialog({
|
||||||
|
title: 'Environment Color',
|
||||||
|
id: 'color-picker',
|
||||||
|
size: 'dynamic',
|
||||||
|
render: ({ hide }) => {
|
||||||
|
return (
|
||||||
|
<EnvironmentColorPicker
|
||||||
|
color={environment.color ?? '#54dc44'}
|
||||||
|
onChange={async (color) => {
|
||||||
|
await patchModel(environment, { color });
|
||||||
|
hide();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -54,6 +54,7 @@
|
|||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"parse-color": "^1.0.0",
|
"parse-color": "^1.0.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
"react-colorful": "^5.6.1",
|
||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user