diff --git a/src/app/components/RoleCreate.svelte b/src/app/components/RoleCreate.svelte index d4ac2472..bc1afa71 100644 --- a/src/app/components/RoleCreate.svelte +++ b/src/app/components/RoleCreate.svelte @@ -24,7 +24,7 @@ loading = true try { - const error = await createRole(url, randomId(), label, description, color, 0) + const error = await createRole(url, randomId(), label, description, color, 1) if (error) { pushToast({theme: "error", message: error}) diff --git a/src/app/components/RoleForm.svelte b/src/app/components/RoleForm.svelte index 2e6342bc..65caaea3 100644 --- a/src/app/components/RoleForm.svelte +++ b/src/app/components/RoleForm.svelte @@ -1,5 +1,5 @@ @@ -26,29 +26,11 @@ description: initialValues.description ?? "", }) - // Preserve any unedited components from the existing color; the form edits hue and lightness. - const baseColor: SpaceRoleColor = { - hue: "", - saturation: "", - lightness: "", - ...initialValues.color, - } - - const initialHue = parseInt(baseColor.hue, 10) - const initialLightness = parseFloat(baseColor.lightness) - - let hue = $state(isNaN(initialHue) ? Math.floor(Math.random() * 360) : initialHue) - let lightness = $state(isNaN(initialLightness) ? 0.5 : initialLightness) - - const color = $derived({ - ...baseColor, - hue: String(hue), - lightness: String(lightness), - }) + let hue = $state(initialValues.color ?? Math.floor(Math.random() * 360)) const back = () => history.back() - const submit = () => onSubmit({...values, color}) + const submit = () => onSubmit({...values, color: hue})
@@ -76,25 +58,15 @@
-
-
- - + style="background-color: {roleColor(hue)}; border-color: var(--line)">
+
{/snippet} diff --git a/src/app/members.ts b/src/app/members.ts index d5e9b0c3..155fa042 100644 --- a/src/app/members.ts +++ b/src/app/members.ts @@ -22,6 +22,7 @@ import { import type {Filter, PublishedRoomMeta, TrustedEvent} from "@welshman/util" import {first, memoize, sortBy, spec, uniq} from "@welshman/lib" import {addRoomMember, manageRelay, pubkey, waitForThunkError} from "@welshman/app" +import {load} from "@welshman/net" import {get} from "svelte/store" import {deriveEventsForUrl, deriveRelaySignedEvents} from "@app/repository" @@ -32,60 +33,32 @@ export const deriveSpaceMembers = (url: string) => export const RELAY_ROLE = 33534 -// An hsl color tuple. Any component may be an empty string, in which case the -// client supplies its own default. Usually only `hue` is set per the spec. -export type SpaceRoleColor = { - hue: string // 0 to 360 - saturation: string // 0 to 1 - lightness: string // 0 to 1 -} - export type SpaceRole = { id: string label: string description: string - color: SpaceRoleColor + color: number // hue, 0 to 360 order: number } -// Defaults filled in for empty color components, chosen to read on both themes. +// Defaults for the components the client supplies, chosen to read on both themes. const DEFAULT_SATURATION = 0.7 const DEFAULT_LIGHTNESS = 0.5 -const roleColorValue = (value: string, fallback: number) => { - const parsed = parseFloat(value) +// Parse the hue from a ["color", hue] tag, falling back to 0. +export const parseRoleColor = (tags: string[][]): number => { + const hue = parseFloat(first(getTags("color", tags))?.[1] ?? "") - return isNaN(parsed) ? fallback : parsed + return isNaN(hue) ? 0 : hue } -// Parse the ["color", hue, saturation, lightness] tag, preserving empty values. -export const parseRoleColor = (tags: string[][]): SpaceRoleColor => { - const tag = first(getTags("color", tags)) ?? [] - - return { - hue: tag[1] ?? "", - saturation: tag[2] ?? "", - lightness: tag[3] ?? "", - } -} - -// Build an hsl() string from a role color, using the role's values or our defaults. -export const roleColor = (color: SpaceRoleColor) => { - const h = roleColorValue(color.hue, 0) - const s = roleColorValue(color.saturation, DEFAULT_SATURATION) - const l = roleColorValue(color.lightness, DEFAULT_LIGHTNESS) - - return `hsl(${h}, ${s * 100}%, ${l * 100}%)` -} +// Build an hsl() string from a role's hue, using our default saturation and lightness. +export const roleColor = (hue: number) => + `hsl(${hue}, ${DEFAULT_SATURATION * 100}%, ${DEFAULT_LIGHTNESS * 100}%)` // A translucent tint of the role color for use as a background fill. -export const roleColorSoft = (color: SpaceRoleColor) => { - const h = roleColorValue(color.hue, 0) - const s = roleColorValue(color.saturation, DEFAULT_SATURATION) - const l = roleColorValue(color.lightness, DEFAULT_LIGHTNESS) - - return `hsl(${h}, ${s * 100}%, ${l * 100}%, 0.15)` -} +export const roleColorSoft = (hue: number) => + `hsl(${hue}, ${DEFAULT_SATURATION * 100}%, ${DEFAULT_LIGHTNESS * 100}%, 0.15)` export const deriveSpaceRoles = (url: string) => derived(deriveRelaySignedEvents(url, [{kinds: [RELAY_ROLE]}]), $events => { @@ -100,7 +73,7 @@ export const deriveSpaceRoles = (url: string) => label: getTagValue("label", event.tags) ?? "", description: getTagValue("description", event.tags) ?? "", color: parseRoleColor(event.tags), - order: parseInt(getTagValue("order", event.tags) ?? "0", 10) || 0, + order: Math.max(1, parseInt(getTagValue("order", event.tags) ?? "1", 10) || 1), }) } } @@ -127,23 +100,29 @@ export const deriveSpaceMemberRoles = (url: string) => return memberRoles }) -// Flatten a color tuple into management params, mirroring the color tag's value order. -const roleColorParams = (color: SpaceRoleColor) => - [color.hue, color.saturation, color.lightness] as unknown as string +// Pull the relay's freshly-signed role event so the UI reflects the change immediately, +// rather than waiting for the live subscription to catch up. +const reloadRole = (url: string, id: string) => + load({relays: [url], filters: [{kinds: [RELAY_ROLE], "#d": [id]}]}) export const createRole = async ( url: string, id: string, label: string, description: string, - color: SpaceRoleColor, + color: number, order: number, ): Promise => { const {error} = await manageRelay(url, { method: "createrole" as ManagementMethod, - params: [id, label, description, roleColorParams(color), order.toString()], + // hue and order go over the wire as JSON numbers, not strings, per the relay's NIP-86 impl. + params: [id, label, description, color, order] as unknown as string[], }) + if (!error) { + await reloadRole(url, id) + } + return error } @@ -152,14 +131,19 @@ export const editRole = async ( id: string, label: string, description: string, - color: SpaceRoleColor, + color: number, order: number, ): Promise => { const {error} = await manageRelay(url, { method: "editrole" as ManagementMethod, - params: [id, label, description, roleColorParams(color), order.toString()], + // hue and order go over the wire as JSON numbers, not strings, per the relay's NIP-86 impl. + params: [id, label, description, color, order] as unknown as string[], }) + if (!error) { + await reloadRole(url, id) + } + return error }