Skip to content

Commit 561d2e2

Browse files
committed
temp
1 parent 25363e2 commit 561d2e2

11 files changed

Lines changed: 500 additions & 4 deletions

File tree

packages/imagekit-editor-dev/src/ImageKitEditor.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
type RequiredMetadata,
2323
type Signer,
2424
type Transformation,
25+
type OnPickImage,
2526
useEditorStore,
2627
} from "./store"
2728
import { themeOverrides } from "./theme"
@@ -82,6 +83,7 @@ interface EditorProps<Metadata extends RequiredMetadata = RequiredMetadata> {
8283
initialImages?: Array<string | InputFileElement<Metadata>>
8384
signer?: Signer<Metadata>
8485
onAddImage?: () => void
86+
onPickImage?: OnPickImage
8587
exportOptions?: HeaderProps<Metadata>["exportOptions"]
8688
focusObjects?: ReadonlyArray<FocusObjects>
8789
onClose: (args: { dirty: boolean; destroy: () => void }) => void
@@ -96,6 +98,19 @@ interface EditorProps<Metadata extends RequiredMetadata = RequiredMetadata> {
9698
* If omitted, the editor defaults to allowing all actions.
9799
*/
98100
getTemplatePermissions?: GetTemplatePermissions
101+
/**
102+
* Pre-load this template by id. The editor calls templateStorage.getTemplate(id).
103+
*/
104+
initialTemplateId?: string
105+
/**
106+
* Hide media library file actions (save as new file, etc.) when the editor
107+
* is used outside the media library context.
108+
*/
109+
hideMediaLibraryFileActions?: boolean
110+
/**
111+
* Whether this is an automation template (canvas-based).
112+
*/
113+
isAutomationTemplate?: boolean
99114
}
100115

101116
function ImageKitEditorImpl<M extends RequiredMetadata>(
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import {
2+
Button,
3+
Flex,
4+
FormControl,
5+
FormLabel,
6+
IconButton,
7+
Input,
8+
Popover,
9+
PopoverBody,
10+
PopoverContent,
11+
PopoverTrigger,
12+
Text,
13+
useDisclosure,
14+
} from "@chakra-ui/react"
15+
import { PiBracketsCurly } from "@react-icons/all-files/pi/PiBracketsCurly"
16+
import { PiX } from "@react-icons/all-files/pi/PiX"
17+
import { type FC, useCallback, useState } from "react"
18+
import { generateVariableName } from "../../variables"
19+
20+
const MAX_LABEL_LENGTH = 64
21+
22+
/**
23+
* Field types that can be variabilized (single scalar value).
24+
*/
25+
const SUPPORTED_FIELD_TYPES = new Set([
26+
"input",
27+
"textarea",
28+
"switch",
29+
"slider",
30+
"color-picker",
31+
"select",
32+
"select-creatable",
33+
"radio-card",
34+
"checkbox-card",
35+
])
36+
37+
export function canBeVariable(fieldType: string | undefined): boolean {
38+
return !!fieldType && SUPPORTED_FIELD_TYPES.has(fieldType)
39+
}
40+
41+
interface Props {
42+
fieldLabel: string
43+
takenNames: Iterable<string>
44+
onCreate: (variable: { name: string; label: string }) => void
45+
}
46+
47+
/**
48+
* A small button shown next to field labels in the sidebar. Clicking opens
49+
* a popover to name the variable. On save, it generates a collision-free
50+
* name and calls onCreate.
51+
*/
52+
export const VariableMarkerButton: FC<Props> = ({
53+
fieldLabel,
54+
takenNames,
55+
onCreate,
56+
}) => {
57+
const { isOpen, onOpen, onClose } = useDisclosure()
58+
const [label, setLabel] = useState(fieldLabel)
59+
60+
const handleOpen = useCallback(() => {
61+
setLabel(fieldLabel)
62+
onOpen()
63+
}, [fieldLabel, onOpen])
64+
65+
const handleSave = () => {
66+
const trimmed = label.trim() || fieldLabel
67+
const name = generateVariableName(trimmed, takenNames)
68+
onCreate({ name, label: trimmed })
69+
onClose()
70+
}
71+
72+
return (
73+
<Popover isOpen={isOpen} onClose={onClose} placement="bottom-start" isLazy>
74+
<PopoverTrigger>
75+
<IconButton
76+
aria-label="Make variable"
77+
icon={<PiBracketsCurly />}
78+
size="xs"
79+
variant="ghost"
80+
opacity={0}
81+
_groupHover={{ opacity: 1 }}
82+
onClick={handleOpen}
83+
/>
84+
</PopoverTrigger>
85+
<PopoverContent w="260px">
86+
<PopoverBody p={3}>
87+
<Flex justify="space-between" align="center" mb={2}>
88+
<Text fontSize="sm" fontWeight="600">
89+
Create variable
90+
</Text>
91+
<IconButton
92+
aria-label="Close"
93+
icon={<PiX />}
94+
size="xs"
95+
variant="ghost"
96+
onClick={onClose}
97+
/>
98+
</Flex>
99+
<FormControl>
100+
<FormLabel fontSize="xs">Label</FormLabel>
101+
<Input
102+
size="sm"
103+
value={label}
104+
onChange={(e) => setLabel(e.target.value.slice(0, MAX_LABEL_LENGTH))}
105+
placeholder="e.g. Headline text"
106+
onKeyDown={(e) => {
107+
if (e.key === "Enter") handleSave()
108+
}}
109+
/>
110+
</FormControl>
111+
<Button
112+
size="sm"
113+
colorScheme="blue"
114+
mt={3}
115+
w="full"
116+
onClick={handleSave}
117+
isDisabled={!label.trim()}
118+
>
119+
Create
120+
</Button>
121+
</PopoverBody>
122+
</PopoverContent>
123+
</Popover>
124+
)
125+
}

packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,11 @@ import {
5454
} from "../../schema"
5555
import { type SyncStatus, useEditorStore } from "../../store"
5656
import { isStepAligned } from "../../utils"
57+
import { walkVariableRefs } from "../../variables"
5758
import AnchorField from "../common/AnchorField"
5859
import CheckboxCardField from "../common/CheckboxCardField"
5960
import ColorPickerField from "../common/ColorPickerField"
61+
import { canBeVariable, VariableMarkerButton } from "./VariableMarkerButton"
6062
import RadiusInputField, {
6163
type RadiusErrors,
6264
type RadiusState,
@@ -254,6 +256,14 @@ export const TransformationConfigSidebar: React.FC = () => {
254256
reset(defaultValues)
255257
}, [reset, defaultValues])
256258

259+
const takenVariableNames = useMemo(() => {
260+
const names = new Set<string>()
261+
for (const t of transformations) {
262+
walkVariableRefs(t.value, (ref) => names.add(ref.$var))
263+
}
264+
return names
265+
}, [transformations])
266+
257267
useEffect(() => {
258268
setTransformationConfigFormDirty(isDirty)
259269
return () => setTransformationConfigFormDirty(false)
@@ -565,9 +575,20 @@ export const TransformationConfigSidebar: React.FC = () => {
565575
)
566576
}
567577
>
568-
<FormLabel htmlFor={field.name} fontSize="sm">
569-
{field.label}
570-
</FormLabel>
578+
<HStack spacing={1} align="center" role="group">
579+
<FormLabel htmlFor={field.name} fontSize="sm" mb={0}>
580+
{field.label}
581+
</FormLabel>
582+
{canBeVariable(field.fieldType) && (
583+
<VariableMarkerButton
584+
fieldLabel={field.label}
585+
takenNames={takenVariableNames}
586+
onCreate={(variable) => {
587+
setValue(field.name, { $var: variable.name, label: variable.label } as any, { shouldDirty: true })
588+
}}
589+
/>
590+
)}
591+
</HStack>
571592
{field.fieldType === "select" ? (
572593
<Controller
573594
name={field.name}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Box, FormControl, FormLabel, Input } from "@chakra-ui/react"
2+
import React from "react"
3+
import type { TransformationField } from "../../schema"
4+
5+
export interface VariableFieldProps {
6+
name: string
7+
label: string
8+
field?: TransformationField
9+
value: unknown
10+
onChange: (value: unknown) => void
11+
}
12+
13+
/**
14+
* Renders an appropriate input for a template variable based on the
15+
* field schema from the transformation it's bound to.
16+
*
17+
* For v1, this renders a text input. Future versions can dispatch on
18+
* `field.fieldType` to render color pickers, sliders, etc.
19+
*/
20+
export const VariableField: React.FC<VariableFieldProps> = ({
21+
name,
22+
label,
23+
field,
24+
value,
25+
onChange,
26+
}) => {
27+
const fieldType = field?.fieldType || "input"
28+
29+
return (
30+
<FormControl>
31+
<FormLabel fontSize="xs" fontWeight="500" color="gray.600">
32+
{label}
33+
</FormLabel>
34+
<Input
35+
size="sm"
36+
value={typeof value === "string" ? value : value != null ? String(value) : ""}
37+
onChange={(e) => onChange(e.target.value)}
38+
placeholder={field?.fieldProps?.defaultValue != null
39+
? String(field.fieldProps.defaultValue)
40+
: name}
41+
/>
42+
</FormControl>
43+
)
44+
}

packages/imagekit-editor-dev/src/index.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,32 @@ export type {
1111
TemplateRecord,
1212
TemplateStorageHttpClient,
1313
TemplateStorageProvider,
14+
TemplateVariable,
1415
} from "./storage"
1516
export {
1617
applyTemplateStorageAccessFailure,
1718
isTemplateAccessDeniedError,
1819
normalizeTransformationStepsForPersistence,
1920
TemplateAccessDeniedError,
2021
} from "./storage"
21-
export type { FileElement, Signer, Transformation } from "./store"
22+
export type { FileElement, Signer, Transformation, OutputDimensions, OnPickImage } from "./store"
2223
export { TRANSFORMATION_STATE_VERSION } from "./store"
24+
export {
25+
buildEditorTransformationString,
26+
buildImageKitUrl,
27+
} from "./transformationConverter"
28+
export {
29+
listVariables,
30+
type VariableDescriptor,
31+
} from "./variables/listVariables"
32+
export {
33+
isVariableRef,
34+
resolveVariableRefs,
35+
generateVariableName,
36+
walkVariableRefs,
37+
type VariableRef,
38+
} from "./variables"
39+
export {
40+
VariableField,
41+
type VariableFieldProps,
42+
} from "./components/variables/VariableField"

packages/imagekit-editor-dev/src/storage/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export type {
1010
TemplateRecord,
1111
TemplateStorageHttpClient,
1212
TemplateStorageProvider,
13+
TemplateVariable,
1314
} from "./types"

packages/imagekit-editor-dev/src/storage/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,22 @@ export interface TemplateCreator {
66
email: string
77
}
88

9+
export interface TemplateVariable {
10+
name: string
11+
label: string
12+
fieldKey: string
13+
transformationKey: string
14+
defaultValue?: unknown
15+
}
16+
917
export interface TemplateRecord {
1018
id: string
1119
clientNumber: string
1220
isPrivate: boolean
1321
name: string
1422
transformations: Omit<Transformation, "id">[]
23+
variables: TemplateVariable[]
24+
isAutomationTemplate?: boolean
1525
/** Whether the active user has this template pinned. */
1626
isPinned: boolean
1727
createdBy: TemplateCreator
@@ -25,9 +35,11 @@ export type SaveTemplateInput = {
2535
id?: string
2636
name: string
2737
transformations: Omit<Transformation, "id">[]
38+
variables?: TemplateVariable[]
2839
clientNumber?: string
2940
isPrivate?: boolean
3041
isPinned?: boolean
42+
isAutomationTemplate?: boolean
3143
createdBy?: TemplateCreator
3244
updatedBy?: TemplateCreator
3345
createdAt?: number

packages/imagekit-editor-dev/src/store.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ import { extractImagePath } from "./utils"
1818

1919
export const TRANSFORMATION_STATE_VERSION = "v1" as const
2020

21+
export interface OutputDimensions {
22+
width: number
23+
height: number
24+
}
25+
26+
export type OnPickImage = () => Promise<string | null | undefined>
27+
2128
export interface Transformation {
2229
id: string
2330
key: string

0 commit comments

Comments
 (0)