mirror of
https://github.com/vegu-ai/talemate.git
synced 2026-09-01 19:48:52 +02:00
* 0.39.0 polish pass: compact help-chat alert, quick-load hover pop, unsaved-settings confirmation, openrouter default model (#189) * address PR #190 review: openrouter reasoning defaults, guard click-path routing, dialog save eligibility + action separation, docs sync (#189) * review round 2: gate wizard Add Client on loaded client types, fix stale reasoning-default strings (#189)
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
- "Per-Scene Agent Overrides: Every agent action that can be switched off globally can now also be switched off for a single scene. Previously only the Visualizer's Prompt Finalization could — a scene could retune how Auto Narration, Revision, Scene Analysis or Long Term Memory behave, but not turn any of them off. The quick-toggle chips in the agent panel follow the same rule: a chip shows the value the loaded scene is actually running on and toggles exactly that value — the scene's override when one is active (marked with a small scene icon, the global value shown in the chip's tooltip), the global setting otherwise."
|
||||
- "Application Settings Redesign: The application settings moved out of their cramped modal into the main view as an always-available Settings tab, matching the World Editor and Templates. The nested tabs-within-tabs layout is replaced by a single sidebar navigation grouped by topic (Game, Appearance, Connections, Presets, Storytelling), with a settings search that finds any setting by name — including individual API-key providers — and jumps straight to it, highlighting the target. All API keys now live on one scrollable page instead of ten separate per-provider pages. Edits are tracked against the stored configuration: an Unsaved-changes indicator with Save and Discard appears in the toolbar, unsaved edits survive switching to other tabs, and configuration changed elsewhere (another window, the help agent) while you are editing is flagged instead of silently overwriting or being overwritten. Existing deep links — for example the 'Set API key' action on a misconfigured client — land on the correct page as before."
|
||||
- "Application Settings: The Settings tab in the top navigation now shows a warning badge with a tooltip while there are unsaved changes, so the state stays visible after switching away. The settings toolbar is also capped to the content width and groups Save / Discard next to the page title on the left instead of the far viewport edge."
|
||||
- "Application Settings: Switching away from the Settings tab while there are unsaved changes now asks what to do with them — Ignore keeps them pending, Save writes them, Discard Changes reverts them — since the warning badge alone was too easy to miss."
|
||||
- "Help Agent: The documentation index is no longer embedded in the chat prompt - the prompt now carries only a compact section overview (~15k fewer tokens per message), and a new find_docs lookup locates the right documentation pages on demand, so the help chat works with small context windows typical of local models."
|
||||
- "Uniform Settings Framework: Agent settings and client settings now share one field-definition schema on the backend and one field renderer on the frontend. Client-specific settings gain the full widget set previously exclusive to agents (sliders, autocompletes, selects with rich choices, per-value notes) plus conditional visibility, and choice lists are delivered to the frontend in a single normalized shape."
|
||||
- "MistralAI Client: Added a Concurrent Inference toggle so batch operations can dispatch multiple requests in parallel. Off by default."
|
||||
@@ -29,9 +30,13 @@
|
||||
- "Pi Bridge Client: The Docker image now ships with pi preinstalled, and pi's configuration directory (models.json, auth.json) is mounted from ./pi next to the compose file, so the client works in Docker out of the box."
|
||||
- "Scene Browser Landing Page: The Quick Load recent-scene cards are smaller and scale with the viewport width, so a full row of recents no longer pushes the Scene Library far down the page."
|
||||
- "Scene Browser Landing Page: The Quick Load card menu trigger moved below each card, so it no longer crowds the card title. Scene save files in the Scene Library gained the same context menu — Timeline, Remove from Quick Load (shown when the save is in Quick Load), and Delete — replacing the bare delete button."
|
||||
- "Scene Browser Landing Page: The Quick Load cards now pop out slightly on mouse hover, without shifting the cards around them."
|
||||
changes:
|
||||
- "Dependencies: Refreshed the dependency lock across the board, including a setuptools bump to 83.0.0 which carries a fix for a source-distribution file-exclusion vulnerability (GHSA-h35f-9h28-mq5c). No dependency required code migrations."
|
||||
- "OpenRouter Client: The default model for newly created OpenRouter clients is now google/gemini-3.6-flash."
|
||||
- "OpenRouter Client: New OpenRouter clients now have reasoning enabled by default, with a budget of 2048 reasoning tokens, so the default model works out of the box. With reasoning off Talemate pre-fills the start of the response to steer it, and some providers — Google and Anthropic among them — reject requests that do that."
|
||||
fixes:
|
||||
- "Help Chat: The warning shown while the Help agent has no configured client no longer stretches to fill the entire help panel — it renders as a compact notice above the chat."
|
||||
- "Character Card Import: A failed description or example dialogue generation no longer wipes the card's own text. Both steps clear the field before regenerating it so the card's version doesn't bias the rewrite, but a generation that errored out or came back empty left the character with nothing at all — an empty description then also blinded the attribute extraction that runs right after it. The card's original description and example dialogue are now restored whenever generation fails, returns nothing, or is cancelled, matching what the import already does when those generation steps are switched off."
|
||||
- "Character Card Import: A failed attribute extraction no longer overwrites the card's attributes. A generation that errored out or came back empty was still written to the character — as a sheet containing nothing but the character's own name — which dropped whatever attributes the card supplied (its gender, for example) and shadowed the character description in every prompt that renders the character sheet. An extraction that produces nothing now keeps the card's attributes, matching what the import already does when the attribute step is switched off."
|
||||
- "Character Card Import: The director's 'Limit character attributes' setting now applies to character card import. The setting governs every other character creation path the director manages, but import ignored it entirely — importing a card with a limit of 5 configured still produced an unbounded sheet, with nothing indicating why. Both import paths (the per-aspect flow and the Creator's Fast one-shot) now generate at most the configured number of attributes. Attributes the card itself supplies are untouched when attribute extraction is switched off."
|
||||
|
||||
@@ -40,7 +40,7 @@ Select your preferred client from the dropdown and click **Add Client** to open
|
||||
Once you complete the client configuration, the wizard will automatically advance to the next step.
|
||||
|
||||
!!! info "Reasoning Models"
|
||||
If you are using a reasoning model (like DeepSeek R1 or GLM), you will need to enable reasoning in the client settings after setup. For OpenRouter users, the default model selected during the wizard (Gemini 3 Flash) has reasoning enabled automatically, but if you switch to a different reasoning-capable model, you'll need to enable it manually in the client settings.
|
||||
If you are using a reasoning model (like DeepSeek R1 or GLM), you will need to enable reasoning in the client settings after setup. For OpenRouter users, new clients have reasoning enabled by default with a budget of 2048 reasoning tokens, so the default model selected during the wizard (Gemini 3.6 Flash) and other reasoning models work out of the box. If you switch to a model that does not reason, you can turn reasoning off in the client settings.
|
||||
|
||||
## Step 3: Configure Long-term Memory
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
@@ -2,7 +2,7 @@
|
||||
|
||||
Application settings open in the main view via the **:material-cog: Settings** tab in the top navigation (or the cogwheel icon on the right side of the top bar). Navigation lives in the left sidebar, grouped by topic, with a **search field** at the top that finds any setting by name and jumps straight to it.
|
||||
|
||||
Changes are collected as you edit — an **Unsaved changes** indicator with **Save** and **Discard** actions appears in the toolbar whenever your edits differ from the stored configuration. Nothing is applied until you save. Unsaved edits survive switching to another main tab and back. While you have unsaved changes, the **Settings** tab in the top navigation also shows a warning badge, so the state stays visible even after you switch to another tab.
|
||||
Changes are collected as you edit — an **Unsaved changes** indicator with **Save** and **Discard** actions appears in the toolbar whenever your edits differ from the stored configuration. Nothing is applied until you save. Switching to another main tab while edits are pending opens a **You have unsaved settings** prompt with three actions: **Ignore** leaves the tab with your edits still pending, **Save** saves them and then leaves, and **Discard Changes** reverts them and then leaves. Moving between pages inside Settings never prompts. Ignored edits survive switching to another main tab and back, and while they are pending the **Settings** tab in the top navigation shows a warning badge, so the state stays visible even after you switch to another tab.
|
||||
|
||||
If the configuration is changed elsewhere while you have unsaved edits — from another window, or by the [help agent](/talemate/user-guide/agents/help/) — a **Changed outside this view — saving overwrites** warning appears in the toolbar instead of your edits being silently overwritten (or silently overwriting the other change). **Discard** loads the latest stored configuration; **Save** overwrites the outside change with your edits.
|
||||
|
||||
|
||||
@@ -369,9 +369,11 @@
|
||||
- path: user-guide/app-settings/game.md
|
||||
title: Game
|
||||
summary: 'Overview of the redesigned settings view (0.39.0: main-view Settings tab with grouped sidebar navigation, settings
|
||||
search, and explicit Save/Discard with unsaved-change tracking) plus the Game group: auto save on game-loop completion,
|
||||
auto progress to the next character, max backscroll message count, the agent activity bar toggle, releasing GPU cache on
|
||||
scene load, and managing a default player character for imported scenes (e.g. ST character cards) that lack one.'
|
||||
search, and explicit Save/Discard with unsaved-change tracking, including the unsaved-settings prompt shown when leaving
|
||||
the Settings tab with pending edits - Ignore/Save/Discard Changes - and the warning badge on the tab) plus the Game
|
||||
group: auto save on game-loop completion, auto progress to the next character, max backscroll message count, the agent
|
||||
activity bar toggle, releasing GPU cache on scene load, and managing a default player character for imported scenes
|
||||
(e.g. ST character cards) that lack one.'
|
||||
- path: user-guide/app-settings/presets.md
|
||||
title: Presets
|
||||
summary: Presets settings group covering inference parameter presets (Analytical, Conversation, and other per-action presets
|
||||
|
||||
@@ -52,7 +52,7 @@ AVAILABLE_MODELS = []
|
||||
# Available providers will be populated dynamically from OpenRouter API once a valid API key is set
|
||||
AVAILABLE_PROVIDERS = []
|
||||
|
||||
DEFAULT_MODEL = "google/gemini-3-flash-preview"
|
||||
DEFAULT_MODEL = "google/gemini-3.6-flash"
|
||||
|
||||
# Sampler parameters whose inclusion in the request payload is user-toggleable.
|
||||
# Some OpenRouter providers hard-error when these are sent for certain models
|
||||
@@ -210,6 +210,11 @@ async_signals.get("config.saved").connect(on_config_saved)
|
||||
class Defaults(ToggleableParametersConfig, CommonDefaults, pydantic.BaseModel):
|
||||
max_token_length: int = 16384
|
||||
model: str = DEFAULT_MODEL
|
||||
# Reasoning on by default: with it off the client is coercible, and several
|
||||
# providers (Google, Anthropic) reject the coercion prefill outright since
|
||||
# the request then ends on a model turn.
|
||||
reason_enabled: bool = True
|
||||
reason_tokens: int = 2048
|
||||
provider_only: list[str] = pydantic.Field(default_factory=list)
|
||||
provider_ignore: list[str] = pydantic.Field(default_factory=list)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<v-toolbar rounded="md" density="compact" color="grey-darken-4" class="pl-2 mb-1 app-settings-toolbar">
|
||||
<v-icon class="mr-2" color="primary">{{ currentPage?.icon || 'mdi-cog' }}</v-icon>
|
||||
<span class="text-subtitle-2 mr-4">{{ currentPage?.group }} <span class="text-muted">/</span> {{ currentPage?.title }}</span>
|
||||
<v-btn color="primary" variant="text" prepend-icon="mdi-check-circle-outline" :disabled="!dirty || embeddingsBusy" @click="saveConfig">Save</v-btn>
|
||||
<v-btn color="primary" variant="text" prepend-icon="mdi-check-circle-outline" :disabled="!canSave" @click="saveConfig">Save</v-btn>
|
||||
<v-btn v-if="dirty" color="muted" variant="text" prepend-icon="mdi-undo" @click="discard">Discard</v-btn>
|
||||
<span v-if="dirty" class="text-muted text-caption mr-2">Unsaved changes.</span>
|
||||
<v-chip v-if="externalChange" size="small" label color="warning" variant="text" prepend-icon="mdi-alert-circle-outline" class="mr-2">
|
||||
@@ -128,6 +128,7 @@ export default {
|
||||
'appearance-preview-clear',
|
||||
'page-changed',
|
||||
'dirty-changed',
|
||||
'save-state-changed',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
@@ -156,6 +157,14 @@ export default {
|
||||
// applies an embedding set — only block saving from that page
|
||||
return this.busy && this.page === 'presets-embeddings';
|
||||
},
|
||||
canSave() {
|
||||
return this.dirty && !this.embeddingsBusy;
|
||||
},
|
||||
saveState() {
|
||||
// what a save from outside this component (the unsaved changes
|
||||
// prompt) needs to know before offering one
|
||||
return { canSave: this.canSave, externalChange: this.externalChange };
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
page(newPage) {
|
||||
@@ -164,6 +173,9 @@ export default {
|
||||
dirty(isDirty) {
|
||||
this.$emit('dirty-changed', isDirty);
|
||||
},
|
||||
saveState(state) {
|
||||
this.$emit('save-state-changed', state);
|
||||
},
|
||||
visible(isVisible) {
|
||||
if (!isVisible) {
|
||||
// leaving the settings tab — the scene should show the saved
|
||||
@@ -354,6 +366,11 @@ export default {
|
||||
},
|
||||
|
||||
saveConfig() {
|
||||
if (!this.canSave) {
|
||||
// callers outside this component (the unsaved changes prompt)
|
||||
// can't see what disables the toolbar's Save button
|
||||
return;
|
||||
}
|
||||
this.mergeSubcomponentConfigs();
|
||||
this.saving = true;
|
||||
this.sendRequest({
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
|
||||
<v-divider class="mb-2"></v-divider>
|
||||
|
||||
<v-alert v-if="!helpAvailable" type="warning" variant="tonal" density="compact" class="ma-2">
|
||||
<v-alert v-if="!helpAvailable" type="warning" variant="tonal" density="compact" class="ma-2 help-unavailable-alert">
|
||||
{{ helpUnavailableReason }}
|
||||
</v-alert>
|
||||
|
||||
@@ -447,6 +447,11 @@ export default {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
/* keep the alert at its natural height - the flex column root would otherwise
|
||||
stretch it to fill the space above the chat card */
|
||||
.help-unavailable-alert {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.chat-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -228,6 +228,17 @@ export default {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tile {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* z-index sits on the tile, not the card: it isn't transitionable, so keeping it
|
||||
tied to the card's own :hover would drop it the instant the pointer leaves,
|
||||
letting a later sibling paint over the card while it scales back down */
|
||||
.tile:hover {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tile :deep(.v-card-title) {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.3;
|
||||
@@ -246,6 +257,15 @@ export default {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.tile :deep(.v-card) {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
/* scale instead of size changes so neighbouring tiles don't shift */
|
||||
.tile :deep(.v-card:not(.v-card--disabled):hover) {
|
||||
transform: scale(1.04);
|
||||
}
|
||||
|
||||
.v-card:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
<v-icon icon="mdi-brain" size="small" class="mr-1" color="primary"></v-icon>
|
||||
</template>
|
||||
<div class="text-body-2">
|
||||
Google's gemini 3 model will be selected by default. Since it's a reasoning model, reasoning will be enabled automatically. If you change to a non-reasoning model in the next setup step (or at a later point), you should turn reasoning off in the client settings.
|
||||
Google's Gemini 3.6 Flash model will be selected by default. Since it's a reasoning model, reasoning will be enabled automatically. If you change to a non-reasoning model in the next setup step (or at a later point), you should turn reasoning off in the client settings.
|
||||
</div>
|
||||
</v-alert>
|
||||
</v-col>
|
||||
@@ -134,7 +134,7 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-btn color="primary" variant="tonal" @click="openClientModal" prepend-icon="mdi-plus">
|
||||
<v-btn color="primary" variant="tonal" :disabled="clientTypesLoading || !clientPresetOptions.length" @click="openClientModal" prepend-icon="mdi-plus">
|
||||
Add Client
|
||||
</v-btn>
|
||||
|
||||
@@ -750,12 +750,6 @@ export default {
|
||||
this.ensureSelectedPreset();
|
||||
const preset = this.buildClientPreset(this.selectedClientPreset);
|
||||
preset._simpleMode = true;
|
||||
|
||||
// Default reason_enabled to true for OpenRouter
|
||||
if (this.selectedClientPreset === 'openrouter') {
|
||||
preset.reason_enabled = true;
|
||||
}
|
||||
|
||||
this.$emit('open-client-modal', preset);
|
||||
},
|
||||
nextOrFinish() {
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
</v-app-bar-nav-icon>
|
||||
|
||||
<v-tabs v-model="tab" color="primary">
|
||||
<v-tab v-for="tab in availableTabs" :key="tab" :value="tab.value" @click.stop="tab.click">
|
||||
<v-tab v-for="tab in availableTabs" :key="tab" :value="tab.value" @click.stop="onTabClick(tab)">
|
||||
<v-icon class="mr-1">{{ tab.icon() }}</v-icon>
|
||||
{{ tab.title() }}
|
||||
<span v-if="tab.alert && tab.alert()" class="ml-1 d-inline-flex">
|
||||
@@ -376,7 +376,8 @@
|
||||
@appearance-preview="onAppearancePreview"
|
||||
@appearance-preview-clear="onAppearancePreviewClear"
|
||||
@page-changed="(page) => appSettingsPage = page"
|
||||
@dirty-changed="(dirty) => appSettingsDirty = dirty" />
|
||||
@dirty-changed="(dirty) => appSettingsDirty = dirty"
|
||||
@save-state-changed="(state) => appSettingsSaveState = state" />
|
||||
</v-tabs-window-item>
|
||||
|
||||
</v-tabs-window>
|
||||
@@ -401,6 +402,26 @@
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="unsavedSettingsPrompt" max-width="480" persistent>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<v-icon start color="warning">mdi-content-save-alert-outline</v-icon>
|
||||
You have unsaved settings
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
You have unsaved changes in Settings. Save them, discard them, or leave them pending and return to Settings later.
|
||||
<p v-if="appSettingsSaveState.externalChange" class="text-warning mt-2 text-caption">
|
||||
The configuration was changed outside this view while you have unsaved edits — saving overwrites it.
|
||||
</p>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="delete" variant="text" prepend-icon="mdi-undo" @click="onUnsavedSettingsDiscard">Discard Changes</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="muted" variant="text" @click="onUnsavedSettingsIgnore">Ignore</v-btn>
|
||||
<v-btn color="primary" variant="text" prepend-icon="mdi-check-circle-outline" :disabled="!appSettingsSaveState.canSave" @click="onUnsavedSettingsSave">Save</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-snackbar v-model="errorNotification" color="red-darken-1" :timeout="3000">
|
||||
{{ errorMessage }}
|
||||
</v-snackbar>
|
||||
@@ -662,6 +683,18 @@ export default {
|
||||
// Synced page state between AppSettingsMenu and AppSettings
|
||||
appSettingsPage: 'gameplay',
|
||||
appSettingsDirty: false,
|
||||
// Whether AppSettings would accept a save right now, and whether the
|
||||
// configuration changed elsewhere while it holds unsaved edits
|
||||
appSettingsSaveState: { canSave: false, externalChange: false },
|
||||
// Unsaved settings guard: tab the user tried to leave for, held while
|
||||
// the confirmation dialog is open
|
||||
unsavedSettingsPrompt: false,
|
||||
unsavedSettingsTarget: null,
|
||||
suppressUnsavedSettingsPrompt: false,
|
||||
revertingTab: false,
|
||||
// The tab the view is actually on — `tab` transiently holds a target
|
||||
// the unsaved settings guard vetoes
|
||||
committedTab: 'home',
|
||||
// Count of outdated prompt template overrides
|
||||
promptsOutdatedCount: 0,
|
||||
// Flag to ensure new-scene navigation only happens once per scene load
|
||||
@@ -681,6 +714,11 @@ export default {
|
||||
},
|
||||
tab: {
|
||||
handler(newTab, oldTab) {
|
||||
if(this.guardUnsavedSettings(newTab)) {
|
||||
return;
|
||||
}
|
||||
this.committedTab = newTab;
|
||||
|
||||
// Save scroll position when leaving main tab
|
||||
if(oldTab === 'main' && this.sceneActive) {
|
||||
this.mainTabScrollPosition = window.scrollY;
|
||||
@@ -1597,7 +1635,10 @@ export default {
|
||||
}
|
||||
this.tab = 'world';
|
||||
this.$nextTick(() => {
|
||||
this.$refs.worldStateManager.show(tab, sub1, sub2, sub3);
|
||||
// the unsaved settings guard can veto the switch above, leaving the
|
||||
// world window item unrendered — its commit path replays the tab's
|
||||
// click, only this sub-navigation is dropped
|
||||
this.$refs.worldStateManager?.show(tab, sub1, sub2, sub3);
|
||||
});
|
||||
},
|
||||
onOpenAgentMessages(agent_name) {
|
||||
@@ -1673,6 +1714,80 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
onTabClick(tabDef) {
|
||||
// the tab's side effect acts on a view the guard may refuse to switch
|
||||
// to — leaveSettingsTab() replays it once the user commits
|
||||
if(this.settingsNavigationBlocked(tabDef.value)) {
|
||||
return;
|
||||
}
|
||||
tabDef.click();
|
||||
},
|
||||
settingsNavigationBlocked(target) {
|
||||
// leaving settings while edits are pending needs a decision first.
|
||||
// Checked against committedTab, not `tab`: v-tabs updates the model
|
||||
// before the click handler runs, so `tab` may already hold the target
|
||||
return this.committedTab === 'settings'
|
||||
&& target !== 'settings'
|
||||
&& this.appSettingsDirty
|
||||
&& !this.suppressUnsavedSettingsPrompt;
|
||||
},
|
||||
guardUnsavedSettings(newTab) {
|
||||
// Re-entrant call caused by the revert below: the view never left the
|
||||
// settings tab, so no tab change side effect should run
|
||||
if(this.revertingTab) {
|
||||
this.revertingTab = false;
|
||||
return true;
|
||||
}
|
||||
if(!this.settingsNavigationBlocked(newTab)) {
|
||||
return false;
|
||||
}
|
||||
// hold the view on settings and let the user decide what to do with
|
||||
// the edits
|
||||
this.unsavedSettingsTarget = newTab;
|
||||
this.unsavedSettingsPrompt = true;
|
||||
this.revertingTab = true;
|
||||
this.tab = 'settings';
|
||||
return true;
|
||||
},
|
||||
onUnsavedSettingsIgnore() {
|
||||
this.leaveSettingsTab();
|
||||
},
|
||||
onUnsavedSettingsSave() {
|
||||
// fire and forget: the save is a websocket round-trip, the dirty
|
||||
// state clears (and with it the tab badge) when it comes back
|
||||
this.$refs.appSettings.saveConfig();
|
||||
this.leaveSettingsTab();
|
||||
},
|
||||
onUnsavedSettingsDiscard() {
|
||||
this.$refs.appSettings.discard();
|
||||
this.leaveSettingsTab();
|
||||
},
|
||||
leaveSettingsTab() {
|
||||
const target = this.unsavedSettingsTarget;
|
||||
this.unsavedSettingsPrompt = false;
|
||||
this.unsavedSettingsTarget = null;
|
||||
if(!target) {
|
||||
return;
|
||||
}
|
||||
// the target may have disappeared while the prompt was open (a scene
|
||||
// unload removes 'main', 'world', ...) — the availableTabs watcher
|
||||
// won't correct it, it already ran while settings (always available)
|
||||
// was selected
|
||||
const targetTab = this.availableTabs.find(tab => tab.value === target)
|
||||
|| this.availableTabs.find(tab => tab.value === 'home');
|
||||
if(!targetTab) {
|
||||
return;
|
||||
}
|
||||
// the edits may still be unsaved (Ignore, or a save whose round-trip
|
||||
// hasn't landed yet) — suppress the guard so completing the navigation
|
||||
// doesn't re-open the dialog
|
||||
this.suppressUnsavedSettingsPrompt = true;
|
||||
this.tab = targetTab.value;
|
||||
this.$nextTick(() => { this.suppressUnsavedSettingsPrompt = false; });
|
||||
// take the same path a real click does — the side effect was skipped
|
||||
// when the guard vetoed the navigation
|
||||
targetTab.click();
|
||||
},
|
||||
onAppSettingsNavigate({ page, anchor }) {
|
||||
if (this.$refs.appSettings) {
|
||||
this.$refs.appSettings.navigate(page, anchor);
|
||||
|
||||
Reference in New Issue
Block a user