Skip to content

Commit 58a9ef0

Browse files
authored
Merge pull request #32 from ruchamahabal/app-3
feat: Repeater & SplitView components, Copy & Paste blocks, Tailwind classes & raw style support
2 parents 752a056 + 1353950 commit 58a9ef0

26 files changed

+472
-82
lines changed

frontend/src/components/AppComponent.vue

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
v-model="boundValue"
88
:data-component-id="block.componentId"
99
:style="styles"
10+
:class="classes"
1011
v-on="componentEvents"
1112
>
1213
<!-- Dynamically render named slots -->
@@ -28,7 +29,7 @@
2829

2930
<script setup lang="ts">
3031
import Block from "@/utils/block"
31-
import { computed, onMounted, ref, useAttrs } from "vue"
32+
import { computed, onMounted, ref, useAttrs, inject } from "vue"
3233
import { useRouter, useRoute } from "vue-router"
3334
import { createResource } from "frappe-ui"
3435
import { getComponentRoot, isDynamicValue, getDynamicValue, isHTML, executeUserScript } from "@/utils/helpers"
@@ -43,8 +44,21 @@ const props = defineProps<{
4344
4445
const componentRef = ref(null)
4546
const styles = computed(() => props.block.getStyles())
47+
const classes = computed(() => {
48+
return [attrs.class, ...props.block.getClasses()]
49+
})
4650
51+
const repeaterContext = inject("repeaterContext", {})
4752
const store = useAppStore()
53+
54+
const getEvaluationContext = () => {
55+
return {
56+
...store.variables,
57+
...store.resources,
58+
...repeaterContext,
59+
}
60+
}
61+
4862
const getComponentProps = () => {
4963
if (!props.block || props.block.isRoot()) return []
5064
@@ -53,7 +67,7 @@ const getComponentProps = () => {
5367
5468
Object.entries(propValues).forEach(([propName, config]) => {
5569
if (isDynamicValue(config)) {
56-
propValues[propName] = getDynamicValue(config, { ...store.resources, ...store.variables })
70+
propValues[propName] = getDynamicValue(config, getEvaluationContext())
5771
}
5872
})
5973
return propValues
@@ -70,7 +84,7 @@ const componentProps = computed(() => {
7084
// visibility
7185
const showComponent = computed(() => {
7286
if (props.block.visibilityCondition) {
73-
const value = getDynamicValue(props.block.visibilityCondition, { ...store.resources, ...store.variables })
87+
const value = getDynamicValue(props.block.visibilityCondition, getEvaluationContext())
7488
return typeof value === "string" ? value === "true" : value
7589
}
7690
return true
@@ -91,7 +105,7 @@ const boundValue = computed({
91105
}
92106
return value
93107
} else if (isDynamicValue(modelValue)) {
94-
return getDynamicValue(modelValue, { ...store.resources, ...store.variables })
108+
return getDynamicValue(modelValue, getEvaluationContext())
95109
}
96110
return modelValue
97111
},
@@ -190,7 +204,7 @@ const componentEvents = computed(() => {
190204
}
191205
} else if (event.action === "Run Script") {
192206
return () => {
193-
executeUserScript(event.script, store.variables, store.resources)
207+
executeUserScript(event.script, store.variables, store.resources, repeaterContext)
194208
}
195209
}
196210
}

frontend/src/components/AppLayout/Header.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<div class="ml-auto flex flex-row items-center gap-2" v-if="!hideMenu">
1616
<template v-for="item in menuItems" :key="item.label">
1717
<div class="flex cursor-pointer items-center gap-2" @click="item.action">
18-
<span class="text-gray-800">{{ item.label }}</span>
18+
<span class="text-base text-gray-800">{{ item.label }}</span>
1919
</div>
2020
</template>
2121
</div>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<template>
2+
<div class="flex flex-row flex-wrap gap-5">
3+
<div
4+
v-if="!data?.length"
5+
class="pointer-events-none flex h-full w-full items-center justify-center p-5 text-base text-gray-500"
6+
>
7+
{{ emptyStateMessage || "No data" }}
8+
</div>
9+
<template v-else v-for="(dataItem, dataIndex) in data" :key="dataItem[dataKey]">
10+
<RepeaterContextProvider :dataItem="dataItem" :dataIndex="dataIndex" :dataKey="dataKey">
11+
<slot v-bind="{ dataItem, dataKey, dataIndex }"></slot>
12+
</RepeaterContextProvider>
13+
</template>
14+
</div>
15+
</template>
16+
17+
<script setup lang="ts">
18+
import RepeaterContextProvider from "@/components/AppLayout/RepeaterContextProvider.vue"
19+
defineProps<{
20+
data?: Array<any>
21+
dataKey: string
22+
emptyStateMessage?: string
23+
}>()
24+
</script>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<template>
2+
<slot />
3+
</template>
4+
5+
<script setup lang="ts">
6+
import { provide, reactive } from "vue"
7+
8+
const props = defineProps<{
9+
dataItem: any
10+
dataIndex: number
11+
dataKey: string
12+
}>()
13+
14+
const context = reactive({
15+
dataItem: props.dataItem,
16+
dataIndex: props.dataIndex,
17+
dataKey: props.dataKey,
18+
})
19+
20+
provide("repeaterContext", context)
21+
</script>

frontend/src/components/AppLayout/Sidebar.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="relative flex h-full min-h-screen w-60 flex-col bg-gray-50 px-2 pt-2">
2+
<div class="sticky bottom-0 left-0 top-0 flex h-full w-60 flex-col bg-gray-50 px-2 pt-2">
33
<button class="mb-1 flex w-56 items-center gap-2 rounded p-2 hover:bg-gray-200">
44
<slot name="header">
55
<div class="rounded-sm">
@@ -15,7 +15,7 @@
1515
</slot>
1616
</button>
1717

18-
<nav class="mt-2 flex flex-col space-y-1">
18+
<nav class="mt-2 flex flex-1 flex-col space-y-1 overflow-y-auto">
1919
<div class="w-full" v-for="item in menuItems" :key="item.label">
2020
<component
2121
:is="item.route_to ? 'router-link' : 'div'"
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<template>
2+
<div class="flex h-screen w-full overflow-hidden">
3+
<div
4+
ref="leftPane"
5+
:style="{ width: leftPaneWidth + 'px' }"
6+
class="flex-shrink-0 border-r border-gray-200"
7+
>
8+
<slot name="left"></slot>
9+
</div>
10+
<div
11+
ref="resizer"
12+
class="w-[1px] flex-shrink-0 cursor-col-resize bg-gray-200 hover:bg-gray-300"
13+
@mousedown="startResize"
14+
></div>
15+
<div class="flex-grow overflow-auto">
16+
<slot name="right"></slot>
17+
</div>
18+
</div>
19+
</template>
20+
21+
<script setup>
22+
import { ref, onMounted } from "vue"
23+
24+
const leftPane = ref(null)
25+
const resizer = ref(null)
26+
const leftPaneWidth = ref(600) // Initial width
27+
28+
let startX = 0
29+
30+
const startResize = (e) => {
31+
startX = e.clientX
32+
window.addEventListener("mousemove", resize)
33+
window.addEventListener("mouseup", stopResize)
34+
}
35+
36+
const resize = (e) => {
37+
const delta = e.clientX - startX
38+
leftPaneWidth.value = Math.max(100, leftPane.value.offsetWidth + delta) // Minimum width of 100px
39+
startX = e.clientX
40+
}
41+
42+
const stopResize = () => {
43+
window.removeEventListener("mousemove", resize)
44+
window.removeEventListener("mouseup", stopResize)
45+
}
46+
47+
onMounted(() => {
48+
if (leftPane.value) {
49+
leftPaneWidth.value = leftPane.value.offsetWidth
50+
}
51+
})
52+
</script>

frontend/src/components/BlockFlexLayoutHandler.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
{ label: 'Horizontal', value: 'row', icon: 'arrow-right', hideLabel: true },
88
{ label: 'Vertical', value: 'column', icon: 'arrow-down', hideLabel: true },
99
]"
10-
:modelValue="blockController.getStyle('flexDirection') || 'row'"
10+
:modelValue="blockController.getStyle('flexDirection')"
1111
@update:modelValue="(val: string | number) => blockController.setStyle('flexDirection', val)"
1212
></OptionToggle>
1313
<PlacementControl v-if="blockController.isFlex()"></PlacementControl>

frontend/src/components/BlockPositionHandler.vue

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,17 @@
3535
class="grid-col-3 dark:bg-zinc-800 grid h-16 w-16 grid-rows-3 gap-1 self-center justify-self-center rounded bg-gray-50 p-2"
3636
>
3737
<div
38-
class="col-span-3 row-start-1 h-2 w-[2px] self-center justify-self-center rounded bg-surface-gray-2"
38+
class="col-span-3 row-start-1 h-2 w-[2px] self-center justify-self-center rounded bg-gray-100"
3939
></div>
4040
<div
41-
class="col-span-3 row-start-3 h-2 w-[2px] self-center justify-self-center rounded bg-surface-gray-2"
41+
class="col-span-3 row-start-3 h-2 w-[2px] self-center justify-self-center rounded bg-gray-100"
4242
></div>
43+
<div class="h-5 w-5 self-center justify-self-center rounded bg-gray-400"></div>
4344
<div
44-
class="surface-grabg-surface-gray-2 h-5 w-5 self-center justify-self-center rounded bg-gray-400"
45+
class="col-span-1 col-start-1 row-start-2 h-[2px] w-2 self-center justify-self-center rounded bg-gray-100"
4546
></div>
4647
<div
47-
class="col-span-1 col-start-1 row-start-2 h-[2px] w-2 self-center justify-self-center rounded bg-surface-gray-2"
48-
></div>
49-
<div
50-
class="col-span-1 col-start-3 row-start-2 h-[2px] w-2 self-center justify-self-center rounded bg-surface-gray-2"
48+
class="col-span-1 col-start-3 row-start-2 h-[2px] w-2 self-center justify-self-center rounded bg-gray-100"
5149
></div>
5250
</div>
5351
<div class="col-span-1 col-start-3 w-16 self-center">

frontend/src/components/CodeEditor.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
</template>
2424

2525
<script setup lang="ts">
26-
import { areObjectsEqual, jsonToJs, jsToJson } from "@/utils/helpers"
26+
import { jsonToJs, jsToJson } from "@/utils/helpers"
2727
import { useDark } from "@vueuse/core"
2828
import ace from "ace-builds"
2929
import "ace-builds/src-min-noconflict/ext-searchbox"
@@ -112,14 +112,14 @@ const setupEditor = () => {
112112
try {
113113
errorMessage.value = ""
114114
let value = aceEditor?.getValue() || ""
115+
debugger
115116
if (
116117
value &&
117118
!value.startsWith("{{") &&
118119
(props.type === "JSON" || typeof props.modelValue === "object")
119120
) {
120121
value = jsonToJs(value)
121-
if (areObjectsEqual(value, props.modelValue)) return
122-
} else if (value === props.modelValue) {
122+
} else if (value === getModelValue()) {
123123
return
124124
}
125125

frontend/src/components/ColorPicker.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
}"
7272
></div>
7373
</div>
74-
<div ref="colorPalette">
74+
<div ref="colorPalette" class="max-w-[11rem]">
7575
<div class="mt-3 flex flex-wrap gap-1.5">
7676
<div
7777
v-for="color in colors"
@@ -143,6 +143,8 @@ const colors = [
143143
if (!isSupported.value) {
144144
colors.push("#B34D4D")
145145
}
146+
// frappe-ui grays
147+
colors.push("#F3F3F3", "#EDEDED")
146148
147149
const setColorSelectorPosition = (color: HashString) => {
148150
const { width, height } = colorMap.value.getBoundingClientRect()

0 commit comments

Comments
 (0)