Improve writing workspace display selection
This commit is contained in:
parent
1c7cf9bee1
commit
77369a86fd
@ -362,14 +362,25 @@ static void WritingWorkspaceLauncherUsesProgressiveScreenPlacement()
|
|||||||
Assert(script.Contains("window-management", StringComparison.Ordinal), "Workspace launcher should check/request window-management permission.");
|
Assert(script.Contains("window-management", StringComparison.Ordinal), "Workspace launcher should check/request window-management permission.");
|
||||||
Assert(script.Contains("sort((a, b) => a.left - b.left", StringComparison.Ordinal), "Screen topology should be derived from physical coordinates.");
|
Assert(script.Contains("sort((a, b) => a.left - b.left", StringComparison.Ordinal), "Screen topology should be derived from physical coordinates.");
|
||||||
Assert(script.Contains("mode: \"three-screen\"", StringComparison.Ordinal), "Three-screen layout decision is missing.");
|
Assert(script.Contains("mode: \"three-screen\"", StringComparison.Ordinal), "Three-screen layout decision is missing.");
|
||||||
Assert(script.Contains("contextDisplay: preferences.contextDisplay === \"right\" ? ordered[ordered.length - 1] : ordered[0]", StringComparison.Ordinal), "Context should default to the left display.");
|
Assert(script.Contains("key: `screen-${index}`", StringComparison.Ordinal), "Detected displays should receive stable option keys.");
|
||||||
Assert(script.Contains("intentDisplay: preferences.intentDisplay === \"left\" ? ordered[0] : ordered[ordered.length - 1]", StringComparison.Ordinal), "Intent should default to the right display.");
|
Assert(script.Contains("displayByPreference", StringComparison.Ordinal), "Launcher should resolve logical and physical display preferences.");
|
||||||
|
Assert(script.Contains("preferences.contextDisplay", StringComparison.Ordinal), "Context display preference is missing.");
|
||||||
|
Assert(script.Contains("preferences.intentDisplay", StringComparison.Ordinal), "Intent display preference is missing.");
|
||||||
Assert(script.Contains("mode: \"two-screen\"", StringComparison.Ordinal), "Two-screen fallback is missing.");
|
Assert(script.Contains("mode: \"two-screen\"", StringComparison.Ordinal), "Two-screen fallback is missing.");
|
||||||
Assert(script.Contains("mode: \"single-screen\"", StringComparison.Ordinal), "One-screen fallback is missing.");
|
Assert(script.Contains("mode: \"single-screen\"", StringComparison.Ordinal), "One-screen fallback is missing.");
|
||||||
Assert(script.Contains("localStorage.setItem(storageKeys.preferences", StringComparison.Ordinal), "Remembered layout preferences are missing.");
|
Assert(script.Contains("localStorage.setItem(storageKeys.preferences", StringComparison.Ordinal), "Remembered layout preferences are missing.");
|
||||||
Assert(script.Contains("window.open(url, name, popupFeatures(display))", StringComparison.Ordinal), "Writing windows should be opened from explicit launcher clicks with placement features.");
|
Assert(script.Contains("window.open(url, name, popupFeatures(display))", StringComparison.Ordinal), "Writing windows should be opened from explicit launcher clicks with placement features.");
|
||||||
|
Assert(script.Contains("window.open(buildWritingUrl(role, scope), stableWindowName(role, scope), popupFeatures(display))", StringComparison.Ordinal), "Focus actions should open missing windows on the selected display.");
|
||||||
|
Assert(script.Contains("getScreenLayout(Boolean(options.requestScreens), options.displayPreferences || {})", StringComparison.Ordinal), "Start action should pass selected display preferences into layout detection.");
|
||||||
|
Assert(script.Contains("openRoleWindow(roles.intent, resolvedScope, layout.intentDisplay)", StringComparison.Ordinal), "Start should open Intent on the selected display instead of converting the launcher window.");
|
||||||
|
Assert(!script.Contains("window.location.assign(buildWritingUrl(roles.intent", StringComparison.Ordinal), "Start should leave the launcher visible while opening the selected writing windows.");
|
||||||
Assert(launcher.Contains("requestScreens: true", StringComparison.Ordinal), "Screen permission should be requested from the Start action.");
|
Assert(launcher.Contains("requestScreens: true", StringComparison.Ordinal), "Screen permission should be requested from the Start action.");
|
||||||
|
Assert(launcher.Contains("workspace.getScreenLayout(true, selectedDisplayPreferences())", StringComparison.Ordinal), "Detect displays action should request physical display details.");
|
||||||
|
Assert(launcher.Contains("workspace.focusRole(role, scope, targetDisplay)", StringComparison.Ordinal), "Direct open/focus actions should use the selected display.");
|
||||||
Assert(view.Contains("data-start-writing-workspace", StringComparison.Ordinal), "Writer dashboard launcher start button is missing.");
|
Assert(view.Contains("data-start-writing-workspace", StringComparison.Ordinal), "Writer dashboard launcher start button is missing.");
|
||||||
|
Assert(view.Contains("data-writing-context-display", StringComparison.Ordinal), "Context display selector is missing.");
|
||||||
|
Assert(view.Contains("data-writing-intent-display", StringComparison.Ordinal), "Intent display selector is missing.");
|
||||||
|
Assert(view.Contains("data-detect-writing-displays", StringComparison.Ordinal), "Detect displays button is missing.");
|
||||||
Assert(view.Contains("data-focus-writing-context", StringComparison.Ordinal), "Focus Context action is missing.");
|
Assert(view.Contains("data-focus-writing-context", StringComparison.Ordinal), "Focus Context action is missing.");
|
||||||
Assert(view.Contains("data-focus-writing-intent", StringComparison.Ordinal), "Focus Intent action is missing.");
|
Assert(view.Contains("data-focus-writing-intent", StringComparison.Ordinal), "Focus Intent action is missing.");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -70,6 +70,25 @@
|
|||||||
<p class="muted writing-workspace-message" data-writing-workspace-message>
|
<p class="muted writing-workspace-message" data-writing-workspace-message>
|
||||||
Open a linked Word manuscript or use a queued scene to start the writing workstation.
|
Open a linked Word manuscript or use a queued scene to start the writing workstation.
|
||||||
</p>
|
</p>
|
||||||
|
<div class="writing-workspace-display-controls" data-writing-workspace-display-controls>
|
||||||
|
<label>
|
||||||
|
<span>Context display</span>
|
||||||
|
<select class="form-select form-select-sm" data-writing-context-display>
|
||||||
|
<option value="left">Left display</option>
|
||||||
|
<option value="primary">Primary display</option>
|
||||||
|
<option value="right">Right display</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Intent display</span>
|
||||||
|
<select class="form-select form-select-sm" data-writing-intent-display>
|
||||||
|
<option value="right">Right display</option>
|
||||||
|
<option value="primary">Primary display</option>
|
||||||
|
<option value="left">Left display</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm" data-detect-writing-displays>Detect displays</button>
|
||||||
|
</div>
|
||||||
<div class="button-row">
|
<div class="button-row">
|
||||||
<button type="button" class="btn btn-primary" data-start-writing-workspace>Start / Reapply Workspace</button>
|
<button type="button" class="btn btn-primary" data-start-writing-workspace>Start / Reapply Workspace</button>
|
||||||
<button type="button" class="btn btn-outline-primary" data-open-writing-context>Open Context</button>
|
<button type="button" class="btn btn-outline-primary" data-open-writing-context>Open Context</button>
|
||||||
|
|||||||
@ -4237,6 +4237,23 @@ body.dragging-location [data-drag-type="location"] {
|
|||||||
min-height: 1.35rem;
|
min-height: 1.35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.writing-workspace-display-controls {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(12rem, 1fr)) auto;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: end;
|
||||||
|
margin: 10px 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.writing-workspace-display-controls label {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--plotline-muted);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.writing-workspace-role-indicator {
|
.writing-workspace-role-indicator {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -4714,6 +4731,10 @@ body.dragging-location [data-drag-type="location"] {
|
|||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.writing-workspace-display-controls {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.writer-checklist-row,
|
.writer-checklist-row,
|
||||||
.writer-add-inline {
|
.writer-add-inline {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
2
PlotLine/wwwroot/css/site.min.css
vendored
2
PlotLine/wwwroot/css/site.min.css
vendored
File diff suppressed because one or more lines are too long
@ -14,6 +14,9 @@
|
|||||||
message: root.querySelector("[data-writing-workspace-message]")
|
message: root.querySelector("[data-writing-workspace-message]")
|
||||||
};
|
};
|
||||||
const startButton = root.querySelector("[data-start-writing-workspace]");
|
const startButton = root.querySelector("[data-start-writing-workspace]");
|
||||||
|
const detectDisplaysButton = root.querySelector("[data-detect-writing-displays]");
|
||||||
|
const contextDisplaySelect = root.querySelector("[data-writing-context-display]");
|
||||||
|
const intentDisplaySelect = root.querySelector("[data-writing-intent-display]");
|
||||||
const contextButton = root.querySelector("[data-open-writing-context]");
|
const contextButton = root.querySelector("[data-open-writing-context]");
|
||||||
const intentButton = root.querySelector("[data-open-writing-intent]");
|
const intentButton = root.querySelector("[data-open-writing-intent]");
|
||||||
const focusContextButton = root.querySelector("[data-focus-writing-context]");
|
const focusContextButton = root.querySelector("[data-focus-writing-context]");
|
||||||
@ -24,6 +27,7 @@
|
|||||||
});
|
});
|
||||||
let activeScope = { ...fallbackScope };
|
let activeScope = { ...fallbackScope };
|
||||||
let latestStatus = null;
|
let latestStatus = null;
|
||||||
|
let latestScreenInfo = null;
|
||||||
|
|
||||||
const text = (node, value) => {
|
const text = (node, value) => {
|
||||||
if (node) node.textContent = value;
|
if (node) node.textContent = value;
|
||||||
@ -40,6 +44,56 @@
|
|||||||
|
|
||||||
const setMessage = (message) => text(statusNodes.message, message || "");
|
const setMessage = (message) => text(statusNodes.message, message || "");
|
||||||
|
|
||||||
|
const selectedDisplayPreferences = () => ({
|
||||||
|
contextDisplay: contextDisplaySelect?.value || "left",
|
||||||
|
intentDisplay: intentDisplaySelect?.value || "right"
|
||||||
|
});
|
||||||
|
|
||||||
|
const displayLabel = (display, fallback) => {
|
||||||
|
if (!display) return fallback;
|
||||||
|
const position = Number.isFinite(display.left) ? `${display.left}, ${display.top}` : "";
|
||||||
|
return `${display.label || fallback}${position ? ` (${position})` : ""}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateDisplayOptions = (screenInfo) => {
|
||||||
|
latestScreenInfo = screenInfo;
|
||||||
|
const screens = screenInfo?.screens || [];
|
||||||
|
if (!contextDisplaySelect || !intentDisplaySelect || !screens.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentContext = contextDisplaySelect.value;
|
||||||
|
const currentIntent = intentDisplaySelect.value;
|
||||||
|
const baseContext = [
|
||||||
|
["left", "Left display"],
|
||||||
|
["primary", "Primary display"],
|
||||||
|
["right", "Right display"]
|
||||||
|
];
|
||||||
|
const baseIntent = [
|
||||||
|
["right", "Right display"],
|
||||||
|
["primary", "Primary display"],
|
||||||
|
["left", "Left display"]
|
||||||
|
];
|
||||||
|
const physicalOptions = screens.map((screen, index) => [
|
||||||
|
screen.key,
|
||||||
|
`${index + 1}: ${displayLabel(screen, "Display")}`
|
||||||
|
]);
|
||||||
|
const fill = (select, options, previous) => {
|
||||||
|
select.replaceChildren();
|
||||||
|
[...options, ...physicalOptions].forEach(([value, label]) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = value;
|
||||||
|
option.textContent = label;
|
||||||
|
select.append(option);
|
||||||
|
});
|
||||||
|
if ([...select.options].some((option) => option.value === previous)) {
|
||||||
|
select.value = previous;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fill(contextDisplaySelect, baseContext, currentContext);
|
||||||
|
fill(intentDisplaySelect, baseIntent, currentIntent);
|
||||||
|
};
|
||||||
|
|
||||||
const render = (status = latestStatus) => {
|
const render = (status = latestStatus) => {
|
||||||
latestStatus = status;
|
latestStatus = status;
|
||||||
activeScope = workspace.statusScope(status, activeScope);
|
activeScope = workspace.statusScope(status, activeScope);
|
||||||
@ -71,21 +125,54 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await workspace.focusRole(role, scope);
|
const screenInfo = latestScreenInfo || await workspace.getScreenLayout(false, selectedDisplayPreferences());
|
||||||
|
updateDisplayOptions(screenInfo);
|
||||||
|
const targetDisplay = role === workspace.roles.context
|
||||||
|
? screenInfo.layout?.contextDisplay
|
||||||
|
: screenInfo.layout?.intentDisplay;
|
||||||
|
await workspace.focusRole(role, scope, targetDisplay);
|
||||||
|
setMessage(`${role} requested on ${displayLabel(targetDisplay, "the selected display")}. If it is behind Word, use the taskbar once; future clicks should reuse it.`);
|
||||||
await refresh();
|
await refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
detectDisplaysButton?.addEventListener("click", async () => {
|
||||||
|
detectDisplaysButton.disabled = true;
|
||||||
|
setMessage("Detecting displays...");
|
||||||
|
try {
|
||||||
|
const screenInfo = await workspace.getScreenLayout(true, selectedDisplayPreferences());
|
||||||
|
updateDisplayOptions(screenInfo);
|
||||||
|
const count = screenInfo.screens?.length || 0;
|
||||||
|
text(statusNodes.layout, screenInfo.layout?.mode || "single-screen");
|
||||||
|
if (count > 0) {
|
||||||
|
setMessage(`Detected ${count} display${count === 1 ? "" : "s"}. Choose target displays, then start or reapply the workspace.`);
|
||||||
|
} else if (screenInfo.permission === "denied") {
|
||||||
|
setMessage("Display permission was denied. PlotDirector will reuse named windows, but your browser may open them on the current monitor.");
|
||||||
|
} else {
|
||||||
|
setMessage("Display detection is unavailable in this browser. PlotDirector will reuse named windows without monitor placement.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.debug("Display detection failed.", error);
|
||||||
|
setMessage("Display detection failed. The workspace can still open using named windows.");
|
||||||
|
} finally {
|
||||||
|
detectDisplaysButton.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
startButton?.addEventListener("click", async () => {
|
startButton?.addEventListener("click", async () => {
|
||||||
startButton.disabled = true;
|
startButton.disabled = true;
|
||||||
setMessage("Starting writing workspace...");
|
setMessage("Starting writing workspace...");
|
||||||
try {
|
try {
|
||||||
const result = await workspace.startWorkspace(activeScope, { requestScreens: true });
|
const result = await workspace.startWorkspace(activeScope, {
|
||||||
|
requestScreens: true,
|
||||||
|
displayPreferences: selectedDisplayPreferences()
|
||||||
|
});
|
||||||
|
updateDisplayOptions(result.screenInfo);
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
setMessage(result.message || `Pop-up blocked for ${result.blocked?.join(", ") || "a writing window"}. Use the explicit open buttons.`);
|
setMessage(result.message || `Pop-up blocked for ${result.blocked?.join(", ") || "a writing window"}. Use the explicit open buttons.`);
|
||||||
} else if (result.reusedCurrentWindowAs) {
|
|
||||||
setMessage("Reusing this window for Writing Intent and opening Context.");
|
|
||||||
} else {
|
} else {
|
||||||
setMessage("Writing workspace ready.");
|
const contextDisplay = displayLabel(result.screenInfo?.layout?.contextDisplay, "selected display");
|
||||||
|
const intentDisplay = displayLabel(result.screenInfo?.layout?.intentDisplay, "selected display");
|
||||||
|
setMessage(`Workspace requested: Context on ${contextDisplay}, Intent on ${intentDisplay}. If your browser ignores placement, use Focus instead of opening more copies.`);
|
||||||
}
|
}
|
||||||
text(statusNodes.layout, result.screenInfo?.layout?.mode || "single-screen");
|
text(statusNodes.layout, result.screenInfo?.layout?.mode || "single-screen");
|
||||||
await refresh();
|
await refresh();
|
||||||
@ -101,6 +188,12 @@
|
|||||||
intentButton?.addEventListener("click", () => openOrFocus(workspace.roles.intent));
|
intentButton?.addEventListener("click", () => openOrFocus(workspace.roles.intent));
|
||||||
focusContextButton?.addEventListener("click", () => openOrFocus(workspace.roles.context));
|
focusContextButton?.addEventListener("click", () => openOrFocus(workspace.roles.context));
|
||||||
focusIntentButton?.addEventListener("click", () => openOrFocus(workspace.roles.intent));
|
focusIntentButton?.addEventListener("click", () => openOrFocus(workspace.roles.intent));
|
||||||
|
contextDisplaySelect?.addEventListener("change", () => {
|
||||||
|
setMessage("Context display preference updated. Click Start / Reapply Workspace to use it.");
|
||||||
|
});
|
||||||
|
intentDisplaySelect?.addEventListener("change", () => {
|
||||||
|
setMessage("Intent display preference updated. Click Start / Reapply Workspace to use it.");
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener("plotdirector:writing-workspace-status", (event) => render(event.detail));
|
document.addEventListener("plotdirector:writing-workspace-status", (event) => render(event.detail));
|
||||||
document.addEventListener(window.PlotDirectorStoryPosition?.eventName || "plotdirector:story-position-changed", (event) => {
|
document.addEventListener(window.PlotDirectorStoryPosition?.eventName || "plotdirector:story-position-changed", (event) => {
|
||||||
@ -119,5 +212,10 @@
|
|||||||
...activeScope,
|
...activeScope,
|
||||||
writingRole: workspace.roles.launcher,
|
writingRole: workspace.roles.launcher,
|
||||||
displayLabel: "Writing Workspace"
|
displayLabel: "Writing Workspace"
|
||||||
}).then(render).catch(() => null);
|
}).then(async (status) => {
|
||||||
|
render(status);
|
||||||
|
const screenInfo = await workspace.getScreenLayout(false, selectedDisplayPreferences());
|
||||||
|
updateDisplayOptions(screenInfo);
|
||||||
|
text(statusNodes.layout, screenInfo.layout?.mode || "adaptive");
|
||||||
|
}).catch(() => null);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -110,17 +110,32 @@
|
|||||||
height: Math.max(320, Number(screen.availHeight ?? screen.height ?? 800)),
|
height: Math.max(320, Number(screen.availHeight ?? screen.height ?? 800)),
|
||||||
isPrimary: Boolean(screen.isPrimary),
|
isPrimary: Boolean(screen.isPrimary),
|
||||||
isInternal: Boolean(screen.isInternal),
|
isInternal: Boolean(screen.isInternal),
|
||||||
|
key: `screen-${index}`,
|
||||||
label: screen.label || (screen.isPrimary ? "Primary display" : `Display ${index + 1}`)
|
label: screen.label || (screen.isPrimary ? "Primary display" : `Display ${index + 1}`)
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => a.left - b.left || a.top - b.top || a.index - b.index);
|
.sort((a, b) => a.left - b.left || a.top - b.top || a.index - b.index);
|
||||||
|
|
||||||
|
const displayByPreference = (ordered, preference, fallback) => {
|
||||||
|
const value = String(preference || "").trim().toLowerCase();
|
||||||
|
if (value.startsWith("screen-")) {
|
||||||
|
return ordered.find((screen) => screen.key.toLowerCase() === value) || fallback;
|
||||||
|
}
|
||||||
|
if (value === "left") return ordered[0] || fallback;
|
||||||
|
if (value === "right") return ordered[ordered.length - 1] || fallback;
|
||||||
|
if (value === "primary") return ordered.find((screen) => screen.isPrimary) || fallback;
|
||||||
|
if (value === "middle" || value === "centre" || value === "center") return ordered[Math.floor(ordered.length / 2)] || fallback;
|
||||||
|
return fallback;
|
||||||
|
};
|
||||||
|
|
||||||
const chooseLayout = (screens, preferences = {}) => {
|
const chooseLayout = (screens, preferences = {}) => {
|
||||||
const ordered = sortScreens(screens);
|
const ordered = sortScreens(screens);
|
||||||
if (ordered.length >= 3) {
|
if (ordered.length >= 3) {
|
||||||
|
const defaultContext = ordered[0];
|
||||||
|
const defaultIntent = ordered[ordered.length - 1];
|
||||||
return {
|
return {
|
||||||
mode: "three-screen",
|
mode: "three-screen",
|
||||||
contextDisplay: preferences.contextDisplay === "right" ? ordered[ordered.length - 1] : ordered[0],
|
contextDisplay: displayByPreference(ordered, preferences.contextDisplay, defaultContext),
|
||||||
intentDisplay: preferences.intentDisplay === "left" ? ordered[0] : ordered[ordered.length - 1],
|
intentDisplay: displayByPreference(ordered, preferences.intentDisplay, defaultIntent),
|
||||||
wordDisplay: ordered[Math.floor(ordered.length / 2)],
|
wordDisplay: ordered[Math.floor(ordered.length / 2)],
|
||||||
displayCount: ordered.length
|
displayCount: ordered.length
|
||||||
};
|
};
|
||||||
@ -129,10 +144,12 @@
|
|||||||
if (ordered.length === 2) {
|
if (ordered.length === 2) {
|
||||||
const primaryRole = preferences.twoScreenPrimary === "intent" ? roles.intent : roles.context;
|
const primaryRole = preferences.twoScreenPrimary === "intent" ? roles.intent : roles.context;
|
||||||
const secondary = ordered.find((screen) => !screen.isPrimary) || ordered[1];
|
const secondary = ordered.find((screen) => !screen.isPrimary) || ordered[1];
|
||||||
|
const defaultContext = primaryRole === roles.context ? secondary : ordered[0];
|
||||||
|
const defaultIntent = primaryRole === roles.intent ? secondary : ordered[0];
|
||||||
return {
|
return {
|
||||||
mode: "two-screen",
|
mode: "two-screen",
|
||||||
contextDisplay: primaryRole === roles.context ? secondary : ordered[0],
|
contextDisplay: displayByPreference(ordered, preferences.contextDisplay, defaultContext),
|
||||||
intentDisplay: primaryRole === roles.intent ? secondary : ordered[0],
|
intentDisplay: displayByPreference(ordered, preferences.intentDisplay, defaultIntent),
|
||||||
wordDisplay: ordered.find((screen) => screen.isPrimary) || ordered[0],
|
wordDisplay: ordered.find((screen) => screen.isPrimary) || ordered[0],
|
||||||
displayCount: ordered.length
|
displayCount: ordered.length
|
||||||
};
|
};
|
||||||
@ -159,24 +176,25 @@
|
|||||||
return `popup=yes,left=${left},top=${top},width=${width},height=${height}`;
|
return `popup=yes,left=${left},top=${top},width=${width},height=${height}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getScreenLayout = async (requestPermission) => {
|
const getScreenLayout = async (requestPermission, preferences = {}) => {
|
||||||
|
const mergedPreferences = { ...readPreferences(), ...preferences };
|
||||||
if (!("getScreenDetails" in window) || typeof window.getScreenDetails !== "function") {
|
if (!("getScreenDetails" in window) || typeof window.getScreenDetails !== "function") {
|
||||||
return { supported: false, permission: "unavailable", layout: chooseLayout([], readPreferences()) };
|
return { supported: false, permission: "unavailable", layout: chooseLayout([], mergedPreferences) };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (navigator.permissions?.query && !requestPermission) {
|
if (navigator.permissions?.query && !requestPermission) {
|
||||||
const permission = await navigator.permissions.query({ name: "window-management" });
|
const permission = await navigator.permissions.query({ name: "window-management" });
|
||||||
if (permission.state !== "granted") {
|
if (permission.state !== "granted") {
|
||||||
return { supported: true, permission: permission.state, layout: chooseLayout([], readPreferences()) };
|
return { supported: true, permission: permission.state, layout: chooseLayout([], mergedPreferences) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const details = await window.getScreenDetails();
|
const details = await window.getScreenDetails();
|
||||||
const screens = sortScreens(details.screens || []);
|
const screens = sortScreens(details.screens || []);
|
||||||
return { supported: true, permission: "granted", screens, currentScreen: details.currentScreen, layout: chooseLayout(screens, readPreferences()) };
|
return { supported: true, permission: "granted", screens, currentScreen: details.currentScreen, layout: chooseLayout(screens, mergedPreferences) };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { supported: true, permission: error?.name === "NotAllowedError" ? "denied" : "unavailable", error, layout: chooseLayout([], readPreferences()) };
|
return { supported: true, permission: error?.name === "NotAllowedError" ? "denied" : "unavailable", error, layout: chooseLayout([], mergedPreferences) };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -264,14 +282,14 @@
|
|||||||
return opened;
|
return opened;
|
||||||
};
|
};
|
||||||
|
|
||||||
const focusRole = async (role, scope) => {
|
const focusRole = async (role, scope, display = null) => {
|
||||||
const ref = windowRefs.get(role);
|
const ref = windowRefs.get(role);
|
||||||
if (ref && !ref.closed) {
|
if (ref && !ref.closed) {
|
||||||
ref.focus();
|
ref.focus();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const opened = window.open(buildWritingUrl(role, scope), stableWindowName(role, scope));
|
const opened = window.open(buildWritingUrl(role, scope), stableWindowName(role, scope), popupFeatures(display));
|
||||||
if (opened) {
|
if (opened) {
|
||||||
windowRefs.set(role, opened);
|
windowRefs.set(role, opened);
|
||||||
opened.focus();
|
opened.focus();
|
||||||
@ -290,39 +308,28 @@
|
|||||||
return { ok: false, message: "Choose a book or open a linked Word manuscript before starting the writing workspace.", status };
|
return { ok: false, message: "Choose a book or open a linked Word manuscript before starting the writing workspace.", status };
|
||||||
}
|
}
|
||||||
|
|
||||||
const screenInfo = await getScreenLayout(Boolean(options.requestScreens));
|
const screenInfo = await getScreenLayout(Boolean(options.requestScreens), options.displayPreferences || {});
|
||||||
const layout = screenInfo.layout;
|
const layout = screenInfo.layout;
|
||||||
const existingContext = roleWindow(status, roles.context, resolvedScope);
|
const existingContext = roleWindow(status, roles.context, resolvedScope);
|
||||||
const existingIntent = roleWindow(status, roles.intent, resolvedScope);
|
const existingIntent = roleWindow(status, roles.intent, resolvedScope);
|
||||||
const currentRole = cleanRole(registration?.writingRole);
|
|
||||||
const opened = [];
|
const opened = [];
|
||||||
const blocked = [];
|
const blocked = [];
|
||||||
|
|
||||||
if (!existingIntent && currentRole !== roles.context && currentRole !== roles.intent) {
|
|
||||||
savePreferences({ reuseLauncherAs: roles.intent, lastLayoutMode: layout.mode });
|
|
||||||
if (!existingContext) {
|
|
||||||
const contextWindow = openRoleWindow(roles.context, resolvedScope, layout.contextDisplay);
|
|
||||||
(contextWindow ? opened : blocked).push(roles.context);
|
|
||||||
}
|
|
||||||
window.location.assign(buildWritingUrl(roles.intent, resolvedScope));
|
|
||||||
return { ok: true, reusedCurrentWindowAs: roles.intent, opened, blocked, status, screenInfo };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!existingContext) {
|
if (!existingContext) {
|
||||||
const contextWindow = openRoleWindow(roles.context, resolvedScope, layout.contextDisplay);
|
const contextWindow = openRoleWindow(roles.context, resolvedScope, layout.contextDisplay);
|
||||||
(contextWindow ? opened : blocked).push(roles.context);
|
(contextWindow ? opened : blocked).push(roles.context);
|
||||||
} else {
|
} else {
|
||||||
await focusRole(roles.context, resolvedScope);
|
await focusRole(roles.context, resolvedScope, layout.contextDisplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existingIntent) {
|
if (!existingIntent) {
|
||||||
const intentWindow = currentRole === roles.intent ? window : openRoleWindow(roles.intent, resolvedScope, layout.intentDisplay);
|
const intentWindow = currentRole === roles.intent ? window : openRoleWindow(roles.intent, resolvedScope, layout.intentDisplay);
|
||||||
(intentWindow ? opened : blocked).push(roles.intent);
|
(intentWindow ? opened : blocked).push(roles.intent);
|
||||||
} else {
|
} else {
|
||||||
await focusRole(roles.intent, resolvedScope);
|
await focusRole(roles.intent, resolvedScope, layout.intentDisplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
savePreferences({ reuseLauncherAs: roles.intent, lastLayoutMode: layout.mode });
|
savePreferences({ lastLayoutMode: layout.mode });
|
||||||
return { ok: blocked.length === 0, opened, blocked, status: await watchWorkspace(resolvedScope), screenInfo };
|
return { ok: blocked.length === 0, opened, blocked, status: await watchWorkspace(resolvedScope), screenInfo };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user