Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement multi-file code editor - WF-100 #724

Draft
wants to merge 13 commits into
base: dev
Choose a base branch
from
6 changes: 5 additions & 1 deletion src/ui/src/builder/BuilderEmbeddedCodeEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const props = defineProps<{
disabled?: boolean;
}>();

const { modelValue, disabled } = toRefs(props);
const { modelValue, disabled, language } = toRefs(props);
const emit = defineEmits(["update:modelValue"]);

const VARIANTS_SETTINGS: Record<
Expand Down Expand Up @@ -55,6 +55,10 @@ watch(modelValue, (newCode) => {
editor.getModel().setValue(newCode);
});

watch(language, () => {
monaco.editor.setModelLanguage(editor.getModel(), language.value);
});

onMounted(() => {
editor = monaco.editor.create(editorContainerEl.value, {
value: modelValue.value,
Expand Down
89 changes: 69 additions & 20 deletions src/ui/src/builder/panels/BuilderCodePanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,31 @@
:scrollable="false"
keyboard-shortcut-key="J"
class="BuilderCodePanel"
@openned="onOpenPanel"
>
<BuilderEmbeddedCodeEditor
v-model="code"
variant="full"
language="python"
:disabled="isDisabled"
@update:model-value="handleUpdate"
></BuilderEmbeddedCodeEditor>
<ShareHorizontalResize :initial-left-size="200">
<template #left>
<ShareSourceFilesTree
:source-files="sourceFileDraft as SourceFilesDirectory"
:path-active="filepathOpen"
:paths-unsaved="pathsUnsaved"
display-add-file-button
class="BuilderCodePanel__content__tree"
@add-file="handleAddFile"
@select="openFile"
/>
</template>
<template #right>
<BuilderEmbeddedCodeEditor
v-model="code"
class="BuilderCodePanel__content__editor"
variant="full"
:language="openedFileLanguage"
:disabled="isDisabled"
@update:model-value="handleUpdate"
/>
</template>
</ShareHorizontalResize>
<template #actionsCompanion>
<div v-if="status" class="status" :class="status.type">
{{ status.message }}
Expand All @@ -28,6 +45,10 @@
import BuilderPanel, { type BuilderPanelAction } from "./BuilderPanel.vue";
import BuilderAsyncLoader from "../BuilderAsyncLoader.vue";
import injectionKeys from "@/injectionKeys";
import ShareSourceFilesTree from "@/components/shared/SharedSourceFilesTree/ShareSourceFilesTree.vue";
import { useSourceFiles } from "@/core/useSourceFiles";
import ShareHorizontalResize from "@/components/shared/ShareHorizontalResize.vue";
import { SourceFilesDirectory } from "@/writerTypes";

defineProps<{
contentsTeleportEl: HTMLElement;
Expand All @@ -40,7 +61,24 @@

const wf = inject(injectionKeys.core);

const code = ref<string>(wf.runCode.value);
const {
sourceFileDraft,
openFile,
code,
openedFileLanguage,
filepathOpen,
pathsUnsaved,
} = useSourceFiles(wf);

function onOpenPanel(open: boolean) {
if (!open) return;
if (filepathOpen.value === undefined) openFile(["main.py"]);
}

function handleAddFile() {
wf.sendCreateSourceFileRequest(["it-works.txt"]);
}

const status = ref<null | {
type: "error" | "success" | "neutral";
message: string;
Expand All @@ -58,36 +96,37 @@
callback: () => {
save();
},
isDisabled: isDisabled.value,
isDisabled: isDisabled.value || isCodeSaved.value,
},
]);

watch(wf.runCode, (newRunCode) => {
code.value = newRunCode;
});

watch(wf.sessionTimestamp, () => {
isDisabled.value = false;
if (isCodeSaved()) return;
if (isCodeSaved.value) return;
status.value = null;
});

function handleUpdate() {
function handleUpdate(e) {

Check warning on line 109 in src/ui/src/builder/panels/BuilderCodePanel.vue

View workflow job for this annotation

GitHub Actions / build (3.9)

'e' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 109 in src/ui/src/builder/panels/BuilderCodePanel.vue

View workflow job for this annotation

GitHub Actions / build (3.9)

'e' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 109 in src/ui/src/builder/panels/BuilderCodePanel.vue

View workflow job for this annotation

GitHub Actions / build (3.9)

'e' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 109 in src/ui/src/builder/panels/BuilderCodePanel.vue

View workflow job for this annotation

GitHub Actions / build (3.10)

'e' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 109 in src/ui/src/builder/panels/BuilderCodePanel.vue

View workflow job for this annotation

GitHub Actions / build (3.10)

'e' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 109 in src/ui/src/builder/panels/BuilderCodePanel.vue

View workflow job for this annotation

GitHub Actions / build (3.10)

'e' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 109 in src/ui/src/builder/panels/BuilderCodePanel.vue

View workflow job for this annotation

GitHub Actions / build (3.11)

'e' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 109 in src/ui/src/builder/panels/BuilderCodePanel.vue

View workflow job for this annotation

GitHub Actions / build (3.11)

'e' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 109 in src/ui/src/builder/panels/BuilderCodePanel.vue

View workflow job for this annotation

GitHub Actions / build (3.11)

'e' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 109 in src/ui/src/builder/panels/BuilderCodePanel.vue

View workflow job for this annotation

GitHub Actions / build (3.12)

'e' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 109 in src/ui/src/builder/panels/BuilderCodePanel.vue

View workflow job for this annotation

GitHub Actions / build (3.12)

'e' is defined but never used. Allowed unused args must match /^_/u
status.value = null;
}

function isCodeSaved() {
return wf.runCode.value == code.value;
}
const isCodeSaved = computed(() => {
if (filepathOpen.value === undefined) return false;
return !pathsUnsaved.value
.map((p) => p.join("/"))
.includes(filepathOpen.value.join("/"));
});

async function save() {
if (filepathOpen.value === undefined) return;
status.value = {
type: "neutral",
message: "Processing...",
};
isDisabled.value = true;
try {
isDisabled.value = true;
await wf.sendCodeSaveRequest(code.value);
await wf.sendCodeSaveRequest(code.value, filepathOpen.value);
isDisabled.value = false;
} catch {
status.value = {
type: "error",
Expand All @@ -104,6 +143,16 @@
</script>

<style scoped>
.BuilderCodePanel__content {
height: 100%;
width: 100%;
}
.BuilderCodePanel__content__tree {
height: 100%;
width: 100%;
padding: 4px;
}

.status {
padding: 2px 12px 2px 12px;
border-radius: 16px;
Expand Down
6 changes: 6 additions & 0 deletions src/ui/src/builder/panels/BuilderPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,21 @@ const props = defineProps<{
keyboardShortcutKey: string;
}>();

const emits = defineEmits({
openned: (open: boolean) => typeof open === "boolean",
});

const collapsed = computed(() => !wfbm.openPanels.value.has(props.panelId));
const order = computed(() => panelIds.indexOf(props.panelId));

function togglePanel(panelId: typeof props.panelId) {
if (wfbm.openPanels.value.has(panelId)) {
wfbm.openPanels.value.delete(panelId);
emits("openned", false);
return;
}
wfbm.openPanels.value.add(panelId);
emits("openned", true);
}

function handleKeydown(ev: KeyboardEvent) {
Expand Down
58 changes: 58 additions & 0 deletions src/ui/src/components/shared/ShareHorizontalResize.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<template>
<div ref="root" class="ShareHorizontalResize" :style="style">
<div class="ShareHorizontalResize__left">
<slot name="left" />
</div>
<hr
class="ShareHorizontalResize__divider"
@mousedown.prevent="handleMouseDown"
/>
<div class="ShareHorizontalResize__right">
<slot name="right" />
</div>
</div>
</template>

<script setup lang="ts">
import { computed, CSSProperties, ref } from "vue";

const props = defineProps({
initialLeftSize: { type: Number, required: true },
});
const root = ref<HTMLElement | null>(null);

let leftSize = ref(props.initialLeftSize);
const style = computed<CSSProperties>(() => ({
gridTemplateColumns: `${leftSize.value}px 1px minmax(0, 100%)`,
}));

function handleMouseDown() {
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);

const rootBoundingRect = root.value.getBoundingClientRect();

function onMouseMove(event: MouseEvent) {
leftSize.value = event.x - rootBoundingRect.left;
}

function onMouseUp() {
document.removeEventListener("mouseup", onMouseUp);
document.removeEventListener("mousemove", onMouseMove);
}
}
</script>

<style scoped>
.ShareHorizontalResize {
display: grid;
height: 100%;
}

.ShareHorizontalResize__divider {
border: none;
background-color: var(--builderSeparatorColor);
height: 100%;
cursor: ew-resize;
}
</style>
52 changes: 47 additions & 5 deletions src/ui/src/components/shared/SharedCollapsible.vue
Original file line number Diff line number Diff line change
@@ -1,29 +1,53 @@
<template>
<details
class="SharedCollapsible"
:open="open"
:class="{ 'SharedCollapsible--customMarker': icons }"
:open="isOpen"
:disabled="disabled"
@toggle="onToggle"
>
<summary><slot name="title" /></summary>
<summary class="SharedCollapsible__summary">
<span
v-if="icons"
class="SharedCollapsible__summary__icon material-symbols-outlined"
>{{ icon }}</span
>
<slot name="title" />
</summary>
<div class="content">
<slot name="content" />
</div>
</details>
</template>

<script setup lang="ts">
defineProps({
import { computed, PropType, ref } from "vue";

const props = defineProps({
open: { type: Boolean, required: false },
disabled: { type: Boolean, required: false },
icons: {
type: Object as PropType<{ open: string; close: string }>,
required: false,
default: undefined,
},
});

const emit = defineEmits({
toggle: (open: boolean) => typeof open === "boolean",
});

const isOpen = ref(props.open);

const icon = computed(() => {
if (props.icons === undefined) return undefined;
return isOpen.value ? props.icons.open : props.icons.close;
});

function onToggle(event) {
emit("toggle", event.newState === "open");
const state = event.newState === "open";
isOpen.value = state;
emit("toggle", state);
}
</script>

Expand All @@ -50,7 +74,7 @@ summary {
cursor: pointer;
}

summary:before {
details summary:before {
content: "";
border-width: 6px;
border-style: solid;
Expand All @@ -64,6 +88,24 @@ summary:before {
transition: 0.3s transform ease;
}

.SharedCollapsible--customMarker summary {
padding-left: unset;
display: grid;
grid-template-columns: auto 1fr;
gap: 6px;
align-items: center;
}
.SharedCollapsible--customMarker summary:before {
content: none;
}
.SharedCollapsible__summary__icon {
height: 18px;
width: 18px;
display: flex;
align-items: center;
justify-content: center;
}

summary:focus-visible:before {
border-color: transparent transparent transparent var(--primaryTextColor);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ exports[`SharedJsonViewer > should render a nested object 1`] = `
disabled="false"
>
<summary
class="SharedCollapsible__summary"
data-v-55c4d015=""
>
<!--v-if-->

<div
class="SharedJsonViewerCollapsible__title"
Expand Down Expand Up @@ -57,8 +59,10 @@ exports[`SharedJsonViewer > should render a nested object 1`] = `
disabled="false"
>
<summary
class="SharedCollapsible__summary"
data-v-55c4d015=""
>
<!--v-if-->

<div
class="SharedJsonViewerCollapsible__title"
Expand Down Expand Up @@ -107,8 +111,10 @@ exports[`SharedJsonViewer > should render a nested object 1`] = `
disabled="false"
>
<summary
class="SharedCollapsible__summary"
data-v-55c4d015=""
>
<!--v-if-->

<div
class="SharedJsonViewerCollapsible__title"
Expand Down Expand Up @@ -171,8 +177,10 @@ exports[`SharedJsonViewer > should render an array 1`] = `
disabled="false"
>
<summary
class="SharedCollapsible__summary"
data-v-55c4d015=""
>
<!--v-if-->

<div
class="SharedJsonViewerCollapsible__title"
Expand Down
Loading
Loading