Skip to content
Draft
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
13 changes: 3 additions & 10 deletions builder/builder/doctype/builder_settings/builder_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"home_page",
"developer_options_section",
"execute_block_scripts_in_editor",
"restrict_click_handlers",
"ai_section",
"ai_api_key",
"persona_survey_done"
Expand Down Expand Up @@ -106,17 +105,11 @@
"label": "Developer Options"
},
{
"default": "Restricted",
"default": "Enable",
"fieldname": "execute_block_scripts_in_editor",
"fieldtype": "Select",
"label": "Execute Block Scripts in Editor",
"options": "Don't Execute\nRestricted\nUnrestricted"
},
{
"default": "1",
"fieldname": "restrict_click_handlers",
"fieldtype": "Check",
"label": "Restrict Click Handlers"
"label": "Client Script Emulation",
"options": "Enable\nDisable"
},
{
"fieldname": "ai_section",
Expand Down
3 changes: 1 addition & 2 deletions builder/builder/doctype/builder_settings/builder_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@ class BuilderSettings(Document):
body_html: DF.Code | None
default_language: DF.Data | None
disable_auto_dark_mode: DF.Check
execute_block_scripts_in_editor: DF.Literal["Don't Execute", "Restricted", "Unrestricted"]
execute_block_scripts_in_editor: DF.Literal["Disable", "Enable"]
favicon: DF.AttachImage | None
head_html: DF.Code | None
home_page: DF.Data | None
restrict_click_handlers: DF.Check
script: DF.Code | None
script_public_url: DF.ReadOnly | None
style: DF.Code | None
Expand Down
16 changes: 16 additions & 0 deletions builder/builder/patches/migrate_client_script_emulation_setting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import frappe


def execute():
current_value = frappe.db.get_single_value(
"Builder Settings",
"execute_block_scripts_in_editor",
)
value = "Disable" if current_value in {"Don't Execute", "Disable"} else "Enable"
frappe.db.set_value(
"Builder Settings",
None,
"execute_block_scripts_in_editor",
value,
update_modified=False,
)
3 changes: 2 additions & 1 deletion builder/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ builder.builder.doctype.builder_client_script.patches.trigger_asset_compression
builder.builder.patches.add_composite_index_to_web_page_view
builder.builder.patches.refactor_builder_variables
builder.builder.patches.reset_builder_page_clicks
builder.builder.patches.migrate_client_script_emulation_setting
execute:frappe.call("builder.builder_analytics.enqueue_web_page_view_ingesion")
execute:frappe.call("builder.builder_analytics.setup_duckdb_table")
execute:frappe.call("builder.builder_analytics.setup_duckdb_table")
1 change: 1 addition & 0 deletions frontend/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ declare module 'vue' {
BuilderBlock: typeof import('./src/components/BuilderBlock.vue')['default']
BuilderBlockTemplates: typeof import('./src/components/BuilderBlockTemplates.vue')['default']
BuilderCanvas: typeof import('./src/components/BuilderCanvas.vue')['default']
BuilderCanvasFrame: typeof import('./src/components/BuilderCanvasFrame.vue')['default']
BuilderCommandPalette: typeof import('./src/components/BuilderCommandPalette.vue')['default']
BuilderLeftPanel: typeof import('./src/components/BuilderLeftPanel.vue')['default']
BuilderRightPanel: typeof import('./src/components/BuilderRightPanel.vue')['default']
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/builder.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ declare interface CanvasProps {
panning: boolean;
background: string;
settingCanvas: boolean;
overlayElement: HTMLElement | null;
frameRoots?: Map<string, HTMLElement>;
breakpoints: Breakpoint[];
}

Expand Down
89 changes: 49 additions & 40 deletions frontend/src/components/BlockEditor.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div
class="editor pointer-events-none fixed box-content select-none ring-2 ring-inset"
class="editor pointer-events-none fixed box-content select-none ring-4 ring-inset"
ref="editor"
:selected="isBlockSelected"
@click.stop="handleClick"
Expand Down Expand Up @@ -37,6 +37,12 @@ import type Block from "@/block";
import useBuilderStore from "@/stores/builderStore";
import useCanvasStore from "@/stores/canvasStore";
import blockController from "@/utils/blockController";
import {
elementFromEditorPoint,
getEventPointInDocument,
getEventPointInEditor,
startCanvasDrag,
} from "@/utils/canvasFrameDom";
import { addPxToNumber } from "@/utils/helpers";
import { Ref, computed, inject, nextTick, onMounted, ref, watch, watchEffect } from "vue";
import setGuides from "../utils/guidesTracker";
Expand Down Expand Up @@ -83,7 +89,7 @@ const props = withDefaults(
const editor = ref(null) as unknown as Ref<HTMLElement>;
const updateTracker = ref(() => {});
const resizing = ref(false);
const guides = setGuides(props.target, canvasProps);
let guides: ReturnType<typeof setGuides>;
const moving = ref(false);
const preventCLick = ref(false);

Expand Down Expand Up @@ -201,6 +207,7 @@ const movable = computed(() => {
});

onMounted(() => {
guides = setGuides(props.target, canvasProps);
updateTracker.value = trackTarget(props.target, editor.value, canvasProps);
});

Expand All @@ -217,14 +224,29 @@ const handleClick = (ev: MouseEvent) => {

const editorWrapper = editor.value;
editorWrapper.classList.add("pointer-events-none");
let element = document.elementFromPoint(ev.x, ev.y) as HTMLElement;
const editorPoint = getEventPointInEditor(ev);
let element = elementFromEditorPoint(editorPoint.x, editorPoint.y) as HTMLElement;
if (!element) return;
if (element.classList.contains("editor")) {
element.classList.remove("pointer-events-auto");
element.classList.add("pointer-events-none");
element = document.elementFromPoint(ev.x, ev.y) as HTMLElement;
element = elementFromEditorPoint(editorPoint.x, editorPoint.y) as HTMLElement;
}
if (element.classList.contains("__builder_component__")) {
element.dispatchEvent(new MouseEvent("click", ev));
const EventConstructor = element.ownerDocument.defaultView?.MouseEvent || MouseEvent;
const point = getEventPointInDocument(ev, element.ownerDocument);
element.dispatchEvent(
new EventConstructor("click", {
bubbles: true,
cancelable: true,
clientX: point.x,
clientY: point.y,
ctrlKey: ev.ctrlKey,
metaKey: ev.metaKey,
shiftKey: ev.shiftKey,
altKey: ev.altKey,
}),
);
}
};

Expand Down Expand Up @@ -256,54 +278,41 @@ const handleMove = (ev: MouseEvent) => {
ev.stopPropagation();
const pauseId = canvasStore.activeCanvas?.history?.pause();
const target = ev.target as HTMLElement;
const startX = ev.clientX;
const startY = ev.clientY;
const startLeft = (props.target as HTMLElement).offsetLeft || 0;
const startTop = (props.target as HTMLElement).offsetTop || 0;

moving.value = true;
guides.showX();

// to disable cursor jitter
const docCursor = document.body.style.cursor;
document.body.style.cursor = "grabbing";
target.style.cursor = "grabbing";

const mousemove = async (mouseMoveEvent: MouseEvent) => {
if (canvasStore.isMarqueeActive) return;
const scale = canvasProps.scale;
const movementX = (mouseMoveEvent.clientX - startX) / scale;
const movementY = (mouseMoveEvent.clientY - startY) / scale;
let finalLeft = startLeft + movementX;
let finalTop = startTop + movementY;
props.block.setStyle("left", addPxToNumber(finalLeft));
props.block.setStyle("top", addPxToNumber(finalTop));
await nextTick();
const { leftOffset, rightOffset } = guides.getPositionOffset();
if (leftOffset !== 0) {
props.block.setStyle("left", addPxToNumber(finalLeft + leftOffset));
}
if (rightOffset !== 0) {
props.block.setStyle("left", addPxToNumber(finalLeft + rightOffset));
}
startCanvasDrag(ev, props.target, {
cursor: "grabbing",
onMove: async ({ event, movementX, movementY }) => {
if (canvasStore.isMarqueeActive) return;
const finalLeft = startLeft + movementX;
const finalTop = startTop + movementY;
props.block.setStyle("left", addPxToNumber(finalLeft));
props.block.setStyle("top", addPxToNumber(finalTop));
await nextTick();
const { leftOffset, rightOffset } = guides.getPositionOffset();
if (leftOffset !== 0) {
props.block.setStyle("left", addPxToNumber(finalLeft + leftOffset));
}
if (rightOffset !== 0) {
props.block.setStyle("left", addPxToNumber(finalLeft + rightOffset));
}

mouseMoveEvent.preventDefault();
preventCLick.value = true;
};
document.addEventListener("mousemove", mousemove);
document.addEventListener(
"mouseup",
(mouseUpEvent) => {
event.preventDefault();
preventCLick.value = true;
},
onEnd: (event) => {
moving.value = false;
document.body.style.cursor = docCursor;
target.style.cursor = "grab";
document.removeEventListener("mousemove", mousemove);
mouseUpEvent.preventDefault();
event.preventDefault();
guides.hideX();
canvasStore.activeCanvas?.history?.resume(pauseId, true);
},
{ once: true },
);
});
};

defineExpose({
Expand Down
88 changes: 45 additions & 43 deletions frontend/src/components/BorderRadiusHandler.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div
ref="handler"
class="border-radius-resize pointer-events-auto absolute h-[10px] w-[10px] cursor-pointer rounded-full border-2 border-blue-400 bg-white"
class="border-radius-resize pointer-events-auto absolute h-[12px] w-[12px] cursor-pointer rounded-full border-2 border-blue-400 bg-white"
:class="{
hidden: !isHandlerVisible,
'border-purple-400': targetBlock.isExtendedFromComponent(),
Expand All @@ -18,8 +18,8 @@

<script setup lang="ts">
import type Block from "@/block";
import { getComputedStyleFor, startCanvasDrag } from "@/utils/canvasFrameDom";
import { getNumberFromPx } from "@/utils/helpers";
import { useElementBounding } from "@vueuse/core";
import type { Ref } from "vue";
import { computed, inject, onMounted, reactive, ref, watchEffect } from "vue";

Expand All @@ -38,13 +38,21 @@ const handlerLeft = ref(10);
const MIN_POSITION = { top: 10, left: 10 };
const MIN_TARGET_SIZE = 25;

const targetBounds = reactive(useElementBounding(props.target));
const targetBounds = reactive({
width: 0,
height: 0,
update() {
const rect = props.target.getBoundingClientRect();
targetBounds.width = rect.width;
targetBounds.height = rect.height;
},
});
const maxDistance = computed(() => Math.min(targetBounds.width, targetBounds.height) / 2);

const maxRadius = computed(() => {
props.targetBlock.getStyle("width");
props.targetBlock.getStyle("height");
const targetStyle = window.getComputedStyle(props.target);
const targetStyle = getComputedStyleFor(props.target);
return Math.min(parseInt(targetStyle.height, 10), parseInt(targetStyle.width, 10)) / 2;
});

Expand All @@ -69,49 +77,43 @@ const setHandlerPosition = (radius: number) => {
};

const handleRounded = (ev: MouseEvent) => {
const startX = ev.clientX;
const startY = ev.clientY;
let lastX = startX;
let lastY = startY;
let lastPoint: { x: number; y: number } | null = null;
updating.value = true;

const handleDimensions = handler.value.getBoundingClientRect();

const mousemove = (mouseMoveEvent: MouseEvent) => {
mouseMoveEvent.preventDefault();
const movementX = mouseMoveEvent.clientX - lastX;
const movementY = mouseMoveEvent.clientY - lastY;
const movement = ((movementX + movementY) / 2) * 2;

if (movement < 0) {
MIN_POSITION.top = -(handleDimensions.height / 2);
MIN_POSITION.left = -(handleDimensions.width / 2);
}

const radius = Math.round(
Math.max(0, Math.min(getNumberFromPx(props.target.style.borderRadius) + movement, maxRadius.value)),
);

borderRadius.value = radius;
setHandlerPosition(radius);
props.targetBlock.setStyle("borderRadius", `${radius}px`);

lastX = mouseMoveEvent.clientX;
lastY = mouseMoveEvent.clientY;
};

const mouseup = (mouseUpEvent: MouseEvent) => {
mouseUpEvent.preventDefault();
if (getNumberFromPx(props.targetBlock.getStyle("borderRadius")) < 10) {
handlerTop.value = MIN_POSITION.top;
handlerLeft.value = MIN_POSITION.left;
}
updating.value = false;
document.removeEventListener("mousemove", mousemove);
};

document.addEventListener("mousemove", mousemove);
document.addEventListener("mouseup", mouseup, { once: true });
startCanvasDrag(ev, props.target, {
onMove: ({ event, point, startPoint }) => {
const previousPoint = lastPoint || startPoint;
event.preventDefault();
const movementX = point.x - previousPoint.x;
const movementY = point.y - previousPoint.y;
const movement = ((movementX + movementY) / 2) * 2;

if (movement < 0) {
MIN_POSITION.top = -(handleDimensions.height / 2);
MIN_POSITION.left = -(handleDimensions.width / 2);
}

const radius = Math.round(
Math.max(0, Math.min(getNumberFromPx(props.target.style.borderRadius) + movement, maxRadius.value)),
);

borderRadius.value = radius;
setHandlerPosition(radius);
props.targetBlock.setStyle("borderRadius", `${radius}px`);

lastPoint = point;
},
onEnd: (event) => {
event.preventDefault();
if (getNumberFromPx(String(props.targetBlock.getStyle("borderRadius") || "")) < 10) {
handlerTop.value = MIN_POSITION.top;
handlerLeft.value = MIN_POSITION.left;
}
updating.value = false;
},
});
};

onMounted(() => {
Expand Down
Loading
Loading