mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-20 16:43:53 +01:00
Better multi-window updates
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
import type { Extension } from '@codemirror/state';
|
||||
import { Compartment, EditorState } from '@codemirror/state';
|
||||
import { Compartment, EditorState, Transaction } from '@codemirror/state';
|
||||
import type { ViewUpdate } from '@codemirror/view';
|
||||
import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
|
||||
import classnames from 'classnames';
|
||||
import { EditorView } from 'codemirror';
|
||||
@@ -19,6 +20,7 @@ export { formatSdl } from 'format-graphql';
|
||||
|
||||
export interface EditorProps {
|
||||
id?: string;
|
||||
forceUpdateKey?: string;
|
||||
readOnly?: boolean;
|
||||
type?: 'text' | 'password';
|
||||
className?: string;
|
||||
@@ -42,6 +44,7 @@ export function Editor({
|
||||
type = 'text',
|
||||
heightMode,
|
||||
contentType,
|
||||
forceUpdateKey,
|
||||
autoFocus,
|
||||
placeholder,
|
||||
useTemplating,
|
||||
@@ -87,6 +90,15 @@ export function Editor({
|
||||
view.dispatch({ effects: languageCompartment.reconfigure(ext) });
|
||||
}, [contentType, autocomplete]);
|
||||
|
||||
useEffect(() => {
|
||||
if (cm.current === null) return;
|
||||
const { view, languageCompartment } = cm.current;
|
||||
const newDoc = defaultValue;
|
||||
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: newDoc ?? '' } });
|
||||
const ext = getLanguageExtension({ contentType, useTemplating, autocomplete });
|
||||
view.dispatch({ effects: languageCompartment.reconfigure(ext) });
|
||||
}, [forceUpdateKey]);
|
||||
|
||||
// Initialize the editor when ref mounts
|
||||
useEffect(() => {
|
||||
if (wrapperRef.current === null || cm.current !== null) return;
|
||||
@@ -218,13 +230,27 @@ function getExtensions({
|
||||
|
||||
// Handle onChange
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (onChange && update.docChanged) {
|
||||
if (onChange && update.docChanged && isViewUpdateFromUserInput(update)) {
|
||||
onChange.current?.(update.state.doc.toString());
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
function isViewUpdateFromUserInput(viewUpdate: ViewUpdate) {
|
||||
// Make sure document has changed, ensuring user events like selections don't count.
|
||||
if (viewUpdate.docChanged) {
|
||||
// Check transactions for any that are direct user input, not changes from Y.js or another extension.
|
||||
for (const transaction of viewUpdate.transactions) {
|
||||
// Not using Transaction.isUserEvent because that only checks for a specific User event type ( "input", "delete", etc.). Checking the annotation directly allows for any type of user event.
|
||||
const userEventType = transaction.annotation(Transaction.userEvent);
|
||||
if (userEventType) return userEventType;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const syncGutterBg = ({
|
||||
parent,
|
||||
className = '',
|
||||
|
||||
@@ -7,7 +7,7 @@ import { IconButton } from './IconButton';
|
||||
import { HStack, VStack } from './Stacks';
|
||||
|
||||
export type InputProps = Omit<HTMLAttributes<HTMLInputElement>, 'onChange' | 'onFocus'> &
|
||||
Pick<EditorProps, 'contentType' | 'useTemplating' | 'autocomplete'> & {
|
||||
Pick<EditorProps, 'contentType' | 'useTemplating' | 'autocomplete' | 'forceUpdateKey'> & {
|
||||
name: string;
|
||||
type?: 'text' | 'password';
|
||||
label: string;
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Input } from './Input';
|
||||
export type PairEditorProps = {
|
||||
pairs: Pair[];
|
||||
onChange: (pairs: Pair[]) => void;
|
||||
forceUpdateKey?: string;
|
||||
className?: string;
|
||||
namePlaceholder?: string;
|
||||
valuePlaceholder?: string;
|
||||
@@ -36,6 +37,7 @@ type PairContainer = {
|
||||
|
||||
export const PairEditor = memo(function PairEditor({
|
||||
pairs: originalPairs,
|
||||
forceUpdateKey,
|
||||
nameAutocomplete,
|
||||
valueAutocomplete,
|
||||
namePlaceholder,
|
||||
@@ -53,6 +55,15 @@ export const PairEditor = memo(function PairEditor({
|
||||
return [...pairs, newPairContainer()];
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Remove empty headers on initial render
|
||||
// TODO: Make this not refresh the entire editor when forceUpdateKey changes, using some
|
||||
// sort of diff method or deterministic IDs based on array index and update key
|
||||
const nonEmpty = originalPairs.filter((h) => !(h.name === '' && h.value === ''));
|
||||
const pairs = nonEmpty.map((pair) => newPairContainer(pair));
|
||||
setPairs([...pairs, newPairContainer()]);
|
||||
}, [forceUpdateKey]);
|
||||
|
||||
const setPairsAndSave = useCallback(
|
||||
(fn: (pairs: PairContainer[]) => PairContainer[]) => {
|
||||
setPairs((oldPairs) => {
|
||||
@@ -139,6 +150,7 @@ export const PairEditor = memo(function PairEditor({
|
||||
pairContainer={p}
|
||||
className="py-1"
|
||||
isLast={isLast}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
nameAutocomplete={nameAutocomplete}
|
||||
valueAutocomplete={valueAutocomplete}
|
||||
namePlaceholder={namePlaceholder}
|
||||
@@ -179,6 +191,7 @@ type FormRowProps = {
|
||||
| 'valuePlaceholder'
|
||||
| 'nameValidate'
|
||||
| 'valueValidate'
|
||||
| 'forceUpdateKey'
|
||||
>;
|
||||
|
||||
const FormRow = memo(function FormRow({
|
||||
@@ -190,6 +203,7 @@ const FormRow = memo(function FormRow({
|
||||
onMove,
|
||||
onEnd,
|
||||
isLast,
|
||||
forceUpdateKey,
|
||||
nameAutocomplete,
|
||||
valueAutocomplete,
|
||||
namePlaceholder,
|
||||
@@ -287,6 +301,7 @@ const FormRow = memo(function FormRow({
|
||||
require={!isLast && !!pairContainer.pair.enabled && !!pairContainer.pair.value}
|
||||
validate={nameValidate}
|
||||
useTemplating
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
containerClassName={classnames(isLast && 'border-dashed')}
|
||||
defaultValue={pairContainer.pair.name}
|
||||
label="Name"
|
||||
@@ -301,6 +316,7 @@ const FormRow = memo(function FormRow({
|
||||
size="sm"
|
||||
containerClassName={classnames(isLast && 'border-dashed')}
|
||||
validate={valueValidate}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
defaultValue={pairContainer.pair.value}
|
||||
label="Value"
|
||||
name="value"
|
||||
|
||||
Reference in New Issue
Block a user