Adjusted functionality and style of class/background/species screens
This commit is contained in:
1 parent
18734acaad
commit
4491e22573
3 files changed
+186
-172
No files matched your search
+40
-7
@@ -350,7 +350,7 @@ function getDesktopReskinBootstrap(externalImageRoot: string | null): string {
|
|||||||
addCharactersMenu();
|
addCharactersMenu();
|
||||||
addResetOnboardingMenu();
|
addResetOnboardingMenu();
|
||||||
bindNativeNavbarSync();
|
bindNativeNavbarSync();
|
||||||
postNativeNavbarModel();
|
queueNativeNavbarModelPost({ isForce: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
const NAVBAR_ACTION_ATTR = "data-5etd-nav-action-id";
|
const NAVBAR_ACTION_ATTR = "data-5etd-nav-action-id";
|
||||||
@@ -593,17 +593,50 @@ function getDesktopReskinBootstrap(externalImageRoot: string | null): string {
|
|||||||
return groups;
|
return groups;
|
||||||
};
|
};
|
||||||
|
|
||||||
const postNativeNavbarModel = () => {
|
let navbarModelLastSignature = "";
|
||||||
|
let isNavbarModelPostQueued = false;
|
||||||
|
|
||||||
|
const postNativeNavbarModel = ({ isForce = false } = {}) => {
|
||||||
if (!window.parent || window.parent === window) return;
|
if (!window.parent || window.parent === window) return;
|
||||||
|
const groups = buildNativeNavbarGroups();
|
||||||
|
let nextSignature = "";
|
||||||
|
try {
|
||||||
|
nextSignature = JSON.stringify(groups);
|
||||||
|
} catch {
|
||||||
|
nextSignature = \`groups:\${groups.length}:\${Date.now()}\`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isForce && nextSignature === navbarModelLastSignature) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navbarModelLastSignature = nextSignature;
|
||||||
|
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
{
|
{
|
||||||
type: "5etd:native-navbar-data",
|
type: "5etd:native-navbar-data",
|
||||||
groups: buildNativeNavbarGroups()
|
groups
|
||||||
},
|
},
|
||||||
"*"
|
"*"
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const queueNativeNavbarModelPost = ({ isForce = false } = {}) => {
|
||||||
|
if (isForce) {
|
||||||
|
isNavbarModelPostQueued = false;
|
||||||
|
postNativeNavbarModel({ isForce: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNavbarModelPostQueued) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isNavbarModelPostQueued = true;
|
||||||
|
window.setTimeout(() => {
|
||||||
|
isNavbarModelPostQueued = false;
|
||||||
|
postNativeNavbarModel();
|
||||||
|
}, 80);
|
||||||
|
};
|
||||||
|
|
||||||
let navbarSyncObserver = null;
|
let navbarSyncObserver = null;
|
||||||
const bindNativeNavbarSync = () => {
|
const bindNativeNavbarSync = () => {
|
||||||
const navbar = document.getElementById("navbar");
|
const navbar = document.getElementById("navbar");
|
||||||
@@ -614,9 +647,9 @@ function getDesktopReskinBootstrap(externalImageRoot: string | null): string {
|
|||||||
navbar.dataset.desktopNavSyncBound = "1";
|
navbar.dataset.desktopNavSyncBound = "1";
|
||||||
navbarSyncObserver?.disconnect?.();
|
navbarSyncObserver?.disconnect?.();
|
||||||
navbarSyncObserver = new MutationObserver(() => {
|
navbarSyncObserver = new MutationObserver(() => {
|
||||||
window.requestAnimationFrame(postNativeNavbarModel);
|
queueNativeNavbarModelPost();
|
||||||
});
|
});
|
||||||
navbarSyncObserver.observe(navbar, { childList: true, subtree: true, attributes: true, characterData: true });
|
navbarSyncObserver.observe(navbar, { childList: true, subtree: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
const runNativeNavbarAction = (actionId) => {
|
const runNativeNavbarAction = (actionId) => {
|
||||||
@@ -649,7 +682,7 @@ function getDesktopReskinBootstrap(externalImageRoot: string | null): string {
|
|||||||
|
|
||||||
if (data.type === "5etd:native-navbar-request") {
|
if (data.type === "5etd:native-navbar-request") {
|
||||||
bindNativeNavbarSync();
|
bindNativeNavbarSync();
|
||||||
postNativeNavbarModel();
|
queueNativeNavbarModelPost({ isForce: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,7 +726,7 @@ html.desktop-reskin .page__header {
|
|||||||
border: 1px solid rgba(109, 164, 223, 0.32) !important;
|
border: 1px solid rgba(109, 164, 223, 0.32) !important;
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
background: linear-gradient(115deg, rgba(17, 29, 47, 0.98), rgba(24, 38, 58, 0.96) 64%, rgba(87, 58, 32, 0.88));
|
background: linear-gradient(115deg, rgba(17, 29, 47, 0.98), rgba(24, 38, 58, 0.96) 64%, rgba(87, 58, 32, 0.88));
|
||||||
backdrop-filter: blur(14px);
|
// backdrop-filter: blur(14px);
|
||||||
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.24);
|
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.24);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+116
-152
@@ -1,7 +1,6 @@
|
|||||||
import { FormEvent, JSX, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { FormEvent, JSX, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
const DEFAULT_PAGE = "/5etools.html";
|
const DEFAULT_PAGE = "/5etools.html";
|
||||||
const CARD_PAGE_SIZE = 8;
|
|
||||||
const APP_CHARACTERS_MENU_ID = "__characters";
|
const APP_CHARACTERS_MENU_ID = "__characters";
|
||||||
|
|
||||||
const CHARACTER_STEPS = [
|
const CHARACTER_STEPS = [
|
||||||
@@ -82,6 +81,10 @@ const FIVEETOOLS_RENDER_STYLESHEET_PATHS = ["css/bootstrap.css", "css/main.css"]
|
|||||||
|
|
||||||
type CharacterModalMode = "closed" | "list" | "editor";
|
type CharacterModalMode = "closed" | "list" | "editor";
|
||||||
type EntityPopoverKind = "class" | "species" | "background";
|
type EntityPopoverKind = "class" | "species" | "background";
|
||||||
|
type PendingEntitySelection = {
|
||||||
|
kind: EntityPopoverKind;
|
||||||
|
option: CharacterSelectionOption;
|
||||||
|
};
|
||||||
|
|
||||||
type RendererHandle = {
|
type RendererHandle = {
|
||||||
setBaseUrl: (value: string) => RendererHandle;
|
setBaseUrl: (value: string) => RendererHandle;
|
||||||
@@ -206,10 +209,6 @@ function getPathnameFromHrefLoose(hrefRaw: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function areTopbarGroupsEquivalent(a: TopbarMenuGroup[], b: TopbarMenuGroup[]): boolean {
|
|
||||||
return JSON.stringify(a) === JSON.stringify(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseNativeTopbarMenuList(
|
function parseNativeTopbarMenuList(
|
||||||
list: Element,
|
list: Element,
|
||||||
registerAction: (ele: HTMLElement) => string,
|
registerAction: (ele: HTMLElement) => string,
|
||||||
@@ -755,17 +754,6 @@ function filterBySourceBuckets(options: CharacterSelectionOption[], sourceBucket
|
|||||||
return options.filter((option) => selected.has(option.sourceBucket));
|
return options.filter((option) => selected.has(option.sourceBucket));
|
||||||
}
|
}
|
||||||
|
|
||||||
function clampPage(page: number, totalItems: number): number {
|
|
||||||
const totalPages = Math.max(1, Math.ceil(totalItems / CARD_PAGE_SIZE));
|
|
||||||
return Math.max(1, Math.min(totalPages, page));
|
|
||||||
}
|
|
||||||
|
|
||||||
function paginateOptions(options: CharacterSelectionOption[], page: number): CharacterSelectionOption[] {
|
|
||||||
const safePage = clampPage(page, options.length);
|
|
||||||
const start = (safePage - 1) * CARD_PAGE_SIZE;
|
|
||||||
return options.slice(start, start + CARD_PAGE_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toEntityRef(option: CharacterSelectionOption): CharacterEntityRef {
|
function toEntityRef(option: CharacterSelectionOption): CharacterEntityRef {
|
||||||
return {
|
return {
|
||||||
entityType: option.entityType,
|
entityType: option.entityType,
|
||||||
@@ -2001,6 +1989,7 @@ function WikiView() {
|
|||||||
const [topbarGroups, setTopbarGroups] = useState<TopbarMenuGroup[]>([]);
|
const [topbarGroups, setTopbarGroups] = useState<TopbarMenuGroup[]>([]);
|
||||||
const frameRef = useRef<HTMLIFrameElement | null>(null);
|
const frameRef = useRef<HTMLIFrameElement | null>(null);
|
||||||
const topbarRef = useRef<HTMLElement | null>(null);
|
const topbarRef = useRef<HTMLElement | null>(null);
|
||||||
|
const topbarGroupsSignatureRef = useRef<string>("");
|
||||||
|
|
||||||
const [characterModalMode, setCharacterModalMode] = useState<CharacterModalMode>("closed");
|
const [characterModalMode, setCharacterModalMode] = useState<CharacterModalMode>("closed");
|
||||||
const [characterStep, setCharacterStep] = useState<number>(0);
|
const [characterStep, setCharacterStep] = useState<number>(0);
|
||||||
@@ -2019,10 +2008,8 @@ function WikiView() {
|
|||||||
const [spellSearch, setSpellSearch] = useState("");
|
const [spellSearch, setSpellSearch] = useState("");
|
||||||
const [equipmentItemInput, setEquipmentItemInput] = useState("");
|
const [equipmentItemInput, setEquipmentItemInput] = useState("");
|
||||||
|
|
||||||
const [classPage, setClassPage] = useState(1);
|
|
||||||
const [speciesPage, setSpeciesPage] = useState(1);
|
|
||||||
const [backgroundPage, setBackgroundPage] = useState(1);
|
|
||||||
const [entityPopover, setEntityPopover] = useState<{ kind: EntityPopoverKind; entityRef: string } | null>(null);
|
const [entityPopover, setEntityPopover] = useState<{ kind: EntityPopoverKind; entityRef: string } | null>(null);
|
||||||
|
const [pendingEntitySelection, setPendingEntitySelection] = useState<PendingEntitySelection | null>(null);
|
||||||
const [featCatalog, setFeatCatalog] = useState<FeatCatalogItem[] | null>(null);
|
const [featCatalog, setFeatCatalog] = useState<FeatCatalogItem[] | null>(null);
|
||||||
const [spellCatalog, setSpellCatalog] = useState<SpellCatalogItem[] | null>(null);
|
const [spellCatalog, setSpellCatalog] = useState<SpellCatalogItem[] | null>(null);
|
||||||
const [itemCatalog, setItemCatalog] = useState<string[]>([]);
|
const [itemCatalog, setItemCatalog] = useState<string[]>([]);
|
||||||
@@ -2081,6 +2068,60 @@ function WikiView() {
|
|||||||
[ensureOptionDetail]
|
[ensureOptionDetail]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const closeEntityPopover = useCallback(() => {
|
||||||
|
setEntityPopover(null);
|
||||||
|
setPendingEntitySelection(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const confirmPendingEntitySelection = useCallback(() => {
|
||||||
|
if (!pendingEntitySelection) {
|
||||||
|
closeEntityPopover();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { kind, option } = pendingEntitySelection;
|
||||||
|
updateDraft((draft) => {
|
||||||
|
if (kind === "class") {
|
||||||
|
return {
|
||||||
|
...draft,
|
||||||
|
selections: {
|
||||||
|
...draft.selections,
|
||||||
|
class: toEntityRef(option),
|
||||||
|
subclass: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind === "species") {
|
||||||
|
return {
|
||||||
|
...draft,
|
||||||
|
selections: {
|
||||||
|
...draft.selections,
|
||||||
|
species: toEntityRef(option)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...draft,
|
||||||
|
selections: {
|
||||||
|
...draft.selections,
|
||||||
|
background: toEntityRef(option)
|
||||||
|
},
|
||||||
|
steps: {
|
||||||
|
...draft.steps,
|
||||||
|
abilityScores: {
|
||||||
|
...draft.steps.abilityScores,
|
||||||
|
backgroundBonusModeIndex: 0,
|
||||||
|
backgroundBonusSelections: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
closeEntityPopover();
|
||||||
|
}, [closeEntityPopover, pendingEntitySelection, updateDraft]);
|
||||||
|
|
||||||
const refreshCharacterSummaries = useCallback(async () => {
|
const refreshCharacterSummaries = useCallback(async () => {
|
||||||
const list = await window.desktopApi.listCharacters();
|
const list = await window.desktopApi.listCharacters();
|
||||||
setCharacterSummaries(list);
|
setCharacterSummaries(list);
|
||||||
@@ -2093,9 +2134,6 @@ function WikiView() {
|
|||||||
setFeatSearch("");
|
setFeatSearch("");
|
||||||
setSpellSearch("");
|
setSpellSearch("");
|
||||||
setEquipmentItemInput("");
|
setEquipmentItemInput("");
|
||||||
setClassPage(1);
|
|
||||||
setSpeciesPage(1);
|
|
||||||
setBackgroundPage(1);
|
|
||||||
setCharacterStep(0);
|
setCharacterStep(0);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -2348,9 +2386,9 @@ function WikiView() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const pLoadAll = runtime.DataUtil?.spell?.pLoadAll;
|
const spellDataUtil = runtime.DataUtil?.spell;
|
||||||
const getCombinedClasses = runtime.Renderer?.spell?.getCombinedClasses;
|
const getCombinedClasses = runtime.Renderer?.spell?.getCombinedClasses;
|
||||||
if (!pLoadAll) {
|
if (!spellDataUtil?.pLoadAll) {
|
||||||
if (!isCancelled) {
|
if (!isCancelled) {
|
||||||
setSpellCatalog([]);
|
setSpellCatalog([]);
|
||||||
}
|
}
|
||||||
@@ -2367,7 +2405,7 @@ function WikiView() {
|
|||||||
sourceLookup = null;
|
sourceLookup = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const spellsRaw = await pLoadAll();
|
const spellsRaw = await spellDataUtil.pLoadAll();
|
||||||
const next = (Array.isArray(spellsRaw) ? spellsRaw : [])
|
const next = (Array.isArray(spellsRaw) ? spellsRaw : [])
|
||||||
.map((spellRaw): SpellCatalogItem | null => {
|
.map((spellRaw): SpellCatalogItem | null => {
|
||||||
if (!isRecord(spellRaw)) {
|
if (!isRecord(spellRaw)) {
|
||||||
@@ -2471,7 +2509,20 @@ function WikiView() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTopbarGroups((current) => (areTopbarGroupsEquivalent(current, data.groups as TopbarMenuGroup[]) ? current : (data.groups as TopbarMenuGroup[])));
|
const nextGroups = data.groups as TopbarMenuGroup[];
|
||||||
|
let nextSignature = "";
|
||||||
|
try {
|
||||||
|
nextSignature = JSON.stringify(nextGroups);
|
||||||
|
} catch {
|
||||||
|
nextSignature = `groups:${nextGroups.length}:${Date.now()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topbarGroupsSignatureRef.current === nextSignature) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
topbarGroupsSignatureRef.current = nextSignature;
|
||||||
|
setTopbarGroups(nextGroups);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2829,39 +2880,6 @@ function WikiView() {
|
|||||||
[characterOptions?.backgrounds, sourceBuckets, backgroundSearch]
|
[characterOptions?.backgrounds, sourceBuckets, backgroundSearch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const classTotalPages = Math.max(1, Math.ceil(filteredClassOptions.length / CARD_PAGE_SIZE));
|
|
||||||
const speciesTotalPages = Math.max(1, Math.ceil(filteredSpeciesOptions.length / CARD_PAGE_SIZE));
|
|
||||||
const backgroundTotalPages = Math.max(1, Math.ceil(filteredBackgroundOptions.length / CARD_PAGE_SIZE));
|
|
||||||
|
|
||||||
const classPageSafe = clampPage(classPage, filteredClassOptions.length);
|
|
||||||
const speciesPageSafe = clampPage(speciesPage, filteredSpeciesOptions.length);
|
|
||||||
const backgroundPageSafe = clampPage(backgroundPage, filteredBackgroundOptions.length);
|
|
||||||
|
|
||||||
const classPageOptions = useMemo(
|
|
||||||
() => paginateOptions(filteredClassOptions, classPageSafe),
|
|
||||||
[filteredClassOptions, classPageSafe]
|
|
||||||
);
|
|
||||||
const speciesPageOptions = useMemo(
|
|
||||||
() => paginateOptions(filteredSpeciesOptions, speciesPageSafe),
|
|
||||||
[filteredSpeciesOptions, speciesPageSafe]
|
|
||||||
);
|
|
||||||
const backgroundPageOptions = useMemo(
|
|
||||||
() => paginateOptions(filteredBackgroundOptions, backgroundPageSafe),
|
|
||||||
[filteredBackgroundOptions, backgroundPageSafe]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setClassPage((current) => clampPage(current, filteredClassOptions.length));
|
|
||||||
}, [filteredClassOptions.length]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSpeciesPage((current) => clampPage(current, filteredSpeciesOptions.length));
|
|
||||||
}, [filteredSpeciesOptions.length]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setBackgroundPage((current) => clampPage(current, filteredBackgroundOptions.length));
|
|
||||||
}, [filteredBackgroundOptions.length]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const selectedRefs = [
|
const selectedRefs = [
|
||||||
characterDraft?.selections.class?.entityRef,
|
characterDraft?.selections.class?.entityRef,
|
||||||
@@ -2883,6 +2901,12 @@ function WikiView() {
|
|||||||
: null;
|
: null;
|
||||||
const entityPopoverDetail = entityPopover ? detailCache[entityPopover.entityRef] ?? null : null;
|
const entityPopoverDetail = entityPopover ? detailCache[entityPopover.entityRef] ?? null : null;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (characterModalMode !== "editor") {
|
||||||
|
closeEntityPopover();
|
||||||
|
}
|
||||||
|
}, [characterModalMode, closeEntityPopover]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (characterModalMode !== "editor" || !entityPopover) {
|
if (characterModalMode !== "editor" || !entityPopover) {
|
||||||
return;
|
return;
|
||||||
@@ -2890,22 +2914,22 @@ function WikiView() {
|
|||||||
|
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
setEntityPopover(null);
|
closeEntityPopover();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("keydown", onKeyDown);
|
window.addEventListener("keydown", onKeyDown);
|
||||||
return () => window.removeEventListener("keydown", onKeyDown);
|
return () => window.removeEventListener("keydown", onKeyDown);
|
||||||
}, [characterModalMode, entityPopover]);
|
}, [characterModalMode, closeEntityPopover, entityPopover]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (characterModalMode !== "editor" || !entityPopover) {
|
if (characterModalMode !== "editor" || !entityPopover) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (characterStep < 0 || characterStep > 2) {
|
if (characterStep < 0 || characterStep > 2) {
|
||||||
setEntityPopover(null);
|
closeEntityPopover();
|
||||||
}
|
}
|
||||||
}, [characterModalMode, characterStep, entityPopover]);
|
}, [characterModalMode, characterStep, closeEntityPopover, entityPopover]);
|
||||||
|
|
||||||
const classSkillData = useMemo(
|
const classSkillData = useMemo(
|
||||||
() => parseClassSkills(selectedClassDetail),
|
() => parseClassSkills(selectedClassDetail),
|
||||||
@@ -4168,7 +4192,7 @@ function WikiView() {
|
|||||||
<div className="entity-layout">
|
<div className="entity-layout">
|
||||||
<div className="entity-list-panel entity-list-panel--solo">
|
<div className="entity-list-panel entity-list-panel--solo">
|
||||||
<div className="entity-card-grid">
|
<div className="entity-card-grid">
|
||||||
{classPageOptions.map((option) => {
|
{filteredClassOptions.map((option) => {
|
||||||
const isSelected = characterDraft.selections.class?.entityRef === option.entityRef;
|
const isSelected = characterDraft.selections.class?.entityRef === option.entityRef;
|
||||||
const imageUrl = buildAssetUrl(wikiUrl, option.imagePath);
|
const imageUrl = buildAssetUrl(wikiUrl, option.imagePath);
|
||||||
const sourceBadge = getSourceBadgeText(option.source, option.sourceEdition);
|
const sourceBadge = getSourceBadgeText(option.source, option.sourceEdition);
|
||||||
@@ -4179,14 +4203,7 @@ function WikiView() {
|
|||||||
key={option.entityRef}
|
key={option.entityRef}
|
||||||
className={isSelected ? "entity-tile is-selected" : "entity-tile"}
|
className={isSelected ? "entity-tile is-selected" : "entity-tile"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateDraft((draft) => ({
|
setPendingEntitySelection({ kind: "class", option });
|
||||||
...draft,
|
|
||||||
selections: {
|
|
||||||
...draft.selections,
|
|
||||||
class: toEntityRef(option),
|
|
||||||
subclass: null
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
openEntityPopover("class", option.entityRef);
|
openEntityPopover("class", option.entityRef);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -4205,22 +4222,6 @@ function WikiView() {
|
|||||||
|
|
||||||
{filteredClassOptions.length === 0 ? <p className="character-empty">No classes match the selected source buckets.</p> : null}
|
{filteredClassOptions.length === 0 ? <p className="character-empty">No classes match the selected source buckets.</p> : null}
|
||||||
|
|
||||||
<div className="entity-pagination">
|
|
||||||
<button type="button" onClick={() => setClassPage((it) => Math.max(1, it - 1))} disabled={classPageSafe <= 1}>
|
|
||||||
Previous
|
|
||||||
</button>
|
|
||||||
<span>
|
|
||||||
Page {classPageSafe}/{classTotalPages} ({filteredClassOptions.length} total)
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setClassPage((it) => Math.min(classTotalPages, it + 1))}
|
|
||||||
disabled={classPageSafe >= classTotalPages}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="entity-list-actions">
|
<div className="entity-list-actions">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -4229,6 +4230,7 @@ function WikiView() {
|
|||||||
if (!characterDraft.selections.class) {
|
if (!characterDraft.selections.class) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setPendingEntitySelection(null);
|
||||||
openEntityPopover("class", characterDraft.selections.class.entityRef);
|
openEntityPopover("class", characterDraft.selections.class.entityRef);
|
||||||
}}
|
}}
|
||||||
disabled={!characterDraft.selections.class}
|
disabled={!characterDraft.selections.class}
|
||||||
@@ -4252,7 +4254,7 @@ function WikiView() {
|
|||||||
<div className="entity-layout">
|
<div className="entity-layout">
|
||||||
<div className="entity-list-panel entity-list-panel--solo">
|
<div className="entity-list-panel entity-list-panel--solo">
|
||||||
<div className="entity-card-grid">
|
<div className="entity-card-grid">
|
||||||
{speciesPageOptions.map((option) => {
|
{filteredSpeciesOptions.map((option) => {
|
||||||
const isSelected = characterDraft.selections.species?.entityRef === option.entityRef;
|
const isSelected = characterDraft.selections.species?.entityRef === option.entityRef;
|
||||||
const imageUrl = buildAssetUrl(wikiUrl, option.imagePath);
|
const imageUrl = buildAssetUrl(wikiUrl, option.imagePath);
|
||||||
const sourceBadge = getSourceBadgeText(option.source, option.sourceEdition);
|
const sourceBadge = getSourceBadgeText(option.source, option.sourceEdition);
|
||||||
@@ -4263,13 +4265,7 @@ function WikiView() {
|
|||||||
key={option.entityRef}
|
key={option.entityRef}
|
||||||
className={isSelected ? "entity-tile is-selected" : "entity-tile"}
|
className={isSelected ? "entity-tile is-selected" : "entity-tile"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateDraft((draft) => ({
|
setPendingEntitySelection({ kind: "species", option });
|
||||||
...draft,
|
|
||||||
selections: {
|
|
||||||
...draft.selections,
|
|
||||||
species: toEntityRef(option)
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
openEntityPopover("species", option.entityRef);
|
openEntityPopover("species", option.entityRef);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -4288,22 +4284,6 @@ function WikiView() {
|
|||||||
|
|
||||||
{filteredSpeciesOptions.length === 0 ? <p className="character-empty">No species match the selected source buckets.</p> : null}
|
{filteredSpeciesOptions.length === 0 ? <p className="character-empty">No species match the selected source buckets.</p> : null}
|
||||||
|
|
||||||
<div className="entity-pagination">
|
|
||||||
<button type="button" onClick={() => setSpeciesPage((it) => Math.max(1, it - 1))} disabled={speciesPageSafe <= 1}>
|
|
||||||
Previous
|
|
||||||
</button>
|
|
||||||
<span>
|
|
||||||
Page {speciesPageSafe}/{speciesTotalPages} ({filteredSpeciesOptions.length} total)
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setSpeciesPage((it) => Math.min(speciesTotalPages, it + 1))}
|
|
||||||
disabled={speciesPageSafe >= speciesTotalPages}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="entity-list-actions">
|
<div className="entity-list-actions">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -4312,6 +4292,7 @@ function WikiView() {
|
|||||||
if (!characterDraft.selections.species) {
|
if (!characterDraft.selections.species) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setPendingEntitySelection(null);
|
||||||
openEntityPopover("species", characterDraft.selections.species.entityRef);
|
openEntityPopover("species", characterDraft.selections.species.entityRef);
|
||||||
}}
|
}}
|
||||||
disabled={!characterDraft.selections.species}
|
disabled={!characterDraft.selections.species}
|
||||||
@@ -4337,7 +4318,7 @@ function WikiView() {
|
|||||||
<div className="entity-layout">
|
<div className="entity-layout">
|
||||||
<div className="entity-list-panel entity-list-panel--solo">
|
<div className="entity-list-panel entity-list-panel--solo">
|
||||||
<div className="entity-card-grid">
|
<div className="entity-card-grid">
|
||||||
{backgroundPageOptions.map((option) => {
|
{filteredBackgroundOptions.map((option) => {
|
||||||
const isSelected = characterDraft.selections.background?.entityRef === option.entityRef;
|
const isSelected = characterDraft.selections.background?.entityRef === option.entityRef;
|
||||||
const imageUrl = buildAssetUrl(wikiUrl, option.imagePath);
|
const imageUrl = buildAssetUrl(wikiUrl, option.imagePath);
|
||||||
const sourceBadge = getSourceBadgeText(option.source, option.sourceEdition);
|
const sourceBadge = getSourceBadgeText(option.source, option.sourceEdition);
|
||||||
@@ -4348,21 +4329,7 @@ function WikiView() {
|
|||||||
key={option.entityRef}
|
key={option.entityRef}
|
||||||
className={isSelected ? "entity-tile is-selected" : "entity-tile"}
|
className={isSelected ? "entity-tile is-selected" : "entity-tile"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateDraft((draft) => ({
|
setPendingEntitySelection({ kind: "background", option });
|
||||||
...draft,
|
|
||||||
selections: {
|
|
||||||
...draft.selections,
|
|
||||||
background: toEntityRef(option)
|
|
||||||
},
|
|
||||||
steps: {
|
|
||||||
...draft.steps,
|
|
||||||
abilityScores: {
|
|
||||||
...draft.steps.abilityScores,
|
|
||||||
backgroundBonusModeIndex: 0,
|
|
||||||
backgroundBonusSelections: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
openEntityPopover("background", option.entityRef);
|
openEntityPopover("background", option.entityRef);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -4383,26 +4350,6 @@ function WikiView() {
|
|||||||
<p className="character-empty">No backgrounds match the selected source buckets.</p>
|
<p className="character-empty">No backgrounds match the selected source buckets.</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div className="entity-pagination">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setBackgroundPage((it) => Math.max(1, it - 1))}
|
|
||||||
disabled={backgroundPageSafe <= 1}
|
|
||||||
>
|
|
||||||
Previous
|
|
||||||
</button>
|
|
||||||
<span>
|
|
||||||
Page {backgroundPageSafe}/{backgroundTotalPages} ({filteredBackgroundOptions.length} total)
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setBackgroundPage((it) => Math.min(backgroundTotalPages, it + 1))}
|
|
||||||
disabled={backgroundPageSafe >= backgroundTotalPages}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="entity-list-actions">
|
<div className="entity-list-actions">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -4411,6 +4358,7 @@ function WikiView() {
|
|||||||
if (!characterDraft.selections.background) {
|
if (!characterDraft.selections.background) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setPendingEntitySelection(null);
|
||||||
openEntityPopover("background", characterDraft.selections.background.entityRef);
|
openEntityPopover("background", characterDraft.selections.background.entityRef);
|
||||||
}}
|
}}
|
||||||
disabled={!characterDraft.selections.background}
|
disabled={!characterDraft.selections.background}
|
||||||
@@ -5973,7 +5921,7 @@ function WikiView() {
|
|||||||
<div
|
<div
|
||||||
className="entity-popover-backdrop"
|
className="entity-popover-backdrop"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
onClick={() => setEntityPopover(null)}
|
onClick={closeEntityPopover}
|
||||||
>
|
>
|
||||||
<section
|
<section
|
||||||
className="entity-popover"
|
className="entity-popover"
|
||||||
@@ -5990,11 +5938,27 @@ function WikiView() {
|
|||||||
? "Species details"
|
? "Species details"
|
||||||
: "Background details"}
|
: "Background details"}
|
||||||
</h4>
|
</h4>
|
||||||
<button type="button" className="is-subtle" onClick={() => setEntityPopover(null)}>
|
<button type="button" className="is-subtle" onClick={closeEntityPopover}>
|
||||||
Close
|
{pendingEntitySelection ? "Cancel" : "Close"}
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
{renderEntityDetailContent(entityPopoverDetail, entityPopover.kind)}
|
<div className="entity-popover-body">{renderEntityDetailContent(entityPopoverDetail, entityPopover.kind)}</div>
|
||||||
|
<footer className="entity-popover-footer">
|
||||||
|
{pendingEntitySelection ? (
|
||||||
|
<>
|
||||||
|
<button type="button" className="is-subtle" onClick={closeEntityPopover}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={confirmPendingEntitySelection}>
|
||||||
|
Add {pendingEntitySelection.kind}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<button type="button" className="is-subtle" onClick={closeEntityPopover}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</footer>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
+30
-13
@@ -69,7 +69,7 @@ body {
|
|||||||
radial-gradient(circle at 16% 0%, rgba(71, 131, 206, 0.34), transparent 34%),
|
radial-gradient(circle at 16% 0%, rgba(71, 131, 206, 0.34), transparent 34%),
|
||||||
radial-gradient(circle at 88% -8%, rgba(234, 163, 78, 0.28), transparent 40%),
|
radial-gradient(circle at 88% -8%, rgba(234, 163, 78, 0.28), transparent 40%),
|
||||||
linear-gradient(180deg, rgba(14, 24, 40, 0.96), rgba(9, 16, 28, 0.97));
|
linear-gradient(180deg, rgba(14, 24, 40, 0.96), rgba(9, 16, 28, 0.97));
|
||||||
backdrop-filter: blur(12px);
|
/* backdrop-filter: blur(12px); */
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
isolation: isolate;
|
isolation: isolate;
|
||||||
@@ -516,9 +516,9 @@ body {
|
|||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
background: linear-gradient(180deg, rgba(14, 22, 35, 0.98), rgba(8, 14, 24, 0.99));
|
background: linear-gradient(180deg, rgba(14, 22, 35, 0.98), rgba(8, 14, 24, 0.99));
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
display: grid;
|
/* display: grid; */
|
||||||
grid-template-rows: auto auto 1fr;
|
/* grid-template-rows: auto auto 1fr; */
|
||||||
overflow: hidden;
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.character-modal button,
|
.character-modal button,
|
||||||
@@ -1529,6 +1529,7 @@ body {
|
|||||||
.entity-card-grid {
|
.entity-card-grid {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
max-height: clamp(260px, 44vh, 560px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
@@ -1737,7 +1738,7 @@ body {
|
|||||||
color: #edf4ff;
|
color: #edf4ff;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 0;
|
padding: 0 !important;
|
||||||
display: block;
|
display: block;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
aspect-ratio: 1 / 1;
|
aspect-ratio: 1 / 1;
|
||||||
@@ -1837,17 +1838,18 @@ body {
|
|||||||
place-items: center;
|
place-items: center;
|
||||||
padding: 18px;
|
padding: 18px;
|
||||||
background: rgba(2, 6, 12, 0.74);
|
background: rgba(2, 6, 12, 0.74);
|
||||||
backdrop-filter: blur(2px);
|
/* backdrop-filter: blur(2px); */
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-popover {
|
.entity-popover {
|
||||||
width: 70%;
|
width: 70%;
|
||||||
max-height: calc(70vh - 36px);
|
max-height: calc(70vh - 36px);
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
border: 1px solid var(--line-strong);
|
border: 1px solid var(--line-strong);
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
background: rgba(8, 12, 20, 0.98);
|
background: rgba(8, 12, 20, 0.98);
|
||||||
padding: 12px;
|
|
||||||
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.45);
|
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.45);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1856,9 +1858,8 @@ body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding-bottom: 10px;
|
padding: 12px;
|
||||||
border-bottom: 1px solid var(--line);
|
border-bottom: 1px solid var(--line);
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-popover-header h4 {
|
.entity-popover-header h4 {
|
||||||
@@ -1867,6 +1868,22 @@ body {
|
|||||||
font-size: 1.05rem;
|
font-size: 1.05rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.entity-popover-body {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-popover-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
background: rgba(5, 9, 16, 0.94);
|
||||||
|
}
|
||||||
|
|
||||||
.entity-detail--popover {
|
.entity-detail--popover {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
@@ -1886,6 +1903,7 @@ body {
|
|||||||
|
|
||||||
.chooser-layout {
|
.chooser-layout {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
max-height: 62vh;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
@@ -2249,10 +2267,10 @@ body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ability-score-strip,
|
/* .ability-score-strip,
|
||||||
.ability-assignment-grid--ability-step {
|
.ability-assignment-grid--ability-step {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
} */
|
||||||
|
|
||||||
.chooser-layout {
|
.chooser-layout {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -2305,7 +2323,6 @@ body {
|
|||||||
width: calc(100vw - 20px);
|
width: calc(100vw - 20px);
|
||||||
max-height: calc(100vh - 20px);
|
max-height: calc(100vh - 20px);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-popover-header {
|
.entity-popover-header {
|
||||||
|
|||||||
Reference in new issue
Block a user