Compare commits
2
Commits
18734acaad
...
7712e971a8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7712e971a8 | ||
|
|
4491e22573 |
No files matched your search
@@ -16,6 +16,12 @@ export interface CharacterEntityRef {
|
||||
entityRef: string;
|
||||
}
|
||||
|
||||
export interface CharacterClassSelection {
|
||||
class: CharacterEntityRef;
|
||||
subclass: CharacterEntityRef | null;
|
||||
level: number;
|
||||
}
|
||||
|
||||
export interface CharacterProfile {
|
||||
name: string;
|
||||
level: number;
|
||||
@@ -94,6 +100,7 @@ export interface CharacterSteps {
|
||||
export interface CharacterSelections {
|
||||
class: CharacterEntityRef | null;
|
||||
subclass: CharacterEntityRef | null;
|
||||
classes: CharacterClassSelection[];
|
||||
background: CharacterEntityRef | null;
|
||||
species: CharacterEntityRef | null;
|
||||
}
|
||||
@@ -219,6 +226,7 @@ const DEFAULT_STEPS: CharacterSteps = {
|
||||
const DEFAULT_SELECTIONS: CharacterSelections = {
|
||||
class: null,
|
||||
subclass: null,
|
||||
classes: [],
|
||||
background: null,
|
||||
species: null
|
||||
};
|
||||
@@ -375,6 +383,63 @@ function normalizeEntityRef(value: unknown, expectedType: CharacterEntityType):
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeClassSelection(value: unknown): CharacterClassSelection | null {
|
||||
if (!isRecord(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const classRef = normalizeEntityRef(value.class, "class");
|
||||
if (!classRef) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const subclassRef = normalizeEntityRef(value.subclass, "subclass");
|
||||
return {
|
||||
class: classRef,
|
||||
subclass: subclassRef,
|
||||
level: toInteger(value.level, 1, 1, 20)
|
||||
};
|
||||
}
|
||||
|
||||
function getTotalClassLevel(classes: CharacterClassSelection[]): number {
|
||||
const total = classes.reduce((sum, cls) => sum + cls.level, 0);
|
||||
return Math.max(1, Math.min(20, total));
|
||||
}
|
||||
|
||||
function normalizeClassSelections(
|
||||
value: unknown,
|
||||
fallbackClass: CharacterEntityRef | null,
|
||||
fallbackSubclass: CharacterEntityRef | null,
|
||||
fallbackLevel: number
|
||||
): CharacterClassSelection[] {
|
||||
const out: CharacterClassSelection[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
for (const raw of value) {
|
||||
const normalized = normalizeClassSelection(raw);
|
||||
if (!normalized) {
|
||||
continue;
|
||||
}
|
||||
if (seen.has(normalized.class.entityRef)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(normalized.class.entityRef);
|
||||
out.push(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
if (!out.length && fallbackClass) {
|
||||
out.push({
|
||||
class: fallbackClass,
|
||||
subclass: fallbackSubclass,
|
||||
level: Math.max(1, Math.min(20, fallbackLevel))
|
||||
});
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
function normalizeProfile(value: unknown): CharacterProfile {
|
||||
const profile = isRecord(value) ? value : {};
|
||||
return {
|
||||
@@ -521,11 +586,17 @@ function normalizeAbout(value: unknown): CharacterAboutStep {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSelections(value: unknown): CharacterSelections {
|
||||
function normalizeSelections(value: unknown, fallbackLevel: number): CharacterSelections {
|
||||
const selections = isRecord(value) ? value : {};
|
||||
const primaryClass = normalizeEntityRef(selections.class, "class");
|
||||
const primarySubclass = normalizeEntityRef(selections.subclass, "subclass");
|
||||
const classes = normalizeClassSelections(selections.classes, primaryClass, primarySubclass, fallbackLevel);
|
||||
const firstClass = classes[0];
|
||||
|
||||
return {
|
||||
class: normalizeEntityRef(selections.class, "class"),
|
||||
subclass: normalizeEntityRef(selections.subclass, "subclass"),
|
||||
class: firstClass?.class ?? primaryClass,
|
||||
subclass: firstClass?.subclass ?? primarySubclass,
|
||||
classes,
|
||||
background: normalizeEntityRef(selections.background, "background"),
|
||||
species: normalizeEntityRef(selections.species, "species")
|
||||
};
|
||||
@@ -549,8 +620,12 @@ function normalizeCharacterRecord(raw: unknown): CharacterDocument {
|
||||
const root = isRecord(raw) ? raw : {};
|
||||
|
||||
const profile = normalizeProfile(root.profile);
|
||||
const selections = normalizeSelections(root.selections);
|
||||
const selections = normalizeSelections(root.selections, profile.level);
|
||||
const steps = normalizeSteps(root.steps);
|
||||
const normalizedProfile: CharacterProfile = {
|
||||
...profile,
|
||||
level: selections.classes.length ? getTotalClassLevel(selections.classes) : profile.level
|
||||
};
|
||||
|
||||
const idCandidate = toString(root.id).trim();
|
||||
const id = idCandidate || randomUUID();
|
||||
@@ -561,7 +636,7 @@ function normalizeCharacterRecord(raw: unknown): CharacterDocument {
|
||||
id,
|
||||
createdAt: toIsoDate(root.createdAt, nowIso),
|
||||
updatedAt: toIsoDate(root.updatedAt, nowIso),
|
||||
profile,
|
||||
profile: normalizedProfile,
|
||||
selections,
|
||||
steps,
|
||||
notes: toString(root.notes).trim()
|
||||
@@ -655,7 +730,7 @@ export function listCharacters(charactersDir: string): CharacterSummary[] {
|
||||
id: character.id,
|
||||
name: character.profile.name,
|
||||
level: character.profile.level,
|
||||
className: character.selections.class?.name ?? null,
|
||||
className: character.selections.classes[0]?.class.name ?? character.selections.class?.name ?? null,
|
||||
backgroundName: character.selections.background?.name ?? null,
|
||||
speciesName: character.selections.species?.name ?? null,
|
||||
createdAt: character.createdAt,
|
||||
|
||||
+40
-7
@@ -350,7 +350,7 @@ function getDesktopReskinBootstrap(externalImageRoot: string | null): string {
|
||||
addCharactersMenu();
|
||||
addResetOnboardingMenu();
|
||||
bindNativeNavbarSync();
|
||||
postNativeNavbarModel();
|
||||
queueNativeNavbarModelPost({ isForce: true });
|
||||
};
|
||||
|
||||
const NAVBAR_ACTION_ATTR = "data-5etd-nav-action-id";
|
||||
@@ -593,17 +593,50 @@ function getDesktopReskinBootstrap(externalImageRoot: string | null): string {
|
||||
return groups;
|
||||
};
|
||||
|
||||
const postNativeNavbarModel = () => {
|
||||
let navbarModelLastSignature = "";
|
||||
let isNavbarModelPostQueued = false;
|
||||
|
||||
const postNativeNavbarModel = ({ isForce = false } = {}) => {
|
||||
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(
|
||||
{
|
||||
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;
|
||||
const bindNativeNavbarSync = () => {
|
||||
const navbar = document.getElementById("navbar");
|
||||
@@ -614,9 +647,9 @@ function getDesktopReskinBootstrap(externalImageRoot: string | null): string {
|
||||
navbar.dataset.desktopNavSyncBound = "1";
|
||||
navbarSyncObserver?.disconnect?.();
|
||||
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) => {
|
||||
@@ -649,7 +682,7 @@ function getDesktopReskinBootstrap(externalImageRoot: string | null): string {
|
||||
|
||||
if (data.type === "5etd:native-navbar-request") {
|
||||
bindNativeNavbarSync();
|
||||
postNativeNavbarModel();
|
||||
queueNativeNavbarModelPost({ isForce: true });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -693,7 +726,7 @@ html.desktop-reskin .page__header {
|
||||
border: 1px solid rgba(109, 164, 223, 0.32) !important;
|
||||
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));
|
||||
backdrop-filter: blur(14px);
|
||||
// backdrop-filter: blur(14px);
|
||||
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,12 @@ interface CharacterEntityRef {
|
||||
entityRef: string;
|
||||
}
|
||||
|
||||
interface CharacterClassSelection {
|
||||
class: CharacterEntityRef;
|
||||
subclass: CharacterEntityRef | null;
|
||||
level: number;
|
||||
}
|
||||
|
||||
interface CharacterProfile {
|
||||
name: string;
|
||||
level: number;
|
||||
@@ -151,6 +157,7 @@ interface CharacterDocument {
|
||||
selections: {
|
||||
class: CharacterEntityRef | null;
|
||||
subclass: CharacterEntityRef | null;
|
||||
classes: CharacterClassSelection[];
|
||||
background: CharacterEntityRef | null;
|
||||
species: CharacterEntityRef | null;
|
||||
};
|
||||
|
||||
+1624
-288
File diff suppressed because it is too large.
Load diff
+141
-13
@@ -69,7 +69,7 @@ body {
|
||||
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%),
|
||||
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;
|
||||
overflow: visible;
|
||||
isolation: isolate;
|
||||
@@ -516,9 +516,9 @@ body {
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(180deg, rgba(14, 22, 35, 0.98), rgba(8, 14, 24, 0.99));
|
||||
box-shadow: var(--shadow);
|
||||
display: grid;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
overflow: hidden;
|
||||
/* display: grid; */
|
||||
/* grid-template-rows: auto auto 1fr; */
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.character-modal button,
|
||||
@@ -1529,6 +1529,7 @@ body {
|
||||
.entity-card-grid {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
max-height: clamp(260px, 44vh, 560px);
|
||||
overflow: auto;
|
||||
padding-right: 4px;
|
||||
gap: 10px;
|
||||
@@ -1737,10 +1738,18 @@ body {
|
||||
color: #edf4ff;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
padding: 0 !important;
|
||||
display: block;
|
||||
min-height: 0;
|
||||
aspect-ratio: 1 / 1;
|
||||
cursor: pointer;
|
||||
transition: border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease;
|
||||
}
|
||||
|
||||
.entity-tile:hover {
|
||||
border-color: rgba(134, 183, 247, 0.88);
|
||||
box-shadow: 0 0 0 1px rgba(121, 178, 249, 0.28);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.entity-tile-media {
|
||||
@@ -1829,6 +1838,97 @@ body {
|
||||
box-shadow: 0 0 0 1px rgba(252, 84, 84, 0.42);
|
||||
}
|
||||
|
||||
.entity-tile.is-disabled {
|
||||
opacity: 0.45;
|
||||
filter: grayscale(0.5);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.character-class-list {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: rgba(9, 14, 24, 0.72);
|
||||
padding: 10px;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.character-class-list h4 {
|
||||
margin: 0;
|
||||
color: #eef4ff;
|
||||
font-size: 0.96rem;
|
||||
}
|
||||
|
||||
.character-class-list-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.character-class-chip {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: rgba(7, 11, 20, 0.84);
|
||||
padding: 8px;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.character-class-chip header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.character-class-chip header strong {
|
||||
color: #eef4ff;
|
||||
}
|
||||
|
||||
.character-class-chip header span {
|
||||
font-size: 0.76rem;
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.character-class-chip-badges {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.character-class-chip-role {
|
||||
border: 1px solid rgba(73, 92, 120, 0.8);
|
||||
border-radius: 999px;
|
||||
padding: 1px 7px;
|
||||
font-size: 0.68rem !important;
|
||||
color: #c5d4ea !important;
|
||||
background: rgba(19, 28, 42, 0.74);
|
||||
}
|
||||
|
||||
.character-class-chip-role.is-primary {
|
||||
border-color: rgba(51, 158, 102, 0.8);
|
||||
color: #dcfff1 !important;
|
||||
background: rgba(21, 108, 67, 0.64);
|
||||
}
|
||||
|
||||
.character-class-chip-note {
|
||||
margin: -2px 0 0;
|
||||
font-size: 0.76rem;
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.character-class-chip-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.character-class-chip-actions span {
|
||||
font-size: 0.86rem;
|
||||
color: var(--text-1);
|
||||
}
|
||||
|
||||
.entity-popover-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@@ -1837,17 +1937,18 @@ body {
|
||||
place-items: center;
|
||||
padding: 18px;
|
||||
background: rgba(2, 6, 12, 0.74);
|
||||
backdrop-filter: blur(2px);
|
||||
/* backdrop-filter: blur(2px); */
|
||||
}
|
||||
|
||||
.entity-popover {
|
||||
width: 70%;
|
||||
max-height: calc(70vh - 36px);
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--line-strong);
|
||||
border-radius: 14px;
|
||||
background: rgba(8, 12, 20, 0.98);
|
||||
padding: 12px;
|
||||
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
@@ -1856,9 +1957,8 @@ body {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.entity-popover-header h4 {
|
||||
@@ -1867,6 +1967,22 @@ body {
|
||||
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 {
|
||||
min-height: 0;
|
||||
}
|
||||
@@ -1886,6 +2002,7 @@ body {
|
||||
|
||||
.chooser-layout {
|
||||
min-height: 0;
|
||||
max-height: 62vh;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
@@ -2083,10 +2200,12 @@ body {
|
||||
|
||||
.spell-level-group {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.spell-level-group h5 {
|
||||
grid-column: 1 / -1;
|
||||
margin: 0;
|
||||
color: #f2f5ff;
|
||||
font-size: 0.9rem;
|
||||
@@ -2109,6 +2228,10 @@ body {
|
||||
background: linear-gradient(145deg, rgba(121, 39, 39, 0.64), rgba(25, 22, 33, 0.95));
|
||||
}
|
||||
|
||||
.spell-pick-card.is-disabled {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.spell-pick-title {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
@@ -2249,10 +2372,10 @@ body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ability-score-strip,
|
||||
/* .ability-score-strip,
|
||||
.ability-assignment-grid--ability-step {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
} */
|
||||
|
||||
.chooser-layout {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -2271,6 +2394,12 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 900px) {
|
||||
.spell-level-group {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 900px) {
|
||||
.entity-list-actions {
|
||||
justify-content: stretch;
|
||||
@@ -2305,7 +2434,6 @@ body {
|
||||
width: calc(100vw - 20px);
|
||||
max-height: calc(100vh - 20px);
|
||||
border-radius: 12px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.entity-popover-header {
|
||||
|
||||
Vendored
+7
@@ -92,6 +92,12 @@ interface CharacterEntityRef {
|
||||
entityRef: string;
|
||||
}
|
||||
|
||||
interface CharacterClassSelection {
|
||||
class: CharacterEntityRef;
|
||||
subclass: CharacterEntityRef | null;
|
||||
level: number;
|
||||
}
|
||||
|
||||
interface CharacterProfile {
|
||||
name: string;
|
||||
level: number;
|
||||
@@ -167,6 +173,7 @@ interface CharacterDocument {
|
||||
selections: {
|
||||
class: CharacterEntityRef | null;
|
||||
subclass: CharacterEntityRef | null;
|
||||
classes: CharacterClassSelection[];
|
||||
background: CharacterEntityRef | null;
|
||||
species: CharacterEntityRef | null;
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user