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