Files
windmill/frontend/src/lib/components/ResourceForm.svelte
T
GuilhemandClaude Opus 5 6749015fbf fix: audit the icon library against brand guidelines (#10722)
* feat: audit the icon library against brand guidelines

Every icon component checked against its brand's own published guidelines for
correct artwork, current colours, and readability on both app surfaces.

- 127 marks now carry a per-theme pair (text-[#light] dark:text-[#dark]), applied
  only where the brand publishes a reversed or dark variant. twMerge where the
  component exposes a class prop, so callers can still pass sizing.
- 296 of 304 brand icons record their source in a comment above the <svg>,
  including the rule where the brand imposes one (Google forbids recolouring,
  Cal.com is deliberately greyscale, Oracle reserves the MySQL dolphin).
- BRAND_COLORS.md is generated from the components, so the table cannot drift
  from the code.
- Marks that were unreadable on a surface: 13 -> 1 on dark, 9 -> 4 on light.
  The remainder are blocked by trademark terms, not unfixed.
- Wrong artwork replaced where a first-party or CC0 source existed: PayPal is
  the real three-colour monogram, Stripe is the bare S rather than an app tile,
  gcloud resolves to Google's mark instead of a generic hexagon.
- Concept icons (CACertificate, DbIcon, Webdav, Asset*, Bcrypt) inherit
  currentColor instead of hardcoding a colour.

Fixes a cross-component CSS bug: ten icons embedded <style> inside their <svg>.
Svelte only scopes a component's top-level style block, so those were injected as
document-global rules under names like .st0 and .cls-2, which four icons each
defined differently. WindmillIcon renders from the root logged-in layout, putting
.st0 { fill:#ffffff } on every page. Class names are now namespaced per icon.

Adds /kitchen_sink/icons, a gallery rendering every icon on both surfaces at once.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat: render brand icons in the surrounding text colour in control strips

A trigger picker mixing lucide glyphs (Webhook, Route, Database) with brand marks
(Kafka, GCP, AWS) read as two sets of controls once the marks became coloured.

Adds an .icon-mono utility that redirects descendant fills to currentColor, applied
by the container rather than passed to the icon. That is what makes it work on every
icon: GoogleCloudIcon has four hardcoded fills, no currentColor and no class prop, so
nothing passed to it could change its colour, and gradient-based marks cannot express
a monochrome variant at all without being redrawn.

- ToggleButton takes a monochromeIcon prop, opt-in since it is used app-wide.
- TriggersBadge, SidebarContent and QuickMenuItem (which backs GlobalSearchModal)
  apply it unconditionally: these are uniform lists where one coloured entry among
  grey ones reads as an error.
- DropdownV2 gains menuClass, because it portals its menu and a wrapper around the
  component cannot style it. CaptureButton passes icon-mono through it.

!important is required because a handful of icons paint through style="fill:…", which
no selector outranks. Stroke is redirected only where one is declared, so shapes
carrying stroke="none" do not sprout outlines. Wrappers use display:contents, so no
layout box is added.

RowIcon is deliberately untouched — table rows keep showing brand colour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: close out the icon provenance gaps

Sources the 8 icons that had none and settles the 54 records whose author rated
itself below "high" and which no verifier ever reached — the earlier run's
verification stage was killed by a session limit.

46 confirmed as already correct, 11 citations corrected, 3 colours corrected.
Two changes were refuted and reverted by the adversarial pass:

- Mysql: the comment had the colour-to-shape mapping inverted. Rasterising the
  first-party asset shows #00758F paints the dolphin and "My" while #F29111 paints
  "SQL", not the reverse. The mark renders monochrome here, so nothing on screen
  was ever wrong — only the note. Also rescoped the trademark sentence to what the
  page literally says.
- AdobeAcrobatSignIcon: a "corrected" citation was rejected on evidence. The agent
  claimed the original URL 404s; three fetches returned HTTP 200 with a genuine
  Adobe SVG whose stylesheet is .a{fill:#584ccc}. Reverted to the original comment,
  which also resolves the one unverified colour change on this branch — #584CCC is
  current and first-party confirmed.

AmqpIcon is deliberately left with no brand colour: AMQP is an OASIS protocol, not
a vendor, and amqp.org publishes no palette.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat: add icons for 11 resource types that had none

19 hub resource types fell back to a generic Boxes glyph. One agent per brand went
looking for a square vector logomark from a first-party source, with an adversarial
check on everything it produced; 11 landed and 8 correctly came back empty.

Added: beamer, campayn, codat, comapeo_server, klaviyo, matteroom, mollie, motimate,
paychex, terra, vectara. Each records its source, and the components follow the
library's conventions — no <style> block (Svelte does not scope those, which is what
made .st0 leak document-wide), gradient ids prefixed with the component name.

The other 8 keep the fallback, which is the right outcome rather than a failure:

- actimo, adrapid, aero_workflow, matteroom-adjacent niche products publish their mark
  only as raster. Upscaled PNGs would look soft beside 300+ vector marks.
- gfw redirects to Global Nature Watch and publishes a wordmark, not a mark.
- leonardoai, localcontexts, weatherapi, webscrapingai serve nothing usable.

No hand-tracing: approximating a mark from a screenshot is invention, not sourcing,
and a wrong logo is worse than the tidy fallback glyph.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat: lettermark fallback for reserved marks, and fix the resources table rows

Icons
- Brands that reserve their logo for licensees no longer ship it. BrandLetterIcon draws
  the initial in the brand's own colour instead: recognisable, not their mark, and not
  invented artwork. Adobe Acrobat Sign and MySQL use it, plus the eight resource types
  whose brands publish no vector mark at all.
  Adobe: "does not allow the use of its product icons by third parties in their products
  or related materials of any kind, except through an Adobe partnership agreement".
  On dark the letter inverts to a filled rounded square, because a mid-tone brand colour
  chosen to read on white goes dim as a foreground on #2e3441. Where white-on-tile is
  also dim, the tile takes a near-black letter instead; light-mode letters are darkened
  along their own hue until they clear 3:1. Every pair was measured, not eyeballed.
- Google Docs was drawing a generic monochrome document glyph while carrying a comment
  claiming Google's colours. Replaced with Google's own 192px product icon.
- Azure was drawn monochrome, justified by a comment citing Microsoft's rule against
  distorting the mark — which drawing it monochrome is. Replaced with Microsoft's own
  logo_azure.svg. Their terms say to use the icons "as they would appear within Azure";
  permitted use is diagrams, training and documentation, which is recorded in the file.
- Adobe Acrobat Sign's artwork was a geometric "A" plus a squiggle, not Adobe's ribbon
  swirl. Moot now that it is a lettermark, but the mark was wrong.
- Gradient, mask and clip ids in the new artwork are namespaced per icon; ids are
  document-global and collide the same way the .st0 class names did.

Resources tables
- Description cells are a fixed two lines: min-h floors short ones, line-clamp ceilings
  long ones, so every row is the same height. Full text on hover via title.
- Widened to 30rem (84 chars/line) and vertically centred. The clamp needs
  display:-webkit-box, which stacks lines from the top, so the span sits in a
  flex items-center wrapper rather than carrying the height itself.
- w-full min-w-0 max-w-[30rem] instead of a fixed w-96, so a narrow viewport shrinks the
  column and truncates rather than forcing the page to scroll sideways.
- The actions column loses its border-l separator and right-aligns the "Shared globally"
  badge, matching the rows that show buttons.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: icon-mono filled lucide outlines and missed currentColor brand marks

Two bugs in the monochrome utility, both from the fill rule being too blunt.

- Lucide icons are outlines: fill="none" with stroke="currentColor" and no fills on
  their children. Forcing fill on every descendant overrode that none and turned each
  glyph into a solid blob. The filled case is now scoped to svgs that do not declare
  fill="none", and svgs that do only get children redirected if they declare a real
  fill of their own — so a brand mark drawn as an outline still works.
- Brand marks that paint with currentColor carry their own text-[#hex] class, so
  redirecting fills left them branded: MQTT stayed #660066, NATS #375C93. The svg now
  inherits the container's colour, which is what actually makes them monochrome.

Also wires the sidebar's trigger section, which was never covered: those links render
through MenuLink, not the sub-item block that had the class.

Verified in the browser across all five shapes an icon can take — lucide outline,
hardcoded fill, currentColor plus brand class, outline root with filled children, and
inline style="fill:#..". Lucide keeps fill:none and a grey stroke; the rest follow the
container.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor: dedicated monochrome trigger icons instead of a CSS override

Reverts the trigger surfaces to the icons that were there before the brand-colour
audit, as ./icons/triggers/ variants. A trigger picker lists brand marks beside lucide
glyphs (Webhook, Route, Database), so a coloured mark reads as a different kind of
thing rather than a peer.

Ten variants, restored from main where they were already monochrome: Kafka, NATS, MQTT,
AMQP, AWS, Azure, Nextcloud, Google, GitHub. Google Cloud is the exception — main's copy
is a greyscale rendition rather than currentColor, so it is rebuilt from the current
four-colour artwork with the fills dropped.

Separate files rather than the CSS override that was there, because coercion cannot work
in general: forcing fills to currentColor breaks lucide's outline icons, which are
fill="none" with a stroke, and marks that set their own text-[#hex] class ignore a fill
rule entirely. Both bugs were live. The .icon-mono utility, ToggleButton's monochromeIcon
prop and the DropdownV2 menuClass pass-through are gone with it.

index.ts documents which folder to use where: ./triggers/ for trigger surfaces, the
full-colour mark for the resource picker, AppConnect and docs, and keep the two in sync.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: trigger pages and global search still used the colour brand marks

The ToggleButtonGroup on each trigger page pairs a brand icon with a lucide Code
glyph, so GCP Pub/Sub rendered Google's four-colour mark next to a monochrome one.
Kafka, NATS, MQTT and the rest had the same wiring; they were just less obvious
because their marks are near-monochrome already.

Repoints all seven trigger pages and the global search nav entries at the
./icons/triggers/ variants. RowIcon is left on the full-colour marks: table rows
show brand colour by design.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: restore the greyscale GCP trigger icon, and show variants in the gallery

The trigger variant had been flattened to currentColor, which collapses Google's cloud
into one flat silhouette and loses the tonal steps that give it shape. The pre-audit
icon was greyscale, not monochrome — #B0B0B0 / #D0D0D0 / #E0E0E0 / #FFFFFF — so it is
restored verbatim from main.

Also globs icons/**/*.svelte in /kitchen_sink/icons so trigger variants render next to
the full-colour marks they shadow, labelled by folder. Comparing the two is the thing
this page was missing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: flow trigger dropdown rows use the desaturated marks too

The flow-graph badge menu still rendered the full-colour brand marks next to
lucide glyphs. Route both dropdowns through triggerIconMapMono: the badge
itself keeps the colour mark, only the rows it opens change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat: rank resource-type search results by best match

Searching the description is what makes `gdrive` findable as "google", but it
also means "google" matches a dozen types that only mention the product in
passing. Rank a match on the type's own name above any description match, and
break ties on where the match starts, so `googleai` leads and a description
opening with "Google OAuth token..." beats one mentioning Google halfway
through.

Applied to all three resource-type searches: the Resource Types tab (whose bare
term also only searched the name until now), the add-resource drawer, and the
schema-narrowing picker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: trigger pages and global search show the full-colour marks

The desaturated variants belong to the two dense lists that sit beside lucide
glyphs -- the sidebar trigger list and the capture dropdown. Everywhere else a
brand mark stands on its own and should be the real one: the per-kind trigger
pages, the command palette, the capture table and the chat tool cards. Records
the rule in icons/index.ts so the next caller picks the right folder.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: address review round on the icon and resource-type work

- AppConnectInner went back to listResourceTypeNames for the list: /resources/type/list
  is not on the public app domain's route allow-list, so a published app's resource
  picker 403'd and, because the throw left connectsManual unset, stayed empty on every
  retry. Descriptions now load best-effort behind it.
- Dropped DropdownV2's menuClass: nothing passes it; the flow-graph badge menu styles
  melt's Menu, which has its own.
- icons/index.ts named two surfaces for the desaturated variants; there are four, and
  the flow-graph badge and the menu it opens differ. Dropped the stale GCloudIcon note.
- GoogleCloudIcon takes width/height again: generic call sites resolve it through
  APP_TO_ICON_COMPONENT and pass no size, so gcloud rendered at 16px after the remap.
- The path explainer is one ResourcePathHint component instead of the same copy twice.
- BRAND_COLORS.md recorded Ansible, Datadog, Deno, DeepL and Toggl as fixed; each
  publishes a second artwork swapped in by class, so their dark hex and ratio were
  wrong. Header no longer claims a generator that isn't in the repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: drop the duplicate gcloud icon and unblock the connect list

GCloudIcon.svelte was rewritten into the same four-colour mark as
GoogleCloudIcon.svelte and nothing pointed at it any more, so it was two files
drawing one logo waiting to drift apart.

The description fetch also sat on the critical path: the "Others" list showed
skeletons until a request for every type's full schema returned -- one that a
published app is guaranteed to get a 403 on. It now runs unawaited, and search
re-ranks when the descriptions land.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: AwsIcon and GoogleIcon take size again

The audit narrowed both to width/height with a 24px default, but every dynamic
call site passes size — RowIcon, the flow trigger badges, ToggleButton, global
search, the chat tool cards, the native-trigger page — so the SQS and Google
marks rendered at 24px wherever a smaller size was asked for. Both take size
again, keep width/height for the call sites that use those, and accept a class
so RowIcon's grey still applies.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: row-strip brand marks keep their colour

RowIcon greyed five of its seven brand marks with text-gray-400 while gcp and
azure rendered in colour. Now that AwsIcon accepts a class, the grey took its
wordmark but not its hardcoded #FF9900 smile, so the SQS row came out half
grey, half orange.

The rule this branch settled on is that only the four trigger menus desaturate;
a table is not one of them. Dropping the class from all five makes the strip
agree with the gcp and azure rows beside them, and with the lucide glyphs
staying grey since they carry no brand colour to keep.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 12:25:21 +02:00

323 lines
9.7 KiB
Svelte

<script lang="ts">
import type { Schema } from '$lib/common'
import type { Resource, ResourceType } from '$lib/gen'
import { onDestroy } from 'svelte'
import { setEditorUnparseable } from './pendingEditorFlush'
import { emptyString, isOwner, urlize } from '$lib/utils'
import { Alert, Skeleton } from './common'
import Path from './Path.svelte'
import LabelsInput from './LabelsInput.svelte'
import Required from './Required.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import SchemaForm from './SchemaForm.svelte'
import SimpleEditor from './SimpleEditor.svelte'
import FilesetEditor from './FilesetEditor.svelte'
import Toggle from './Toggle.svelte'
import TestConnection from './TestConnection.svelte'
import { Pen } from 'lucide-svelte'
import autosize from '$lib/autosize'
import GfmMarkdown from './GfmMarkdown.svelte'
import TestTriggerConnection from './triggers/TestTriggerConnection.svelte'
import GitHubAppIntegration from './GitHubAppIntegration.svelte'
import Button from './common/button/Button.svelte'
import ResourceGen from './copilot/ResourceGen.svelte'
import SyncResourceTypes from './SyncResourceTypes.svelte'
import Label from './Label.svelte'
import ResourcePathHint from './ResourcePathHint.svelte'
interface Props {
path: string
initialPath: string
hidePath?: boolean
labels: string[] | undefined
description: string
args: Record<string, any>
wsSpecific: boolean
isValid: boolean
viewJsonSchema: boolean
jsonError: string
deployTo: string | undefined
can_write: boolean
resource_type: string | undefined
resourceTypeInfo: ResourceType | undefined
resourceSchema: Schema | undefined
loadingSchema: boolean
resourceToEdit: Resource | undefined
onLoadResourceType?: () => void
/** Workspace the path is validated against and the connection is tested in;
* defaults to the nav workspace. */
workspace?: string | undefined
}
let {
path = $bindable(),
initialPath,
hidePath = false,
labels = $bindable(),
description = $bindable(),
args = $bindable(),
wsSpecific = $bindable(),
isValid = $bindable(),
viewJsonSchema = $bindable(),
jsonError = $bindable(),
deployTo,
can_write,
resource_type,
resourceTypeInfo,
resourceSchema,
loadingSchema,
resourceToEdit,
onLoadResourceType,
workspace = undefined
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let editDescription = $state(false)
let rawCode: string | undefined = $state(undefined)
let textFileContent: string = $state('')
// This field is a bare SimpleEditor parsed here, so it never passes through JsonEditor —
// it has to register itself, or a caller persisting what is on screen would save the
// last value that parsed and leave without the text in front of the user.
const unparseableKey = {}
onDestroy(() => setEditorUnparseable(unparseableKey, false))
function parseJson() {
try {
args = JSON.parse(rawCode ?? '')
jsonError = ''
} catch (e) {
jsonError = e.message
}
}
function parseTextFileContent() {
args = { content: textFileContent }
}
// The raw JSON editor is the active input whenever the "As JSON" toggle is on,
// or no schema-based form can be rendered (e.g. the resource type is missing
// from the workspace). In both cases rawCode must be seeded from args.
let usesRawEditor = $derived(
!loadingSchema &&
(viewJsonSchema ||
(!resourceTypeInfo?.is_fileset && !(resourceSchema && resourceSchema.properties)))
)
$effect(() => {
if (rawCode !== undefined) parseJson()
})
// Both halves, and from the current parse rather than from a transition: `rawCode`
// outlives the raw editor, so text that does not parse is the user's to fix exactly
// while that editor is the active input — which the schema loading and the resource
// type flip as well as the toggle, and only the toggle reseeds `rawCode`.
$effect(() => {
setEditorUnparseable(unparseableKey, usesRawEditor && jsonError !== '')
})
$effect(() => {
if (usesRawEditor && rawCode === undefined) {
rawCode = JSON.stringify(args, null, 2)
}
})
// Seed the JSON editor when the resource type schema is missing
// (restores the old ResourceEditor's catch-block behavior)
$effect(() => {
if (resource_type && !loadingSchema && !resourceSchema && rawCode === undefined) {
rawCode = JSON.stringify(args, null, 2)
}
})
$effect(() => {
if (textFileContent) parseTextFileContent()
})
$effect(() => {
if (resourceTypeInfo?.format_extension && !resourceTypeInfo?.is_fileset && !viewJsonSchema) {
textFileContent = args?.content ?? ''
}
})
</script>
{#if !emptyString(resourceTypeInfo?.description)}
<GfmMarkdown md={urlize(resourceTypeInfo?.description ?? '', 'md')} prose="sm" noPadding />
{/if}
{#if !hidePath}
<div>
{#if !can_write}
<div class="my-2">
<Alert type="warning" title="Only read access">
You only have read access to this resource and cannot edit it
</Alert>
</div>
{/if}
<Label label="Path">
<ResourcePathHint />
<Path
disabled={initialPath != '' && !isOwner(initialPath, $userStore, ws)}
bind:path
{initialPath}
namePlaceholder="resource"
kind="resource"
workspaceOverride={workspace}
/>
</Label>
</div>
{/if}
<LabelsInput bind:labels class="-mt-4" />
{#if deployTo}
<Label
label="Workspace specific"
tooltip="Prevents this resource from being deployed to prod/staging. When enabled, any variable referenced via $var: inside the resource value is also automatically marked workspace-specific. Disabling this toggle does not un-mark those variables — they may be referenced by other resources."
>
<Toggle bind:checked={wsSpecific} />
</Label>
{/if}
<div class="flex flex-col gap-1">
<h4 class="inline-flex items-center gap-2 text-xs text-emphasis font-semibold"
>Resource description <Required required={false} />
{#if can_write}
<Button
variant="subtle"
unifiedSize="xs"
btnClasses={editDescription ? 'bg-surface-hover' : ''}
startIcon={{ icon: Pen }}
on:click={() => (editDescription = !editDescription)}
/>
{/if}
</h4>
{#if can_write && editDescription}
<div class="relative">
<div class="text-2xs text-primary absolute -top-4 right-0">GH Markdown</div>
<textarea
class="text-xs text-primary font-normal"
disabled={!can_write}
use:autosize
bind:value={description}
placeholder="Describe what this resource is for"
></textarea>
</div>
{:else if description == undefined || description == ''}
<div class="text-xs text-secondary font-normal">No description provided</div>
{:else}
<GfmMarkdown md={description} prose="sm" noPadding />
{/if}
</div>
<div class="flex flex-col gap-1">
<div class="w-full flex gap-4 flex-row-reverse items-center">
<Toggle
bind:checked={viewJsonSchema}
on:change={(e) => {
if (e.detail) {
rawCode = JSON.stringify(args, null, 2)
} else if (resourceTypeInfo?.format_extension && !resourceTypeInfo?.is_fileset) {
textFileContent = args?.content ?? ''
}
}}
options={{
right: 'As JSON'
}}
/>
<ResourceGen
bind:args
resourceType={resource_type}
resourceName={path}
resourceDescription={description}
{resourceSchema}
/>
{#if resourceToEdit?.resource_type === 'nats' || resourceToEdit?.resource_type === 'kafka'}
<TestTriggerConnection kind={resourceToEdit?.resource_type} args={{ connection: args }} />
{:else}
<TestConnection
resourceType={resourceToEdit?.resource_type}
{args}
workspaceOverride={workspace}
/>
{/if}
{#if resource_type === 'git_repository' && $workspaceStore && ($userStore?.is_admin || $userStore?.is_super_admin)}
<GitHubAppIntegration
resourceType={resource_type}
{args}
{description}
onArgsUpdate={(newArgs) => {
args = newArgs
if (viewJsonSchema) {
rawCode = JSON.stringify(args, null, 2)
}
}}
onDescriptionUpdate={(newDescription) => (description = newDescription)}
/>
{/if}
</div>
<div>
{#if loadingSchema}
<Skeleton layout={[[4]]} />
{:else if !viewJsonSchema && resourceTypeInfo?.is_fileset}
<div class="mt-1 flex items-center gap-2">
<h5 class="inline-flex items-center gap-4">Fileset</h5>
<ResourceGen
bind:args
resourceType={resource_type}
resourceName={path}
resourceDescription={description}
{resourceSchema}
isFileset
/>
</div>
<FilesetEditor bind:args />
{:else if !viewJsonSchema && resourceSchema && resourceSchema?.properties}
{#if resourceTypeInfo?.format_extension}
<h5 class="mt-1 inline-flex items-center gap-4">
File content ({resourceTypeInfo.format_extension})
</h5>
<div class="">
<SimpleEditor
autoHeight
lang={resourceTypeInfo.format_extension}
bind:code={textFileContent}
fixedOverflowWidgets={false}
/>
</div>
{:else}
<SchemaForm
onlyMaskPassword
noDelete
disabled={!can_write}
compact
schema={resourceSchema}
bind:args
bind:isValid
{workspace}
/>
{/if}
{:else if !can_write}
<input type="text" disabled value={rawCode} />
{:else}
{#if !viewJsonSchema && !resourceSchema}
<div class="flex flex-col gap-2 mb-4">
<p class="text-red-500 dark:text-red-400 text-xs">
Resource type '{resource_type}' not found in your workspace
</p>
<SyncResourceTypes resourceType={resource_type} onSynced={() => onLoadResourceType?.()} />
<p class="italic text-secondary text-xs"> Define the value in JSON directly </p>
</div>
{/if}
{#if !emptyString(jsonError)}<span class="text-red-400 text-xs mb-1 flex flex-row-reverse"
>{jsonError}</span
>{:else}<div class="py-2"></div>{/if}
<div class="bg-surface-tertiary rounded-md border py-2.5">
<SimpleEditor autoHeight lang="json" bind:code={rawCode} />
</div>
{/if}
</div>
</div>