mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-22 16:48:30 +02:00
Tweak light theme, high contrast themes, and fix env null reference
This commit is contained in:
@@ -2,6 +2,49 @@ import type { PluginDefinition } from '@yaakapp/api';
|
|||||||
|
|
||||||
export const plugin: PluginDefinition = {
|
export const plugin: PluginDefinition = {
|
||||||
themes: [
|
themes: [
|
||||||
|
{
|
||||||
|
id: 'high-contrast',
|
||||||
|
label: 'High Contrast Light',
|
||||||
|
dark: false,
|
||||||
|
base: {
|
||||||
|
surface: 'white',
|
||||||
|
surfaceHighlight: 'hsl(218,24%,93%)',
|
||||||
|
text: 'black',
|
||||||
|
textSubtle: 'hsl(217,24%,40%)',
|
||||||
|
textSubtlest: 'hsl(217,24%,40%)',
|
||||||
|
border: 'hsl(217,22%,50%)',
|
||||||
|
borderSubtle: 'hsl(217,22%,60%)',
|
||||||
|
primary: 'hsl(267,67%,47%)',
|
||||||
|
secondary: 'hsl(218,18%,53%)',
|
||||||
|
info: 'hsl(206,100%,36%)',
|
||||||
|
success: 'hsl(155,100%,26%)',
|
||||||
|
notice: 'hsl(45,100%,31%)',
|
||||||
|
warning: 'hsl(30,99%,34%)',
|
||||||
|
danger: 'hsl(334,100%,35%)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'high-contrast-dark',
|
||||||
|
label: 'High Contrast Dark',
|
||||||
|
dark: true,
|
||||||
|
base: {
|
||||||
|
surface: 'hsl(0,0%,0%)',
|
||||||
|
surfaceHighlight: 'hsl(0,0%,20%)',
|
||||||
|
text: 'hsl(0,0%,100%)',
|
||||||
|
textSubtle: 'hsl(0,0%,90%)',
|
||||||
|
textSubtlest: 'hsl(0,0%,80%)',
|
||||||
|
selection: 'hsl(276,100%,30%)',
|
||||||
|
surfaceActive: 'hsl(276,100%,30%)',
|
||||||
|
border: 'hsl(0,0%,60%)',
|
||||||
|
primary: 'hsl(266,100%,85%)',
|
||||||
|
secondary: 'hsl(242,20%,72%)',
|
||||||
|
info: 'hsl(208,100%,83%)',
|
||||||
|
success: 'hsl(150,100%,63%)',
|
||||||
|
notice: 'hsl(49,100%,77%)',
|
||||||
|
warning: 'hsl(28,100%,73%)',
|
||||||
|
danger: 'hsl(343,100%,79%)',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'catppuccin-frappe',
|
id: 'catppuccin-frappe',
|
||||||
label: 'Catppuccin Frappé',
|
label: 'Catppuccin Frappé',
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ impl<'a> DbContext<'a> {
|
|||||||
&Environment {
|
&Environment {
|
||||||
workspace_id: workspace_id.to_string(),
|
workspace_id: workspace_id.to_string(),
|
||||||
name: "Global Variables".to_string(),
|
name: "Global Variables".to_string(),
|
||||||
|
parent_model: "workspace".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
&UpdateSource::Background,
|
&UpdateSource::Background,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { EnvironmentColorIndicator } from './EnvironmentColorIndicator';
|
|||||||
import { EnvironmentSharableTooltip } from './EnvironmentSharableTooltip';
|
import { EnvironmentSharableTooltip } from './EnvironmentSharableTooltip';
|
||||||
|
|
||||||
export function EnvironmentEditor({
|
export function EnvironmentEditor({
|
||||||
environment: selectedEnvironment,
|
environment,
|
||||||
hideName,
|
hideName,
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
@@ -32,7 +32,7 @@ export function EnvironmentEditor({
|
|||||||
hideName?: boolean;
|
hideName?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
const workspaceId = selectedEnvironment.workspaceId;
|
const workspaceId = environment.workspaceId;
|
||||||
const isEncryptionEnabled = useIsEncryptionEnabled();
|
const isEncryptionEnabled = useIsEncryptionEnabled();
|
||||||
const valueVisibility = useKeyValue<boolean>({
|
const valueVisibility = useKeyValue<boolean>({
|
||||||
namespace: 'global',
|
namespace: 'global',
|
||||||
@@ -41,15 +41,15 @@ export function EnvironmentEditor({
|
|||||||
});
|
});
|
||||||
const { allEnvironments } = useEnvironmentsBreakdown();
|
const { allEnvironments } = useEnvironmentsBreakdown();
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(variables: PairWithId[]) => patchModel(selectedEnvironment, { variables }),
|
(variables: PairWithId[]) => patchModel(environment, { variables }),
|
||||||
[selectedEnvironment],
|
[environment],
|
||||||
);
|
);
|
||||||
const [forceUpdateKey, regenerateForceUpdateKey] = useRandomKey();
|
const [forceUpdateKey, regenerateForceUpdateKey] = useRandomKey();
|
||||||
|
|
||||||
// Gather a list of env names from other environments to help the user get them aligned
|
// Gather a list of env names from other environments to help the user get them aligned
|
||||||
const nameAutocomplete = useMemo<GenericCompletionConfig>(() => {
|
const nameAutocomplete = useMemo<GenericCompletionConfig>(() => {
|
||||||
const options: GenericCompletionOption[] = [];
|
const options: GenericCompletionOption[] = [];
|
||||||
if (isBaseEnvironment(selectedEnvironment)) {
|
if (isBaseEnvironment(environment)) {
|
||||||
return { options };
|
return { options };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,8 +59,10 @@ export function EnvironmentEditor({
|
|||||||
const containingEnvs = allEnvironments.filter((e) =>
|
const containingEnvs = allEnvironments.filter((e) =>
|
||||||
e.variables.some((v) => v.name === name),
|
e.variables.some((v) => v.name === name),
|
||||||
);
|
);
|
||||||
const isAlreadyInActive = containingEnvs.find((e) => e.id === selectedEnvironment.id);
|
const isAlreadyInActive = containingEnvs.find((e) => e.id === environment.id);
|
||||||
if (isAlreadyInActive) continue;
|
if (isAlreadyInActive) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
options.push({
|
options.push({
|
||||||
label: name,
|
label: name,
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
@@ -68,7 +70,7 @@ export function EnvironmentEditor({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { options };
|
return { options };
|
||||||
}, [selectedEnvironment, allEnvironments]);
|
}, [environment, allEnvironments]);
|
||||||
|
|
||||||
const validateName = useCallback((name: string) => {
|
const validateName = useCallback((name: string) => {
|
||||||
// Empty just means the variable doesn't have a name yet and is unusable
|
// Empty just means the variable doesn't have a name yet and is unusable
|
||||||
@@ -79,10 +81,8 @@ export function EnvironmentEditor({
|
|||||||
const valueType = !isEncryptionEnabled && valueVisibility.value ? 'text' : 'password';
|
const valueType = !isEncryptionEnabled && valueVisibility.value ? 'text' : 'password';
|
||||||
const allVariableAreEncrypted = useMemo(
|
const allVariableAreEncrypted = useMemo(
|
||||||
() =>
|
() =>
|
||||||
selectedEnvironment.variables.every(
|
environment.variables.every((v) => v.value === '' || analyzeTemplate(v.value) !== 'insecure'),
|
||||||
(v) => v.value === '' || analyzeTemplate(v.value) !== 'insecure',
|
[environment.variables],
|
||||||
),
|
|
||||||
[selectedEnvironment.variables],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const encryptEnvironment = (environment: Environment) => {
|
const encryptEnvironment = (environment: Environment) => {
|
||||||
@@ -100,11 +100,11 @@ export function EnvironmentEditor({
|
|||||||
return (
|
return (
|
||||||
<VStack space={4} className={className}>
|
<VStack space={4} className={className}>
|
||||||
<Heading className="w-full flex items-center gap-0.5">
|
<Heading className="w-full flex items-center gap-0.5">
|
||||||
<EnvironmentColorIndicator clickToEdit environment={selectedEnvironment ?? null} />
|
<EnvironmentColorIndicator clickToEdit environment={environment ?? null} />
|
||||||
{!hideName && <div className="mr-2">{selectedEnvironment?.name}</div>}
|
{!hideName && <div className="mr-2">{environment?.name}</div>}
|
||||||
{isEncryptionEnabled ? (
|
{isEncryptionEnabled ? (
|
||||||
!allVariableAreEncrypted ? (
|
!allVariableAreEncrypted ? (
|
||||||
<BadgeButton color="notice" onClick={() => encryptEnvironment(selectedEnvironment)}>
|
<BadgeButton color="notice" onClick={() => encryptEnvironment(environment)}>
|
||||||
Encrypt All Variables
|
Encrypt All Variables
|
||||||
</BadgeButton>
|
</BadgeButton>
|
||||||
) : (
|
) : (
|
||||||
@@ -121,21 +121,21 @@ export function EnvironmentEditor({
|
|||||||
color="secondary"
|
color="secondary"
|
||||||
rightSlot={<EnvironmentSharableTooltip />}
|
rightSlot={<EnvironmentSharableTooltip />}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await patchModel(selectedEnvironment, { public: !selectedEnvironment.public });
|
await patchModel(environment, { public: !environment.public });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{selectedEnvironment.public ? 'Sharable' : 'Private'}
|
{environment.public ? 'Sharable' : 'Private'}
|
||||||
</BadgeButton>
|
</BadgeButton>
|
||||||
</Heading>
|
</Heading>
|
||||||
{selectedEnvironment.public && (!isEncryptionEnabled || !allVariableAreEncrypted) && (
|
{environment.public && (!isEncryptionEnabled || !allVariableAreEncrypted) && (
|
||||||
<DismissibleBanner
|
<DismissibleBanner
|
||||||
id={`warn-unencrypted-${selectedEnvironment.id}`}
|
id={`warn-unencrypted-${environment.id}`}
|
||||||
color="notice"
|
color="notice"
|
||||||
className="mr-3"
|
className="mr-3"
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
label: 'Encrypt Variables',
|
label: 'Encrypt Variables',
|
||||||
onClick: () => encryptEnvironment(selectedEnvironment),
|
onClick: () => encryptEnvironment(environment),
|
||||||
color: 'primary',
|
color: 'primary',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@@ -153,15 +153,11 @@ export function EnvironmentEditor({
|
|||||||
valueType={valueType}
|
valueType={valueType}
|
||||||
valueAutocompleteVariables
|
valueAutocompleteVariables
|
||||||
valueAutocompleteFunctions
|
valueAutocompleteFunctions
|
||||||
forceUpdateKey={`${selectedEnvironment.id}::${forceUpdateKey}`}
|
forceUpdateKey={`${environment.id}::${forceUpdateKey}`}
|
||||||
pairs={selectedEnvironment.variables}
|
pairs={environment.variables}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
stateKey={`environment.${selectedEnvironment.id}`}
|
stateKey={`environment.${environment.id}`}
|
||||||
forcedEnvironmentId={
|
forcedEnvironmentId={environment.id}
|
||||||
// Editing the base environment should resolve variables using the active environment.
|
|
||||||
// Editing a sub environment should resolve variables as if it's the active environment
|
|
||||||
isBaseEnvironment(selectedEnvironment) ? undefined : selectedEnvironment.id
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
import type { Environment, EnvironmentVariable } from '@yaakapp-internal/models';
|
import type { Environment, EnvironmentVariable } from '@yaakapp-internal/models';
|
||||||
import { foldersAtom } from '@yaakapp-internal/models';
|
import { foldersAtom } from '@yaakapp-internal/models';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { jotaiStore } from '../lib/jotai';
|
import { jotaiStore } from '../lib/jotai';
|
||||||
|
import { isFolderEnvironment } from '../lib/model_util';
|
||||||
|
import { useActiveEnvironment } from './useActiveEnvironment';
|
||||||
import { useActiveRequest } from './useActiveRequest';
|
import { useActiveRequest } from './useActiveRequest';
|
||||||
import { useEnvironmentsBreakdown } from './useEnvironmentsBreakdown';
|
import { useEnvironmentsBreakdown } from './useEnvironmentsBreakdown';
|
||||||
import { useParentFolders } from './useParentFolders';
|
import { useParentFolders } from './useParentFolders';
|
||||||
|
|
||||||
export function useEnvironmentVariables(environmentId: string | null) {
|
export function useEnvironmentVariables(targetEnvironmentId: string | null) {
|
||||||
const { baseEnvironment, folderEnvironments, subEnvironments } = useEnvironmentsBreakdown();
|
const { baseEnvironment, folderEnvironments, allEnvironments } = useEnvironmentsBreakdown();
|
||||||
const activeEnvironment = subEnvironments.find((e) => e.id === environmentId) ?? null;
|
const activeEnvironment = useActiveEnvironment();
|
||||||
|
const targetEnvironment = allEnvironments.find((e) => e.id === targetEnvironmentId) ?? null;
|
||||||
const activeRequest = useActiveRequest();
|
const activeRequest = useActiveRequest();
|
||||||
const parentFolders = useParentFolders(activeRequest);
|
const folders = useAtomValue(foldersAtom);
|
||||||
|
const activeFolder = folders.find((f) => f.id === targetEnvironment?.parentId) ?? null;
|
||||||
|
const parentFolders = useParentFolders(activeFolder ?? activeRequest);
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const varMap: Record<string, WrappedEnvironmentVariable> = {};
|
const varMap: Record<string, WrappedEnvironmentVariable> = {};
|
||||||
@@ -18,9 +24,15 @@ export function useEnvironmentVariables(environmentId: string | null) {
|
|||||||
wrapVariables(folderEnvironments.find((fe) => fe.parentId === f.id) ?? null),
|
wrapVariables(folderEnvironments.find((fe) => fe.parentId === f.id) ?? null),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Folder environments also can auto-complete from the active environment
|
||||||
|
const activeEnvironmentVariables =
|
||||||
|
targetEnvironment != null && isFolderEnvironment(targetEnvironment)
|
||||||
|
? wrapVariables(activeEnvironment)
|
||||||
|
: [];
|
||||||
|
|
||||||
const allVariables = [
|
const allVariables = [
|
||||||
...folderVariables,
|
...folderVariables,
|
||||||
...wrapVariables(activeEnvironment),
|
...activeEnvironmentVariables,
|
||||||
...wrapVariables(baseEnvironment),
|
...wrapVariables(baseEnvironment),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -32,7 +44,7 @@ export function useEnvironmentVariables(environmentId: string | null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(varMap);
|
return Object.values(varMap);
|
||||||
}, [activeEnvironment, baseEnvironment, folderEnvironments, parentFolders]);
|
}, [activeEnvironment, baseEnvironment, folderEnvironments, parentFolders, targetEnvironment]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WrappedEnvironmentVariable {
|
export interface WrappedEnvironmentVariable {
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ function getParentFolders(
|
|||||||
): Folder[] {
|
): Folder[] {
|
||||||
if (currentModel == null) return [];
|
if (currentModel == null) return [];
|
||||||
|
|
||||||
const folder = currentModel.folderId ? folders.find((f) => f.id === currentModel.folderId) : null;
|
const parentFolder = currentModel.folderId ? folders.find((f) => f.id === currentModel.folderId) : null;
|
||||||
if (folder == null) {
|
if (parentFolder == null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [folder, ...getParentFolders(folders, folder)];
|
return [parentFolder, ...getParentFolders(folders, parentFolder)];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,3 +51,11 @@ export function getCharsetFromContentType(headers: HttpResponseHeader[]): string
|
|||||||
export function isBaseEnvironment(environment: Environment): boolean {
|
export function isBaseEnvironment(environment: Environment): boolean {
|
||||||
return environment.parentId == null;
|
return environment.parentId == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isSubEnvironment(environment: Environment): boolean {
|
||||||
|
return environment.parentModel == 'environment';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFolderEnvironment(environment: Environment): boolean {
|
||||||
|
return environment.parentModel == 'folder';
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const yaakDark = {
|
|||||||
base: {
|
base: {
|
||||||
surface: 'hsl(244,23%,14%)',
|
surface: 'hsl(244,23%,14%)',
|
||||||
surfaceHighlight: 'hsl(244,23%,20%)',
|
surfaceHighlight: 'hsl(244,23%,20%)',
|
||||||
text: 'hsl(245,23%,84%)',
|
text: 'hsl(245,23%,85%)',
|
||||||
textSubtle: 'hsl(245,18%,58%)',
|
textSubtle: 'hsl(245,18%,58%)',
|
||||||
textSubtlest: 'hsl(245,18%,45%)',
|
textSubtlest: 'hsl(245,18%,45%)',
|
||||||
border: 'hsl(244,23%,25%)',
|
border: 'hsl(244,23%,25%)',
|
||||||
@@ -67,11 +67,11 @@ const yaakDark = {
|
|||||||
},
|
},
|
||||||
responsePane: {
|
responsePane: {
|
||||||
surface: 'hsl(243,23%,16%)',
|
surface: 'hsl(243,23%,16%)',
|
||||||
border: 'hsl(246,23%,22.72%)',
|
border: 'hsl(246,23%,23%)',
|
||||||
},
|
},
|
||||||
appHeader: {
|
appHeader: {
|
||||||
surface: 'hsl(244,23%,12%)',
|
surface: 'hsl(244,23%,12%)',
|
||||||
border: 'hsl(244,23%,20.8%)',
|
border: 'hsl(244,23%,21%)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -83,22 +83,22 @@ const yaakLight = {
|
|||||||
base: {
|
base: {
|
||||||
surface: 'hsl(0,0%,100%)',
|
surface: 'hsl(0,0%,100%)',
|
||||||
surfaceHighlight: 'hsl(218,24%,87%)',
|
surfaceHighlight: 'hsl(218,24%,87%)',
|
||||||
text: 'hsl(217,24%,15%)',
|
text: 'hsl(217,24%,10%)',
|
||||||
textSubtle: 'hsl(217,24%,40%)',
|
textSubtle: 'hsl(217,24%,40%)',
|
||||||
textSubtlest: 'hsl(217,24%,58%)',
|
textSubtlest: 'hsl(217,24%,58%)',
|
||||||
border: 'hsl(217,22%,93%)',
|
border: 'hsl(217,22%,90%)',
|
||||||
primary: 'hsl(266,100%,70%)',
|
primary: 'hsl(266,100%,60%)',
|
||||||
secondary: 'hsl(220,24%,59%)',
|
secondary: 'hsl(220,24%,50%)',
|
||||||
info: 'hsl(206,100%,48%)',
|
info: 'hsl(206,100%,40%)',
|
||||||
success: 'hsl(155,95%,33%)',
|
success: 'hsl(139,66%,34%)',
|
||||||
notice: 'hsl(45,100%,41%)',
|
notice: 'hsl(45,100%,34%)',
|
||||||
warning: 'hsl(30,100%,43%)',
|
warning: 'hsl(30,100%,36%)',
|
||||||
danger: 'hsl(335,75%,57%)',
|
danger: 'hsl(335,75%,48%)',
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
sidebar: {
|
sidebar: {
|
||||||
surface: 'hsl(220,20%,97%)',
|
surface: 'hsl(220,20%,98%)',
|
||||||
border: 'hsl(217,22%,93%)',
|
border: 'hsl(217,22%,88%)',
|
||||||
surfaceHighlight: 'hsl(217,25%,90%)',
|
surfaceHighlight: 'hsl(217,25%,90%)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev --force",
|
"dev": "vite dev --force",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "eslint . --ext .ts,.tsx"
|
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/commands": "^6.8.1",
|
"@codemirror/commands": "^6.8.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user