@dxos/plugin-client 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -159,9 +159,9 @@ var InvitationQR = ({ id, url, onCancel }) => {
159
159
  className: "text-fg-muted",
160
160
  children: t("qr-code.description", { ns: meta.profile.key })
161
161
  }),
162
- /* @__PURE__ */ jsxs("div", {
162
+ /* @__PURE__ */ jsxs(Layout.Grid, {
163
163
  role: "group",
164
- className: "grid grid-cols-[1fr_min-content]",
164
+ cols: ["fill", "min"],
165
165
  children: [/* @__PURE__ */ jsx(Layout.Flex, {
166
166
  justify: "center",
167
167
  classNames: "py-4",
@@ -1 +1 @@
1
- {"version":3,"file":"chunk-DevicesContainer.mjs","names":[],"sources":["../../src/containers/DevicesContainer/DevicesContainer.tsx"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { useCallback, useEffect, useState } from 'react';\n\nimport * as Hooks from '@dxos/app-framework/Hooks';\nimport * as EffectEx from '@dxos/effect/EffectEx';\nimport { type Invitation } from '@dxos/halo';\nimport { useDevices, useInvitationFlow } from '@dxos/halo-react';\nimport { log } from '@dxos/log';\nimport { useClient } from '@dxos/react-client';\nimport { useNetworkStatus } from '@dxos/react-client/mesh';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as UiHooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport * as QrCode from '@dxos/react-ui/QrCode';\nimport * as SystemButton from '@dxos/react-ui/SystemButton';\nimport { AuthCode, Centered, Emoji, Viewport } from '@dxos/shell/react';\nimport { osTranslations } from '@dxos/ui-theme';\nimport { hexToEmoji } from '@dxos/util';\n\nimport { DevicesForm } from '#components';\nimport { meta } from '#meta';\nimport { ClientOperation } from '#operations';\nimport { ClientCapabilities, ClientOptions } from '#types';\n\nexport type DevicesContainerProps = Pick<ClientOptions.ClientPluginOptions, 'identityTestActions'> & {\n createInvitationUrl?: (invitationCode: string) => string;\n};\n\nexport const DevicesContainer = ({ createInvitationUrl, identityTestActions }: DevicesContainerProps) => {\n const { invokePromise } = Hooks.useOperationInvoker();\n const devices = useDevices();\n const { swarm: connectionState } = useNetworkStatus();\n\n const handleLogout = useCallback(() => invokePromise(ClientOperation.ResetStorage, {}), [invokePromise]);\n\n const handleRecover = useCallback(\n () => invokePromise(ClientOperation.ResetStorage, { mode: 'recover' }),\n [invokePromise],\n );\n\n const handleJoinNewIdentity = useCallback(\n () => invokePromise(ClientOperation.ResetStorage, { mode: 'join-new-identity' }),\n [invokePromise],\n );\n\n return (\n <DevicesForm\n devices={devices}\n connectionState={connectionState}\n invitation={createInvitationUrl && <DeviceInvitation createInvitationUrl={createInvitationUrl} />}\n onLogout={handleLogout}\n onRecover={identityTestActions ? handleRecover : undefined}\n onJoinNewIdentity={identityTestActions ? handleJoinNewIdentity : undefined}\n />\n );\n};\n\ntype DeviceInvitationProps = {\n flow?: Invitation.Flow;\n createInvitationUrl: (invitationCode: string) => string;\n onInvitationDone: () => void;\n onInvitationCreate: () => void;\n};\n\nconst DeviceInvitation = (props: Pick<DeviceInvitationProps, 'createInvitationUrl'>) => {\n // `client.config` only — the network status above keeps this container on the client regardless\n // (Missing API 9). The gate matters: an invitation code in a production console is a live secret.\n const client = useClient();\n const [identityService] = Hooks.useCapabilities(ClientCapabilities.IdentityService);\n const [flow, setFlow] = useState<Invitation.Flow>();\n // Latched before the share resolves, so a second click cannot open a second live invitation.\n const [pending, setPending] = useState(false);\n\n const onInvitationCreate = useCallback(() => {\n if (!identityService || pending || flow) {\n return;\n }\n setPending(true);\n // Requested explicitly because `share()` defaults to no authentication, which would leave the\n // invitation code as the only factor guarding an identity.\n void EffectEx.runPromise(identityService.share({ authMethod: 'shared-secret' }))\n .then(async (created) => {\n // Playwright reads this line off the console to drive the device-invitation flows.\n if (client.config.values.runtime?.app?.env?.DX_ENVIRONMENT !== 'production') {\n log.info(JSON.stringify({ invitationCode: await EffectEx.runPromise(created.code) }));\n }\n setFlow(created);\n })\n .catch((err) => log.catch(err))\n .finally(() => setPending(false));\n }, [client, identityService, pending, flow]);\n\n const onInvitationDone = useCallback(() => {\n // Cancel before dropping the handle: clearing local state alone leaves the host side listening.\n if (flow) {\n void EffectEx.runPromise(flow.cancel()).catch((err) => log.catch(err));\n }\n setFlow(undefined);\n }, [flow]);\n\n if (flow) {\n return <DeviceInvitationImpl {...props} {...{ flow, onInvitationCreate, onInvitationDone }} />;\n } else {\n return <InvitationSection {...props} {...{ onInvitationCreate, onInvitationDone }} />;\n }\n};\n\nconst DeviceInvitationImpl = ({\n flow,\n createInvitationUrl,\n onInvitationDone,\n onInvitationCreate,\n}: DeviceInvitationProps) => {\n const client = useClient();\n const { event, code } = useInvitationFlow(flow);\n const url = code && createInvitationUrl(code);\n\n // Logged separately from the invitation code Playwright reads on creation, because the host only\n // learns the auth code once a guest has connected.\n useEffect(() => {\n if (\n event?._tag === 'readyForAuthentication' &&\n client.config.values.runtime?.app?.env?.DX_ENVIRONMENT !== 'production'\n ) {\n log.info(JSON.stringify({ authCode: event.authCode }));\n }\n }, [event?._tag]);\n\n // Every terminal event returns to the creation view; parking on the completion icon would strand\n // a failed or cancelled invitation with no way to retry.\n useEffect(() => {\n if (event && (event._tag === 'success' || event._tag === 'cancelled' || event._tag === 'error')) {\n onInvitationDone();\n }\n }, [event?._tag]);\n\n return <InvitationSection {...{ event, invitationId: flow?.id, url, onInvitationDone, onInvitationCreate }} />;\n};\n\ntype InvitationComponentProps = Partial<\n Pick<DeviceInvitationProps, 'onInvitationDone' | 'onInvitationCreate'> & {\n event: Invitation.Event;\n invitationId: string;\n url: string;\n }\n>;\n\nconst InvitationSection = ({\n event,\n invitationId = 'never',\n url = 'never',\n onInvitationDone = () => {},\n onInvitationCreate = () => {},\n}: InvitationComponentProps) => {\n const { t } = UiHooks.useTranslation(meta.profile.key);\n const authCode = event?._tag === 'readyForAuthentication' ? event.authCode : undefined;\n const activeView = !event\n ? 'init'\n : event._tag === 'cancelled' || event._tag === 'error' || event._tag === 'success'\n ? 'complete'\n : authCode\n ? 'auth-code'\n : 'qr-code';\n\n return activeView === 'init' ? (\n <>\n <p className='text-fg-muted mb-2'>{t('add-device.description')}</p>\n <Button.Root\n icon='ph--plus--regular'\n label={t('create-device-invitation.label')}\n disabled={!!event}\n classNames='w-full'\n data-testid='devicesContainer.createInvitation'\n onClick={onInvitationCreate}\n />\n </>\n ) : (\n <Viewport.Root activeView={activeView}>\n <Viewport.Views>\n <Viewport.View id='init'>\n {/* This view intentionally left blank while conditionally rendering the viewport. */}\n </Viewport.View>\n <Viewport.View id='complete'>\n <InvitationComplete succeeded={event?._tag === 'success'} />\n </Viewport.View>\n <Viewport.View id='auth-code'>\n <InvitationAuthCode id={invitationId} code={authCode ?? 'never'} onCancel={onInvitationDone} />\n </Viewport.View>\n <Viewport.View id='qr-code'>\n <InvitationQR id={invitationId} url={url} onCancel={onInvitationDone} />\n </Viewport.View>\n </Viewport.Views>\n </Viewport.Root>\n );\n};\n\nconst InvitationQR = ({ id, url, onCancel }: { id: string; url: string; onCancel: () => void }) => {\n const { t } = UiHooks.useTranslation(osTranslations);\n const qrLabel = UiHooks.useId('devices-container__qr-code');\n const emoji = hexToEmoji(id);\n return (\n <>\n <p className='text-fg-muted'>{t('qr-code.description', { ns: meta.profile.key })}</p>\n <div role='group' className='grid grid-cols-[1fr_min-content]'>\n <Layout.Flex justify='center' classNames='py-4'>\n <div className='w-full md:max-w-80 aspect-square relative text-fg-muted'>\n <QrCode.QrCode aria-labelledby={qrLabel} errorCorrection='Q' value={url ?? 'never'} />\n <Centered>\n <Emoji text={emoji} />\n </Centered>\n </div>\n </Layout.Flex>\n <span id={qrLabel} className='sr-only'>\n {t('qr.label')}\n </span>\n </div>\n {/* TODO(burdon): Factor out button bar */}\n <Layout.Flex justify='center'>\n <Layout.Flex gap='sm'>\n <SystemButton.Clipboard value={url ?? 'never'} />\n <Button.Root variant='ghost' onClick={onCancel}>\n {t('cancel.label')}\n </Button.Root>\n </Layout.Flex>\n </Layout.Flex>\n </>\n );\n};\n\nconst InvitationAuthCode = ({ id, code, onCancel }: { id: string; code: string; onCancel: () => void }) => {\n const { t } = UiHooks.useTranslation(osTranslations);\n const emoji = hexToEmoji(id);\n\n return (\n <>\n <p className='text-fg-muted'>{t('auth-other-device-emoji.message')}</p>\n {emoji && <Emoji text={emoji} className='mx-auto my-2 text-center' />}\n <p className='text-fg-muted'>{t('auth-code.message')}</p>\n <AuthCode code={code} large classNames='mx-auto my-2 text-center grow' />\n <Button.Root variant='ghost' onClick={onCancel}>\n {t('cancel.label')}\n </Button.Root>\n </>\n );\n};\n\nconst InvitationComplete = ({ succeeded }: { succeeded: boolean }) => {\n return succeeded ? (\n <Icon.Icon icon='ph--check--regular' size='xl' classNames='m-trim-xs' />\n ) : (\n <Icon.Icon icon='ph--x--regular' size='xl' classNames='m-trim-xs' />\n );\n};\n\nDevicesContainer.displayName = 'DevicesContainer';\n"],"mappings":"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"}
1
+ {"version":3,"file":"chunk-DevicesContainer.mjs","names":[],"sources":["../../src/containers/DevicesContainer/DevicesContainer.tsx"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { useCallback, useEffect, useState } from 'react';\n\nimport * as Hooks from '@dxos/app-framework/Hooks';\nimport * as EffectEx from '@dxos/effect/EffectEx';\nimport { type Invitation } from '@dxos/halo';\nimport { useDevices, useInvitationFlow } from '@dxos/halo-react';\nimport { log } from '@dxos/log';\nimport { useClient } from '@dxos/react-client';\nimport { useNetworkStatus } from '@dxos/react-client/mesh';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as UiHooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport * as QrCode from '@dxos/react-ui/QrCode';\nimport * as SystemButton from '@dxos/react-ui/SystemButton';\nimport { AuthCode, Centered, Emoji, Viewport } from '@dxos/shell/react';\nimport { osTranslations } from '@dxos/ui-theme';\nimport { hexToEmoji } from '@dxos/util';\n\nimport { DevicesForm } from '#components';\nimport { meta } from '#meta';\nimport { ClientOperation } from '#operations';\nimport { ClientCapabilities, ClientOptions } from '#types';\n\nexport type DevicesContainerProps = Pick<ClientOptions.ClientPluginOptions, 'identityTestActions'> & {\n createInvitationUrl?: (invitationCode: string) => string;\n};\n\nexport const DevicesContainer = ({ createInvitationUrl, identityTestActions }: DevicesContainerProps) => {\n const { invokePromise } = Hooks.useOperationInvoker();\n const devices = useDevices();\n const { swarm: connectionState } = useNetworkStatus();\n\n const handleLogout = useCallback(() => invokePromise(ClientOperation.ResetStorage, {}), [invokePromise]);\n\n const handleRecover = useCallback(\n () => invokePromise(ClientOperation.ResetStorage, { mode: 'recover' }),\n [invokePromise],\n );\n\n const handleJoinNewIdentity = useCallback(\n () => invokePromise(ClientOperation.ResetStorage, { mode: 'join-new-identity' }),\n [invokePromise],\n );\n\n return (\n <DevicesForm\n devices={devices}\n connectionState={connectionState}\n invitation={createInvitationUrl && <DeviceInvitation createInvitationUrl={createInvitationUrl} />}\n onLogout={handleLogout}\n onRecover={identityTestActions ? handleRecover : undefined}\n onJoinNewIdentity={identityTestActions ? handleJoinNewIdentity : undefined}\n />\n );\n};\n\ntype DeviceInvitationProps = {\n flow?: Invitation.Flow;\n createInvitationUrl: (invitationCode: string) => string;\n onInvitationDone: () => void;\n onInvitationCreate: () => void;\n};\n\nconst DeviceInvitation = (props: Pick<DeviceInvitationProps, 'createInvitationUrl'>) => {\n // `client.config` only — the network status above keeps this container on the client regardless\n // (Missing API 9). The gate matters: an invitation code in a production console is a live secret.\n const client = useClient();\n const [identityService] = Hooks.useCapabilities(ClientCapabilities.IdentityService);\n const [flow, setFlow] = useState<Invitation.Flow>();\n // Latched before the share resolves, so a second click cannot open a second live invitation.\n const [pending, setPending] = useState(false);\n\n const onInvitationCreate = useCallback(() => {\n if (!identityService || pending || flow) {\n return;\n }\n setPending(true);\n // Requested explicitly because `share()` defaults to no authentication, which would leave the\n // invitation code as the only factor guarding an identity.\n void EffectEx.runPromise(identityService.share({ authMethod: 'shared-secret' }))\n .then(async (created) => {\n // Playwright reads this line off the console to drive the device-invitation flows.\n if (client.config.values.runtime?.app?.env?.DX_ENVIRONMENT !== 'production') {\n log.info(JSON.stringify({ invitationCode: await EffectEx.runPromise(created.code) }));\n }\n setFlow(created);\n })\n .catch((err) => log.catch(err))\n .finally(() => setPending(false));\n }, [client, identityService, pending, flow]);\n\n const onInvitationDone = useCallback(() => {\n // Cancel before dropping the handle: clearing local state alone leaves the host side listening.\n if (flow) {\n void EffectEx.runPromise(flow.cancel()).catch((err) => log.catch(err));\n }\n setFlow(undefined);\n }, [flow]);\n\n if (flow) {\n return <DeviceInvitationImpl {...props} {...{ flow, onInvitationCreate, onInvitationDone }} />;\n } else {\n return <InvitationSection {...props} {...{ onInvitationCreate, onInvitationDone }} />;\n }\n};\n\nconst DeviceInvitationImpl = ({\n flow,\n createInvitationUrl,\n onInvitationDone,\n onInvitationCreate,\n}: DeviceInvitationProps) => {\n const client = useClient();\n const { event, code } = useInvitationFlow(flow);\n const url = code && createInvitationUrl(code);\n\n // Logged separately from the invitation code Playwright reads on creation, because the host only\n // learns the auth code once a guest has connected.\n useEffect(() => {\n if (\n event?._tag === 'readyForAuthentication' &&\n client.config.values.runtime?.app?.env?.DX_ENVIRONMENT !== 'production'\n ) {\n log.info(JSON.stringify({ authCode: event.authCode }));\n }\n }, [event?._tag]);\n\n // Every terminal event returns to the creation view; parking on the completion icon would strand\n // a failed or cancelled invitation with no way to retry.\n useEffect(() => {\n if (event && (event._tag === 'success' || event._tag === 'cancelled' || event._tag === 'error')) {\n onInvitationDone();\n }\n }, [event?._tag]);\n\n return <InvitationSection {...{ event, invitationId: flow?.id, url, onInvitationDone, onInvitationCreate }} />;\n};\n\ntype InvitationComponentProps = Partial<\n Pick<DeviceInvitationProps, 'onInvitationDone' | 'onInvitationCreate'> & {\n event: Invitation.Event;\n invitationId: string;\n url: string;\n }\n>;\n\nconst InvitationSection = ({\n event,\n invitationId = 'never',\n url = 'never',\n onInvitationDone = () => {},\n onInvitationCreate = () => {},\n}: InvitationComponentProps) => {\n const { t } = UiHooks.useTranslation(meta.profile.key);\n const authCode = event?._tag === 'readyForAuthentication' ? event.authCode : undefined;\n const activeView = !event\n ? 'init'\n : event._tag === 'cancelled' || event._tag === 'error' || event._tag === 'success'\n ? 'complete'\n : authCode\n ? 'auth-code'\n : 'qr-code';\n\n return activeView === 'init' ? (\n <>\n <p className='text-fg-muted mb-2'>{t('add-device.description')}</p>\n <Button.Root\n icon='ph--plus--regular'\n label={t('create-device-invitation.label')}\n disabled={!!event}\n classNames='w-full'\n data-testid='devicesContainer.createInvitation'\n onClick={onInvitationCreate}\n />\n </>\n ) : (\n <Viewport.Root activeView={activeView}>\n <Viewport.Views>\n <Viewport.View id='init'>\n {/* This view intentionally left blank while conditionally rendering the viewport. */}\n </Viewport.View>\n <Viewport.View id='complete'>\n <InvitationComplete succeeded={event?._tag === 'success'} />\n </Viewport.View>\n <Viewport.View id='auth-code'>\n <InvitationAuthCode id={invitationId} code={authCode ?? 'never'} onCancel={onInvitationDone} />\n </Viewport.View>\n <Viewport.View id='qr-code'>\n <InvitationQR id={invitationId} url={url} onCancel={onInvitationDone} />\n </Viewport.View>\n </Viewport.Views>\n </Viewport.Root>\n );\n};\n\nconst InvitationQR = ({ id, url, onCancel }: { id: string; url: string; onCancel: () => void }) => {\n const { t } = UiHooks.useTranslation(osTranslations);\n const qrLabel = UiHooks.useId('devices-container__qr-code');\n const emoji = hexToEmoji(id);\n return (\n <>\n <p className='text-fg-muted'>{t('qr-code.description', { ns: meta.profile.key })}</p>\n <Layout.Grid role='group' cols={['fill', 'min']}>\n <Layout.Flex justify='center' classNames='py-4'>\n <div className='w-full md:max-w-80 aspect-square relative text-fg-muted'>\n <QrCode.QrCode aria-labelledby={qrLabel} errorCorrection='Q' value={url ?? 'never'} />\n <Centered>\n <Emoji text={emoji} />\n </Centered>\n </div>\n </Layout.Flex>\n <span id={qrLabel} className='sr-only'>\n {t('qr.label')}\n </span>\n </Layout.Grid>\n {/* TODO(burdon): Factor out button bar */}\n <Layout.Flex justify='center'>\n <Layout.Flex gap='sm'>\n <SystemButton.Clipboard value={url ?? 'never'} />\n <Button.Root variant='ghost' onClick={onCancel}>\n {t('cancel.label')}\n </Button.Root>\n </Layout.Flex>\n </Layout.Flex>\n </>\n );\n};\n\nconst InvitationAuthCode = ({ id, code, onCancel }: { id: string; code: string; onCancel: () => void }) => {\n const { t } = UiHooks.useTranslation(osTranslations);\n const emoji = hexToEmoji(id);\n\n return (\n <>\n <p className='text-fg-muted'>{t('auth-other-device-emoji.message')}</p>\n {emoji && <Emoji text={emoji} className='mx-auto my-2 text-center' />}\n <p className='text-fg-muted'>{t('auth-code.message')}</p>\n <AuthCode code={code} large classNames='mx-auto my-2 text-center grow' />\n <Button.Root variant='ghost' onClick={onCancel}>\n {t('cancel.label')}\n </Button.Root>\n </>\n );\n};\n\nconst InvitationComplete = ({ succeeded }: { succeeded: boolean }) => {\n return succeeded ? (\n <Icon.Icon icon='ph--check--regular' size='xl' classNames='m-trim-xs' />\n ) : (\n <Icon.Icon icon='ph--x--regular' size='xl' classNames='m-trim-xs' />\n );\n};\n\nDevicesContainer.displayName = 'DevicesContainer';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,oBAAoB,EAAE,qBAAqB,0BAAiD;CACvG,MAAM,EAAE,kBAAkB,MAAM,oBAAoB;CACpD,MAAM,UAAU,WAAW;CAC3B,MAAM,EAAE,OAAO,oBAAoB,iBAAiB;CAEpD,MAAM,eAAe,kBAAkB,cAAc,gBAAgB,cAAc,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC;CAEvG,MAAM,gBAAgB,kBACd,cAAc,gBAAgB,cAAc,EAAE,MAAM,UAAU,CAAC,GACrE,CAAC,aAAa,CAChB;CAEA,MAAM,wBAAwB,kBACtB,cAAc,gBAAgB,cAAc,EAAE,MAAM,oBAAoB,CAAC,GAC/E,CAAC,aAAa,CAChB;CAEA,OACE,oBAAC,aAAD;EACW;EACQ;EACjB,YAAY,uBAAuB,oBAAC,kBAAD,EAAuC,oBAAsB,CAAA;EAChG,UAAU;EACV,WAAW,sBAAsB,gBAAgB,KAAA;EACjD,mBAAmB,sBAAsB,wBAAwB,KAAA;CAClE,CAAA;AAEL;AASA,IAAM,oBAAoB,UAA8D;CAGtF,MAAM,SAAS,UAAU;CACzB,MAAM,CAAC,mBAAmB,MAAM,gBAAgB,mBAAmB,eAAe;CAClF,MAAM,CAAC,MAAM,WAAW,SAA0B;CAElD,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAE5C,MAAM,qBAAqB,kBAAkB;EAC3C,IAAI,CAAC,mBAAmB,WAAW,MACjC;EAEF,WAAW,IAAI;EAGf,SAAc,WAAW,gBAAgB,MAAM,EAAE,YAAY,gBAAgB,CAAC,CAAC,CAAA,CAC5E,KAAK,OAAO,YAAY;GAEvB,IAAI,OAAO,OAAO,OAAO,SAAS,KAAK,KAAK,mBAAmB,cAC7D,IAAI,KAAK,KAAK,UAAU,EAAE,gBAAgB,MAAM,SAAS,WAAW,QAAQ,IAAI,EAAE,CAAC,GAAA,KAAA,GAAA;IAAA,YAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA,KAAA;GAAA,CAAC;GAEtF,QAAQ,OAAO;EACjB,CAAC,CAAA,CACA,OAAO,QAAQ,IAAI,MAAM,KAAE,KAAA,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA,KAAA;EAAA,CAAC,CAAC,CAAA,CAC7B,cAAc,WAAW,KAAK,CAAC;CACpC,GAAG;EAAC;EAAQ;EAAiB;EAAS;CAAI,CAAC;CAE3C,MAAM,mBAAmB,kBAAkB;EAEzC,IAAI,MACF,SAAc,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,QAAQ,IAAI,MAAM,KAAE,KAAA,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA,KAAA;EAAA,CAAC,CAAC;EAEvE,QAAQ,KAAA,CAAS;CACnB,GAAG,CAAC,IAAI,CAAC;CAET,IAAI,MACF,OAAO,oBAAC,sBAAD;EAAsB,GAAI;EAAa;EAAM;EAAoB;CAAqB,CAAA;MAE7F,OAAO,oBAAC,mBAAD;EAAmB,GAAI;EAAa;EAAoB;CAAqB,CAAA;AAExF;AAEA,IAAM,wBAAwB,EAC5B,MACA,qBACA,kBACA,yBAC2B;CAC3B,MAAM,SAAS,UAAU;CACzB,MAAM,EAAE,OAAO,SAAS,kBAAkB,IAAI;CAC9C,MAAM,MAAM,QAAQ,oBAAoB,IAAI;CAI5C,gBAAgB;EACd,IACE,OAAO,SAAS,4BAChB,OAAO,OAAO,OAAO,SAAS,KAAK,KAAK,mBAAmB,cAE3D,IAAI,KAAK,KAAK,UAAU,EAAE,UAAU,MAAM,SAAS,CAAC,GAAA,KAAA,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA,KAAA;EAAA,CAAC;CAEzD,GAAG,CAAC,OAAO,IAAI,CAAC;CAIhB,gBAAgB;EACd,IAAI,UAAU,MAAM,SAAS,aAAa,MAAM,SAAS,eAAe,MAAM,SAAS,UACrF,iBAAiB;CAErB,GAAG,CAAC,OAAO,IAAI,CAAC;CAEhB,OAAO,oBAAC,mBAAD;EAAyB;EAAO,cAAc,MAAM;EAAI;EAAK;EAAkB;CAAuB,CAAA;AAC/G;AAUA,IAAM,qBAAqB,EACzB,OACA,eAAe,SACf,MAAM,SACN,yBAAyB,CAAC,GAC1B,2BAA2B,CAAC,QACE;CAC9B,MAAM,EAAE,MAAM,QAAQ,eAAe,KAAK,QAAQ,GAAG;CACrD,MAAM,WAAW,OAAO,SAAS,2BAA2B,MAAM,WAAW,KAAA;CAC7E,MAAM,aAAa,CAAC,QAChB,SACA,MAAM,SAAS,eAAe,MAAM,SAAS,WAAW,MAAM,SAAS,YACrE,aACA,WACE,cACA;CAER,OAAO,eAAe,SACpB,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,KAAD;EAAG,WAAU;EAAsB,UAAA,EAAE,wBAAwB;CAAK,CAAA,GAClE,oBAAC,OAAO,MAAR;EACE,MAAK;EACL,OAAO,EAAE,gCAAgC;EACzC,UAAU,CAAC,CAAC;EACZ,YAAW;EACX,eAAY;EACZ,SAAS;CACV,CAAA,CACD,EAAA,CAAA,IAEF,oBAAC,SAAS,MAAV;EAA2B;EACzB,UAAA,qBAAC,SAAS,OAAV,EAAA,UAAA;GACE,oBAAC,SAAS,MAAV,EAAe,IAAG,OAEH,CAAA;GACf,oBAAC,SAAS,MAAV;IAAe,IAAG;IAChB,UAAA,oBAAC,oBAAD,EAAoB,WAAW,OAAO,SAAS,UAAY,CAAA;GAC9C,CAAA;GACf,oBAAC,SAAS,MAAV;IAAe,IAAG;IAChB,UAAA,oBAAC,oBAAD;KAAoB,IAAI;KAAc,MAAM,YAAY;KAAS,UAAU;IAAmB,CAAA;GACjF,CAAA;GACf,oBAAC,SAAS,MAAV;IAAe,IAAG;IAChB,UAAA,oBAAC,cAAD;KAAc,IAAI;KAAmB;KAAK,UAAU;IAAmB,CAAA;GAC1D,CAAA;EACD,EAAA,CAAA;CACH,CAAA;AAEnB;AAEA,IAAM,gBAAgB,EAAE,IAAI,KAAK,eAAkE;CACjG,MAAM,EAAE,MAAM,QAAQ,eAAe,cAAc;CACnD,MAAM,UAAU,QAAQ,MAAM,4BAA4B;CAC1D,MAAM,QAAQ,WAAW,EAAE;CAC3B,OACE,qBAAA,UAAA,EAAA,UAAA;EACE,oBAAC,KAAD;GAAG,WAAU;GAAiB,UAAA,EAAE,uBAAuB,EAAE,IAAI,KAAK,QAAQ,IAAI,CAAC;EAAK,CAAA;EACpF,qBAAC,OAAO,MAAR;GAAa,MAAK;GAAQ,MAAM,CAAC,QAAQ,KAAK;GAA9C,UAAA,CACE,oBAAC,OAAO,MAAR;IAAa,SAAQ;IAAS,YAAW;IACvC,UAAA,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,oBAAC,OAAO,QAAR;MAAe,mBAAiB;MAAS,iBAAgB;MAAI,OAAO,OAAO;KAAU,CAAA,GACrF,oBAAC,UAAD,EAAA,UACE,oBAAC,OAAD,EAAO,MAAM,MAAQ,CAAA,EACb,CAAA,CACP;;GACM,CAAA,GACb,oBAAC,QAAD;IAAM,IAAI;IAAS,WAAU;IAC1B,UAAA,EAAE,UAAU;GACT,CAAA,CACK;;EAEb,oBAAC,OAAO,MAAR;GAAa,SAAQ;GACnB,UAAA,qBAAC,OAAO,MAAR;IAAa,KAAI;IAAjB,UAAA,CACE,oBAAC,aAAa,WAAd,EAAwB,OAAO,OAAO,QAAU,CAAA,GAChD,oBAAC,OAAO,MAAR;KAAa,SAAQ;KAAQ,SAAS;KACnC,UAAA,EAAE,cAAc;IACN,CAAA,CACF;;EACF,CAAA;CACb,EAAA,CAAA;AAEN;AAEA,IAAM,sBAAsB,EAAE,IAAI,MAAM,eAAmE;CACzG,MAAM,EAAE,MAAM,QAAQ,eAAe,cAAc;CACnD,MAAM,QAAQ,WAAW,EAAE;CAE3B,OACE,qBAAA,UAAA,EAAA,UAAA;EACE,oBAAC,KAAD;GAAG,WAAU;GAAiB,UAAA,EAAE,iCAAiC;EAAK,CAAA;EACrE,SAAS,oBAAC,OAAD;GAAO,MAAM;GAAO,WAAU;EAA4B,CAAA;EACpE,oBAAC,KAAD;GAAG,WAAU;GAAiB,UAAA,EAAE,mBAAmB;EAAK,CAAA;EACxD,oBAAC,UAAD;GAAgB;GAAM,OAAA;GAAM,YAAW;EAAiC,CAAA;EACxE,oBAAC,OAAO,MAAR;GAAa,SAAQ;GAAQ,SAAS;GACnC,UAAA,EAAE,cAAc;EACN,CAAA;CACb,EAAA,CAAA;AAEN;AAEA,IAAM,sBAAsB,EAAE,gBAAwC;CACpE,OAAO,YACL,oBAAC,KAAK,MAAN;EAAW,MAAK;EAAqB,MAAK;EAAK,YAAW;CAAa,CAAA,IAEvE,oBAAC,KAAK,MAAN;EAAW,MAAK;EAAiB,MAAK;EAAK,YAAW;CAAa,CAAA;AAEvE;AAEA,iBAAiB,cAAc"}