Skip to content

Commit

Permalink
feat: introduce CoreColorInput + last colors selected. WF-9
Browse files Browse the repository at this point in the history
  • Loading branch information
madeindjs committed Jul 5, 2024
1 parent 6da67a1 commit 12d9c03
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 26 deletions.
Binary file added docs/framework/public/components/colorinput.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 7 additions & 7 deletions src/ui/src/builder/BuilderFieldsColor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,10 @@

<div v-if="mode == 'pick' || mode == 'css'" class="main">
<div v-if="mode == 'pick'" class="pickerContainer">
<input
<BaseInputColor
ref="pickerEl"
type="color"
:value="component.content[fieldKey]"
@input="handleInput"
@update:value="handleInput"
/>
</div>

Expand All @@ -70,10 +69,11 @@ import {
ref,
toRefs,
} from "vue";
import { Component } from "../writerTypes";
import { useComponentActions } from "./useComponentActions";
import BaseInputColor from "../core_components/base/BaseInputColor.vue";
import injectionKeys from "../injectionKeys";
import { Component } from "../writerTypes";
import BuilderTemplateInput from "./BuilderTemplateInput.vue";
import { useComponentActions } from "./useComponentActions";
const wf = inject(injectionKeys.core);
const ssbm = inject(injectionKeys.builderManager);
Expand Down Expand Up @@ -125,11 +125,11 @@ const setMode = async (newMode: Mode) => {
autofocus();
};
const handleInput = (ev: Event) =>
const handleInput = (ev: Event | string) =>
setContentValue(
component.value.id,
fieldKey.value,
(ev.target as HTMLInputElement).value,
typeof ev === "string" ? ev : (ev.target as HTMLInputElement).value,
);
onMounted(() => {
Expand Down
16 changes: 9 additions & 7 deletions src/ui/src/builder/BuilderFieldsShadow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,10 @@
<div class="header">
<div class="name">Color</div>
</div>
<input
<BaseInputColor
ref="paramColorEl"
type="color"
:value="parsedValue?.color"
@input="handleInput"
@update:value="handleInput"
/>
</div>
</div>
Expand All @@ -130,6 +129,7 @@
<script setup lang="ts">
import {
ComponentPublicInstance,
computed,
inject,
nextTick,
Expand All @@ -139,10 +139,11 @@ import {
ref,
toRefs,
} from "vue";
import { Component } from "../writerTypes";
import { useComponentActions } from "./useComponentActions";
import BaseInputColor from "../core_components/base/BaseInputColor.vue";
import injectionKeys from "../injectionKeys";
import { Component } from "../writerTypes";
import BuilderTemplateInput from "./BuilderTemplateInput.vue";
import { useComponentActions } from "./useComponentActions";
const wf = inject(injectionKeys.core);
const ssbm = inject(injectionKeys.builderManager);
Expand All @@ -154,7 +155,8 @@ const paramOffsetXEl: Ref<HTMLInputElement> = ref(null);
const paramOffsetYEl: Ref<HTMLInputElement> = ref(null);
const paramBlurRadiusEl: Ref<HTMLInputElement> = ref(null);
const paramSpreadRadiusEl: Ref<HTMLInputElement> = ref(null);
const paramColorEl: Ref<HTMLInputElement> = ref(null);
const paramColorEl: Ref<ComponentPublicInstance<typeof BaseInputColor>> =
ref(null);
type Mode = "pick" | "css" | "default";
Expand Down Expand Up @@ -205,7 +207,7 @@ const handleInput = () => {
const offsetY = paramOffsetYEl.value.value;
const blurRadius = paramBlurRadiusEl.value.value;
const spreadRadius = paramSpreadRadiusEl.value.value;
const color = paramColorEl.value.value;
const color = paramColorEl.value?.getValue();
const boxShadowCSS = `${offsetX}px ${offsetY}px ${blurRadius}px ${spreadRadius}px ${color}`;
setContentValue(component.value.id, fieldKey.value, boxShadowCSS);
};
Expand Down
17 changes: 9 additions & 8 deletions src/ui/src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ref, Ref } from "vue";
import { computed, ref, Ref } from "vue";
import {
Component,
ComponentMap,
InstancePath,
MailItem,
UserFunction,
} from "../writerTypes";
import { auditAndFixComponents } from "./auditAndFix";
import { loadExtensions } from "./loadExtensions";
import { parseAccessor } from "./parsing";
import {
getSupportedComponentTypes,
getComponentDefinition,
getSupportedComponentTypes,
} from "./templateMap";
import * as typeHierarchy from "./typeHierarchy";
import { auditAndFixComponents } from "./auditAndFix";
import { parseAccessor } from "./parsing";
import { loadExtensions } from "./loadExtensions";

const RECONNECT_DELAY_MS = 1000;
const KEEP_ALIVE_DELAY_MS = 60000;
Expand Down Expand Up @@ -127,7 +127,7 @@ export function generateCore() {
Object.entries(mutations).forEach(([key, value]) => {
/*
Splits the key while respecting escaped dots.
For example, "files.myfile\.sh" will be split into ["files", "myfile.sh"]
For example, "files.myfile\.sh" will be split into ["files", "myfile.sh"]
*/

const mutationFlag = key.charAt(0);
Expand Down Expand Up @@ -443,8 +443,8 @@ export function generateCore() {
*/
async function sendComponentUpdate(): Promise<void> {
/*
Ensure that the backend receives only components
created by the frontend (Builder-managed components, BMC),
Ensure that the backend receives only components
created by the frontend (Builder-managed components, BMC),
and not the components it generated (Code-managed components, CMC).
*/

Expand Down Expand Up @@ -594,6 +594,7 @@ export function generateCore() {
getSessionTimestamp,
getUserState,
isChildOf,
components: computed(() => Object.values(components.value)),
};

return core;
Expand Down
2 changes: 2 additions & 0 deletions src/ui/src/core/templateMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import CoreTags from "../core_components/content/CoreTags.vue";
import CoreAvatar from "../core_components/content/CoreAvatar.vue";
// input
import CoreCheckboxInput from "../core_components/input/CoreCheckboxInput.vue";
import CoreColorInput from "../core_components/input/CoreColorInput.vue";
import CoreDateInput from "../core_components/input/CoreDateInput.vue";
import CoreDropdownInput from "../core_components/input/CoreDropdownInput.vue";
import CoreFileInput from "../core_components/input/CoreFileInput.vue";
Expand Down Expand Up @@ -92,6 +93,7 @@ const templateMap = {
textareainput: CoreTextareaInput,
numberinput: CoreNumberInput,
sliderinput: CoreSliderInput,
colorinput: CoreColorInput,
dateinput: CoreDateInput,
radioinput: CoreRadioInput,
checkboxinput: CoreCheckboxInput,
Expand Down
60 changes: 60 additions & 0 deletions src/ui/src/core_components/base/BaseInputColor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<template>
<input
ref="pickerEl"
type="color"
class="BaseInputColor"
:value="value"
:list="id"
@input="handleInput"
@change="handleChange"
/>
<datalist :id="id">
<option v-for="color of customColors" :key="color">{{ color }}</option>
</datalist>
</template>

<script setup lang="ts">
import { ref } from "vue";
import useCustomColors from "./hooks/useCustomColors";
import useId from "./hooks/useId";
const pickerEl = ref<HTMLInputElement | undefined>();
defineProps({
id: { type: String, default: () => useId() },
value: { type: String, required: false, default: undefined },
});
const customColors = useCustomColors();
const emit = defineEmits({
"update:value": (value: string) => typeof value === "string",
change: (value: string) => typeof value === "string",
});
function focus(options?: Parameters<HTMLInputElement["focus"]>[0]) {
pickerEl?.value.focus(options);
}
function getValue() {
return pickerEl?.value.value;
}
defineExpose({ focus, getValue });
function handleInput(event: Event) {
emit("update:value", (event.target as HTMLInputElement).value);
}
function handleChange(event: Event) {
emit("change", (event.target as HTMLInputElement).value);
}
</script>

<style scoped>
.BaseInputColor {
width: 100%;
border: 0;
outline: none;
}
</style>
20 changes: 20 additions & 0 deletions src/ui/src/core_components/base/hooks/useCustomColors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { computed, inject } from "vue";
import injectionKeys from "../../../injectionKeys";

/**
* Get the list of colors used in any component settings
*/
export default function useCustomColors() {
const wf = inject(injectionKeys.core);

return computed(() => {
const colors = new Set(
wf?.components?.value
.flatMap((component) => Object.entries(component.content))
.filter(([key, _]) => String(key).endsWith("Color"))
.map(([_, value]) => value),
);

return Array.from(colors);
});
}
9 changes: 9 additions & 0 deletions src/ui/src/core_components/base/hooks/useId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Simply generate a uniq ID to use as HTML `id` attribute
*/
export default function useId() {
// `crypto.randomUUID` is only available in HTTPS context
return typeof crypto.randomUUID === "function"
? crypto.randomUUID()
: Date.now().toString();
}
83 changes: 83 additions & 0 deletions src/ui/src/core_components/input/CoreColorInput.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<template>
<BaseInputWrapper
ref="rootInstance"
:label="fields.label.value"
class="CoreColorInput"
>
<BaseInputColor
:value="formValue"
@update:value="handleInput($event, 'wf-change')"
@change="handleInput($event, 'wf-change-finish')"
/>
</BaseInputWrapper>
</template>

<script lang="ts">
import { ComponentPublicInstance } from "vue";
import { cssClasses } from "../../renderer/sharedStyleFields";
import { FieldType } from "../../writerTypes";
import BaseInputColor from "../base/BaseInputColor.vue";
import BaseInputWrapper from "../base/BaseInputWrapper.vue";
const description =
"A user input component that allows users to select a color using a color picker interface.";
const onChangeHandlerStub = `
def onchange_handler(state, payload):
# Set the state variable "new_color" to the new value, provided as string.
state["new_color"] = payload`;
export default {
writer: {
name: "Color Input",
description,
category: "Input",
fields: {
label: {
name: "Label",
init: "Input Label",
type: FieldType.Text,
},
cssClasses,
},
events: {
"wf-change": {
desc: "Capture changes as they happen.",
stub: onChangeHandlerStub,
bindable: true,
},
"wf-change-finish": {
desc: "Capture changes once this control has lost focus.",
stub: onChangeHandlerStub,
},
},
},
};
</script>
<script setup lang="ts">
import { inject, ref } from "vue";
import injectionKeys from "../../injectionKeys";
import { useFormValueBroker } from "../../renderer/useFormValueBroker";
const fields = inject(injectionKeys.evaluatedFields);
const rootInstance = ref<ComponentPublicInstance | null>(null);
const wf = inject(injectionKeys.core);
const instancePath = inject(injectionKeys.instancePath);
const { formValue, handleInput } = useFormValueBroker<string>(
wf,
instancePath,
rootInstance,
);
</script>

<style scoped>
@import "../../renderer/sharedStyles.css";
@import "../../renderer/colorTransformations.css";
.CoreColorInput {
width: fit-content;
}
</style>
8 changes: 4 additions & 4 deletions src/ui/src/renderer/useFormValueBroker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ComponentPublicInstance, computed, Ref, ref, watch } from "vue";
import { Core, InstancePath } from "../writerTypes";
import { useEvaluator } from "../renderer/useEvaluator";
import { Core, InstancePath } from "../writerTypes";

/**
*
Expand All @@ -10,14 +10,14 @@ import { useEvaluator } from "../renderer/useEvaluator";
* @param componentId
* @returns
*/
export function useFormValueBroker(
export function useFormValueBroker<T = any>(
wf: Core,
instancePath: InstancePath,
emitterEl: Ref<HTMLElement | ComponentPublicInstance>,
) {
const formValue: Ref<any> = ref();
const formValue: Ref<T> = ref();
const isBusy = ref(false);
const queuedEvent: Ref<{ eventValue: any; emitEventType: string }> =
const queuedEvent: Ref<{ eventValue: T; emitEventType: string }> =
ref(null);

const componentId = instancePath.at(-1).componentId;
Expand Down

0 comments on commit 12d9c03

Please sign in to comment.