Skip to content

Commit

Permalink
Various editor bugs pt.2 (#773)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kitenite authored Nov 12, 2024
1 parent 8a39322 commit db7ee63
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 52 deletions.
4 changes: 2 additions & 2 deletions apps/studio/electron/preload/webview/elements/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ function getComputedStyle(element: HTMLElement): Record<string, string> {
string,
string
>;
computedStyle.width = '';
computedStyle.height = '';
computedStyle.width = 'auto';
computedStyle.height = 'auto';
return computedStyle;
}

Expand Down
5 changes: 1 addition & 4 deletions apps/studio/src/lib/editor/engine/chat/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,5 @@ function getTemplateNodeString(templateNode: TemplateNode) {
}

function getUserInstructionString(instructions: string) {
return `<instruction>
Please edit the selected code or the entire file following these instructions: \t${instructions}
If you make a change, rewrite the entire file.
</instruction>`;
return `<instruction>Please edit the selected code or the entire file following these instructions: ${instructions}\nIf you make a change, rewrite the entire file.</instruction>`;
}
2 changes: 1 addition & 1 deletion apps/studio/src/lib/editor/styles/autolayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function parseModeAndValue(value: string): {
mode: LayoutMode;
layoutValue: string;
} {
if (value === 'fit-content') {
if (value === 'fit-content' || value === 'auto') {
return { mode: LayoutMode.Fit, layoutValue: value };
}
if (value === '100%' || value === 'auto') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import {
parseModeAndValue,
} from '@/lib/editor/styles/autolayout';
import type { SingleStyle } from '@/lib/editor/styles/models';
import { handleNumberInputKeyDown } from '@/lib/editor/styles/numberUnit';
import {
getDefaultUnit,
handleNumberInputKeyDown,
parsedValueToString,
stringToParsedValue,
} from '@/lib/editor/styles/numberUnit';
import { Icons } from '@onlook/ui/icons';
import { toast } from '@onlook/ui/use-toast';
import { observer } from 'mobx-react-lite';
Expand Down Expand Up @@ -34,8 +39,9 @@ const AutoLayoutInput = observer(({ elementStyle }: { elementStyle: SingleStyle
setValue(newValue);
}, [editorEngine.style.selectedStyle]);

const emitValue = (newLayoutValue: string) => {
const numValue = Number.parseFloat(newLayoutValue);
const emitValue = (newValue: string) => {
const { layoutValue, mode } = parseModeAndValue(newValue);
const numValue = parseFloat(layoutValue);

const { min, max } = elementStyle.params || {};
if (min !== undefined && numValue < min) {
Expand All @@ -56,6 +62,13 @@ const AutoLayoutInput = observer(({ elementStyle }: { elementStyle: SingleStyle
return;
}

const { numberVal, unitVal } = stringToParsedValue(
newValue,
mode === LayoutMode.Relative || mode === LayoutMode.Fill,
);
const newUnit = getDefaultUnit(unitVal);
const newLayoutValue = parsedValueToString(numberVal, newUnit);

setValue(newLayoutValue);
sendStyleUpdate(newLayoutValue);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ export interface SuggestionsListRef {
handleKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
}

export const SuggestionsList = forwardRef<
export const AutoComplete = forwardRef<
SuggestionsListRef,
{
setClasses: React.Dispatch<React.SetStateAction<string>>;
showSuggestions: boolean;
setShowSuggestions: React.Dispatch<React.SetStateAction<boolean>>;
currentInput: string;
setCurrentInput: React.Dispatch<React.SetStateAction<string>>;
setCurrentInput: (value: string) => void;
}
>(({ setClasses, showSuggestions, setShowSuggestions, currentInput, setCurrentInput }, ref) => {
>(({ setCurrentInput, showSuggestions, setShowSuggestions, currentInput }, ref) => {
const [suggestions, setSuggestions] = useState<string[]>([]);
const [selectedSuggestion, setSelectedSuggestion] = useState(0);
const [currentWordInfo, setCurrentWordInfo] = useState<{
Expand Down Expand Up @@ -57,7 +56,6 @@ export const SuggestionsList = forwardRef<
};

const handleInput = (value: string, cursorPosition: number) => {
setCurrentInput(value);
const wordInfo = getWordAtCursor(value, cursorPosition);
setCurrentWordInfo(wordInfo);

Expand Down Expand Up @@ -93,7 +91,7 @@ export const SuggestionsList = forwardRef<
newClass +
currentInput.slice(currentWordInfo.endIndex);

setClasses(newValue);
setCurrentInput(newValue);
setShowSuggestions(false);
}
} else if (e.key === 'Escape') {
Expand Down Expand Up @@ -150,7 +148,7 @@ export const SuggestionsList = forwardRef<
newClass +
currentInput.slice(currentWordInfo.endIndex);

setClasses(newValue);
setCurrentInput(newValue);
setShowSuggestions(false);
};

Expand Down Expand Up @@ -217,4 +215,4 @@ export const SuggestionsList = forwardRef<
);
});

SuggestionsList.displayName = 'SuggestionsList';
AutoComplete.displayName = 'AutoComplete';
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import { Icons } from '@onlook/ui/icons';
import { Textarea } from '@onlook/ui/textarea';
import { observer } from 'mobx-react-lite';
import { useEffect, useRef, useState } from 'react';
import { SuggestionsList, type SuggestionsListRef } from './AutoComplete';
import { AutoComplete, type SuggestionsListRef } from './AutoComplete';

interface History {
past: string[];
present: string;
future: string[];
}

const TailwindInput = observer(() => {
const editorEngine = useEditorEngine();
Expand All @@ -17,14 +23,92 @@ const TailwindInput = observer(() => {

const instanceRef = useRef<HTMLTextAreaElement>(null);
const [instance, setInstance] = useState<TemplateNode | undefined>();
const [instanceClasses, setInstanceClasses] = useState<string>('');
const [instanceHistory, setInstanceHistory] = useState<History>({
past: [],
present: '',
future: [],
});
const [isInstanceFocused, setIsInstanceFocused] = useState(false);

const rootRef = useRef<HTMLTextAreaElement>(null);
const [root, setRoot] = useState<TemplateNode | undefined>();
const [rootClasses, setRootClasses] = useState<string>('');
const [rootHistory, setRootHistory] = useState<History>({
past: [],
present: '',
future: [],
});
const [isRootFocused, setIsRootFocused] = useState(false);

const updateHistory = (
value: string,
{ past, present, future }: History,
setHistory: React.Dispatch<React.SetStateAction<History>>,
) => {
setHistory({
past: [...past, present],
present: value,
future: [],
});
};

const undo = (history: History, setHistory: React.Dispatch<React.SetStateAction<History>>) => {
const { past, present, future } = history;
if (past.length === 0) {
return;
}

const previous = past[past.length - 1];
const newPast = past.slice(0, past.length - 1);

setHistory({
past: newPast,
present: previous,
future: [present, ...future],
});
};

const redo = (history: History, setHistory: React.Dispatch<React.SetStateAction<History>>) => {
const { past, present, future } = history;
if (future.length === 0) {
return;
}

const next = future[0];
const newFuture = future.slice(1);

setHistory({
past: [...past, present],
present: next,
future: newFuture,
});
};

const handleKeyDown = (
e: React.KeyboardEvent<HTMLTextAreaElement>,
history: History,
setHistory: React.Dispatch<React.SetStateAction<History>>,
) => {
if (showSuggestions) {
suggestionRef.current?.handleKeyDown(e);
return;
}

if (e.key === 'Enter' || e.key === 'Tab' || e.key === 'Escape') {
e.currentTarget.blur();
e.preventDefault();
return;
}

if ((e.metaKey || e.ctrlKey) && e.key === 'z') {
e.preventDefault();
if (e.shiftKey) {
redo(history, setHistory);
} else {
undo(history, setHistory);
}
}
};

useEffect(() => {
if (editorEngine.elements.selected.length > 0) {
const selectedEl = editorEngine.elements.selected[0];
Expand All @@ -40,8 +124,8 @@ const TailwindInput = observer(() => {
setSelector(null);
setInstance(undefined);
setRoot(undefined);
setInstanceClasses('');
setRootClasses('');
setInstanceHistory({ past: [], present: '', future: [] });
setRootHistory({ past: [], present: '', future: [] });
}
}, [editorEngine.elements.selected, editorEngine.ast.layers]);

Expand All @@ -53,7 +137,12 @@ const TailwindInput = observer(() => {
MainChannels.GET_TEMPLATE_NODE_CLASS,
newInstance,
);
setInstanceClasses(instanceClasses.join(' '));
const classes = instanceClasses.join(' ');
setInstanceHistory({
past: [],
present: classes,
future: [],
});
}
}

Expand All @@ -65,7 +154,12 @@ const TailwindInput = observer(() => {
MainChannels.GET_TEMPLATE_NODE_CLASS,
newRoot,
);
setRootClasses(rootClasses.join(' '));
const classes = rootClasses.join(' ');
setRootHistory({
past: [],
present: classes,
future: [],
});
}
}

Expand All @@ -92,22 +186,14 @@ const TailwindInput = observer(() => {

const handleInput = (
e: React.FormEvent<HTMLTextAreaElement>,
setClasses: React.Dispatch<React.SetStateAction<string>>,
history: History,
setHistory: React.Dispatch<React.SetStateAction<History>>,
) => {
const { value, selectionStart } = e.currentTarget;
setClasses(value);
updateHistory(value, history, setHistory);
suggestionRef.current?.handleInput(value, selectionStart);
};

const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (showSuggestions) {
suggestionRef.current?.handleKeyDown(e);
} else if (e.key === 'Enter' || e.key === 'Tab' || e.key === 'Escape') {
e.currentTarget.blur();
e.preventDefault();
}
};

const adjustHeight = (textarea: HTMLTextAreaElement) => {
textarea.style.height = 'auto';
textarea.style.height = `${textarea.scrollHeight + 20}px`;
Expand All @@ -117,13 +203,13 @@ const TailwindInput = observer(() => {
if (instanceRef.current) {
adjustHeight(instanceRef.current);
}
}, [instanceClasses]);
}, [instanceHistory.present]);

useEffect(() => {
if (rootRef.current) {
adjustHeight(rootRef.current);
}
}, [rootClasses]);
}, [rootHistory.present]);

const EnterIndicator = () => {
return (
Expand All @@ -144,9 +230,9 @@ const TailwindInput = observer(() => {
ref={instanceRef}
className="w-full text-xs text-foreground-active break-normal bg-background-onlook/75 focus-visible:ring-0"
placeholder="Add tailwind classes here"
value={instanceClasses}
onInput={(e) => handleInput(e, setInstanceClasses)}
onKeyDown={handleKeyDown}
value={instanceHistory.present}
onInput={(e) => handleInput(e, instanceHistory, setInstanceHistory)}
onKeyDown={(e) => handleKeyDown(e, instanceHistory, setInstanceHistory)}
onBlur={(e) => {
setShowSuggestions(false);
setIsInstanceFocused(false);
Expand All @@ -155,13 +241,15 @@ const TailwindInput = observer(() => {
onFocus={() => setIsInstanceFocused(true)}
/>
{isInstanceFocused && (
<SuggestionsList
currentInput={instanceClasses}
<AutoComplete
currentInput={instanceHistory.present}
showSuggestions={showSuggestions}
setCurrentInput={setInstanceClasses}
ref={suggestionRef}
setShowSuggestions={setShowSuggestions}
setClasses={setInstanceClasses}
setCurrentInput={(newValue: string) => {
updateHistory(newValue, instanceHistory, setInstanceHistory);
instance && createCodeDiffRequest(instance, newValue);
}}
/>
)}
</div>
Expand All @@ -177,9 +265,9 @@ const TailwindInput = observer(() => {
ref={rootRef}
className="w-full text-xs text-foreground-active break-normal bg-background-onlook/75 focus-visible:ring-0 resize-none"
placeholder="Add tailwind classes here"
value={rootClasses}
onInput={(e) => handleInput(e, setRootClasses)}
onKeyDown={handleKeyDown}
value={rootHistory.present}
onInput={(e) => handleInput(e, rootHistory, setRootHistory)}
onKeyDown={(e) => handleKeyDown(e, rootHistory, setRootHistory)}
onBlur={(e) => {
setShowSuggestions(false);
setIsRootFocused(false);
Expand All @@ -188,13 +276,15 @@ const TailwindInput = observer(() => {
onFocus={() => setIsRootFocused(true)}
/>
{isRootFocused && (
<SuggestionsList
<AutoComplete
ref={suggestionRef}
showSuggestions={showSuggestions}
currentInput={rootClasses}
setCurrentInput={setRootClasses}
currentInput={rootHistory.present}
setShowSuggestions={setShowSuggestions}
setClasses={setRootClasses}
setCurrentInput={(newValue: string) => {
updateHistory(newValue, rootHistory, setRootHistory);
root && createCodeDiffRequest(root, newValue);
}}
/>
)}
</div>
Expand Down

0 comments on commit db7ee63

Please sign in to comment.