mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-11 20:00:29 +01:00
Fixes for last commit
This commit is contained in:
@@ -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,
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user