Fixes for last commit

This commit is contained in:
Gregory Schier
2025-11-01 09:33:57 -07:00
parent 6ad4e7bbb5
commit 0f9975339c
6 changed files with 109 additions and 105 deletions

View File

@@ -38,14 +38,12 @@ impl<'a> DbContext<'a> {
let mut stmt = self.conn.prepare(sql.as_str()).expect("Failed to prepare query");
match stmt.query_row(&*params.as_params(), M::from_row) {
Ok(result) => Ok(result),
Err(rusqlite::Error::QueryReturnedNoRows) => {
Err(ModelNotFound(format!(
r#"table "{}" {} == {}"#,
M::table_name().into_iden().to_string(),
col.into_iden().to_string(),
value_debug
)))
}
Err(rusqlite::Error::QueryReturnedNoRows) => Err(ModelNotFound(format!(
r#"table "{}" {} == {}"#,
M::table_name().into_iden().to_string(),
col.into_iden().to_string(),
value_debug
))),
Err(e) => Err(crate::error::Error::SqlError(e)),
}
}
@@ -69,7 +67,7 @@ impl<'a> DbContext<'a> {
.expect("Failed to run find on DB")
}
pub fn find_all<'s, M>(&self) -> crate::error::Result<Vec<M>>
pub fn find_all<'s, M>(&self) -> Result<Vec<M>>
where
M: Into<AnyModel> + Clone + UpsertModelInfo,
{
@@ -117,7 +115,7 @@ impl<'a> DbContext<'a> {
Ok(items.map(|v| v.unwrap()).collect())
}
pub fn upsert<M>(&self, model: &M, source: &UpdateSource) -> crate::error::Result<M>
pub fn upsert<M>(&self, model: &M, source: &UpdateSource) -> Result<M>
where
M: Into<AnyModel> + From<AnyModel> + UpsertModelInfo + Clone,
{
@@ -139,7 +137,7 @@ impl<'a> DbContext<'a> {
other_values: Vec<(impl IntoIden + Eq, impl Into<SimpleExpr>)>,
update_columns: Vec<impl IntoIden>,
source: &UpdateSource,
) -> crate::error::Result<M>
) -> Result<M>
where
M: Into<AnyModel> + From<AnyModel> + UpsertModelInfo + Clone,
{
@@ -178,7 +176,7 @@ impl<'a> DbContext<'a> {
Ok(m)
}
pub(crate) fn delete<'s, M>(&self, m: &M, source: &UpdateSource) -> crate::error::Result<M>
pub(crate) fn delete<'s, M>(&self, m: &M, source: &UpdateSource) -> Result<M>
where
M: Into<AnyModel> + Clone + UpsertModelInfo,
{

View File

@@ -1,10 +1,13 @@
import type { Environment, EnvironmentVariable, Workspace } from '@yaakapp-internal/models';
import type { Environment, Workspace } from '@yaakapp-internal/models';
import { duplicateModel, patchModel } from '@yaakapp-internal/models';
import { atom, useAtomValue } from 'jotai';
import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { createSubEnvironmentAndActivate } from '../commands/createEnvironment';
import { activeWorkspaceAtom, activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
import { environmentsBreakdownAtom, useEnvironmentsBreakdown, } from '../hooks/useEnvironmentsBreakdown';
import {
environmentsBreakdownAtom,
useEnvironmentsBreakdown,
} from '../hooks/useEnvironmentsBreakdown';
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
import { jotaiStore } from '../lib/jotai';
import { isBaseEnvironment } from '../lib/model_util';
@@ -16,6 +19,7 @@ import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
import { IconTooltip } from './core/IconTooltip';
import { InlineCode } from './core/InlineCode';
import type { PairEditorHandle } from './core/PairEditor';
import { SplitLayout } from './core/SplitLayout';
import type { TreeNode } from './core/tree/common';
import type { TreeHandle, TreeProps } from './core/tree/Tree';
@@ -26,12 +30,12 @@ import { EnvironmentSharableTooltip } from './EnvironmentSharableTooltip';
interface Props {
initialEnvironmentId: string | null;
addOrFocusVariable?: EnvironmentVariable;
setRef?: (ref: PairEditorHandle | null) => void;
}
type TreeModel = Environment | Workspace;
export function EnvironmentEditDialog({ initialEnvironmentId, addOrFocusVariable }: Props) {
export function EnvironmentEditDialog({ initialEnvironmentId, setRef }: Props) {
const { allEnvironments, baseEnvironment, baseEnvironments } = useEnvironmentsBreakdown();
const [selectedEnvironmentId, setSelectedEnvironmentId] = useState<string | null>(
initialEnvironmentId ?? null,
@@ -75,9 +79,9 @@ export function EnvironmentEditDialog({ initialEnvironmentId, addOrFocusVariable
</div>
) : (
<EnvironmentEditor
setRef={setRef}
className="pl-4 pt-3"
environment={selectedEnvironment}
addOrFocusVariable={addOrFocusVariable}
/>
)}
</div>

View File

@@ -1,4 +1,4 @@
import type { Environment, EnvironmentVariable } from '@yaakapp-internal/models';
import type { Environment } from '@yaakapp-internal/models';
import { patchModel } from '@yaakapp-internal/models';
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
import classNames from 'classnames';
@@ -17,7 +17,7 @@ import { BadgeButton } from './core/BadgeButton';
import { DismissibleBanner } from './core/DismissibleBanner';
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
import { Heading } from './core/Heading';
import type { Pair, PairEditorHandle, PairWithId } from './core/PairEditor';
import type { PairEditorHandle, PairWithId } from './core/PairEditor';
import { ensurePairId } from './core/PairEditor.util';
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
import { EnvironmentColorIndicator } from './EnvironmentColorIndicator';
@@ -27,10 +27,10 @@ interface Props {
environment: Environment;
hideName?: boolean;
className?: string;
addOrFocusVariable?: EnvironmentVariable;
setRef?: (n: PairEditorHandle | null) => void;
}
export function EnvironmentEditor({ environment, hideName, className, addOrFocusVariable }: Props) {
export function EnvironmentEditor({ environment, hideName, className, setRef }: Props) {
const workspaceId = environment.workspaceId;
const isEncryptionEnabled = useIsEncryptionEnabled();
const valueVisibility = useKeyValue<boolean>({
@@ -96,40 +96,6 @@ export function EnvironmentEditor({ environment, hideName, className, addOrFocus
});
};
const { pairs, autoFocusValue } = useMemo<{
pairs: Pair[];
autoFocusValue?: string;
}>(() => {
if (addOrFocusVariable != null) {
const existing = environment.variables.find(
(v) => v.id === addOrFocusVariable.id || v.name === addOrFocusVariable.name,
);
if (existing) {
return {
pairs: environment.variables,
autoFocusValue: existing.id,
};
} else {
const newPair = ensurePairId(addOrFocusVariable);
return {
pairs: [...environment.variables, newPair],
autoFocusValue: newPair.id,
};
}
} else {
return { pairs: environment.variables };
}
}, [addOrFocusVariable, environment.variables]);
const initPairEditor = useCallback(
(n: PairEditorHandle | null) => {
if (n && autoFocusValue) {
n.focusValue(autoFocusValue);
}
},
[autoFocusValue],
);
return (
<div
className={classNames(
@@ -188,7 +154,7 @@ export function EnvironmentEditor({ environment, hideName, className, addOrFocus
)}
</div>
<PairOrBulkEditor
setRef={initPairEditor}
setRef={setRef}
className="h-full"
allowMultilineValues
preferenceName="environment"
@@ -199,7 +165,7 @@ export function EnvironmentEditor({ environment, hideName, className, addOrFocus
valueAutocompleteVariables="environment"
valueAutocompleteFunctions
forceUpdateKey={`${environment.id}::${forceUpdateKey}`}
pairs={pairs}
pairs={environment.variables}
onChange={handleChange}
stateKey={`environment.${environment.id}`}
forcedEnvironmentId={environment.id}

View File

@@ -320,14 +320,14 @@ export function Editor({
const onClickVariable = useCallback(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async (v: WrappedEnvironmentVariable, _tagValue: string, _startPos: number) => {
editEnvironment(v.environment, { addOrFocusVariable: v.variable });
await editEnvironment(v.environment, { addOrFocusVariable: v.variable });
},
[],
);
const onClickMissingVariable = useCallback(async (name: string) => {
const activeEnvironment = jotaiStore.get(activeEnvironmentAtom);
editEnvironment(activeEnvironment, { addOrFocusVariable: { name, value: '', enabled: true } });
await editEnvironment(activeEnvironment, { addOrFocusVariable: { name, value: '', enabled: true } });
}, []);
const [, { focusParamValue }] = useRequestEditor();

View File

@@ -131,42 +131,44 @@ function BaseInput({
const [obscured, setObscured] = useStateWithDeps(type === 'password', [type]);
const [hasChanged, setHasChanged] = useStateWithDeps<boolean>(false, [forceUpdateKey]);
const editorRef = useRef<EditorView | null>(null);
const skipNextFocus = useRef<boolean>(false);
const initEditorRef = useCallback(
(cm: EditorView | null) => {
editorRef.current = cm;
if (cm == null) {
setRef?.(null);
return;
}
const handle: InputHandle = {
focus: () => {
cm.focus();
cm.dispatch({ selection: { anchor: cm.state.doc.length, head: cm.state.doc.length } });
},
isFocused: () => cm.hasFocus ?? false,
value: () => cm.state.doc.toString() ?? '',
dispatch: (...args) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
cm.dispatch(...(args as any));
},
selectAll() {
cm.focus();
cm.dispatch({
selection: { anchor: 0, head: cm.state.doc.length },
});
},
};
setRef?.(handle);
},
[setRef],
const handle = useMemo<InputHandle>(
() => ({
focus: () => {
if (editorRef.current == null) return;
const anchor = editorRef.current.state.doc.length;
skipNextFocus.current = true;
editorRef.current.focus();
editorRef.current.dispatch({ selection: { anchor, head: anchor }, scrollIntoView: true });
},
isFocused: () => editorRef.current?.hasFocus ?? false,
value: () => editorRef.current?.state.doc.toString() ?? '',
dispatch: (...args) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
editorRef.current?.dispatch(...(args as any));
},
selectAll() {
if (editorRef.current == null) return;
editorRef.current.focus();
editorRef.current.dispatch({
selection: { anchor: 0, head: editorRef.current.state.doc.length },
});
},
}),
[],
);
const setEditorRef = useCallback(
(h: EditorView | null) => {
editorRef.current = h;
setRef?.(handle);
},
[handle, setRef],
);
const lastWindowFocus = useRef<number>(0);
useEffect(() => {
const fn = () => (lastWindowFocus.current = Date.now());
const fn = () => (skipNextFocus.current = true);
window.addEventListener('focus', fn);
return () => {
window.removeEventListener('focus', fn);
@@ -176,11 +178,7 @@ function BaseInput({
const handleFocus = useCallback(() => {
if (readOnly) return;
// Select all text of input when it's focused to match standard browser behavior.
// This should not, however, select when the input is focused due to a window focus event, so
// we handle that case as well.
const windowJustFocused = Date.now() - lastWindowFocus.current < 200;
if (!windowJustFocused) {
if (!skipNextFocus.current) {
editorRef.current?.dispatch({
selection: { anchor: 0, head: editorRef.current.state.doc.length },
});
@@ -188,6 +186,7 @@ function BaseInput({
setFocused(true);
onFocus?.();
skipNextFocus.current = false;
}, [onFocus, readOnly]);
const handleBlur = useCallback(async () => {
@@ -299,7 +298,7 @@ function BaseInput({
)}
>
<Editor
setRef={initEditorRef}
setRef={setEditorRef}
id={id.current}
hideGutter
singleLine={!multiLine}
@@ -402,6 +401,7 @@ function EncryptionInput({
setState({ fieldType: 'encrypted', security, value, obscured: true, error: null });
// We're calling this here because we want the input to be fully initialized so the caller
// can do stuff like change the selection.
console.log('INIT FIRST');
setRef?.(inputRef.current);
},
onError: (value) => {
@@ -415,10 +415,12 @@ function EncryptionInput({
},
});
} else if (isEncryptionEnabled && !defaultValue) {
console.log('INIT SECOND');
// Default to encrypted field for new encrypted inputs
setState({ fieldType: 'encrypted', security, value: '', obscured: true, error: null });
setRef?.(inputRef.current);
requestAnimationFrame(() => setRef?.(inputRef.current));
} else if (isEncryptionEnabled) {
console.log('INIT THIRD');
// Don't obscure plain text when encryption is enabled
setState({
fieldType: 'text',
@@ -427,7 +429,9 @@ function EncryptionInput({
obscured: false,
error: null,
});
requestAnimationFrame(() => setRef?.(inputRef.current));
} else {
console.log('INIT FOURTH');
// Don't obscure plain text when encryption is disabled
setState({
fieldType: 'text',
@@ -436,7 +440,7 @@ function EncryptionInput({
obscured: true,
error: null,
});
setRef?.(inputRef.current);
requestAnimationFrame(() => setRef?.(inputRef.current));
}
}, [defaultValue, isEncryptionEnabled, setRef, setState, state.value]);
@@ -467,7 +471,7 @@ function EncryptionInput({
[handleChange, state],
);
const handleSetInputRef = useCallback((h: InputHandle | null) => {
const setInputRef = useCallback((h: InputHandle | null) => {
inputRef.current = h;
}, []);
@@ -580,7 +584,7 @@ function EncryptionInput({
return (
<BaseInput
setRef={handleSetInputRef}
setRef={setInputRef}
disableObscureToggle
autocompleteFunctions={autocompleteFunctions}
autocompleteVariables={autocompleteVariables}

View File

@@ -1,16 +1,44 @@
import type { Environment, EnvironmentVariable } from '@yaakapp-internal/models';
import { updateModel } from '@yaakapp-internal/models';
import { openFolderSettings } from '../commands/openFolderSettings';
import type { PairEditorHandle } from '../components/core/PairEditor';
import { ensurePairId } from '../components/core/PairEditor.util';
import { EnvironmentEditDialog } from '../components/EnvironmentEditDialog';
import { environmentsBreakdownAtom } from '../hooks/useEnvironmentsBreakdown';
import { toggleDialog } from './dialog';
import { jotaiStore } from './jotai';
interface Options {
addOrFocusVariable?: EnvironmentVariable;
}
export function editEnvironment(environment: Environment | null, options: Options = {}) {
if (environment?.parentModel === 'folder' && environment.parentId != null) {
openFolderSettings(environment.parentId, 'variables');
export async function editEnvironment(
initialEnvironment: Environment | null,
options: Options = {},
) {
if (initialEnvironment?.parentModel === 'folder' && initialEnvironment.parentId != null) {
openFolderSettings(initialEnvironment.parentId, 'variables');
} else {
const { addOrFocusVariable } = options;
const { baseEnvironment } = jotaiStore.get(environmentsBreakdownAtom);
let environment = initialEnvironment ?? baseEnvironment;
let focusId: string | null = null;
if (addOrFocusVariable && environment != null) {
const existing = environment.variables.find(
(v) => v.id === addOrFocusVariable.id || v.name === addOrFocusVariable.name,
);
if (existing) {
focusId = existing.id ?? null;
} else {
const newVar = ensurePairId(addOrFocusVariable);
environment = { ...environment, variables: [...environment.variables, newVar] };
await updateModel(environment);
environment.variables.push(newVar);
focusId = newVar.id;
}
}
toggleDialog({
id: 'environment-editor',
noPadding: true,
@@ -19,7 +47,11 @@ export function editEnvironment(environment: Environment | null, options: Option
render: () => (
<EnvironmentEditDialog
initialEnvironmentId={environment?.id ?? null}
addOrFocusVariable={options.addOrFocusVariable}
setRef={(pairEditor: PairEditorHandle | null) => {
if (focusId) {
pairEditor?.focusValue(focusId);
}
}}
/>
),
});