{
})
const showResizer = computed(() => {
- return !props.block.isRoot() && isBlockSelected.value && !canvasStore.isDragging
+ return (
+ !props.block.isRoot() &&
+ isBlockSelected.value &&
+ !canvasStore.isDragging &&
+ !props.block.getParentBlock()?.isGrid() &&
+ !(props.block.isHTML() && !props.block.isSVG() && !props.block.isIframe())
+ )
})
const isBlockSelected = computed(() => {
diff --git a/frontend/src/components/ComponentLayers.vue b/frontend/src/components/ComponentLayers.vue
index 0a39fb33..ea8b57d2 100644
--- a/frontend/src/components/ComponentLayers.vue
+++ b/frontend/src/components/ComponentLayers.vue
@@ -56,12 +56,22 @@
-
+
+
+
+
+
+
{{ canvasStore.activeCanvas?.activeBreakpoint }}
diff --git a/frontend/src/components/ComponentProperties.vue b/frontend/src/components/ComponentProperties.vue
index 2f653148..97592076 100644
--- a/frontend/src/components/ComponentProperties.vue
+++ b/frontend/src/components/ComponentProperties.vue
@@ -54,12 +54,23 @@
+
+
+
@@ -93,6 +104,7 @@ import Code from "@/components/Code.vue"
import blockController from "@/utils/blockController"
import { useStudioCompletions } from "@/utils/useStudioCompletions"
import type { CompletionContext } from "@codemirror/autocomplete"
+import { Switch } from "@/json_types"
const props = defineProps<{
block?: Block
diff --git a/frontend/src/components/ComponentStyles.vue b/frontend/src/components/ComponentStyles.vue
index b4028ed3..18a97b3e 100644
--- a/frontend/src/components/ComponentStyles.vue
+++ b/frontend/src/components/ComponentStyles.vue
@@ -426,6 +426,20 @@ const styleSectionProperties = [
return blockController.getStyle("borderColor")
},
},
+ {
+ component: ColorInput,
+ getProps: () => {
+ return {
+ label: "Text Color",
+ value: blockController.getStyle("color"),
+ }
+ },
+ events: {
+ change: (val: StyleValue) => blockController.setStyle("color", val),
+ },
+ allowDynamicValue: true,
+ searchKeyWords: "Text, Color, TextColor, Text Color",
+ },
{
component: InlineInput,
getProps: () => {
@@ -500,6 +514,27 @@ const styleSectionProperties = [
return blockController.getStyle("borderRadius")
},
},
+ {
+ component: InlineInput,
+ getProps: () => {
+ return {
+ label: "Z-Index",
+ modelValue: blockController.getStyle("zIndex"),
+ }
+ },
+ searchKeyWords: "Z, Index, ZIndex, Z Index",
+ events: {
+ "update:modelValue": (val: StyleValue) => blockController.setStyle("zIndex", val),
+ },
+ condition: () =>
+ !blockController.multipleBlocksSelected() &&
+ !blockController.isRoot() &&
+ blockController.getStyle("position") !== "static",
+ allowDynamicValue: true,
+ getValue: () => {
+ return blockController.getStyle("zIndex")
+ },
+ },
{
component: InlineInput,
getProps: () => {
@@ -535,27 +570,6 @@ const styleSectionProperties = [
"update:modelValue": (val: StyleValue) => blockController.setStyle("boxShadow", val),
},
},
- {
- component: InlineInput,
- getProps: () => {
- return {
- label: "Z-Index",
- modelValue: blockController.getStyle("zIndex"),
- }
- },
- searchKeyWords: "Z, Index, ZIndex, Z Index",
- events: {
- "update:modelValue": (val: StyleValue) => blockController.setStyle("zIndex", val),
- },
- condition: () =>
- !blockController.multipleBlocksSelected() &&
- !blockController.isRoot() &&
- blockController.getStyle("position") !== "static",
- allowDynamicValue: true,
- getValue: () => {
- return blockController.getStyle("zIndex")
- },
- },
]
const rawStyleSectionProperties = [
diff --git a/frontend/src/components/PropsEditor.vue b/frontend/src/components/PropsEditor.vue
index c6d74f5a..0de4b017 100644
--- a/frontend/src/components/PropsEditor.vue
+++ b/frontend/src/components/PropsEditor.vue
@@ -9,7 +9,7 @@
v-if="propName === 'modelValue'"
:block="block"
@update:modelValue="(value) => bindVariable(propName, value)"
- :class="{ 'mt-1 self-start': config.inputType === 'code' }"
+ :class="{ 'mt-1 self-start': isCodeField(config.inputType) }"
:formatValuesAsTemplate="false"
>
@@ -34,12 +34,32 @@
props.block?.setProp(propName, value)"
/>
props.block?.setProp(propName, newValue)"
+ :required="config.required"
+ :completions="(context: CompletionContext) => getCompletions(context, block?.getCompletions())"
+ :showLineNumbers="false"
+ height="250px"
+ class="overflow-hidden"
+ :actionButton="{
+ icon: 'maximize-2',
+ label: 'Expand',
+ handler: () => {
+ if (!props.block) return
+ canvasStore.editHTML(props.block)
+ },
+ }"
+ />
+ ()
const getCompletions = useStudioCompletions()
+const canvasStore = useCanvasStore()
const componentInstance = computed(() => {
if (!props.block?.componentName || props.block.isStudioComponent) return {}
@@ -175,6 +197,10 @@ function getStudioComponentProps(componentInputs: ComponentInput[]): ComponentPr
return _props
}
+const isCodeField = (inputType: string) => {
+ return ["code", "html"].includes(inputType)
+}
+
// variable binding
const boundValue = computed({
get() {
diff --git a/frontend/src/components/StudioComponent.vue b/frontend/src/components/StudioComponent.vue
index 0984904f..20462be8 100644
--- a/frontend/src/components/StudioComponent.vue
+++ b/frontend/src/components/StudioComponent.vue
@@ -113,6 +113,7 @@ import { isDynamicValue } from "@/utils/code"
import type { CanvasProps } from "@/types/StudioCanvas"
import type { RepeaterContext } from "@/types"
+import type HTML from "@/components/AppLayout/HTML.vue"
import useCodeStore from "@/stores/codeStore"
const props = withDefaults(
@@ -134,7 +135,7 @@ const canvasStore = useCanvasStore()
const codeStore = useCodeStore()
const isComponentReady = ref(false)
-const editor = ref | null>(null)
+const editor = ref | InstanceType | null>(null)
const classes = computed(() => {
return [attrs.class, "__studio_component__", "outline-none", "select-none", ...props.block.getClasses()]
diff --git a/frontend/src/data/components.ts b/frontend/src/data/components.ts
index 7082bdee..77b6b89f 100644
--- a/frontend/src/data/components.ts
+++ b/frontend/src/data/components.ts
@@ -36,6 +36,7 @@ import LucideALargeSmall from "~icons/lucide/a-large-small"
import LucideEdit from "~icons/lucide/edit"
import LucideMessageSquare from "~icons/lucide/message-square"
import LucideListTree from "~icons/lucide/list-tree"
+import LucideCode from "~icons/lucide/code"
import LucideRepeat from "~icons/lucide/repeat"
import LucideFrame from "~icons/lucide/frame"
import LucideSidebar from "~icons/lucide/sidebar"
@@ -765,6 +766,15 @@ export const COMPONENTS: FrappeUIComponents = {
title: "Repeater",
icon: LucideRepeat,
},
+ HTML: {
+ name: "HTML",
+ title: "HTML",
+ icon: LucideCode,
+ initialState: {
+ html: "Your HTML content here
",
+ },
+ useOverridenPropTypes: true,
+ },
Header: {
name: "Header",
title: "Header",
diff --git a/frontend/src/globals.ts b/frontend/src/globals.ts
index f672e34c..8efff350 100644
--- a/frontend/src/globals.ts
+++ b/frontend/src/globals.ts
@@ -57,6 +57,7 @@ import Header from "@/components/AppLayout/Header.vue"
import Sidebar from "@/components/AppLayout/Sidebar.vue"
import SplitView from "@/components/AppLayout/SplitView.vue"
import Repeater from "@/components/AppLayout/Repeater.vue"
+import HTML from "@/components/AppLayout/HTML.vue"
import CardList from "@/components/AppLayout/CardList.vue"
import AvatarCard from "@/components/AppLayout/AvatarCard.vue"
import Audio from "@/components/AppLayout/Audio.vue"
@@ -125,6 +126,7 @@ export function registerGlobalComponents(app: App) {
app.component("Sidebar", Sidebar)
app.component("SplitView", SplitView)
app.component("Repeater", Repeater)
+ app.component("HTML", HTML)
app.component("CardList", CardList)
app.component("AvatarCard", AvatarCard)
app.component("Audio", Audio)
diff --git a/frontend/src/json_types/index.ts b/frontend/src/json_types/index.ts
index 9c603c84..60164ebf 100644
--- a/frontend/src/json_types/index.ts
+++ b/frontend/src/json_types/index.ts
@@ -45,6 +45,7 @@ export { default as Audio } from "./studio/Audio.json"
export { default as AvatarCard } from "./studio/AvatarCard.json"
export { default as BottomTabs } from "./studio/BottomTabs.json"
export { default as CardList } from "./studio/CardList.json"
+export { default as HTML } from "./studio/HTML.json"
export { default as Header } from "./studio/Header.json"
export { default as ImageView } from "./studio/ImageView.json"
export { default as MarkdownEditor } from "./studio/MarkdownEditor.json"
diff --git a/frontend/src/json_types/studio/HTML.json b/frontend/src/json_types/studio/HTML.json
new file mode 100644
index 00000000..1ff61b7a
--- /dev/null
+++ b/frontend/src/json_types/studio/HTML.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$ref": "#/definitions/HTMLProps",
+ "definitions": {
+ "HTMLProps": {
+ "type": "object",
+ "properties": {
+ "html": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "html"
+ ],
+ "additionalProperties": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/pages/StudioPage.vue b/frontend/src/pages/StudioPage.vue
index 0db5189a..3d4900a6 100644
--- a/frontend/src/pages/StudioPage.vue
+++ b/frontend/src/pages/StudioPage.vue
@@ -74,6 +74,36 @@
class="no-scrollbar dark:bg-zinc-900 absolute bottom-0 right-0 top-[var(--toolbar-height)] z-20 overflow-auto border-l-[1px] bg-white shadow-lg dark:border-gray-800"
/>