Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ builder/public/page_scripts
builder/public/page_styles
builder/www/_builder.html
builder/public/dist
codedb.snapshot
3 changes: 3 additions & 0 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<script setup lang="ts">
import useBuilderStore from "@/stores/builderStore";
import usePageStore from "@/stores/pageStore";
import { useBuilderSettingsSync } from "@/composables/useLiveDocSync";
import { UseDark } from "@vueuse/components";
import { useTitle } from "@vueuse/core";
import { FrappeUIProvider } from "frappe-ui";
Expand All @@ -24,6 +25,8 @@ const builderStore = useBuilderStore();
const pageStore = usePageStore();
const route = useRoute();

useBuilderSettingsSync();

provide("sessionUser", sessionUser);

const title = computed(() => {
Expand Down
42 changes: 39 additions & 3 deletions frontend/src/components/BlockContextMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@ import { promptCreateComponent } from "@/utils/dialogs";
import useBuilderStore from "@/stores/builderStore";
import useCanvasStore from "@/stores/canvasStore";
import useComponentStore from "@/stores/componentStore";
import usePageStore from "@/stores/pageStore";
import getBlockTemplate from "@/utils/blockTemplate";
import { confirm, detachBlockFromComponent, getBlockCopy, triggerCopyEvent } from "@/utils/helpers";
import { useStorage } from "@vueuse/core";
import { Ref, inject, nextTick, ref } from "vue";
import { Ref, inject, nextTick, ref, computed } from "vue";
import { useExternalEditor, createEditorContext } from "@/composables/useExternalEditor";
import { toast } from "frappe-ui";
const builderStore = useBuilderStore();
const componentStore = useComponentStore();
const canvasStore = useCanvasStore();
const pageStore = usePageStore();
const { isExternalEditorActive, openInExternalEditor, editorName } = useExternalEditor();
const contextMenu = ref(null) as unknown as Ref<InstanceType<typeof ContextMenu>>;
const triggeredFromLayersPanel = ref(false);
Expand Down Expand Up @@ -58,11 +62,33 @@ const pasteStyle = () => {
block.value.updateStyles(copiedStyle.value?.style as BlockStyleObjects);
};
const openScriptInExternalEditor = async (scriptType: "blockClientScript" | "blockDataScript") => {
if (!block.value.blockId) return;
const context = createEditorContext(
"Builder Page",
pageStore.selectedPage || pageStore.pageName,
undefined,
block.value.blockId,
scriptType,
);
if (!context) return;
const result = await openInExternalEditor(context);
const scriptName = scriptType === "blockClientScript" ? "Client Script" : "Data Script";
if (!result.success) {
toast.error(result.error || `Failed to open ${scriptName.toLowerCase()} in ${editorName.value}`);
} else {
toast.success(`${scriptName} opened in ${editorName.value}`);
}
};
const duplicateBlock = () => {
block.value.duplicateBlock();
};
const contextMenuOptions: ContextMenuOption[] = [
const contextMenuOptions = computed((): ContextMenuOption[] => [
{
label: "Edit with AI",
action: () => {
Expand Down Expand Up @@ -249,6 +275,16 @@ const contextMenuOptions: ContextMenuOption[] = [
condition: () => block.value.isExtendedFromComponent(),
disabled: () => builderStore.readOnlyMode,
},
{
label: `Open Client Script in ${editorName.value}`,
action: () => openScriptInExternalEditor("blockClientScript"),
condition: () => isExternalEditorActive.value && !block.value.isRoot(),
},
{
label: `Open Data Script in ${editorName.value}`,
action: () => openScriptInExternalEditor("blockDataScript"),
condition: () => isExternalEditorActive.value && !block.value.isRoot(),
},
{
label: "Save as Block Template",
action: () => {
Expand Down Expand Up @@ -315,7 +351,7 @@ const contextMenuOptions: ContextMenuOption[] = [
},
disabled: () => builderStore.readOnlyMode,
},
];
]);
defineExpose({
showContextMenu,
Expand Down
42 changes: 37 additions & 5 deletions frontend/src/components/Controls/CodeEditor.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
<template>
<div class="code-editor relative flex flex-col gap-1">
<span class="text-p-sm font-medium text-ink-gray-8" v-show="label">
{{ label }}
<span v-if="isDirty" class="text-[10px] text-gray-600">●</span>
</span>
<div
class="code-editor relative flex flex-col gap-1"
:class="{
'-mt-6': isExternalEditorActive && !label,
}">
<div class="flex items-center justify-between">
<div>
<span class="text-p-sm font-medium text-ink-gray-8" v-show="label">
{{ label }}
<span v-if="isDirty" class="text-[10px] text-gray-600">●</span>
</span>
</div>
<Button
v-if="isExternalEditorActive && externalEditorContext"
@click="handleOpenInExternalEditor"
variant="ghost"
size="sm"
class="!gap-1 text-p-xs"
icon-right="arrow-up-right">
{{ `Open in ${editorName}` }}
</Button>
</div>
<div
:style="{
'min-height': height,
Expand Down Expand Up @@ -44,6 +61,8 @@
<script setup lang="ts">
import { ref, VNodeRef, watch } from "vue";
import CodeMirrorEditor from "./CodeMirror/CodeMirrorEditor.vue";
import { useExternalEditor, type OpenScriptRequest } from "@/composables/useExternalEditor";
import { toast } from "frappe-ui";

const props = withDefaults(
defineProps<{
Expand All @@ -62,6 +81,7 @@ const props = withDefaults(
icon: string;
handler: () => void;
};
externalEditorContext?: OpenScriptRequest;
}>(),
{
type: "JSON",
Expand All @@ -75,6 +95,18 @@ const props = withDefaults(
},
);

const { isExternalEditorActive, openInExternalEditor, editorName } = useExternalEditor();

const handleOpenInExternalEditor = async () => {
if (!props.externalEditorContext) return;
const result = await openInExternalEditor(props.externalEditorContext);
if (!result.success) {
toast.error(result.error || `Failed to open in ${editorName.value}`);
} else {
toast.success(`Code opened in ${editorName.value}`);
}
};

const emit = defineEmits(["save", "update:modelValue"]);
const editor = ref<VNodeRef | null>(null);

Expand Down
8 changes: 7 additions & 1 deletion frontend/src/components/PageClientScriptManager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@
:autofocus="false"
:show-save-button="true"
@save="updateScript"
:show-line-numbers="true"></CodeEditor>
:show-line-numbers="true"
:external-editor-context="getEditorContext()"></CodeEditor>
</div>
</div>
</template>
Expand All @@ -141,6 +142,7 @@ import CodeEditor from "./Controls/CodeEditor.vue";
import CSSIcon from "./Icons/CSS.vue";
import GripVertical from "./Icons/GripVertical.vue";
import JavaScriptIcon from "./Icons/JavaScript.vue";
import { createEditorContext } from "@/composables/useExternalEditor";

const { capture } = useTelemetry();

Expand Down Expand Up @@ -202,6 +204,10 @@ const selectScript = (script: attachedScript) => {
});
};

const getEditorContext = () => {
return createEditorContext("Builder Client Script", activeScript.value?.script_name, "script");
};

const updateScript = (value: string) => {
if (!activeScript.value || builderStore.readOnlyMode) return;

Expand Down
23 changes: 18 additions & 5 deletions frontend/src/components/PageScript.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@
:autofocus="true"
@save="savePageDataScript"
:showSaveButton="true"
:show-line-numbers="true"></CodeEditor>
:show-line-numbers="true"
:external-editor-context="getPageEditorContext('page_data_script')"></CodeEditor>
<CodeEditor
v-model="pageStore.pageData"
type="JSON"
Expand Down Expand Up @@ -108,9 +109,10 @@
@save="saveBlockClientScript"
:showSaveButton="true"
:show-line-numbers="true"
:external-editor-context="getBlockEditorContext('blockClientScript')"
description='Use Block Client Script to add interactivity to your block. You can access the current DOM node using the keyword `this`. All Block props are accessible using the read-only `props` object.<br>
<b>Example:</b> <pre style="display:inline; font-size: 11px;">this.addEventListener("click", () => { console.log(props) })</pre><br><br>
For more details on how to write data script, refer to <b><a class="underline" href="https://docs.frappe.io/builder/data-script" target="_blank">this documentation</a></b>.'></CodeEditor>
<b>Example:</b> <pre style="display:inline; font-size: 11px;">this.addEventListener("click", () => { console.log(props) })</pre><br><br>
For more details on how to write data script, refer to <b><a class="underline" href="https://docs.frappe.io/builder/data-script" target="_blank">this documentation</a></b>.'></CodeEditor>
</div>
<div v-else>
<div class="flex gap-4">
Expand All @@ -125,7 +127,8 @@
:autofocus="true"
@save="saveBlockDataScript"
:showSaveButton="true"
:show-line-numbers="true"></CodeEditor>
:show-line-numbers="true"
:external-editor-context="getBlockEditorContext('blockDataScript')"></CodeEditor>
<div class="-mt-5 w-1/3 p-4" height="calc(100% - 110px)">
<CodeEditor
v-model="blockData"
Expand Down Expand Up @@ -169,6 +172,7 @@ import CodeEditor from "./Controls/CodeEditor.vue";
import TabButtons from "./Controls/TabButtons.vue";
import PageClientScriptManager from "./PageClientScriptManager.vue";
import PropsEditor from "./PropsEditor.vue";
import { createEditorContext } from "@/composables/useExternalEditor";

const { capture } = useTelemetry();

Expand Down Expand Up @@ -211,10 +215,19 @@ const blockData = computed(() => {
? blockDataStore.getBlockData(
blockController.getFirstSelectedBlock().blockId,
showInheritedBlockData.value ? "all" : "own",
) || {}
) || {}
: {};
});

const getPageEditorContext = (field: string) => {
return createEditorContext("Builder Page", props.page?.name, field);
};

const getBlockEditorContext = (blockField: "blockClientScript" | "blockDataScript") => {
const block = blockController.getFirstSelectedBlock();
return createEditorContext("Builder Page", props.page?.name, undefined, block?.blockId, blockField);
};

const savePageDataScript = (value: string) => {
webPages.setValue
.submit({
Expand Down
17 changes: 13 additions & 4 deletions frontend/src/components/Settings/GlobalCode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
height="100px"
class="shrink-0"
@update:modelValue="builderStore.updateBuilderSettings('head_html', $event)"
:showLineNumbers="true"></CodeEditor>
:showLineNumbers="true"
:externalEditorContext="getEditorContext('head_html')"></CodeEditor>
<CodeEditor
label="<body> HTML"
type="HTML"
Expand All @@ -17,7 +18,8 @@
height="100px"
class="shrink-0"
@update:modelValue="builderStore.updateBuilderSettings('body_html', $event)"
:showLineNumbers="true"></CodeEditor>
:showLineNumbers="true"
:externalEditorContext="getEditorContext('body_html')"></CodeEditor>
<CodeEditor
label="Client Script"
type="JavaScript"
Expand All @@ -26,7 +28,8 @@
height="100px"
class="shrink-0"
@update:modelValue="(code) => builderStore.updateBuilderSettings('script', code)"
:showLineNumbers="true"></CodeEditor>
:showLineNumbers="true"
:externalEditorContext="getEditorContext('script')"></CodeEditor>
<CodeEditor
label="Style"
type="CSS"
Expand All @@ -35,13 +38,19 @@
height="100px"
class="shrink-0"
@update:modelValue="(code) => builderStore.updateBuilderSettings('style', code)"
:showLineNumbers="true"></CodeEditor>
:showLineNumbers="true"
:externalEditorContext="getEditorContext('style')"></CodeEditor>
</div>
</template>
<script setup lang="ts">
import CodeEditor from "@/components/Controls/CodeEditor.vue";
import { builderSettings } from "@/data/builderSettings";
import useBuilderStore from "@/stores/builderStore";
import { createEditorContext } from "@/composables/useExternalEditor";

const builderStore = useBuilderStore();

const getEditorContext = (field: string) => {
return createEditorContext("Builder Settings", "Builder Settings", field);
};
</script>
Loading
Loading