Preserve selected character when activating
This commit is contained in:
@@ -61,6 +61,8 @@ const rollVisibilitySelect = mustSelect("roll-visibility");
|
||||
|
||||
type AppState = {
|
||||
user: UserSummary | null;
|
||||
activeCharacterId: string | null;
|
||||
selectedCharacterId: string | null;
|
||||
campaigns: CampaignSummary[];
|
||||
selectedCampaignId: string | null;
|
||||
selectedCampaign: CampaignDetails | null;
|
||||
@@ -71,6 +73,8 @@ type AppState = {
|
||||
|
||||
const state: AppState = {
|
||||
user: null,
|
||||
activeCharacterId: null,
|
||||
selectedCharacterId: null,
|
||||
campaigns: [],
|
||||
selectedCampaignId: null,
|
||||
selectedCampaign: null,
|
||||
@@ -138,11 +142,21 @@ campaignSelect.addEventListener("change", async () => {
|
||||
const selected = campaignSelect.value;
|
||||
state.selectedCampaignId = selected.length > 0 ? selected : null;
|
||||
await reloadSelectedCampaign();
|
||||
syncSelectedCharacter();
|
||||
renderCharacterSelect();
|
||||
renderSkillSelect();
|
||||
connectStateEvents();
|
||||
renderAll();
|
||||
renderCampaignMeta();
|
||||
renderCampaignDetails();
|
||||
renderCampaignLog();
|
||||
});
|
||||
});
|
||||
|
||||
characterSelect.addEventListener("change", () => {
|
||||
state.selectedCharacterId = characterSelect.value.length > 0 ? characterSelect.value : null;
|
||||
renderSkillSelect();
|
||||
});
|
||||
|
||||
refreshCampaignButton.addEventListener("click", async () => {
|
||||
await runAction(async () => {
|
||||
await reloadSelectedCampaign();
|
||||
@@ -179,6 +193,8 @@ activateCharacterButton.addEventListener("click", async () => {
|
||||
}
|
||||
|
||||
await activateCharacter(characterId);
|
||||
state.activeCharacterId = characterId;
|
||||
state.selectedCharacterId = characterId;
|
||||
await reloadAll();
|
||||
setMessage("Active character updated.", false);
|
||||
});
|
||||
@@ -270,10 +286,12 @@ async function reloadSession(): Promise<void> {
|
||||
try {
|
||||
const me = await getMe();
|
||||
state.user = me.user;
|
||||
state.activeCharacterId = me.activeCharacterId ?? null;
|
||||
state.selectedCampaignId = me.currentCampaignId ?? state.selectedCampaignId;
|
||||
}
|
||||
catch {
|
||||
state.user = null;
|
||||
state.activeCharacterId = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,6 +335,7 @@ async function reloadSelectedCampaign(): Promise<void> {
|
||||
}
|
||||
|
||||
state.selectedCampaign = await getCampaign(state.selectedCampaignId);
|
||||
syncSelectedCharacter();
|
||||
}
|
||||
|
||||
async function reloadCampaignLog(): Promise<void> {
|
||||
@@ -368,6 +387,8 @@ function resetStateAfterLogout(): void {
|
||||
}
|
||||
|
||||
function resetAuthenticatedState(): void {
|
||||
state.activeCharacterId = null;
|
||||
state.selectedCharacterId = null;
|
||||
state.campaigns = [];
|
||||
state.selectedCampaignId = null;
|
||||
state.selectedCampaign = null;
|
||||
@@ -404,7 +425,9 @@ function renderCampaignMeta(): void {
|
||||
}
|
||||
|
||||
const isGm = state.selectedCampaign.gm.id === state.user.id;
|
||||
campaignMetaElement.textContent = `Selected: ${state.selectedCampaign.name} (${state.selectedCampaign.rulesetId}) - ${isGm ? "You are GM" : "Player context"}`;
|
||||
const activeCharacter = state.selectedCampaign.characters.find((character) => character.id === state.activeCharacterId);
|
||||
const activeLabel = activeCharacter ? ` | Active: ${activeCharacter.name}` : "";
|
||||
campaignMetaElement.textContent = `Selected: ${state.selectedCampaign.name} (${state.selectedCampaign.rulesetId}) - ${isGm ? "You are GM" : "Player context"}${activeLabel}`;
|
||||
}
|
||||
|
||||
function renderCampaignDetails(): void {
|
||||
@@ -433,14 +456,21 @@ function renderCampaignDetails(): void {
|
||||
function renderCharacterSelect(): void {
|
||||
if (!state.selectedCampaign) {
|
||||
characterSelect.innerHTML = "";
|
||||
state.selectedCharacterId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedCharacterId = resolveSelectedCharacterId();
|
||||
|
||||
const options = state.selectedCampaign.characters
|
||||
.map((character) => `<option value="${character.id}">${character.name}</option>`)
|
||||
.map((character) => {
|
||||
const selected = character.id === selectedCharacterId ? " selected" : "";
|
||||
return `<option value="${character.id}"${selected}>${character.name}</option>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
characterSelect.innerHTML = options;
|
||||
state.selectedCharacterId = selectedCharacterId;
|
||||
}
|
||||
|
||||
function renderSkillSelect(): void {
|
||||
@@ -449,7 +479,9 @@ function renderSkillSelect(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = state.selectedCampaign.skills
|
||||
const selectedCharacterId = resolveSelectedCharacterId();
|
||||
const characterSkills = state.selectedCampaign.skills.filter((skill) => skill.characterId === selectedCharacterId);
|
||||
const options = characterSkills
|
||||
.map((skill) => `<option value="${skill.id}">${skill.name} (${skill.diceRollDefinition})</option>`)
|
||||
.join("");
|
||||
|
||||
@@ -467,8 +499,11 @@ function selectedSkillFromCampaign(): SkillSummary | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectedCharacterId = resolveSelectedCharacterId();
|
||||
const selectedSkillId = skillSelect.value;
|
||||
return state.selectedCampaign.skills.find((skill) => skill.id === selectedSkillId) ?? null;
|
||||
return state.selectedCampaign.skills
|
||||
.filter((skill) => skill.characterId === selectedCharacterId)
|
||||
.find((skill) => skill.id === selectedSkillId) ?? null;
|
||||
}
|
||||
|
||||
function renderCampaignLog(): void {
|
||||
@@ -554,3 +589,33 @@ function mustButton(id: string): HTMLButtonElement {
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
function syncSelectedCharacter(): void {
|
||||
if (!state.selectedCampaign) {
|
||||
state.selectedCharacterId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const availableIds = new Set(state.selectedCampaign.characters.map((character) => character.id));
|
||||
if (state.selectedCharacterId && availableIds.has(state.selectedCharacterId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.activeCharacterId && availableIds.has(state.activeCharacterId)) {
|
||||
state.selectedCharacterId = state.activeCharacterId;
|
||||
return;
|
||||
}
|
||||
|
||||
state.selectedCharacterId = state.selectedCampaign.characters.length > 0
|
||||
? state.selectedCampaign.characters[0].id
|
||||
: null;
|
||||
}
|
||||
|
||||
function resolveSelectedCharacterId(): string | null {
|
||||
if (!state.selectedCampaign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
syncSelectedCharacter();
|
||||
return state.selectedCharacterId;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ const rollForm = mustForm("roll-form");
|
||||
const rollVisibilitySelect = mustSelect("roll-visibility");
|
||||
const state = {
|
||||
user: null,
|
||||
activeCharacterId: null,
|
||||
selectedCharacterId: null,
|
||||
campaigns: [],
|
||||
selectedCampaignId: null,
|
||||
selectedCampaign: null,
|
||||
@@ -87,10 +89,19 @@ campaignSelect.addEventListener("change", async () => {
|
||||
const selected = campaignSelect.value;
|
||||
state.selectedCampaignId = selected.length > 0 ? selected : null;
|
||||
await reloadSelectedCampaign();
|
||||
syncSelectedCharacter();
|
||||
renderCharacterSelect();
|
||||
renderSkillSelect();
|
||||
connectStateEvents();
|
||||
renderAll();
|
||||
renderCampaignMeta();
|
||||
renderCampaignDetails();
|
||||
renderCampaignLog();
|
||||
});
|
||||
});
|
||||
characterSelect.addEventListener("change", () => {
|
||||
state.selectedCharacterId = characterSelect.value.length > 0 ? characterSelect.value : null;
|
||||
renderSkillSelect();
|
||||
});
|
||||
refreshCampaignButton.addEventListener("click", async () => {
|
||||
await runAction(async () => {
|
||||
await reloadSelectedCampaign();
|
||||
@@ -121,6 +132,8 @@ activateCharacterButton.addEventListener("click", async () => {
|
||||
throw new Error("Select a character to activate.");
|
||||
}
|
||||
await activateCharacter(characterId);
|
||||
state.activeCharacterId = characterId;
|
||||
state.selectedCharacterId = characterId;
|
||||
await reloadAll();
|
||||
setMessage("Active character updated.", false);
|
||||
});
|
||||
@@ -196,10 +209,12 @@ async function reloadSession() {
|
||||
try {
|
||||
const me = await getMe();
|
||||
state.user = me.user;
|
||||
state.activeCharacterId = me.activeCharacterId ?? null;
|
||||
state.selectedCampaignId = me.currentCampaignId ?? state.selectedCampaignId;
|
||||
}
|
||||
catch {
|
||||
state.user = null;
|
||||
state.activeCharacterId = null;
|
||||
}
|
||||
}
|
||||
async function ensureRulesets() {
|
||||
@@ -233,6 +248,7 @@ async function reloadSelectedCampaign() {
|
||||
return;
|
||||
}
|
||||
state.selectedCampaign = await getCampaign(state.selectedCampaignId);
|
||||
syncSelectedCharacter();
|
||||
}
|
||||
async function reloadCampaignLog() {
|
||||
if (!state.selectedCampaignId) {
|
||||
@@ -274,6 +290,8 @@ function resetStateAfterLogout() {
|
||||
closeStateEvents();
|
||||
}
|
||||
function resetAuthenticatedState() {
|
||||
state.activeCharacterId = null;
|
||||
state.selectedCharacterId = null;
|
||||
state.campaigns = [];
|
||||
state.selectedCampaignId = null;
|
||||
state.selectedCampaign = null;
|
||||
@@ -305,7 +323,9 @@ function renderCampaignMeta() {
|
||||
return;
|
||||
}
|
||||
const isGm = state.selectedCampaign.gm.id === state.user.id;
|
||||
campaignMetaElement.textContent = `Selected: ${state.selectedCampaign.name} (${state.selectedCampaign.rulesetId}) - ${isGm ? "You are GM" : "Player context"}`;
|
||||
const activeCharacter = state.selectedCampaign.characters.find((character) => character.id === state.activeCharacterId);
|
||||
const activeLabel = activeCharacter ? ` | Active: ${activeCharacter.name}` : "";
|
||||
campaignMetaElement.textContent = `Selected: ${state.selectedCampaign.name} (${state.selectedCampaign.rulesetId}) - ${isGm ? "You are GM" : "Player context"}${activeLabel}`;
|
||||
}
|
||||
function renderCampaignDetails() {
|
||||
if (!state.selectedCampaign) {
|
||||
@@ -329,19 +349,27 @@ function renderCampaignDetails() {
|
||||
function renderCharacterSelect() {
|
||||
if (!state.selectedCampaign) {
|
||||
characterSelect.innerHTML = "";
|
||||
state.selectedCharacterId = null;
|
||||
return;
|
||||
}
|
||||
const selectedCharacterId = resolveSelectedCharacterId();
|
||||
const options = state.selectedCampaign.characters
|
||||
.map((character) => `<option value="${character.id}">${character.name}</option>`)
|
||||
.map((character) => {
|
||||
const selected = character.id === selectedCharacterId ? " selected" : "";
|
||||
return `<option value="${character.id}"${selected}>${character.name}</option>`;
|
||||
})
|
||||
.join("");
|
||||
characterSelect.innerHTML = options;
|
||||
state.selectedCharacterId = selectedCharacterId;
|
||||
}
|
||||
function renderSkillSelect() {
|
||||
if (!state.selectedCampaign) {
|
||||
skillSelect.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
const options = state.selectedCampaign.skills
|
||||
const selectedCharacterId = resolveSelectedCharacterId();
|
||||
const characterSkills = state.selectedCampaign.skills.filter((skill) => skill.characterId === selectedCharacterId);
|
||||
const options = characterSkills
|
||||
.map((skill) => `<option value="${skill.id}">${skill.name} (${skill.diceRollDefinition})</option>`)
|
||||
.join("");
|
||||
skillSelect.innerHTML = options;
|
||||
@@ -355,8 +383,11 @@ function selectedSkillFromCampaign() {
|
||||
if (!state.selectedCampaign) {
|
||||
return null;
|
||||
}
|
||||
const selectedCharacterId = resolveSelectedCharacterId();
|
||||
const selectedSkillId = skillSelect.value;
|
||||
return state.selectedCampaign.skills.find((skill) => skill.id === selectedSkillId) ?? null;
|
||||
return state.selectedCampaign.skills
|
||||
.filter((skill) => skill.characterId === selectedCharacterId)
|
||||
.find((skill) => skill.id === selectedSkillId) ?? null;
|
||||
}
|
||||
function renderCampaignLog() {
|
||||
if (state.campaignLog.length === 0) {
|
||||
@@ -426,3 +457,27 @@ function mustButton(id) {
|
||||
}
|
||||
return element;
|
||||
}
|
||||
function syncSelectedCharacter() {
|
||||
if (!state.selectedCampaign) {
|
||||
state.selectedCharacterId = null;
|
||||
return;
|
||||
}
|
||||
const availableIds = new Set(state.selectedCampaign.characters.map((character) => character.id));
|
||||
if (state.selectedCharacterId && availableIds.has(state.selectedCharacterId)) {
|
||||
return;
|
||||
}
|
||||
if (state.activeCharacterId && availableIds.has(state.activeCharacterId)) {
|
||||
state.selectedCharacterId = state.activeCharacterId;
|
||||
return;
|
||||
}
|
||||
state.selectedCharacterId = state.selectedCampaign.characters.length > 0
|
||||
? state.selectedCampaign.characters[0].id
|
||||
: null;
|
||||
}
|
||||
function resolveSelectedCharacterId() {
|
||||
if (!state.selectedCampaign) {
|
||||
return null;
|
||||
}
|
||||
syncSelectedCharacter();
|
||||
return state.selectedCharacterId;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user