@crockery/fellowship-components 0.1.0 → 0.3.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.
- package/README.md +7 -2
- package/RELEASING.md +9 -45
- package/dist/chunks/data-source-C-Gs7kRl.js +2 -0
- package/dist/chunks/decorate-B_ZTN9li.js.map +1 -1
- package/dist/chunks/dungeon-cast-browser-Cyh519NI.js +705 -0
- package/dist/chunks/dungeon-cast-browser-Cyh519NI.js.map +1 -0
- package/dist/chunks/{dungeon-map-IPN6sEDj.js → dungeon-map-xFaPasVr.js} +113 -138
- package/dist/chunks/dungeon-map-xFaPasVr.js.map +1 -0
- package/dist/chunks/{talent-calculator-Ch1Pbbba.js → talent-calculator-CIPNbqK5.js} +84 -114
- package/dist/chunks/talent-calculator-CIPNbqK5.js.map +1 -0
- package/dist/data/cast-data-source.d.ts +5 -0
- package/dist/data/cast-data-source.d.ts.map +1 -0
- package/dist/data/data-source.d.ts +5 -16
- package/dist/data/data-source.d.ts.map +1 -1
- package/dist/data/package-cast-data-source.d.ts +8 -0
- package/dist/data/package-cast-data-source.d.ts.map +1 -0
- package/dist/data/package-data-source.d.ts +3 -1
- package/dist/data/package-data-source.d.ts.map +1 -1
- package/dist/data/package-talent-data-source.d.ts +2 -0
- package/dist/data/package-talent-data-source.d.ts.map +1 -1
- package/dist/data/talent-data-source.d.ts +4 -10
- package/dist/data/talent-data-source.d.ts.map +1 -1
- package/dist/dungeon-cast-browser/dungeon-cast-browser.d.ts +36 -0
- package/dist/dungeon-cast-browser/dungeon-cast-browser.d.ts.map +1 -0
- package/dist/dungeon-cast-browser/dungeon-cast-browser.styles.d.ts +2 -0
- package/dist/dungeon-cast-browser/dungeon-cast-browser.styles.d.ts.map +1 -0
- package/dist/dungeon-cast-browser/index.d.ts +4 -0
- package/dist/dungeon-cast-browser/index.d.ts.map +1 -0
- package/dist/dungeon-cast-browser/render-model.d.ts +5 -0
- package/dist/dungeon-cast-browser/render-model.d.ts.map +1 -0
- package/dist/dungeon-cast-browser/types.d.ts +60 -0
- package/dist/dungeon-cast-browser/types.d.ts.map +1 -0
- package/dist/dungeon-cast-browser.js +7 -0
- package/dist/dungeon-cast-browser.js.map +1 -0
- package/dist/dungeon-map.js +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -5
- package/dist/react/dungeon-cast-browser.d.ts +12 -0
- package/dist/react/dungeon-cast-browser.d.ts.map +1 -0
- package/dist/react/dungeon-cast-browser.js +19 -0
- package/dist/react/dungeon-cast-browser.js.map +1 -0
- package/dist/react/dungeon-map.js +1 -1
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/talent-calculator.js +1 -1
- package/dist/react.js +4 -3
- package/dist/talent-calculator.js +1 -1
- package/package.json +35 -25
- package/src/dungeon-cast-browser/README.md +22 -0
- package/dist/chunks/data-source-CoIFvV72.js +0 -10
- package/dist/chunks/data-source-CoIFvV72.js.map +0 -1
- package/dist/chunks/dungeon-map-IPN6sEDj.js.map +0 -1
- package/dist/chunks/talent-calculator-Ch1Pbbba.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dungeon-cast-browser-Cyh519NI.js","names":["#abortController","#loadDungeonCasts","#renderStatus","#visibleEnemies","#handleSearch","#renderEnemy","#renderEmptySearch","#loadToken","#sameIdentity","#renderCast","#selectCast","#handleCastKeydown","#handleAbilityImageError","#statusCard"],"sources":["../../src/data/package-cast-data-source.ts","../../src/dungeon-cast-browser/dungeon-cast-browser.styles.ts","../../src/dungeon-cast-browser/render-model.ts","../../src/dungeon-cast-browser/dungeon-cast-browser.ts"],"sourcesContent":["import { createPackageFellowshipDataClient } from \"@crockery/fellowship-data/client\";\n\nimport type { FellowshipCastDataSource } from \"./cast-data-source.js\";\n\n/** @deprecated Create a package Fellowship data client instead. */\nexport interface PackageFellowshipCastDataSourceOptions {\n readonly assetBaseUrl?: string;\n}\n\n/** @deprecated Use `createPackageFellowshipDataClient` from `@crockery/fellowship-data/client`. */\nexport const createPackageFellowshipCastDataSource = (\n options: PackageFellowshipCastDataSourceOptions = {},\n): FellowshipCastDataSource => {\n const client = createPackageFellowshipDataClient(options);\n return {\n loadDungeonCasts: (dungeonId, signal) => client.loadDungeonCasts(dungeonId, signal),\n resolveAssetUrl: (asset) => client.resolveAssetUrl(asset),\n };\n};\n","import { css } from \"lit\";\n\nexport const dungeonCastBrowserStyles = css`\n :host {\n --fellowship-cast-canvas: var(--_fellowship-field-canvas);\n --fellowship-cast-surface: var(--_fellowship-field-surface);\n --fellowship-cast-raised: var(--_fellowship-field-surface-raised);\n --fellowship-cast-ink: var(--_fellowship-field-ink);\n --fellowship-cast-muted: var(--_fellowship-field-muted);\n --fellowship-cast-tertiary: var(--_fellowship-field-tertiary);\n --fellowship-cast-border: var(--_fellowship-field-border);\n --fellowship-cast-border-strong: var(--_fellowship-field-border-strong);\n --fellowship-cast-interrupt: var(--_fellowship-field-accent);\n --fellowship-cast-stop: var(--_fellowship-field-brass);\n --fellowship-cast-focus: var(--_fellowship-field-focus);\n --fellowship-cast-font-family: var(--_fellowship-field-font-family);\n --fellowship-cast-tactical-font-family: var(--_fellowship-field-mono-font-family);\n\n display: block;\n inline-size: 100%;\n min-block-size: 320px;\n color: var(--fellowship-cast-ink);\n font-family: var(--fellowship-cast-font-family);\n container-type: inline-size;\n -webkit-font-smoothing: antialiased;\n }\n\n :host([hidden]) {\n display: none;\n }\n\n :host([fill]) {\n min-block-size: 0;\n block-size: 100%;\n overflow: hidden;\n }\n\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n\n .browser {\n min-block-size: inherit;\n overflow: hidden;\n border: 1px solid var(--fellowship-cast-border);\n border-radius: calc(var(--_fellowship-field-radius) + 2px);\n background: var(--fellowship-cast-canvas);\n }\n\n :host([fill]) .browser {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n min-block-size: 0;\n block-size: 100%;\n }\n\n .toolbar {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 12px;\n align-items: end;\n padding: 16px;\n border-block-end: 1px solid var(--fellowship-cast-border);\n background: var(--fellowship-cast-surface);\n }\n\n .eyebrow,\n .summary,\n .enemy-id,\n .ability-id,\n .capability,\n .marker {\n font-family: var(--fellowship-cast-tactical-font-family);\n }\n\n .eyebrow {\n margin: 0 0 4px;\n color: var(--fellowship-cast-interrupt);\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n }\n\n h2 {\n margin: 0;\n font-size: clamp(18px, 3cqi, 24px);\n font-weight: 600;\n letter-spacing: 0.01em;\n line-height: 1.08;\n }\n\n .summary {\n margin: 6px 0 0;\n color: var(--fellowship-cast-tertiary);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.04em;\n }\n\n .search-label {\n display: grid;\n gap: 4px;\n min-inline-size: min(240px, 38cqi);\n color: var(--fellowship-cast-muted);\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.04em;\n }\n\n .search-input {\n min-block-size: 44px;\n inline-size: 100%;\n border: 1px solid var(--fellowship-cast-border-strong);\n border-radius: var(--_fellowship-field-radius);\n background: var(--_fellowship-field-inset);\n color: var(--fellowship-cast-ink);\n font: inherit;\n font-size: 14px;\n padding-inline: 12px;\n }\n\n .search-input::placeholder {\n color: var(--fellowship-cast-tertiary);\n }\n\n .search-input:focus-visible,\n .cast-row:focus-visible,\n .retry-button:focus-visible {\n outline: 2px solid var(--fellowship-cast-focus);\n outline-offset: 2px;\n }\n\n .enemy-list {\n display: grid;\n }\n\n :host([fill]) .enemy-list {\n min-block-size: 0;\n align-content: start;\n overflow-y: auto;\n overscroll-behavior: contain;\n }\n\n .enemy {\n min-inline-size: 0;\n border-block-end: 1px solid var(--fellowship-cast-border);\n }\n\n .enemy:last-child {\n border-block-end: 0;\n }\n\n .enemy-header {\n display: grid;\n grid-template-columns: 4px minmax(0, 1fr) auto;\n gap: 12px;\n align-items: center;\n min-block-size: 52px;\n padding: 8px 16px;\n background: color-mix(in srgb, var(--fellowship-cast-raised) 72%, transparent);\n }\n\n .enemy-rule {\n align-self: stretch;\n background: var(--fellowship-cast-interrupt);\n }\n\n .enemy-name {\n overflow: hidden;\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n line-height: 1.15;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .enemy-id {\n overflow: hidden;\n margin: 3px 0 0;\n color: var(--fellowship-cast-tertiary);\n font-size: 10px;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .enemy-count {\n color: var(--fellowship-cast-muted);\n font-family: var(--fellowship-cast-tactical-font-family);\n font-size: 10px;\n font-variant-numeric: tabular-nums;\n }\n\n .cast-list {\n display: grid;\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n .cast-list > li + li {\n border-block-start: 1px solid var(--fellowship-cast-border);\n }\n\n .cast-row {\n position: relative;\n display: grid;\n grid-template-columns: 40px minmax(0, 1fr) auto auto;\n gap: 12px;\n align-items: center;\n inline-size: 100%;\n min-block-size: 64px;\n padding: 8px 16px 8px 28px;\n border: 0;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: start;\n cursor: pointer;\n transition: background-color 140ms cubic-bezier(0.23, 1, 0.32, 1);\n }\n\n .cast-row:not(:disabled):hover {\n background: color-mix(in srgb, var(--fellowship-cast-interrupt) 7%, transparent);\n }\n\n .cast-row[aria-pressed=\"true\"] {\n background: color-mix(in srgb, var(--fellowship-cast-interrupt) 11%, transparent);\n }\n\n .cast-row[aria-pressed=\"true\"]::before {\n position: absolute;\n inset: 12px auto 12px 0;\n inline-size: 3px;\n background: var(--fellowship-cast-interrupt);\n content: \"\";\n }\n\n .cast-row:disabled {\n color: var(--fellowship-cast-tertiary);\n cursor: not-allowed;\n }\n\n .ability-image,\n .ability-fallback {\n inline-size: 40px;\n block-size: 40px;\n border: 1px solid var(--fellowship-cast-border-strong);\n border-radius: 4px;\n background: var(--fellowship-cast-raised);\n }\n\n .ability-image {\n display: block;\n object-fit: cover;\n }\n\n .ability-fallback {\n display: grid;\n place-items: center;\n color: var(--fellowship-cast-muted);\n font-family: var(--fellowship-cast-tactical-font-family);\n font-size: 11px;\n font-weight: 600;\n }\n\n .ability-image[hidden],\n .ability-fallback[hidden] {\n display: none;\n }\n\n .ability-copy {\n min-inline-size: 0;\n }\n\n .ability-name {\n display: block;\n overflow: hidden;\n font-size: 14px;\n font-weight: 600;\n line-height: 1.15;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .ability-id {\n display: block;\n overflow: hidden;\n margin-block-start: 4px;\n color: var(--fellowship-cast-tertiary);\n font-size: 10px;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .capability {\n min-inline-size: 102px;\n padding: 5px 8px;\n border: 1px solid currentColor;\n border-radius: 999px;\n color: var(--fellowship-cast-interrupt);\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.04em;\n text-align: center;\n white-space: nowrap;\n }\n\n .capability[data-kind=\"varies\"] {\n border-style: dashed;\n }\n\n .capability[data-kind=\"stop\"] {\n color: var(--fellowship-cast-stop);\n }\n\n .capability[data-kind=\"none\"] {\n color: var(--fellowship-cast-tertiary);\n }\n\n .marker {\n min-inline-size: 72px;\n color: var(--fellowship-cast-stop);\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.06em;\n text-align: end;\n text-transform: uppercase;\n }\n\n .marker[data-empty=\"true\"] {\n visibility: hidden;\n }\n\n .status {\n display: grid;\n min-block-size: 280px;\n padding: 32px;\n place-items: center;\n text-align: center;\n }\n\n :host([fill]) .status {\n min-block-size: 0;\n }\n\n .status-card {\n display: grid;\n max-inline-size: 360px;\n gap: 8px;\n justify-items: center;\n }\n\n .status-mark {\n inline-size: 32px;\n block-size: 4px;\n margin-block-end: 8px;\n background: var(--fellowship-cast-interrupt);\n }\n\n .status-title {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n }\n\n .status-copy {\n margin: 0;\n color: var(--fellowship-cast-muted);\n font-size: 14px;\n line-height: 1.45;\n }\n\n .retry-button {\n min-block-size: 44px;\n margin-block-start: 8px;\n padding-inline: 16px;\n border: 1px solid var(--fellowship-cast-interrupt);\n border-radius: var(--_fellowship-field-radius);\n background: transparent;\n color: var(--fellowship-cast-ink);\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n }\n\n @container (max-width: 560px) {\n .toolbar {\n grid-template-columns: 1fr;\n }\n\n .search-label {\n min-inline-size: 0;\n }\n\n .cast-row {\n grid-template-columns: 40px minmax(0, 1fr) auto;\n padding-inline: 16px;\n }\n\n .marker {\n position: absolute;\n inset: 5px 8px auto auto;\n min-inline-size: 0;\n font-size: 8px;\n }\n\n .ability-id,\n .enemy-id {\n display: none;\n }\n\n .capability {\n min-inline-size: 0;\n padding-inline: 6px;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .cast-row {\n transition: none;\n }\n }\n`;\n","import type { Ability, ImageAssetRef } from \"@crockery/fellowship-data\";\n\nimport type { DungeonCastData, FellowshipCastDataSource } from \"../data/cast-data-source.js\";\nimport type {\n CastCapabilityLabel,\n DungeonCastBrowserModel,\n DungeonCastEnemyModel,\n DungeonCastModel,\n FellowshipCastIdentity,\n} from \"./types.js\";\n\nexport const castIdentityKey = ({ enemyId, abilityId }: FellowshipCastIdentity): string =>\n `${enemyId}\\u0000${abilityId}`;\n\nconst abilityImage = (ability: Ability): ImageAssetRef | null =>\n ability.images.find(({ size }) => size === \"200\") ?? ability.images[0] ?? null;\n\nconst capabilityLabel = (\n canInterrupt: boolean,\n interruptVaries: boolean,\n canStop: boolean,\n): CastCapabilityLabel => {\n if (interruptVaries) return \"Interrupt varies\";\n if (canInterrupt) return \"Interrupt\";\n if (canStop) return \"Stop\";\n return \"No response\";\n};\n\nexport const createDungeonCastBrowserModel = (\n data: DungeonCastData,\n source: FellowshipCastDataSource,\n): DungeonCastBrowserModel => {\n const seenEnemyIds = new Set<string>();\n const enemies: DungeonCastEnemyModel[] = [];\n\n for (const placement of data.dungeon.enemies) {\n if (seenEnemyIds.has(placement.id)) continue;\n seenEnemyIds.add(placement.id);\n\n const enemy = data.enemies.get(placement.id);\n if (!enemy) continue;\n\n const casts: DungeonCastModel[] = (data.abilitiesByEnemy.get(enemy.id) ?? [])\n .filter((ability) => ability.castInterruptibility !== undefined)\n .map((ability) => {\n const canInterrupt = ability.castInterruptibility !== \"never\";\n const interruptVaries = ability.castInterruptibility === \"sometimes\";\n const canStop = enemy.canBeControlled;\n const image = abilityImage(ability);\n let imageUrl: string | null = null;\n if (image) {\n try {\n imageUrl = source.resolveAssetUrl(image);\n } catch {\n imageUrl = null;\n }\n }\n return {\n identity: { enemyId: enemy.id, abilityId: ability.id },\n ability,\n enemy,\n canInterrupt,\n interruptVaries,\n canStop,\n actionable: canInterrupt || canStop,\n capabilityLabel: capabilityLabel(canInterrupt, interruptVaries, canStop),\n imageUrl,\n };\n });\n\n if (casts.some(({ actionable }) => actionable)) enemies.push({ enemy, casts });\n }\n\n return {\n dungeon: data.dungeon,\n enemies,\n castCount: enemies.reduce((total, enemy) => total + enemy.casts.length, 0),\n };\n};\n","import { html, LitElement, nothing, type PropertyValues, type TemplateResult } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { repeat } from \"lit/directives/repeat.js\";\n\nimport type { FellowshipCastDataSource } from \"../data/cast-data-source.js\";\nimport { FellowshipDataError } from \"../data/data-source.js\";\nimport { createPackageFellowshipCastDataSource } from \"../data/package-cast-data-source.js\";\nimport { installFieldManualFonts } from \"../styles/field-manual-fonts.js\";\nimport { fieldManualStyles } from \"../styles/field-manual.styles.js\";\nimport { dungeonCastBrowserStyles } from \"./dungeon-cast-browser.styles.js\";\nimport { castIdentityKey, createDungeonCastBrowserModel } from \"./render-model.js\";\nimport type {\n ConfiguredCastFilter,\n DungeonCastBrowserModel,\n DungeonCastEnemyModel,\n DungeonCastModel,\n FellowshipCastBrowserErrorDetail,\n FellowshipCastBrowserReadyDetail,\n FellowshipCastIdentity,\n FellowshipCastSelectedDetail,\n} from \"./types.js\";\n\ntype ComponentStatus = \"empty\" | \"loading\" | \"ready\" | \"error\";\n\nconst eventOptions = { bubbles: true, composed: true } as const;\n\n/**\n * @fires fellowship-cast-browser-ready - Fired after dungeon casts have been loaded and joined.\n * @fires fellowship-cast-browser-error - Fired when dungeon cast data cannot be loaded.\n * @fires fellowship-cast-selected - Fired when a user selects an actionable cast.\n */\n@customElement(\"fellowship-dungeon-cast-browser\")\nexport class FellowshipDungeonCastBrowserElement extends LitElement {\n static override styles = [fieldManualStyles, dungeonCastBrowserStyles];\n\n @property({ type: String, attribute: \"dungeon-id\" }) dungeonId = \"\";\n @property({ attribute: false }) selectedCast: FellowshipCastIdentity | null = null;\n @property({ attribute: false }) configuredCasts: readonly FellowshipCastIdentity[] = [];\n @property({ type: String, attribute: \"configured-filter\" })\n configuredFilter: ConfiguredCastFilter = \"all\";\n @property({ type: Boolean, reflect: true }) fill = false;\n @property({ type: String, attribute: \"asset-base-url\" }) assetBaseUrl: string | null = null;\n @property({ attribute: false }) dataSource: FellowshipCastDataSource | null = null;\n\n @state() private _status: ComponentStatus = \"empty\";\n @state() private _error: FellowshipCastBrowserErrorDetail | null = null;\n @state() private _model: DungeonCastBrowserModel | null = null;\n @state() private _query = \"\";\n\n #abortController: AbortController | null = null;\n #loadToken = 0;\n\n override connectedCallback(): void {\n super.connectedCallback();\n installFieldManualFonts(this.ownerDocument);\n }\n\n override disconnectedCallback(): void {\n this.#abortController?.abort();\n super.disconnectedCallback();\n }\n\n protected override updated(changed: PropertyValues<this>): void {\n super.updated(changed);\n if (changed.has(\"dungeonId\") || changed.has(\"dataSource\") || changed.has(\"assetBaseUrl\")) {\n queueMicrotask(() => {\n if (this.isConnected) void this.#loadDungeonCasts();\n });\n }\n }\n\n retry(): void {\n void this.#loadDungeonCasts();\n }\n\n get renderModel(): DungeonCastBrowserModel | null {\n return this._model;\n }\n\n override render(): TemplateResult {\n if (this._status !== \"ready\" || !this._model) {\n return html`<section class=\"browser\" aria-label=\"Dungeon cast browser\">${this.#renderStatus()}</section>`;\n }\n\n const visibleEnemies = this.#visibleEnemies(this._model);\n return html`\n <section class=\"browser\" aria-label=${`${this._model.dungeon.name ?? this._model.dungeon.id} casts`}>\n <header class=\"toolbar\" part=\"toolbar\">\n <div>\n <p class=\"eyebrow\">Dungeon cast index</p>\n <h2>${this._model.dungeon.name ?? this._model.dungeon.id}</h2>\n <p class=\"summary\">\n ${this._model.enemies.length} enemies · ${this._model.castCount} cast-bearing abilities\n </p>\n </div>\n <label class=\"search-label\">\n Search casts\n <input\n class=\"search-input\"\n type=\"search\"\n placeholder=\"Enemy or ability\"\n .value=${this._query}\n @input=${this.#handleSearch}\n />\n </label>\n </header>\n ${\n visibleEnemies.length > 0\n ? html`<div class=\"enemy-list\" part=\"enemy-list\">${repeat(\n visibleEnemies,\n ({ enemy }) => enemy.id,\n (enemy) => this.#renderEnemy(enemy),\n )}</div>`\n : this.#renderEmptySearch()\n }\n </section>\n `;\n }\n\n async #loadDungeonCasts(): Promise<void> {\n const dungeonId = this.dungeonId.trim();\n const token = ++this.#loadToken;\n this.#abortController?.abort();\n this.#abortController = new AbortController();\n this._model = null;\n this._error = null;\n this._query = \"\";\n\n if (!dungeonId) {\n this._status = \"empty\";\n return;\n }\n\n this._status = \"loading\";\n const source =\n this.dataSource ??\n createPackageFellowshipCastDataSource(\n this.assetBaseUrl ? { assetBaseUrl: this.assetBaseUrl } : {},\n );\n\n try {\n const data = await source.loadDungeonCasts(dungeonId, this.#abortController.signal);\n if (token !== this.#loadToken || this.#abortController.signal.aborted) return;\n if (data.manifest.schemaVersion !== 1) {\n throw new FellowshipDataError(\n \"unsupported-schema\",\n `Unsupported fellowship-data schema version: ${String(data.manifest.schemaVersion)}.`,\n );\n }\n const model = createDungeonCastBrowserModel(data, source);\n this._model = model;\n this._status = \"ready\";\n this.dispatchEvent(\n new CustomEvent<FellowshipCastBrowserReadyDetail>(\"fellowship-cast-browser-ready\", {\n ...eventOptions,\n detail: { model, data },\n }),\n );\n } catch (error: unknown) {\n if (token !== this.#loadToken || this.#abortController.signal.aborted) return;\n const detail: FellowshipCastBrowserErrorDetail = {\n code:\n error instanceof FellowshipDataError\n ? error.code\n : ((error as { code?: string }).code ?? \"data-load-failed\"),\n message: error instanceof Error ? error.message : String(error),\n ...(error instanceof Error && error.cause !== undefined ? { cause: error.cause } : {}),\n };\n this._error = detail;\n this._status = \"error\";\n this.dispatchEvent(\n new CustomEvent<FellowshipCastBrowserErrorDetail>(\"fellowship-cast-browser-error\", {\n ...eventOptions,\n detail,\n }),\n );\n }\n }\n\n #visibleEnemies(model: DungeonCastBrowserModel): readonly DungeonCastEnemyModel[] {\n const query = this._query.trim().toLocaleLowerCase();\n const configuredFilter =\n this.configuredFilter === \"configured\" || this.configuredFilter === \"unconfigured\"\n ? this.configuredFilter\n : \"all\";\n return model.enemies.flatMap((enemy) => {\n const enemyMatches = `${enemy.enemy.name ?? \"\"} ${enemy.enemy.id}`\n .toLocaleLowerCase()\n .includes(query);\n const casts = enemy.casts.filter((cast) => {\n const queryMatches =\n !query ||\n enemyMatches ||\n `${cast.ability.name ?? \"\"} ${cast.ability.id}`.toLocaleLowerCase().includes(query);\n if (!queryMatches) return false;\n const configured = this.configuredCasts.some((identity) =>\n this.#sameIdentity(identity, cast.identity),\n );\n return (\n configuredFilter === \"all\" ||\n (configuredFilter === \"configured\" && configured) ||\n (configuredFilter === \"unconfigured\" && !configured)\n );\n });\n return casts.length > 0 ? [{ ...enemy, casts }] : [];\n });\n }\n\n #renderEnemy(model: DungeonCastEnemyModel): TemplateResult {\n return html`\n <article class=\"enemy\" part=\"enemy\">\n <header class=\"enemy-header\" part=\"enemy-header\">\n <span class=\"enemy-rule\" aria-hidden=\"true\"></span>\n <div>\n <h3 class=\"enemy-name\">${model.enemy.name ?? model.enemy.id}</h3>\n <p class=\"enemy-id\">${model.enemy.id}</p>\n </div>\n <span class=\"enemy-count\">${model.casts.length} ${model.casts.length === 1 ? \"cast\" : \"casts\"}</span>\n </header>\n <ul class=\"cast-list\">\n ${repeat(\n model.casts,\n ({ identity }) => castIdentityKey(identity),\n (cast) => html`<li>${this.#renderCast(cast)}</li>`,\n )}\n </ul>\n </article>\n `;\n }\n\n #renderCast(cast: DungeonCastModel): TemplateResult {\n const selected = this.#sameIdentity(this.selectedCast, cast.identity);\n const configured = this.configuredCasts.some((identity) =>\n this.#sameIdentity(identity, cast.identity),\n );\n const capabilityKind = cast.interruptVaries\n ? \"varies\"\n : cast.canInterrupt\n ? \"interrupt\"\n : cast.canStop\n ? \"stop\"\n : \"none\";\n const fallback = (cast.ability.name ?? cast.ability.id)\n .split(/\\s+/)\n .map((part) => part[0])\n .join(\"\")\n .slice(0, 2)\n .toUpperCase();\n\n return html`\n <button\n class=\"cast-row\"\n part=\"cast\"\n type=\"button\"\n ?disabled=${!cast.actionable}\n aria-pressed=${String(selected)}\n data-configured=${String(configured)}\n @click=${() => this.#selectCast(cast)}\n @keydown=${this.#handleCastKeydown}\n >\n ${\n cast.imageUrl\n ? html`<img\n class=\"ability-image\"\n part=\"cast-icon\"\n src=${cast.imageUrl}\n alt=\"\"\n @error=${this.#handleAbilityImageError}\n /><span\n class=\"ability-fallback\"\n part=\"cast-icon-fallback\"\n aria-hidden=\"true\"\n hidden\n >${fallback}</span\n >`\n : html`<span class=\"ability-fallback\" part=\"cast-icon-fallback\" aria-hidden=\"true\">${fallback}</span>`\n }\n <span class=\"ability-copy\">\n <span class=\"ability-name\">${cast.ability.name ?? cast.ability.id}</span>\n <span class=\"ability-id\">${cast.ability.id}</span>\n </span>\n <span class=\"capability\" data-kind=${capabilityKind}>${cast.capabilityLabel}</span>\n <span class=\"marker\" data-empty=${String(!configured)}>${configured ? \"Configured\" : nothing}</span>\n </button>\n `;\n }\n\n #renderStatus(): TemplateResult {\n if (this._status === \"loading\") {\n return this.#statusCard(\n \"Loading cast index\",\n \"Joining dungeon enemies with their cast data.\",\n );\n }\n if (this._status === \"error\") {\n return html`\n <div class=\"status\" role=\"alert\">\n <div class=\"status-card\">\n <span class=\"status-mark\" aria-hidden=\"true\"></span>\n <h2 class=\"status-title\">Cast data unavailable</h2>\n <p class=\"status-copy\">${this._error?.message ?? \"The dungeon cast index could not be loaded.\"}</p>\n <button class=\"retry-button\" type=\"button\" @click=${this.retry}>Retry</button>\n </div>\n </div>\n `;\n }\n if (this._status === \"ready\") {\n return this.#statusCard(\n \"No actionable casts\",\n \"This dungeon has no enemies with casts that can be interrupted or stopped.\",\n );\n }\n return this.#statusCard(\"Select a dungeon\", \"Choose a dungeon to inspect enemy casts.\");\n }\n\n #renderEmptySearch(): TemplateResult {\n return this.#statusCard(\n \"No matching casts\",\n `No enemy or ability matches “${this._query.trim()}”.`,\n );\n }\n\n #statusCard(title: string, copy: string): TemplateResult {\n return html`\n <div class=\"status\">\n <div class=\"status-card\">\n <span class=\"status-mark\" aria-hidden=\"true\"></span>\n <h2 class=\"status-title\">${title}</h2>\n <p class=\"status-copy\">${copy}</p>\n </div>\n </div>\n `;\n }\n\n #sameIdentity(left: FellowshipCastIdentity | null, right: FellowshipCastIdentity): boolean {\n return left?.enemyId === right.enemyId && left.abilityId === right.abilityId;\n }\n\n #selectCast(cast: DungeonCastModel): void {\n if (!cast.actionable) return;\n this.dispatchEvent(\n new CustomEvent<FellowshipCastSelectedDetail>(\"fellowship-cast-selected\", {\n ...eventOptions,\n detail: { cast },\n }),\n );\n }\n\n #handleSearch = (event: InputEvent): void => {\n this._query = (event.currentTarget as HTMLInputElement).value;\n };\n\n #handleAbilityImageError = (event: Event): void => {\n const image = event.currentTarget as HTMLImageElement;\n image.hidden = true;\n const fallback = image.nextElementSibling;\n if (fallback instanceof HTMLElement) fallback.hidden = false;\n };\n\n #handleCastKeydown = (event: KeyboardEvent): void => {\n const movement =\n event.key === \"ArrowDown\" || event.key === \"ArrowRight\"\n ? 1\n : event.key === \"ArrowUp\" || event.key === \"ArrowLeft\"\n ? -1\n : 0;\n if (movement === 0 && event.key !== \"Home\" && event.key !== \"End\") return;\n const buttons = [\n ...this.renderRoot.querySelectorAll<HTMLButtonElement>(\".cast-row:not(:disabled)\"),\n ];\n if (buttons.length === 0) return;\n const current = buttons.indexOf(event.currentTarget as HTMLButtonElement);\n const nextIndex =\n event.key === \"Home\"\n ? 0\n : event.key === \"End\"\n ? buttons.length - 1\n : (current + movement + buttons.length) % buttons.length;\n buttons[nextIndex]?.focus();\n event.preventDefault();\n };\n}\n\nexport const defineFellowshipDungeonCastBrowser = (): void => {\n if (\n typeof globalThis.customElements !== \"undefined\" &&\n !globalThis.customElements.get(\"fellowship-dungeon-cast-browser\")\n ) {\n globalThis.customElements.define(\n \"fellowship-dungeon-cast-browser\",\n FellowshipDungeonCastBrowserElement,\n );\n }\n};\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"fellowship-dungeon-cast-browser\": FellowshipDungeonCastBrowserElement;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,44 +1,19 @@
|
|
|
1
|
-
import { t as e } from "./data-source-
|
|
1
|
+
import { t as e } from "./data-source-C-Gs7kRl.js";
|
|
2
2
|
import { n as t, r as n, t as r } from "./decorate-B_ZTN9li.js";
|
|
3
|
-
import {
|
|
4
|
-
import { LitElement as
|
|
5
|
-
import { customElement as
|
|
6
|
-
import { repeat as
|
|
7
|
-
import { styleMap as
|
|
3
|
+
import { createPackageFellowshipDataClient as i } from "@crockery/fellowship-data/client";
|
|
4
|
+
import { LitElement as a, css as o, html as s, nothing as c } from "lit";
|
|
5
|
+
import { customElement as l, property as u, query as d, state as f } from "lit/decorators.js";
|
|
6
|
+
import { repeat as p } from "lit/directives/repeat.js";
|
|
7
|
+
import { styleMap as m } from "lit/directives/style-map.js";
|
|
8
|
+
import { findImage as h } from "@crockery/fellowship-data";
|
|
8
9
|
//#region src/data/package-data-source.ts
|
|
9
|
-
var g
|
|
10
|
-
|
|
11
|
-
}, v = () => (g ??= Promise.all([
|
|
12
|
-
import("@crockery/fellowship-data/dungeons"),
|
|
13
|
-
import("@crockery/fellowship-data/enemies"),
|
|
14
|
-
import("@crockery/fellowship-data/manifest")
|
|
15
|
-
]).then(([t, n, r]) => {
|
|
16
|
-
let i = r.default;
|
|
17
|
-
if (i.schemaVersion !== 1) throw new e("unsupported-schema", `Unsupported fellowship-data schema version: ${String(i.schemaVersion)}.`);
|
|
10
|
+
var g = (e = {}) => {
|
|
11
|
+
let t = i(e);
|
|
18
12
|
return {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
enemies: new Map(n.default.map((e) => [e.id, e]))
|
|
13
|
+
loadDungeon: (e, n) => t.loadDungeon(e, n),
|
|
14
|
+
resolveAssetUrl: (e) => t.resolveAssetUrl(e)
|
|
22
15
|
};
|
|
23
|
-
}
|
|
24
|
-
throw g = void 0, t instanceof e ? t : new e("data-load-failed", "Unable to load @crockery/fellowship-data.", t);
|
|
25
|
-
}), g), y = (t = {}) => ({
|
|
26
|
-
async loadDungeon(t, n) {
|
|
27
|
-
_(n);
|
|
28
|
-
let r = await v();
|
|
29
|
-
_(n);
|
|
30
|
-
let i = r.dungeons.get(t);
|
|
31
|
-
if (!i) throw new e("dungeon-not-found", `No dungeon with ID “${t}” exists in @crockery/fellowship-data.`);
|
|
32
|
-
return {
|
|
33
|
-
manifest: r.manifest,
|
|
34
|
-
dungeon: i,
|
|
35
|
-
enemies: r.enemies
|
|
36
|
-
};
|
|
37
|
-
},
|
|
38
|
-
resolveAssetUrl(e) {
|
|
39
|
-
return t.assetBaseUrl ? a(e, { baseUrl: t.assetBaseUrl }) : a(e);
|
|
40
|
-
}
|
|
41
|
-
}), b = s`
|
|
16
|
+
}, _ = o`
|
|
42
17
|
:host {
|
|
43
18
|
--fellowship-map-soot: var(--_fellowship-field-canvas);
|
|
44
19
|
--fellowship-map-ink: var(--_fellowship-field-ink);
|
|
@@ -484,7 +459,7 @@ var g, _ = (t) => {
|
|
|
484
459
|
transform: none;
|
|
485
460
|
}
|
|
486
461
|
}
|
|
487
|
-
`,
|
|
462
|
+
`, v = .018, y = .036, b = .06, x = .12, S = (e, t, n) => Math.min(n, Math.max(t, e)), C = (e) => {
|
|
488
463
|
let t = e.map(({ position: e }) => e.x), n = e.map(({ position: e }) => e.y), r = Math.min(...t), i = Math.max(...t), a = Math.min(...n), o = Math.max(...n);
|
|
489
464
|
return {
|
|
490
465
|
x: r,
|
|
@@ -492,91 +467,91 @@ var g, _ = (t) => {
|
|
|
492
467
|
width: i - r,
|
|
493
468
|
height: o - a
|
|
494
469
|
};
|
|
495
|
-
},
|
|
496
|
-
let t = e.x + e.width / 2, n = e.y + e.height / 2, r = Math.max(
|
|
470
|
+
}, w = (e) => {
|
|
471
|
+
let t = e.x + e.width / 2, n = e.y + e.height / 2, r = Math.max(b, e.width + v * 2), i = Math.max(x, e.height + y * 2);
|
|
497
472
|
return {
|
|
498
|
-
x:
|
|
499
|
-
y:
|
|
473
|
+
x: S(t - r / 2, 0, Math.max(0, 1 - r)),
|
|
474
|
+
y: S(n - i / 2, 0, Math.max(0, 1 - i)),
|
|
500
475
|
width: Math.min(r, 1),
|
|
501
476
|
height: Math.min(i, 1)
|
|
502
477
|
};
|
|
503
|
-
},
|
|
478
|
+
}, T = (e) => ({
|
|
504
479
|
x: e.reduce((e, t) => e + t.position.x, 0) / e.length,
|
|
505
480
|
y: e.reduce((e, t) => e + t.position.y, 0) / e.length
|
|
506
|
-
}),
|
|
481
|
+
}), E = (e) => {
|
|
507
482
|
let t = e.map(({ enemy: e }) => e?.dungeonScore).filter((e) => typeof e == "number");
|
|
508
483
|
return t.length > 0 ? t.reduce((e, t) => e + t, 0) : null;
|
|
509
|
-
},
|
|
510
|
-
let n =
|
|
484
|
+
}, D = (e, t) => {
|
|
485
|
+
let n = C(t);
|
|
511
486
|
return {
|
|
512
487
|
id: e,
|
|
513
488
|
enemies: t,
|
|
514
489
|
enemyBounds: n,
|
|
515
|
-
visualBounds:
|
|
516
|
-
center:
|
|
517
|
-
dungeonScore:
|
|
490
|
+
visualBounds: w(n),
|
|
491
|
+
center: T(t),
|
|
492
|
+
dungeonScore: E(t)
|
|
518
493
|
};
|
|
519
|
-
},
|
|
520
|
-
let r = [],
|
|
521
|
-
return e.dungeon.enemies.forEach((
|
|
522
|
-
if (
|
|
523
|
-
if (!
|
|
494
|
+
}, O = (e) => e !== null && Number.isFinite(e.x) && Number.isFinite(e.y) && e.x >= 0 && e.x <= 1 && e.y >= 0 && e.y <= 1, k = (e, t, n) => {
|
|
495
|
+
let r = [], i = /* @__PURE__ */ new Map();
|
|
496
|
+
return e.dungeon.enemies.forEach((a, o) => {
|
|
497
|
+
if (a.mapImageId !== t.id) return;
|
|
498
|
+
if (!O(a.mapPosition)) {
|
|
524
499
|
r.push({
|
|
525
500
|
code: "invalid-map-position",
|
|
526
|
-
message: `Skipped ${
|
|
527
|
-
placementIndex:
|
|
528
|
-
enemyId:
|
|
501
|
+
message: `Skipped ${a.id}: its map position is missing or outside the map.`,
|
|
502
|
+
placementIndex: o,
|
|
503
|
+
enemyId: a.id
|
|
529
504
|
});
|
|
530
505
|
return;
|
|
531
506
|
}
|
|
532
|
-
let
|
|
533
|
-
|
|
507
|
+
let s = e.enemies.get(a.id) ?? null;
|
|
508
|
+
s || r.push({
|
|
534
509
|
code: "missing-enemy",
|
|
535
|
-
message: `No enemy metadata exists for ${
|
|
536
|
-
placementIndex:
|
|
537
|
-
enemyId:
|
|
510
|
+
message: `No enemy metadata exists for ${a.id}.`,
|
|
511
|
+
placementIndex: o,
|
|
512
|
+
enemyId: a.id
|
|
538
513
|
});
|
|
539
|
-
let
|
|
514
|
+
let c = s ? h(s.images, {
|
|
540
515
|
type: "enemy",
|
|
541
516
|
size: "200"
|
|
542
|
-
}) : null,
|
|
543
|
-
if (
|
|
544
|
-
|
|
517
|
+
}) : null, l = null;
|
|
518
|
+
if (c) try {
|
|
519
|
+
l = n.resolveAssetUrl(c);
|
|
545
520
|
} catch (e) {
|
|
546
521
|
r.push({
|
|
547
522
|
code: "asset-url-error",
|
|
548
|
-
message: `Could not resolve the image URL for ${
|
|
549
|
-
placementIndex:
|
|
550
|
-
enemyId:
|
|
523
|
+
message: `Could not resolve the image URL for ${a.id}: ${String(e)}.`,
|
|
524
|
+
placementIndex: o,
|
|
525
|
+
enemyId: a.id
|
|
551
526
|
});
|
|
552
527
|
}
|
|
553
|
-
else
|
|
528
|
+
else s && r.push({
|
|
554
529
|
code: "missing-enemy-image",
|
|
555
|
-
message: `No 200px enemy image exists for ${
|
|
556
|
-
placementIndex:
|
|
557
|
-
enemyId:
|
|
530
|
+
message: `No 200px enemy image exists for ${a.id}.`,
|
|
531
|
+
placementIndex: o,
|
|
532
|
+
enemyId: a.id
|
|
558
533
|
});
|
|
559
|
-
let
|
|
560
|
-
instanceId: `${e.dungeon.id}:${t.id}:${
|
|
561
|
-
enemy:
|
|
562
|
-
placement:
|
|
563
|
-
position:
|
|
564
|
-
imageUrl:
|
|
565
|
-
},
|
|
566
|
-
id:
|
|
534
|
+
let u = {
|
|
535
|
+
instanceId: `${e.dungeon.id}:${t.id}:${o}`,
|
|
536
|
+
enemy: s,
|
|
537
|
+
placement: a,
|
|
538
|
+
position: a.mapPosition,
|
|
539
|
+
imageUrl: l
|
|
540
|
+
}, d = a.groupId === null ? `singleton:${o}` : `group:${a.groupId}`, f = i.get(d) ?? {
|
|
541
|
+
id: a.groupId,
|
|
567
542
|
enemies: []
|
|
568
543
|
};
|
|
569
|
-
|
|
544
|
+
f.enemies.push(u), i.set(d, f);
|
|
570
545
|
}), {
|
|
571
546
|
dungeon: e.dungeon,
|
|
572
547
|
mapImage: t,
|
|
573
|
-
groups: [...
|
|
548
|
+
groups: [...i.values()].map(({ id: e, enemies: t }) => D(e, t)),
|
|
574
549
|
warnings: r
|
|
575
550
|
};
|
|
576
|
-
},
|
|
551
|
+
}, A = (e, t) => {
|
|
577
552
|
let n = (e.id.split(":").at(-1) ?? e.id).replace(/^T_(?:Map_)?/i, "").replace(/^map[-_]/i, "").replaceAll(/[_-]+/g, " ").replace(/([a-z\d])([A-Z])/g, "$1 $2").trim(), r = t.id.split(".").at(-1)?.toLowerCase();
|
|
578
553
|
return (n.split(/\s+/).filter((e) => e.toLowerCase() !== r).join(" ") || n || "Map").replace(/\b[a-z]/g, (e) => e.toUpperCase());
|
|
579
|
-
},
|
|
554
|
+
}, j = (e, t, n) => Math.min(n, Math.max(t, e)), M = class {
|
|
580
555
|
constructor() {
|
|
581
556
|
this.#e = 0, this.#t = 0, this.#n = 0, this.#r = 0, this.#i = 1, this.#a = 0, this.#o = 0, this.maxZoom = 8;
|
|
582
557
|
}
|
|
@@ -610,26 +585,26 @@ var g, _ = (t) => {
|
|
|
610
585
|
}
|
|
611
586
|
zoomAt(e, t = this.#e / 2, n = this.#t / 2) {
|
|
612
587
|
let r = this.scale, i = (t - this.#a) / r, a = (n - this.#o) / r;
|
|
613
|
-
return this.#i =
|
|
588
|
+
return this.#i = j(this.#i * e, 1, Math.max(1, this.maxZoom)), this.#a = t - i * this.scale, this.#o = n - a * this.scale, this.#s(), this.state;
|
|
614
589
|
}
|
|
615
590
|
panBy(e, t) {
|
|
616
591
|
return this.#a += e, this.#o += t, this.#s(), this.state;
|
|
617
592
|
}
|
|
618
593
|
#s() {
|
|
619
594
|
let e = this.#n * this.scale, t = this.#r * this.scale;
|
|
620
|
-
this.#a = e <= this.#e ? (this.#e - e) / 2 :
|
|
595
|
+
this.#a = e <= this.#e ? (this.#e - e) / 2 : j(this.#a, this.#e - e, 0), this.#o = t <= this.#t ? (this.#t - t) / 2 : j(this.#o, this.#t - t, 0);
|
|
621
596
|
}
|
|
622
|
-
},
|
|
597
|
+
}, N = {
|
|
623
598
|
bubbles: !0,
|
|
624
599
|
composed: !0
|
|
625
|
-
},
|
|
600
|
+
}, P = class extends a {
|
|
626
601
|
constructor(...e) {
|
|
627
602
|
super(...e), this.dungeonId = "", this.mapImageId = null, this.assetBaseUrl = null, this.dataSource = null, this.renderEnemy = null, this.renderGroup = null, this.mapLabelFormatter = null, this.showControls = !0, this.showMapSelector = !0, this.maxZoom = 8, this.externalOverlay = !1, this._status = "empty", this._error = null, this._data = null, this._model = null, this._selectedMap = null, this._viewportState = {
|
|
628
603
|
zoom: 1,
|
|
629
604
|
scale: 1,
|
|
630
605
|
x: 0,
|
|
631
606
|
y: 0
|
|
632
|
-
}, this._isPanning = !1, this._enemyRenderContent = /* @__PURE__ */ new Map(), this._groupRenderContent = /* @__PURE__ */ new Map(), this._mapLabels = /* @__PURE__ */ new Map(), this.#e = null, this.#t = null, this.#n = 0, this.#r = 0, this.#i = null, this.#a = new
|
|
607
|
+
}, this._isPanning = !1, this._enemyRenderContent = /* @__PURE__ */ new Map(), this._groupRenderContent = /* @__PURE__ */ new Map(), this._mapLabels = /* @__PURE__ */ new Map(), this.#e = null, this.#t = null, this.#n = 0, this.#r = 0, this.#i = null, this.#a = new M(), this.#o = /* @__PURE__ */ new Map(), this.#s = null, this.#c = 0, this.#l = null, this.#u = /* @__PURE__ */ new Set(), this.#d = [], this.#j = (e) => {
|
|
633
608
|
if (![
|
|
634
609
|
"ArrowLeft",
|
|
635
610
|
"ArrowRight",
|
|
@@ -702,7 +677,7 @@ var g, _ = (t) => {
|
|
|
702
677
|
};
|
|
703
678
|
}
|
|
704
679
|
static {
|
|
705
|
-
this.styles = [n,
|
|
680
|
+
this.styles = [n, _];
|
|
706
681
|
}
|
|
707
682
|
#e;
|
|
708
683
|
#t;
|
|
@@ -763,7 +738,7 @@ var g, _ = (t) => {
|
|
|
763
738
|
transform: `translate3d(${t.x}px, ${t.y}px, 0) scale(${t.scale})`,
|
|
764
739
|
"--fellowship-map-inverse-scale": String(1 / t.scale)
|
|
765
740
|
} : {};
|
|
766
|
-
return
|
|
741
|
+
return s`
|
|
767
742
|
<section class="viewer" aria-label=${this.#f}>
|
|
768
743
|
<div
|
|
769
744
|
class="viewport"
|
|
@@ -780,13 +755,13 @@ var g, _ = (t) => {
|
|
|
780
755
|
@pointerup=${this.#I}
|
|
781
756
|
@pointercancel=${this.#I}
|
|
782
757
|
>
|
|
783
|
-
${e ?
|
|
758
|
+
${e ? s`
|
|
784
759
|
<div
|
|
785
760
|
class="stage"
|
|
786
761
|
id="map-stage"
|
|
787
|
-
role=${n ===
|
|
762
|
+
role=${n === c ? c : "tabpanel"}
|
|
788
763
|
aria-labelledby=${n}
|
|
789
|
-
style=${
|
|
764
|
+
style=${m(r)}
|
|
790
765
|
>
|
|
791
766
|
<canvas
|
|
792
767
|
class="map-canvas"
|
|
@@ -797,12 +772,12 @@ var g, _ = (t) => {
|
|
|
797
772
|
.height=${e.mapImage.height}
|
|
798
773
|
></canvas>
|
|
799
774
|
<div class="overlay" part="overlay">
|
|
800
|
-
${this.externalOverlay ?
|
|
775
|
+
${this.externalOverlay ? s`<slot name="overlay"></slot>` : this.#b(e)}
|
|
801
776
|
</div>
|
|
802
777
|
</div>
|
|
803
778
|
${this.#E(e)} ${this.#D()}
|
|
804
|
-
${e.groups.length === 0 && this._status === "ready" ?
|
|
805
|
-
` :
|
|
779
|
+
${e.groups.length === 0 && this._status === "ready" ? s`<div class="empty-badge">No enemy placements on this map</div>` : c}
|
|
780
|
+
` : c}
|
|
806
781
|
${this.#O()}
|
|
807
782
|
<p class="visually-hidden" id="map-instructions">
|
|
808
783
|
Drag to pan. Use the mouse wheel, plus and minus keys, or zoom controls to zoom. Press
|
|
@@ -816,9 +791,9 @@ var g, _ = (t) => {
|
|
|
816
791
|
return this._data ? `${this._data.dungeon.name ?? this._data.dungeon.id} dungeon map` : "Fellowship dungeon map";
|
|
817
792
|
}
|
|
818
793
|
get #p() {
|
|
819
|
-
if (!this.showMapSelector || !this._model || this._model.dungeon.mapImages.length <= 1 || !this._selectedMap) return
|
|
794
|
+
if (!this.showMapSelector || !this._model || this._model.dungeon.mapImages.length <= 1 || !this._selectedMap) return c;
|
|
820
795
|
let e = this._model.dungeon.mapImages.findIndex((e) => e.id === this._selectedMap?.id);
|
|
821
|
-
return e >= 0 ? `map-tab-${e}` :
|
|
796
|
+
return e >= 0 ? `map-tab-${e}` : c;
|
|
822
797
|
}
|
|
823
798
|
async #m() {
|
|
824
799
|
let t = this.dungeonId.trim(), n = ++this.#n;
|
|
@@ -827,7 +802,7 @@ var g, _ = (t) => {
|
|
|
827
802
|
return;
|
|
828
803
|
}
|
|
829
804
|
this._status = "loading";
|
|
830
|
-
let r = this.dataSource ??
|
|
805
|
+
let r = this.dataSource ?? g(this.assetBaseUrl ? { assetBaseUrl: this.assetBaseUrl } : {});
|
|
831
806
|
this.#e = r;
|
|
832
807
|
try {
|
|
833
808
|
let i = await r.loadDungeon(t, this.#t.signal);
|
|
@@ -850,20 +825,20 @@ var g, _ = (t) => {
|
|
|
850
825
|
});
|
|
851
826
|
return;
|
|
852
827
|
}
|
|
853
|
-
let i = ++this.#r, a =
|
|
828
|
+
let i = ++this.#r, a = k(t, r, n);
|
|
854
829
|
this._model = a, this._selectedMap = r, this._status = "loading", this._error = null, await this.updateComplete;
|
|
855
830
|
let o = this._viewportElement;
|
|
856
831
|
o && (this.#a.maxZoom = Math.max(1, this.maxZoom), this.#a.setDimensions(o.clientWidth, o.clientHeight, r.width, r.height), e && this.#a.fit(), this._viewportState = this.#a.state, await this.updateComplete);
|
|
857
832
|
try {
|
|
858
833
|
if (await this.#g(r, n, i), i !== this.#r) return;
|
|
859
834
|
this._status = "ready", this.dispatchEvent(new CustomEvent("fellowship-map-ready", {
|
|
860
|
-
...
|
|
835
|
+
...N,
|
|
861
836
|
detail: {
|
|
862
837
|
model: a,
|
|
863
838
|
data: t
|
|
864
839
|
}
|
|
865
840
|
})), a.warnings.length > 0 && this.dispatchEvent(new CustomEvent("fellowship-map-warning", {
|
|
866
|
-
...
|
|
841
|
+
...N,
|
|
867
842
|
detail: {
|
|
868
843
|
warnings: a.warnings,
|
|
869
844
|
model: a
|
|
@@ -894,7 +869,7 @@ var g, _ = (t) => {
|
|
|
894
869
|
message: t instanceof Error ? t.message : String(t)
|
|
895
870
|
};
|
|
896
871
|
this._error = n, this._status = "error", this.dispatchEvent(new CustomEvent("fellowship-map-error", {
|
|
897
|
-
...
|
|
872
|
+
...N,
|
|
898
873
|
detail: n
|
|
899
874
|
}));
|
|
900
875
|
}
|
|
@@ -932,14 +907,14 @@ var g, _ = (t) => {
|
|
|
932
907
|
#y() {
|
|
933
908
|
let e = /* @__PURE__ */ new Map(), t = this._data?.dungeon;
|
|
934
909
|
if (t) for (let n of t.mapImages) try {
|
|
935
|
-
e.set(n.id, this.mapLabelFormatter?.(n, t) ??
|
|
910
|
+
e.set(n.id, this.mapLabelFormatter?.(n, t) ?? A(n, t));
|
|
936
911
|
} catch (r) {
|
|
937
|
-
this.#B(`map-label:${n.id}`, "map-label-formatter-error", r), e.set(n.id,
|
|
912
|
+
this.#B(`map-label:${n.id}`, "map-label-formatter-error", r), e.set(n.id, A(n, t));
|
|
938
913
|
}
|
|
939
914
|
this._mapLabels = e;
|
|
940
915
|
}
|
|
941
916
|
#b(e) {
|
|
942
|
-
return
|
|
917
|
+
return p(e.groups, (t) => this.#x(e, t), (t) => this.#S(e, t));
|
|
943
918
|
}
|
|
944
919
|
#x(e, t) {
|
|
945
920
|
return `${e.mapImage.id}:${String(t.id)}:${t.enemies[0]?.instanceId ?? "empty"}`;
|
|
@@ -950,15 +925,15 @@ var g, _ = (t) => {
|
|
|
950
925
|
top: `${n.y * 100}%`,
|
|
951
926
|
width: `${n.width * 100}%`,
|
|
952
927
|
height: `${n.height * 100}%`
|
|
953
|
-
}, i = this.#x(e, t), a = this.renderGroup && this._groupRenderContent.has(i) ? this._groupRenderContent.get(i) ??
|
|
954
|
-
return
|
|
928
|
+
}, i = this.#x(e, t), a = this.renderGroup && this._groupRenderContent.has(i) ? this._groupRenderContent.get(i) ?? c : this.#C(t);
|
|
929
|
+
return s` <div class="group-anchor" part="group" style=${m(r)}>${a}</div> `;
|
|
955
930
|
}
|
|
956
931
|
#C(e) {
|
|
957
|
-
return
|
|
932
|
+
return s`
|
|
958
933
|
<div class="default-group">
|
|
959
934
|
<div class="group-ring" aria-hidden="true"></div>
|
|
960
935
|
<span class="group-label">${`G${e.id ?? "–"}${e.dungeonScore === null ? "" : ` · ${e.dungeonScore}`}`}</span>
|
|
961
|
-
${
|
|
936
|
+
${p(e.enemies, (e) => e.instanceId, (t) => this.#w(e, t))}
|
|
962
937
|
</div>
|
|
963
938
|
`;
|
|
964
939
|
}
|
|
@@ -966,12 +941,12 @@ var g, _ = (t) => {
|
|
|
966
941
|
let n = e.visualBounds, r = {
|
|
967
942
|
left: `${(t.position.x - n.x) / n.width * 100}%`,
|
|
968
943
|
top: `${(t.position.y - n.y) / n.height * 100}%`
|
|
969
|
-
}, i = this.renderEnemy && this._enemyRenderContent.has(t.instanceId) ? this._enemyRenderContent.get(t.instanceId) ??
|
|
970
|
-
return
|
|
944
|
+
}, i = this.renderEnemy && this._enemyRenderContent.has(t.instanceId) ? this._enemyRenderContent.get(t.instanceId) ?? c : this.#T(t), a = t.enemy?.name ?? t.placement.id;
|
|
945
|
+
return s`
|
|
971
946
|
<div
|
|
972
947
|
class="enemy-anchor"
|
|
973
948
|
part="enemy"
|
|
974
|
-
style=${
|
|
949
|
+
style=${m(r)}
|
|
975
950
|
role="img"
|
|
976
951
|
aria-label=${a}
|
|
977
952
|
title=${a}
|
|
@@ -982,17 +957,17 @@ var g, _ = (t) => {
|
|
|
982
957
|
}
|
|
983
958
|
#T(e) {
|
|
984
959
|
let t = (e.enemy?.name ?? e.placement.id).split(/\s+/).map((e) => e[0]).join("").slice(0, 2).toUpperCase();
|
|
985
|
-
return
|
|
960
|
+
return s`
|
|
986
961
|
<span class="enemy-marker" data-boss=${String(e.enemy?.isBoss === !0)}>
|
|
987
|
-
${e.imageUrl ?
|
|
962
|
+
${e.imageUrl ? s`<img class="enemy-image" src=${e.imageUrl} alt="" draggable="false" />` : t}
|
|
988
963
|
</span>
|
|
989
964
|
`;
|
|
990
965
|
}
|
|
991
966
|
#E(e) {
|
|
992
967
|
let t = e.dungeon.mapImages;
|
|
993
|
-
return !this.showMapSelector || t.length <= 1 ?
|
|
968
|
+
return !this.showMapSelector || t.length <= 1 ? c : s`
|
|
994
969
|
<div class="map-selector" part="map-selector" role="tablist" aria-label="Dungeon maps">
|
|
995
|
-
${
|
|
970
|
+
${p(t, (e) => e.id, (e, t) => s`
|
|
996
971
|
<button
|
|
997
972
|
class="map-selector-button"
|
|
998
973
|
id=${`map-tab-${t}`}
|
|
@@ -1013,7 +988,7 @@ var g, _ = (t) => {
|
|
|
1013
988
|
`;
|
|
1014
989
|
}
|
|
1015
990
|
#D() {
|
|
1016
|
-
return !this.showControls || !this._model ?
|
|
991
|
+
return !this.showControls || !this._model ? c : s`
|
|
1017
992
|
<div class="controls" part="controls" role="group" aria-label="Map zoom controls">
|
|
1018
993
|
<button
|
|
1019
994
|
class="control-button"
|
|
@@ -1049,15 +1024,15 @@ var g, _ = (t) => {
|
|
|
1049
1024
|
`;
|
|
1050
1025
|
}
|
|
1051
1026
|
#O() {
|
|
1052
|
-
if (this._status === "ready") return
|
|
1027
|
+
if (this._status === "ready") return c;
|
|
1053
1028
|
let e = this._status === "loading", t = this._status === "empty", n = e ? "Charting dungeon" : t ? "Choose a dungeon" : "Map unavailable", r = e ? "Loading Fellowship data and preparing the tactical layer." : t ? "Set the dungeon-id attribute or dungeonId property to render a map." : this._error?.message ?? "An unexpected map error occurred.";
|
|
1054
|
-
return
|
|
1029
|
+
return s`
|
|
1055
1030
|
<div class="status" part="status" role=${this._status === "error" ? "alert" : "status"}>
|
|
1056
1031
|
<div class="status-card">
|
|
1057
1032
|
<div class="status-mark" aria-hidden="true"><span>${e ? "…" : "✦"}</span></div>
|
|
1058
1033
|
<p class="status-title">${n}</p>
|
|
1059
1034
|
<p class="status-copy">${r}</p>
|
|
1060
|
-
${this._status === "error" ?
|
|
1035
|
+
${this._status === "error" ? s`<button class="retry-button" type="button" @click=${this.retry}>Retry</button>` : c}
|
|
1061
1036
|
</div>
|
|
1062
1037
|
</div>
|
|
1063
1038
|
`;
|
|
@@ -1067,7 +1042,7 @@ var g, _ = (t) => {
|
|
|
1067
1042
|
}
|
|
1068
1043
|
#A(e) {
|
|
1069
1044
|
e.id !== this._selectedMap?.id && (this.mapImageId = e.id, this.dispatchEvent(new CustomEvent("fellowship-map-image-change", {
|
|
1070
|
-
...
|
|
1045
|
+
...N,
|
|
1071
1046
|
detail: {
|
|
1072
1047
|
mapImageId: e.id,
|
|
1073
1048
|
mapImage: e
|
|
@@ -1089,7 +1064,7 @@ var g, _ = (t) => {
|
|
|
1089
1064
|
}
|
|
1090
1065
|
#z(e) {
|
|
1091
1066
|
this._viewportState = this.#a.state, this.dispatchEvent(new CustomEvent("fellowship-map-viewport-change", {
|
|
1092
|
-
...
|
|
1067
|
+
...N,
|
|
1093
1068
|
detail: {
|
|
1094
1069
|
viewport: this.#a.state,
|
|
1095
1070
|
source: e
|
|
@@ -1108,37 +1083,37 @@ var g, _ = (t) => {
|
|
|
1108
1083
|
let e = this.#d;
|
|
1109
1084
|
this.#d = [];
|
|
1110
1085
|
for (let t of e) this.dispatchEvent(new CustomEvent("fellowship-map-error", {
|
|
1111
|
-
...
|
|
1086
|
+
...N,
|
|
1112
1087
|
detail: t
|
|
1113
1088
|
}));
|
|
1114
1089
|
}
|
|
1115
1090
|
};
|
|
1116
|
-
r([
|
|
1091
|
+
r([u({
|
|
1117
1092
|
type: String,
|
|
1118
1093
|
attribute: "dungeon-id"
|
|
1119
|
-
})],
|
|
1094
|
+
})], P.prototype, "dungeonId", void 0), r([u({
|
|
1120
1095
|
type: String,
|
|
1121
1096
|
attribute: "map-image-id"
|
|
1122
|
-
})],
|
|
1097
|
+
})], P.prototype, "mapImageId", void 0), r([u({
|
|
1123
1098
|
type: String,
|
|
1124
1099
|
attribute: "asset-base-url"
|
|
1125
|
-
})],
|
|
1100
|
+
})], P.prototype, "assetBaseUrl", void 0), r([u({ attribute: !1 })], P.prototype, "dataSource", void 0), r([u({ attribute: !1 })], P.prototype, "renderEnemy", void 0), r([u({ attribute: !1 })], P.prototype, "renderGroup", void 0), r([u({ attribute: !1 })], P.prototype, "mapLabelFormatter", void 0), r([u({
|
|
1126
1101
|
type: Boolean,
|
|
1127
1102
|
attribute: "show-controls"
|
|
1128
|
-
})],
|
|
1103
|
+
})], P.prototype, "showControls", void 0), r([u({
|
|
1129
1104
|
type: Boolean,
|
|
1130
1105
|
attribute: "show-map-selector"
|
|
1131
|
-
})],
|
|
1106
|
+
})], P.prototype, "showMapSelector", void 0), r([u({
|
|
1132
1107
|
type: Number,
|
|
1133
1108
|
attribute: "max-zoom"
|
|
1134
|
-
})],
|
|
1109
|
+
})], P.prototype, "maxZoom", void 0), r([u({
|
|
1135
1110
|
type: Boolean,
|
|
1136
1111
|
attribute: !1
|
|
1137
|
-
})],
|
|
1138
|
-
var
|
|
1139
|
-
globalThis.customElements !== void 0 && !globalThis.customElements.get("fellowship-dungeon-map") && globalThis.customElements.define("fellowship-dungeon-map",
|
|
1112
|
+
})], P.prototype, "externalOverlay", void 0), r([f()], P.prototype, "_status", void 0), r([f()], P.prototype, "_error", void 0), r([f()], P.prototype, "_data", void 0), r([f()], P.prototype, "_model", void 0), r([f()], P.prototype, "_selectedMap", void 0), r([f()], P.prototype, "_viewportState", void 0), r([f()], P.prototype, "_isPanning", void 0), r([f()], P.prototype, "_enemyRenderContent", void 0), r([f()], P.prototype, "_groupRenderContent", void 0), r([f()], P.prototype, "_mapLabels", void 0), r([d(".viewport")], P.prototype, "_viewportElement", void 0), r([d("canvas")], P.prototype, "_canvasElement", void 0), P = r([l("fellowship-dungeon-map")], P);
|
|
1113
|
+
var F = () => {
|
|
1114
|
+
globalThis.customElements !== void 0 && !globalThis.customElements.get("fellowship-dungeon-map") && globalThis.customElements.define("fellowship-dungeon-map", P);
|
|
1140
1115
|
};
|
|
1141
1116
|
//#endregion
|
|
1142
|
-
export {
|
|
1117
|
+
export { A as a, k as i, F as n, g as o, M as r, P as t };
|
|
1143
1118
|
|
|
1144
|
-
//# sourceMappingURL=dungeon-map-
|
|
1119
|
+
//# sourceMappingURL=dungeon-map-xFaPasVr.js.map
|