visio-svelte-viewer 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +593 -351
- package/dist/index.server.js +593 -351
- package/dist/runtime.js +593 -351
- package/dist/types/src/backstage.d.ts +7 -1
- package/dist/types/src/contract.d.ts +1 -1
- package/dist/types/src/controller.d.ts +5 -0
- package/dist/types/src/ribbon.d.ts +3 -2
- package/dist/types/src/styles.d.ts +1 -1
- package/dist/types/src/viewer-backstage.d.ts +2 -5
- package/dist/types/src/viewer-input.d.ts +0 -2
- package/dist/types/src/viewer-options.d.ts +22 -0
- package/dist/types/src/viewer-ruler.d.ts +6 -0
- package/dist/types/src/viewer-search.d.ts +16 -16
- package/dist/types/src/viewer-share.d.ts +20 -0
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { VISIO_RASTER_IMAGE_LIMITS, VISIO_VISIBILITY_LIMITS, getVisioPageLayers, inspectVisioRasterImage, loadVisio, resolveVisioPageVisibility, validateVisioForeignVector } from "ooxml-core/visio";
|
|
2
|
-
import { attachKeyTips, registerControls } from "ooxml-ui/controls";
|
|
2
|
+
import { attachKeyTips, readOfficeProfile, registerControls, sanitizeOfficeProfile, writeOfficeProfile } from "ooxml-ui/controls";
|
|
3
3
|
import { getIcon, registerIcon } from "ooxml-ui/icons";
|
|
4
|
+
import { definePresence } from "ooxml-ui/presence";
|
|
4
5
|
import "svelte/internal/disclose-version";
|
|
5
6
|
import * as $ from "svelte/internal/client";
|
|
6
7
|
import { readonly, writable } from "svelte/store";
|
|
@@ -1074,6 +1075,13 @@ var ViewerController = class {
|
|
|
1074
1075
|
async undo() {
|
|
1075
1076
|
return this.#mutate("undo");
|
|
1076
1077
|
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Adopt a package from a shared session as one undoable history step. Emits `document-change`
|
|
1080
|
+
* with kind `remote`, which a session must not publish back.
|
|
1081
|
+
*/
|
|
1082
|
+
async applyRemoteSource(bytes) {
|
|
1083
|
+
return this.#mutate("remote", void 0, bytes);
|
|
1084
|
+
}
|
|
1077
1085
|
async redo() {
|
|
1078
1086
|
return this.#mutate("redo");
|
|
1079
1087
|
}
|
|
@@ -1097,13 +1105,13 @@ var ViewerController = class {
|
|
|
1097
1105
|
#sourceEditState() {
|
|
1098
1106
|
return this.#sourceFormat === "vsdx" && this.#state.document?.format === "vsdx" ? this.#history?.state ?? EMPTY_EDIT_STATE : EMPTY_EDIT_STATE;
|
|
1099
1107
|
}
|
|
1100
|
-
async #mutate(kind, commands) {
|
|
1108
|
+
async #mutate(kind, commands, remote) {
|
|
1101
1109
|
this.#assertAlive();
|
|
1102
1110
|
const history = this.#history;
|
|
1103
1111
|
if (!history || this.#sourceFormat !== "vsdx" || this.#state.document?.format !== "vsdx") throw new Error("Load a VSDX file before editing. Model-only documents are read only.");
|
|
1104
1112
|
if (this.#state.loading || this.#state.edit.busy) throw new Error("Another document operation is in progress.");
|
|
1105
1113
|
const target = kind === "undo" ? history.undoTarget : kind === "redo" ? history.redoTarget : void 0;
|
|
1106
|
-
if (kind
|
|
1114
|
+
if ((kind === "undo" || kind === "redo") && !target) return;
|
|
1107
1115
|
const id = ++this.#editId;
|
|
1108
1116
|
const loadId = this.#loadId;
|
|
1109
1117
|
const current = () => !this.#destroyed && id === this.#editId && loadId === this.#loadId && history === this.#history;
|
|
@@ -1123,6 +1131,9 @@ var ViewerController = class {
|
|
|
1123
1131
|
assertCurrent();
|
|
1124
1132
|
if (edited.bytes.byteLength > 33554432) throw new Error("The modified VSDX exceeds 32 MiB.");
|
|
1125
1133
|
document = edited.document;
|
|
1134
|
+
} else if (kind === "remote") {
|
|
1135
|
+
if (remote.byteLength > 33554432) throw new Error("The shared VSDX exceeds 32 MiB.");
|
|
1136
|
+
document = await this.parser(Uint8Array.from(remote));
|
|
1126
1137
|
} else document = await this.parser(Uint8Array.from(target.bytes));
|
|
1127
1138
|
assertCurrent();
|
|
1128
1139
|
assertViewableDocument(document);
|
|
@@ -1137,6 +1148,7 @@ var ViewerController = class {
|
|
|
1137
1148
|
const selection = this.#state.selectedShape;
|
|
1138
1149
|
const selectedShape = selection && visibleSelection(document, this.#state.pageIndex, selection, visible) ? selection : null;
|
|
1139
1150
|
if (edited) history.append(edited.bytes, edited.diagnostics);
|
|
1151
|
+
else if (kind === "remote") history.append(Uint8Array.from(remote), []);
|
|
1140
1152
|
else history.move(target);
|
|
1141
1153
|
this.#visible = visible;
|
|
1142
1154
|
this.#searchIndex = searchIndex;
|
|
@@ -1375,28 +1387,10 @@ function selection(target) {
|
|
|
1375
1387
|
}
|
|
1376
1388
|
/** Own every DOM listener and abort them together when the surface is disposed. */
|
|
1377
1389
|
function wireViewerInputs(controls, controller, fit) {
|
|
1378
|
-
const { viewport,
|
|
1390
|
+
const { viewport, zoomSlider } = controls;
|
|
1379
1391
|
const events = new ((viewport.ownerDocument.defaultView?.AbortController) ?? AbortController)();
|
|
1380
1392
|
const options = { signal: events.signal };
|
|
1381
|
-
searchInput.addEventListener("input", () => controller.setSearchQuery(searchInput.value), options);
|
|
1382
|
-
searchInput.addEventListener("keydown", (event) => {
|
|
1383
|
-
if (event.isComposing) return;
|
|
1384
|
-
if (event.key === "Enter") {
|
|
1385
|
-
event.preventDefault();
|
|
1386
|
-
if (event.shiftKey) controller.previousSearchResult();
|
|
1387
|
-
else controller.nextSearchResult();
|
|
1388
|
-
}
|
|
1389
|
-
if (event.key === "Escape") {
|
|
1390
|
-
event.preventDefault();
|
|
1391
|
-
controller.setSearchQuery("");
|
|
1392
|
-
}
|
|
1393
|
-
}, options);
|
|
1394
1393
|
zoomSlider.addEventListener("input", () => controller.setZoom(zoomSlider.value / 100), options);
|
|
1395
|
-
commandRoot.addEventListener("click", (event) => {
|
|
1396
|
-
const action = event.target?.closest?.("button")?.dataset.action;
|
|
1397
|
-
if (action === "search-next") controller.nextSearchResult();
|
|
1398
|
-
if (action === "search-previous") controller.previousSearchResult();
|
|
1399
|
-
}, options);
|
|
1400
1394
|
viewport.addEventListener("click", (event) => controller.selectShape(selection(targetShape$1(event))), options);
|
|
1401
1395
|
viewport.addEventListener("keydown", (event) => {
|
|
1402
1396
|
const target = targetShape$1(event);
|
|
@@ -1537,22 +1531,12 @@ button:focus-visible,select:focus-visible,input:focus-visible,textarea:focus-vis
|
|
|
1537
1531
|
button[aria-pressed="true"] { color:var(--_vv-accent); background:var(--_vv-accent-soft); }
|
|
1538
1532
|
.toolbar { flex:none; min-width:0; border-bottom:1px solid var(--_vv-border); background:var(--_vv-surface); }
|
|
1539
1533
|
.spacer { flex:1; }
|
|
1540
|
-
.ribbon-tabs { height:35px; display:flex; align-items:stretch; padding:0 4px; border-bottom:1px solid var(--_vv-border); }
|
|
1541
|
-
.ribbon-tabs button { position:relative; min-height:34px; padding:7px 14px; border:0; border-radius:0; background:transparent; color:var(--_vv-muted); font-size:12px; font-weight:500; }
|
|
1542
|
-
.ribbon-tabs button[aria-selected="true"] { color:var(--_vv-accent); }
|
|
1543
|
-
.ribbon-tabs button[aria-selected="true"]::after { content:""; position:absolute; inset:auto 0 -1px; height:2.5px; background:var(--_vv-accent); }
|
|
1544
1534
|
.ribbon-content { min-height:82px; display:flex; align-items:stretch; gap:0; padding:2px 4px; overflow-x:auto; }
|
|
1545
1535
|
.ribbon-group { display:flex; flex-direction:column; justify-content:space-between; flex:none; gap:2px; padding:3px 10px 4px; border-right:1px solid var(--_vv-border); }
|
|
1546
1536
|
.group-label { font-size:10px; line-height:12px; color:var(--_vv-muted); text-align:center; }
|
|
1547
1537
|
.ribbon-actions { display:flex; align-items:stretch; gap:2px; flex:1; }
|
|
1548
1538
|
.ribbon-command { display:flex; flex-direction:column; justify-content:center; align-items:center; gap:2px; min-width:70px; padding:2px 6px; border-color:transparent; background:transparent; font-size:11px; white-space:nowrap; }
|
|
1549
1539
|
.ribbon-hint { align-self:center; flex:none; margin:0; padding:0 16px; font-size:11px; line-height:18px; color:var(--_vv-muted); }
|
|
1550
|
-
.search-group { width:330px; }
|
|
1551
|
-
.search-controls { display:flex; align-items:center; gap:4px; flex-wrap:wrap; }
|
|
1552
|
-
.search-controls label { display:flex; align-items:center; min-width:0; gap:6px; flex:1; font-size:11px; }
|
|
1553
|
-
.search-controls input { width:100%; min-width:0; padding:3px 6px; font-size:11px; }
|
|
1554
|
-
.search-controls button { padding:3px 6px; font-size:10px; background:var(--_vv-secondary); }
|
|
1555
|
-
.search-controls [role="status"] { display:block; width:100%; min-height:15px; font-size:10px; line-height:15px; color:var(--_vv-muted); overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
|
|
1556
1540
|
.workspace { position:relative; display:flex; flex:1; min-width:0; min-height:0; overflow:hidden; }
|
|
1557
1541
|
.pane-heading { height:37px; display:flex; align-items:center; justify-content:space-between; gap:8px; padding:8px 10px; border-bottom:1px solid var(--_vv-border); font-size:11px; font-weight:600; }
|
|
1558
1542
|
.inspector-kind { color:var(--_vv-muted); font-size:10px; font-weight:400; }
|
|
@@ -1611,17 +1595,8 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
1611
1595
|
button,select,input,textarea { min-height:44px; }
|
|
1612
1596
|
button { min-width:44px; }
|
|
1613
1597
|
summary { min-height:44px; line-height:44px; }
|
|
1614
|
-
.ribbon-tabs { height:44px; }
|
|
1615
|
-
.ribbon-tabs button { min-height:44px; padding:8px 16px; }
|
|
1616
1598
|
.ribbon-content { min-height:86px; }
|
|
1617
1599
|
.ribbon-group { padding:4px 8px; }
|
|
1618
|
-
.search-group { width:100%; border-right:0; }
|
|
1619
|
-
.search-controls { gap:4px; }
|
|
1620
|
-
.search-controls label { font-size:12px; }
|
|
1621
|
-
.search-controls input { font-size:12px; }
|
|
1622
|
-
.search-controls button { min-width:44px; font-size:0; padding:3px; }
|
|
1623
|
-
.search-controls [data-action="search-previous"]::before { content:"‹"; font-size:22px; }
|
|
1624
|
-
.search-controls [data-action="search-next"]::before { content:"›"; font-size:22px; }
|
|
1625
1600
|
#home-panel>.ribbon-group:not(.search-group):not(.undo-group) { display:none; }
|
|
1626
1601
|
.ribbon-command { min-width:90px; }
|
|
1627
1602
|
.workspace { display:block; overflow:auto; }
|
|
@@ -1640,26 +1615,17 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
1640
1615
|
button,select,input,textarea { min-height:44px; }
|
|
1641
1616
|
button { min-width:44px; }
|
|
1642
1617
|
summary,.layer-controls label { min-height:44px; line-height:44px; }
|
|
1643
|
-
.ribbon-tabs { min-height:44px; height:44px; }
|
|
1644
|
-
.ribbon-tabs button { min-height:44px; }
|
|
1645
1618
|
.ribbon-content { min-height:96px; }
|
|
1646
|
-
.search-group { width:380px; max-width:100%; }
|
|
1647
1619
|
.notes-strip button { min-height:44px; height:44px; }
|
|
1648
1620
|
.layer-controls input { min-height:20px; }
|
|
1649
1621
|
}
|
|
1650
1622
|
/* Suite ribbon: tabs lead directly into Visio command groups. */
|
|
1651
|
-
.ribbon-tabs { height:40px; }
|
|
1652
|
-
.ribbon-tabs button { min-height:39px; padding:8px 16px; }
|
|
1653
1623
|
.ribbon-content { min-height:104px; padding:4px 4px; }
|
|
1654
|
-
.search-group { order:2; width:320px; }
|
|
1655
1624
|
.ribbon-hint { order:3; padding:10px 18px; }
|
|
1656
1625
|
.ribbon-command { min-width:82px; }
|
|
1657
1626
|
.pane-close { display:none; }
|
|
1658
1627
|
@media(max-width:760px) {
|
|
1659
|
-
.ribbon-tabs { height:44px; }
|
|
1660
|
-
.ribbon-tabs button { min-height:44px; }
|
|
1661
1628
|
.ribbon-content { min-height:54px; padding:4px 0; }
|
|
1662
|
-
.search-group { flex:0 0 100%; width:100%; min-height:54px; }
|
|
1663
1629
|
.search-feedback,.group-label { display:none; }
|
|
1664
1630
|
.ribbon-content { overflow-x:auto; scroll-snap-type:x mandatory; }
|
|
1665
1631
|
.workspace { display:flex; position:relative; overflow:hidden; }
|
|
@@ -1695,12 +1661,10 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
1695
1661
|
.ribbon-tools .ribbon-group { display:flex; width:100%; min-height:64px; border:0; padding:0; }
|
|
1696
1662
|
.ribbon-tools .ribbon-actions { justify-content:space-between; }
|
|
1697
1663
|
.ribbon-tools .ribbon-command { min-width:88px; font-size:12px; }
|
|
1698
|
-
.ribbon-tools .search-group { order:1; flex:auto; }
|
|
1699
1664
|
.ribbon-tools .search-feedback { display:block; }
|
|
1700
1665
|
.status { flex-wrap:nowrap; gap:4px; min-height:48px; }
|
|
1701
1666
|
.status .notes-strip { display:none; }
|
|
1702
1667
|
}
|
|
1703
|
-
@media(pointer:coarse) { .ribbon-tabs {height:45px;min-height:45px;} .ribbon-tabs button {min-height:44px;height:44px;} }
|
|
1704
1668
|
@media(prefers-reduced-motion:reduce) { * { scroll-behavior:auto !important; } }
|
|
1705
1669
|
`;
|
|
1706
1670
|
//#endregion
|
|
@@ -1710,9 +1674,9 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
1710
1674
|
* `styles.ts` string moves into this directory when it is next changed.
|
|
1711
1675
|
*/
|
|
1712
1676
|
var canvasAndRibbonStyles = [
|
|
1713
|
-
"/*\n * Microsoft Visio ribbon layout on the shared ooxml-ui controls. Viewer theme tokens feed the\n * shared --office-* tokens, so host themes still apply. Loaded after the legacy styles.ts.\n */\n:host {\n --office-foreground: var(--_vv-ink);\n --office-muted-foreground: var(--_vv-muted);\n --office-background: var(--_vv-surface);\n --office-surface: var(--_vv-accent-soft);\n --office-border: var(--_vv-border);\n --office-accent: var(--_vv-accent);\n --office-accent-foreground: var(--vv-accent-ink, #fff);\n --office-ring: var(--vv-focus, var(--_vv-accent));\n --office-font: 'Segoe UI', Arial, sans-serif;\n}\n\n/* Tab row: Quick Access Toolbar, then Visio's tabs. */\n.ribbon-head {\n display: flex;\n align-items: stretch;\n min-width: 0;\n border-bottom: 1px solid var(--_vv-border);\n}\n.qat {\n display: flex;\n align-items: center;\n gap: 0;\n padding: 0 6px;\n border-right: 1px solid var(--_vv-border);\n}\n.qat office-ui-button {\n --office-target-size: 30px;\n}\n.ribbon-head .ribbon-tabs {\n flex: 0 1 auto;\n min-width: 0;\n height: 36px;\n border-bottom: 0;\n overflow-x: auto;\n scrollbar-width: none;\n}\n.ribbon-head .ribbon-tabs button {\n flex: none;\n min-height: 35px;\n padding: 6px 12px;\n font-size: 13px;\n}\n\n/* Panels: groups of large commands and three-row stacks, as dense as Visio's. */\n.ribbon-content {\n min-height: 96px;\n padding: 2px 2px 0;\n}\n.ribbon-row {\n display: flex;\n flex-wrap: nowrap;\n align-items: stretch;\n gap: 0;\n padding: 0;\n border: 0;\n background: transparent;\n}\n.ribbon-row office-ui-ribbon-group,\n.tools-content office-ui-ribbon-group {\n min-width: 0;\n}\noffice-ui-ribbon-group office-ui-button,\noffice-ui-ribbon-group office-ui-menu-button {\n --office-target-size: 24px;\n}\noffice-ui-ribbon-group [data-size='large'] {\n --office-target-size: 30px;\n}\noffice-ui-button[variant='stacked']::part(button) {\n min-width: 48px;\n min-height: 66px;\n padding: 2px 6px;\n font-size: 12px;\n}\noffice-ui-menu-button[variant='stacked']::part(button) {\n min-width: 48px;\n min-height: 50px;\n font-size: 12px;\n}\noffice-ui-ribbon-group [data-size='small']::part(button) {\n justify-content: flex-start;\n font-size: 12px;\n}\noffice-ui-ribbon-stack:not([orientation]) {\n justify-content: flex-start;\n padding-top: 2px;\n}\noffice-ui-ribbon-stack:not([orientation]) > * {\n min-height: 24px;\n}\noffice-ui-ribbon-group > office-ui-ribbon-stack > office-ui-ribbon-stack[orientation] {\n min-height: 32px;\n}\noffice-ui-select[data-combo] {\n --office-target-size: 24px;\n}\noffice-ui-menu-button[data-active]::part(button) {\n background: var(--_vv-accent-soft);\n box-shadow: inset 0 0 0 1px var(--_vv-accent);\n}\n[data-unsupported] {\n cursor: not-allowed;\n}\n.ribbon-check {\n display: flex;\n align-items: center;\n gap: 6px;\n min-height: 24px;\n padding: 0 6px;\n font-size: 12px;\n white-space: nowrap;\n cursor: pointer;\n}\n.ribbon-check[data-unsupported] {\n color: var(--_vv-muted);\n opacity: 0.6;\n}\n.ribbon-hint {\n display: none;\n}\n\n/*
|
|
1677
|
+
"/*\n * Microsoft Visio ribbon layout on the shared ooxml-ui controls. Viewer theme tokens feed the\n * shared --office-* tokens, so host themes still apply. Loaded after the legacy styles.ts.\n */\n:host {\n --office-foreground: var(--_vv-ink);\n --office-muted-foreground: var(--_vv-muted);\n --office-background: var(--_vv-surface);\n --office-surface: var(--_vv-accent-soft);\n --office-border: var(--_vv-border);\n --office-accent: var(--_vv-accent);\n --office-accent-foreground: var(--vv-accent-ink, #fff);\n --office-ring: var(--vv-focus, var(--_vv-accent));\n --office-font: 'Segoe UI', Arial, sans-serif;\n}\n\n/* Tab row: Quick Access Toolbar, then Visio's tabs. */\n.ribbon-head {\n display: flex;\n align-items: stretch;\n min-width: 0;\n border-bottom: 1px solid var(--_vv-border);\n}\n.qat {\n display: flex;\n align-items: center;\n gap: 0;\n padding: 0 6px;\n border-right: 1px solid var(--_vv-border);\n}\n.qat office-ui-button {\n --office-target-size: 30px;\n}\n.ribbon-head .ribbon-tabs {\n flex: 0 1 auto;\n min-width: 0;\n height: 36px;\n border-bottom: 0;\n overflow-x: auto;\n scrollbar-width: none;\n}\n.ribbon-head .ribbon-tabs button {\n flex: none;\n min-height: 35px;\n padding: 6px 12px;\n font-size: 13px;\n}\n\n/* Panels: groups of large commands and three-row stacks, as dense as Visio's. */\n.ribbon-content {\n min-height: 96px;\n padding: 2px 2px 0;\n}\n.ribbon-row {\n display: flex;\n flex-wrap: nowrap;\n align-items: stretch;\n gap: 0;\n padding: 0;\n border: 0;\n background: transparent;\n}\n.ribbon-row office-ui-ribbon-group,\n.tools-content office-ui-ribbon-group {\n min-width: 0;\n}\noffice-ui-ribbon-group office-ui-button,\noffice-ui-ribbon-group office-ui-menu-button {\n --office-target-size: 24px;\n}\noffice-ui-ribbon-group [data-size='large'] {\n --office-target-size: 30px;\n}\noffice-ui-button[variant='stacked']::part(button) {\n min-width: 48px;\n min-height: 66px;\n padding: 2px 6px;\n font-size: 12px;\n}\noffice-ui-menu-button[variant='stacked']::part(button) {\n min-width: 48px;\n min-height: 50px;\n font-size: 12px;\n}\noffice-ui-ribbon-group [data-size='small']::part(button) {\n justify-content: flex-start;\n font-size: 12px;\n}\noffice-ui-ribbon-stack:not([orientation]) {\n justify-content: flex-start;\n padding-top: 2px;\n}\noffice-ui-ribbon-stack:not([orientation]) > * {\n min-height: 24px;\n}\noffice-ui-ribbon-group > office-ui-ribbon-stack > office-ui-ribbon-stack[orientation] {\n min-height: 32px;\n}\noffice-ui-select[data-combo] {\n --office-target-size: 24px;\n}\noffice-ui-menu-button[data-active]::part(button) {\n background: var(--_vv-accent-soft);\n box-shadow: inset 0 0 0 1px var(--_vv-accent);\n}\n[data-unsupported] {\n cursor: not-allowed;\n}\n.ribbon-check {\n display: flex;\n align-items: center;\n gap: 6px;\n min-height: 24px;\n padding: 0 6px;\n font-size: 12px;\n white-space: nowrap;\n cursor: pointer;\n}\n.ribbon-check[data-unsupported] {\n color: var(--_vv-muted);\n opacity: 0.6;\n}\n.ribbon-hint {\n display: none;\n}\n\n/* Drawing window with optional rulers. */\n.canvas-area {\n display: grid;\n flex: 1;\n min-width: 0;\n min-height: 0;\n grid-template-columns: 0 1fr;\n grid-template-rows: 0 1fr;\n}\n.canvas-area[data-ruler='true'] {\n grid-template-columns: 18px 1fr;\n grid-template-rows: 18px 1fr;\n}\n.canvas-area > .viewport {\n grid-column: 2;\n grid-row: 2;\n}\n.ruler,\n.ruler-corner {\n display: none;\n background: var(--_vv-surface);\n}\n.canvas-area[data-ruler='true'] > .ruler,\n.canvas-area[data-ruler='true'] > .ruler-corner {\n display: block;\n}\n.ruler-corner {\n grid-column: 1;\n grid-row: 1;\n border-right: 1px solid var(--_vv-border);\n border-bottom: 1px solid var(--_vv-border);\n}\n/* The shared office-ui-ruler draws ticks, labels and its own edge. */\n.ruler-h {\n grid-column: 2;\n grid-row: 1;\n}\n.ruler-v {\n grid-column: 1;\n grid-row: 2;\n}\n\n.page-bar {\n display: flex;\n flex: none;\n align-items: stretch;\n min-width: 0;\n border-top: 1px solid var(--_vv-border);\n background: var(--_vv-secondary);\n}\n.page-bar office-ui-menu-button {\n --office-target-size: 28px;\n flex: none;\n padding: 0 2px;\n}\n.page-tabs {\n flex: 1;\n min-width: 0;\n border-top: 0;\n}\noffice-ui-status-bar.status {\n gap: 16px;\n min-height: 29px;\n padding: 0 8px 0 12px;\n font-size: 11px;\n background: var(--_vv-surface);\n color: var(--_vv-muted);\n}\noffice-ui-status-item[value=''] {\n display: none;\n}\noffice-ui-status-item[data-page-status] {\n color: var(--_vv-ink);\n}\n.status-message {\n flex: 1;\n min-width: 0;\n}\n.viewport[data-tool='rectangle'],\n.viewport[data-tool='rectangle'] .paper [data-shape-id] {\n cursor: crosshair;\n}\n.viewport[data-tool='rectangle'] {\n touch-action: none;\n}\n.draw-preview {\n fill: var(--_vv-accent-soft);\n stroke: var(--_vv-accent);\n stroke-width: 1.5px;\n stroke-dasharray: 4 3;\n pointer-events: none;\n}\n.viewport[data-grid='true'] .paper {\n --_vv-grid-minor: rgb(0 0 0 / 7%);\n --_vv-grid-major: rgb(0 0 0 / 16%);\n background-image:\n linear-gradient(to right, var(--_vv-grid-major) 1px, transparent 1px),\n linear-gradient(to top, var(--_vv-grid-major) 1px, transparent 1px),\n linear-gradient(to right, var(--_vv-grid-minor) 1px, transparent 1px),\n linear-gradient(to top, var(--_vv-grid-minor) 1px, transparent 1px);\n background-size:\n var(--vv-inch, 96px) var(--vv-inch, 96px),\n var(--vv-inch, 96px) var(--vv-inch, 96px),\n calc(var(--vv-inch, 96px) / 4) calc(var(--vv-inch, 96px) / 4),\n calc(var(--vv-inch, 96px) / 4) calc(var(--vv-inch, 96px) / 4);\n background-position: left bottom;\n}\n:host(:fullscreen) {\n width: 100vw;\n height: 100vh;\n}\n\n@media (max-width: 760px) {\n .ribbon-row {\n flex: 1;\n min-width: 0;\n }\n .qat office-ui-button {\n --office-target-size: 44px;\n }\n .ribbon-tools .tools-content office-ui-ribbon-group {\n width: 100%;\n }\n office-ui-status-bar.status {\n gap: 4px;\n min-height: 48px;\n }\n office-ui-status-bar .notes-strip,\n office-ui-status-bar .status-message,\n office-ui-status-item[data-shape-status],\n office-ui-status-item[data-page-status] {\n display: none;\n }\n .zoom-controls {\n flex: 1;\n min-width: 0;\n --office-zoom-slider-width: 0;\n }\n}\n\n/* Visio's Tell me box sits right after the tabs. */\n.tell-me {\n flex: 0 1 280px;\n min-width: 140px;\n margin: 0 8px;\n --office-target-size: 28px;\n}\n.presentation-mode {\n --office-target-size: 24px;\n}\n@media (max-width: 760px) {\n .tell-me {\n display: none;\n }\n}\n",
|
|
1714
1678
|
"/* Visio's Shapes window: stencil views, More/Quick Shapes rows and a two-column master grid. */\n.shapes-pane {\n display: flex;\n flex: 0 0 232px;\n flex-direction: column;\n width: 232px;\n min-width: 0;\n overflow: hidden;\n border-right: 1px solid var(--_vv-border);\n background: var(--_vv-secondary);\n}\n.shapes-pane .pane-heading {\n height: 42px;\n font-size: 18px;\n font-weight: 400;\n color: var(--_vv-accent);\n}\n.pane-collapse {\n min-height: 28px;\n width: 28px;\n padding: 0;\n border-color: transparent;\n background: transparent;\n font-size: 18px;\n line-height: 1;\n}\n.shapes-views {\n display: flex;\n gap: 14px;\n padding: 4px 12px 0;\n border-bottom: 1px solid var(--_vv-border);\n}\n.shapes-views button {\n position: relative;\n min-height: 28px;\n padding: 2px 0 6px;\n border: 0;\n border-radius: 0;\n background: transparent;\n color: var(--_vv-muted);\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.shapes-views button[aria-selected='true'] {\n color: var(--_vv-ink);\n}\n.shapes-views button[aria-selected='true']::after {\n content: '';\n position: absolute;\n inset: auto 0 -1px;\n height: 2px;\n background: var(--_vv-accent);\n}\n#shapes-stencils,\n#shapes-search {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 4px 0 8px;\n}\n.shapes-row {\n display: flex;\n align-items: center;\n width: 100%;\n min-height: 30px;\n padding: 0 12px;\n border: 0;\n border-radius: 0;\n background: transparent;\n font-size: 12px;\n text-align: left;\n}\n.shapes-row::after {\n content: '›';\n margin-left: auto;\n font-size: 15px;\n}\n.stencil-title {\n margin: 2px 0;\n padding: 6px 12px;\n border-left: 3px solid var(--_vv-accent);\n background: var(--_vv-accent-soft);\n font-size: 12px;\n font-weight: 600;\n}\n.masters {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 2px;\n margin: 0;\n padding: 4px 6px;\n list-style: none;\n}\n.masters li[hidden] {\n display: none;\n}\n.master {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 40px;\n padding: 4px 6px;\n border-color: transparent;\n background: transparent;\n font-size: 11px;\n line-height: 1.2;\n text-align: left;\n cursor: grab;\n}\n.master svg {\n flex: none;\n width: 28px;\n height: 28px;\n fill: var(--_vv-surface);\n stroke: var(--_vv-accent);\n stroke-width: 1.2;\n stroke-linejoin: round;\n}\n.master[data-unsupported] {\n cursor: not-allowed;\n opacity: 0.45;\n}\n.master[data-unsupported]:hover {\n border-color: transparent;\n background: transparent;\n}\n.shapes-search-field {\n display: block;\n width: calc(100% - 24px);\n margin: 6px 12px;\n}\n.shapes-empty {\n margin: 8px 12px;\n color: var(--_vv-muted);\n font-size: 12px;\n}\n@media (max-width: 760px) {\n .shapes-pane {\n position: absolute;\n inset: 0 auto 0 0;\n z-index: 3;\n width: min(260px, 100%);\n box-shadow: 8px 0 24px #0002;\n }\n .master {\n min-height: 44px;\n }\n}\n.shapes-strip {\n display: flex;\n flex: 0 0 28px;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n width: 28px;\n min-width: 28px;\n padding: 8px 0;\n border: 0;\n border-right: 1px solid var(--_vv-border);\n border-radius: 0;\n background: var(--_vv-secondary);\n color: var(--_vv-accent);\n font-size: 16px;\n}\n.shapes-strip[hidden] {\n display: none;\n}\n.shapes-strip-label {\n writing-mode: vertical-rl;\n color: var(--_vv-ink);\n font-size: 12px;\n}\n/* Visio's floating Pan & Zoom window. */\n.pan-zoom {\n position: absolute;\n right: 16px;\n bottom: 16px;\n z-index: 5;\n width: 240px;\n border: 1px solid var(--_vv-border);\n background: var(--_vv-surface);\n box-shadow: 0 6px 20px rgb(0 0 0 / 18%);\n}\n.pan-zoom[hidden] {\n display: none;\n}\n.pan-zoom-heading {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 2px 4px 2px 10px;\n border-bottom: 1px solid var(--_vv-border);\n font-size: 12px;\n font-weight: 600;\n}\n.pan-zoom-close {\n min-height: 24px;\n width: 24px;\n padding: 0;\n border-color: transparent;\n background: transparent;\n}\n.pan-zoom-view {\n display: grid;\n place-items: center;\n height: 180px;\n padding: 8px;\n background: var(--_vv-bg);\n cursor: crosshair;\n touch-action: none;\n}\n.pan-zoom-view svg {\n max-width: 100%;\n max-height: 100%;\n background: #fff;\n box-shadow: 0 1px 3px rgb(0 0 0 / 20%);\n}\n.pan-zoom-frame {\n fill: rgb(220 38 38 / 8%);\n stroke: #dc2626;\n stroke-width: 2px;\n}\n",
|
|
1715
|
-
"/* Visio's File tab and backstage. */\n:host {\n position: relative;\n}\n
|
|
1679
|
+
"/* Visio's File tab and backstage. */\n:host {\n position: relative;\n}\n/* The shared office-ui-backstage draws the File column; these style Visio's page content. */\n.backstage {\n --office-accent: var(--_vv-accent);\n --office-accent-foreground: var(--vv-accent-ink, #fff);\n}\n.backstage-page h1 {\n margin: 0 0 24px;\n font-size: 34px;\n font-weight: 300;\n}\n.backstage-page h2 {\n margin: 20px 0 8px;\n font-size: 16px;\n font-weight: 600;\n}\n.backstage-file {\n margin-top: 0 !important;\n font-size: 20px !important;\n font-weight: 400 !important;\n overflow-wrap: anywhere;\n}\n.backstage-facts {\n display: grid;\n grid-template-columns: max-content 1fr;\n gap: 8px 24px;\n max-width: 520px;\n margin: 0;\n font-size: 13px;\n}\n.backstage-facts dt {\n color: var(--_vv-muted);\n}\n.backstage-facts dd {\n margin: 0;\n overflow-wrap: anywhere;\n}\n.backstage-action {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 2px;\n width: min(420px, 100%);\n min-height: 64px;\n margin: 0 0 10px;\n padding: 10px 16px;\n text-align: left;\n background: var(--_vv-surface);\n}\n.backstage-action strong {\n font-size: 14px;\n font-weight: 600;\n}\n.backstage-action span,\n.backstage-muted,\n.backstage-note-count {\n color: var(--_vv-muted);\n font-size: 12px;\n}\n.backstage-templates {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n}\n.backstage-templates .backstage-action,\n.backstage-templates ::slotted(*) {\n width: 200px;\n min-height: 120px;\n}\n/* Account: the shared profile editor, then Visio's product information. */\n.backstage-page office-ui-account {\n max-width: 560px;\n}\n.backstage-product {\n margin-top: 24px;\n}\n/* Share with people: a live session between windows of this browser. */\n.share-panel {\n max-width: 560px;\n margin-bottom: 16px;\n}\n.share-panel h2 {\n margin-top: 0 !important;\n}\n.share-field {\n display: grid;\n gap: 4px;\n max-width: 320px;\n margin: 12px 0;\n font-size: 13px;\n}\n.share-room {\n min-height: 30px;\n padding: 2px 8px;\n border: 1px solid var(--_vv-border);\n border-radius: 3px;\n background: var(--_vv-surface);\n color: inherit;\n font: inherit;\n}\n.share-buttons button {\n min-height: 32px;\n padding: 0 16px;\n}\n.share-buttons button[hidden] {\n display: none;\n}\n.share-status {\n min-height: 20px;\n font-size: 13px;\n}\n",
|
|
1716
1680
|
"/*\n * Drawing canvas focus. SVG outlines are drawn in drawing units (inches here), so a browser\n * focus ring on a shape becomes a page-sized blob. Shapes show focus with a dashed accent\n * stroke instead, only for keyboard focus; selection keeps its solid accent stroke.\n */\n.paper [data-shape-id]:focus,\n.paper [data-shape-id]:focus-visible {\n outline: none;\n}\n.paper [data-shape-id]:focus-visible > [data-geometry] {\n stroke: var(--vv-focus, var(--_vv-accent)) !important;\n stroke-width: 0.04 !important;\n stroke-dasharray: 0.08 0.05;\n}\n"
|
|
1717
1681
|
].join("\n");
|
|
1718
1682
|
//#endregion
|
|
@@ -2829,8 +2793,7 @@ function ribbonCommands(root) {
|
|
|
2829
2793
|
const commands = [];
|
|
2830
2794
|
const context = (node) => {
|
|
2831
2795
|
const group = node.closest("office-ui-ribbon-group")?.getAttribute("label") ?? "";
|
|
2832
|
-
|
|
2833
|
-
return [panel ? root.querySelector(`[aria-controls="${panel.id}"]`)?.textContent ?? "" : "", group].filter(Boolean).join(" › ");
|
|
2796
|
+
return [node.closest("[data-ribbon-tab]")?.dataset.label ?? "", group].filter(Boolean).join(" › ");
|
|
2834
2797
|
};
|
|
2835
2798
|
for (const node of root.querySelectorAll(".toolbar [command]")) {
|
|
2836
2799
|
const id = node.getAttribute("command");
|
|
@@ -2962,50 +2925,32 @@ function quickAccess(doc) {
|
|
|
2962
2925
|
return qat;
|
|
2963
2926
|
}
|
|
2964
2927
|
/**
|
|
2965
|
-
* The Visio ribbon
|
|
2966
|
-
*
|
|
2928
|
+
* The Visio ribbon on the shared `office-ui-ribbon`: Quick Access Toolbar, File, one panel per
|
|
2929
|
+
* tab and Tell me. The shared element owns the tab row, selection and arrow-key movement;
|
|
2930
|
+
* commands emit `ribbon-action` events for the router.
|
|
2967
2931
|
*/
|
|
2968
2932
|
function createRibbon(doc) {
|
|
2969
|
-
const
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
const
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
file.type = "button";
|
|
2981
|
-
file.className = "file-tab";
|
|
2982
|
-
file.textContent = "File";
|
|
2983
|
-
file.setAttribute("aria-haspopup", "dialog");
|
|
2984
|
-
file.setAttribute("aria-expanded", "false");
|
|
2985
|
-
head.append(quickAccess(doc), file, tabs, createTellMe(doc));
|
|
2986
|
-
toolbar.append(head);
|
|
2933
|
+
const ribbon = doc.createElement("office-ui-ribbon");
|
|
2934
|
+
ribbon.className = "toolbar";
|
|
2935
|
+
ribbon.setAttribute("role", "group");
|
|
2936
|
+
ribbon.setAttribute("aria-label", "Diagram controls");
|
|
2937
|
+
ribbon.setAttribute("label", "Ribbon");
|
|
2938
|
+
ribbon.setAttribute("selected", "home");
|
|
2939
|
+
const qat = quickAccess(doc);
|
|
2940
|
+
qat.slot = "quick-access";
|
|
2941
|
+
const tellMe = createTellMe(doc);
|
|
2942
|
+
tellMe.slot = "search";
|
|
2943
|
+
ribbon.append(qat, tellMe);
|
|
2987
2944
|
for (const [key, name, build] of RIBBON_TABS) {
|
|
2988
|
-
const selected = key === "home";
|
|
2989
|
-
const tab = doc.createElement("button");
|
|
2990
|
-
tab.type = "button";
|
|
2991
|
-
tab.id = `${key}-tab`;
|
|
2992
|
-
tab.dataset.tab = key;
|
|
2993
|
-
tab.textContent = name;
|
|
2994
|
-
tab.setAttribute("role", "tab");
|
|
2995
|
-
tab.setAttribute("aria-selected", String(selected));
|
|
2996
|
-
tab.setAttribute("aria-controls", `${key}-panel`);
|
|
2997
|
-
if (!selected) tab.tabIndex = -1;
|
|
2998
2945
|
const panel = doc.createElement("div");
|
|
2999
2946
|
panel.className = "ribbon-content";
|
|
3000
2947
|
panel.id = `${key}-panel`;
|
|
3001
|
-
panel.
|
|
3002
|
-
panel.
|
|
3003
|
-
panel.hidden = !selected;
|
|
2948
|
+
panel.dataset.ribbonTab = key;
|
|
2949
|
+
panel.dataset.label = name;
|
|
3004
2950
|
build(doc, panel);
|
|
3005
|
-
|
|
3006
|
-
toolbar.append(panel);
|
|
2951
|
+
ribbon.append(panel);
|
|
3007
2952
|
}
|
|
3008
|
-
return
|
|
2953
|
+
return ribbon;
|
|
3009
2954
|
}
|
|
3010
2955
|
//#endregion
|
|
3011
2956
|
//#region src/ribbon-keytips.ts
|
|
@@ -3111,13 +3056,13 @@ function generated(label, used, singles) {
|
|
|
3111
3056
|
}
|
|
3112
3057
|
/** Annotate the ribbon for KeyTips. Labels and ids are static application strings. */
|
|
3113
3058
|
function applyKeyTips(toolbar) {
|
|
3114
|
-
toolbar.
|
|
3059
|
+
toolbar.setAttribute("file-keytip", "F");
|
|
3115
3060
|
toolbar.querySelector(".tell-me").dataset.keytip = "Q";
|
|
3116
3061
|
toolbar.querySelector(".qat [command=\"undo\"]").dataset.keytip = "1";
|
|
3117
3062
|
toolbar.querySelector(".qat [command=\"redo\"]").dataset.keytip = "2";
|
|
3118
|
-
for (const
|
|
3119
|
-
tab
|
|
3120
|
-
|
|
3063
|
+
for (const panel of toolbar.querySelectorAll("[data-ribbon-tab]")) {
|
|
3064
|
+
const tab = panel.dataset.ribbonTab;
|
|
3065
|
+
panel.dataset.tabKeytip = TABS[tab] ?? tab.slice(0, 1).toUpperCase();
|
|
3121
3066
|
}
|
|
3122
3067
|
for (const panel of toolbar.querySelectorAll(".ribbon-content")) {
|
|
3123
3068
|
panel.dataset.keytipLevel = "";
|
|
@@ -3222,98 +3167,42 @@ function createPageTabs(doc) {
|
|
|
3222
3167
|
}
|
|
3223
3168
|
//#endregion
|
|
3224
3169
|
//#region src/viewer-search.ts
|
|
3225
|
-
/** Builds the Find group: a search field, previous/next result buttons and a live status. */
|
|
3226
|
-
function createSearchGroup(doc) {
|
|
3227
|
-
const group = doc.createElement("div");
|
|
3228
|
-
group.className = "ribbon-group search-group";
|
|
3229
|
-
group.setAttribute("role", "group");
|
|
3230
|
-
group.setAttribute("aria-label", "Find");
|
|
3231
|
-
const controls = doc.createElement("div");
|
|
3232
|
-
controls.className = "search-controls";
|
|
3233
|
-
controls.setAttribute("role", "search");
|
|
3234
|
-
controls.setAttribute("aria-label", "Diagram text search");
|
|
3235
|
-
const label = doc.createElement("label");
|
|
3236
|
-
label.append("Find ");
|
|
3237
|
-
const input = doc.createElement("input");
|
|
3238
|
-
input.type = "search";
|
|
3239
|
-
input.setAttribute("aria-label", "Search diagram text");
|
|
3240
|
-
input.placeholder = "Text across pages";
|
|
3241
|
-
input.maxLength = TEXT_SEARCH_LIMITS.queryCharacters;
|
|
3242
|
-
input.autocomplete = "off";
|
|
3243
|
-
input.spellcheck = false;
|
|
3244
|
-
input.setAttribute("aria-describedby", "search-status");
|
|
3245
|
-
label.append(input);
|
|
3246
|
-
const step = (action, name, text) => {
|
|
3247
|
-
const button = doc.createElement("button");
|
|
3248
|
-
button.type = "button";
|
|
3249
|
-
button.dataset.action = action;
|
|
3250
|
-
button.setAttribute("aria-label", name);
|
|
3251
|
-
button.textContent = text;
|
|
3252
|
-
return button;
|
|
3253
|
-
};
|
|
3254
|
-
const status = doc.createElement("span");
|
|
3255
|
-
status.id = "search-status";
|
|
3256
|
-
status.setAttribute("role", "status");
|
|
3257
|
-
status.setAttribute("aria-live", "polite");
|
|
3258
|
-
status.setAttribute("aria-atomic", "true");
|
|
3259
|
-
controls.append(label, step("search-previous", "Previous matching shape", "Previous"), step("search-next", "Next matching shape", "Next"), status);
|
|
3260
|
-
const caption = doc.createElement("span");
|
|
3261
|
-
caption.className = "group-label";
|
|
3262
|
-
caption.textContent = "Find in drawing";
|
|
3263
|
-
group.append(controls, caption);
|
|
3264
|
-
return group;
|
|
3265
|
-
}
|
|
3266
3170
|
/**
|
|
3267
|
-
* Visio opens Find from Home > Editing rather than keeping a box in the ribbon. The
|
|
3268
|
-
* sits under the ribbon, hidden until Find... or Ctrl+F
|
|
3171
|
+
* Visio opens Find from Home > Editing rather than keeping a box in the ribbon. The shared
|
|
3172
|
+
* `office-ui-find-bar` sits under the ribbon, hidden until Find... or Ctrl+F; Enter and
|
|
3173
|
+
* Shift+Enter step through matching shapes and Escape clears, then closes.
|
|
3269
3174
|
*/
|
|
3270
3175
|
function createFindBar(doc) {
|
|
3271
|
-
const bar = doc.createElement("
|
|
3176
|
+
const bar = doc.createElement("office-ui-find-bar");
|
|
3272
3177
|
bar.className = "find-bar";
|
|
3273
|
-
bar.
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
close.title = "Close Find (Esc)";
|
|
3280
|
-
close.textContent = "×";
|
|
3281
|
-
bar.append(createSearchGroup(doc), close);
|
|
3178
|
+
bar.setAttribute("label", "Diagram text search");
|
|
3179
|
+
bar.setAttribute("input-label", "Search diagram text");
|
|
3180
|
+
bar.setAttribute("placeholder", "Text across pages");
|
|
3181
|
+
bar.setAttribute("maxlength", String(TEXT_SEARCH_LIMITS.queryCharacters));
|
|
3182
|
+
bar.setAttribute("previous-label", "Previous matching shape");
|
|
3183
|
+
bar.setAttribute("next-label", "Next matching shape");
|
|
3282
3184
|
return bar;
|
|
3283
3185
|
}
|
|
3284
|
-
/**
|
|
3285
|
-
function wireFindBar(bar,
|
|
3186
|
+
/** Route the find bar's queries and steps to the controller; `closed` restores focus. */
|
|
3187
|
+
function wireFindBar(bar, controller, closed) {
|
|
3286
3188
|
const events = new ((bar.ownerDocument.defaultView?.AbortController) ?? AbortController)();
|
|
3287
|
-
const
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
},
|
|
3294
|
-
bar.addEventListener("
|
|
3295
|
-
if (event.key === "Escape" && input.value === "") close();
|
|
3296
|
-
}, {
|
|
3297
|
-
signal: events.signal,
|
|
3298
|
-
capture: true
|
|
3299
|
-
});
|
|
3189
|
+
const options = { signal: events.signal };
|
|
3190
|
+
bar.addEventListener("office-find-input", (event) => controller.setSearchQuery(event.detail.query), options);
|
|
3191
|
+
bar.addEventListener("office-find-step", (event) => {
|
|
3192
|
+
const { direction } = event.detail;
|
|
3193
|
+
if (direction === "next") controller.nextSearchResult();
|
|
3194
|
+
else controller.previousSearchResult();
|
|
3195
|
+
}, options);
|
|
3196
|
+
bar.addEventListener("office-find-close", closed, options);
|
|
3300
3197
|
return () => events.abort();
|
|
3301
3198
|
}
|
|
3302
|
-
function
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
next: root.querySelector("[data-action=\"search-next\"]")
|
|
3308
|
-
};
|
|
3309
|
-
}
|
|
3310
|
-
function renderSearchControls(controls, state) {
|
|
3311
|
-
if (controls.input.value !== state.search.query) controls.input.value = state.search.query;
|
|
3312
|
-
controls.input.disabled = !state.document?.pages.length;
|
|
3313
|
-
controls.previous.disabled = controls.next.disabled = !state.search.results.length;
|
|
3314
|
-
controls.status.textContent = state.document ? textSearchStatus(state.search) : "Open a diagram to search";
|
|
3199
|
+
function renderFindBar(bar, state) {
|
|
3200
|
+
bar.value = state.search.query;
|
|
3201
|
+
bar.toggleAttribute("disabled", !state.document?.pages.length);
|
|
3202
|
+
bar.toggleAttribute("navigation-disabled", !state.search.results.length);
|
|
3203
|
+
bar.status = state.document ? textSearchStatus(state.search) : "Open a diagram to search";
|
|
3315
3204
|
const result = state.search.results[state.search.activeIndex];
|
|
3316
|
-
|
|
3205
|
+
bar.statusTitle = result ? `${result.pageName} · ${result.shapeName}: ${result.preview}` : "";
|
|
3317
3206
|
}
|
|
3318
3207
|
//#endregion
|
|
3319
3208
|
//#region src/viewer-fit.ts
|
|
@@ -3607,6 +3496,17 @@ var BACKSTAGE_FOOTER = [
|
|
|
3607
3496
|
["feedback", "Feedback"],
|
|
3608
3497
|
["options", "Options"]
|
|
3609
3498
|
];
|
|
3499
|
+
/** The navigation items, with optional disabled states (Save and Close follow the drawing). */
|
|
3500
|
+
function backstageItems(state = {}) {
|
|
3501
|
+
const item = (id, label, group) => ({
|
|
3502
|
+
id,
|
|
3503
|
+
label,
|
|
3504
|
+
...group ? { group } : {},
|
|
3505
|
+
...state[id]?.disabled ? { disabled: true } : {},
|
|
3506
|
+
...state[id]?.title ? { title: state[id].title } : {}
|
|
3507
|
+
});
|
|
3508
|
+
return [...BACKSTAGE_ITEMS.map(([id, label]) => item(id, label)), ...BACKSTAGE_FOOTER.map(([id, label]) => item(id, label, "footer"))];
|
|
3509
|
+
}
|
|
3610
3510
|
var LOCAL = "Documents stay in this browser; nothing is uploaded.";
|
|
3611
3511
|
function el(doc, tag, className, text) {
|
|
3612
3512
|
const node = doc.createElement(tag);
|
|
@@ -3630,7 +3530,6 @@ function action(doc, id, label, description, unsupported) {
|
|
|
3630
3530
|
function page(doc, id, title, ...content) {
|
|
3631
3531
|
const section = el(doc, "section", "backstage-page");
|
|
3632
3532
|
section.dataset.backstagePage = id;
|
|
3633
|
-
section.hidden = true;
|
|
3634
3533
|
section.setAttribute("aria-labelledby", `backstage-${id}-title`);
|
|
3635
3534
|
const heading = el(doc, "h1", "", title);
|
|
3636
3535
|
heading.id = `backstage-${id}-title`;
|
|
@@ -3638,31 +3537,55 @@ function page(doc, id, title, ...content) {
|
|
|
3638
3537
|
return section;
|
|
3639
3538
|
}
|
|
3640
3539
|
/**
|
|
3540
|
+
* Share with people: a live session (Yjs) between tabs and windows of this browser. The room name
|
|
3541
|
+
* is all a second window needs; nothing is uploaded.
|
|
3542
|
+
*/
|
|
3543
|
+
function share(doc) {
|
|
3544
|
+
const panel = el(doc, "section", "share-panel");
|
|
3545
|
+
panel.setAttribute("aria-labelledby", "share-title");
|
|
3546
|
+
const title = el(doc, "h2", "", "Share with people");
|
|
3547
|
+
title.id = "share-title";
|
|
3548
|
+
const field = el(doc, "label", "share-field");
|
|
3549
|
+
const room = el(doc, "input", "share-room");
|
|
3550
|
+
room.type = "text";
|
|
3551
|
+
room.maxLength = 64;
|
|
3552
|
+
room.autocomplete = "off";
|
|
3553
|
+
room.spellcheck = false;
|
|
3554
|
+
field.append(el(doc, "span", "", "Session name"), room);
|
|
3555
|
+
const buttons = el(doc, "div", "share-buttons");
|
|
3556
|
+
const start = el(doc, "button", "share-start", "Start sharing");
|
|
3557
|
+
start.type = "button";
|
|
3558
|
+
start.dataset.share = "start";
|
|
3559
|
+
const stop = el(doc, "button", "share-stop", "Stop sharing");
|
|
3560
|
+
stop.type = "button";
|
|
3561
|
+
stop.dataset.share = "stop";
|
|
3562
|
+
stop.hidden = true;
|
|
3563
|
+
buttons.append(start, stop);
|
|
3564
|
+
const status = el(doc, "p", "share-status");
|
|
3565
|
+
status.setAttribute("role", "status");
|
|
3566
|
+
const people = doc.createElement("office-ui-presence");
|
|
3567
|
+
people.className = "share-people";
|
|
3568
|
+
people.setAttribute("label", "People in this session");
|
|
3569
|
+
panel.append(title, el(doc, "p", "", "Edit this drawing together with other windows of this browser. Open the same session name in another window to join."), field, buttons, status, people, el(doc, "p", "backstage-muted", "The whole drawing is shared; when two people change it at once, the last change wins. Nothing leaves this device."));
|
|
3570
|
+
return panel;
|
|
3571
|
+
}
|
|
3572
|
+
/** Visio's Account page: the shared Office profile editor and product information. */
|
|
3573
|
+
function account(doc) {
|
|
3574
|
+
const profile = doc.createElement("office-ui-account");
|
|
3575
|
+
const product = el(doc, "section", "backstage-product");
|
|
3576
|
+
product.append(el(doc, "h2", "", "Product Information"), el(doc, "p", "", "Visio viewer (beta). No sign-in or subscription is used."), el(doc, "p", "backstage-muted", LOCAL));
|
|
3577
|
+
profile.append(product);
|
|
3578
|
+
return profile;
|
|
3579
|
+
}
|
|
3580
|
+
/**
|
|
3641
3581
|
* Visio's backstage: a full-window File view with a navigation column and one page per item.
|
|
3642
3582
|
* Static application structure only; document values are filled in later as text.
|
|
3643
3583
|
*/
|
|
3644
3584
|
function createBackstage(doc) {
|
|
3645
|
-
const root =
|
|
3646
|
-
root.
|
|
3647
|
-
root.setAttribute("
|
|
3648
|
-
root.
|
|
3649
|
-
root.setAttribute("aria-label", "File");
|
|
3650
|
-
const nav = el(doc, "nav", "backstage-nav");
|
|
3651
|
-
nav.setAttribute("aria-label", "File");
|
|
3652
|
-
const back = el(doc, "button", "backstage-back", "←");
|
|
3653
|
-
back.type = "button";
|
|
3654
|
-
back.dataset.backstage = "back";
|
|
3655
|
-
back.setAttribute("aria-label", "Back to the drawing");
|
|
3656
|
-
back.title = "Back (Esc)";
|
|
3657
|
-
const item = ([id, label]) => {
|
|
3658
|
-
const button = el(doc, "button", "backstage-item", label);
|
|
3659
|
-
button.type = "button";
|
|
3660
|
-
button.dataset.backstageItem = id;
|
|
3661
|
-
return button;
|
|
3662
|
-
};
|
|
3663
|
-
const footer = el(doc, "div", "backstage-footer");
|
|
3664
|
-
footer.append(...BACKSTAGE_FOOTER.map(item));
|
|
3665
|
-
nav.append(back, ...BACKSTAGE_ITEMS.map(item), footer);
|
|
3585
|
+
const root = doc.createElement("office-ui-backstage");
|
|
3586
|
+
root.className = "backstage";
|
|
3587
|
+
root.setAttribute("back-label", "Back to the drawing");
|
|
3588
|
+
root.items = backstageItems();
|
|
3666
3589
|
const facts = el(doc, "dl", "backstage-facts");
|
|
3667
3590
|
for (const [key, label] of [
|
|
3668
3591
|
["pages", "Pages"],
|
|
@@ -3688,67 +3611,61 @@ function createBackstage(doc) {
|
|
|
3688
3611
|
input.accept = ".vsdx,.vsd";
|
|
3689
3612
|
input.hidden = true;
|
|
3690
3613
|
input.setAttribute("aria-label", "Choose a Visio drawing");
|
|
3691
|
-
const preview =
|
|
3692
|
-
preview.
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
root.append(
|
|
3614
|
+
const preview = doc.createElement("office-ui-print-preview");
|
|
3615
|
+
preview.className = "backstage-print-preview";
|
|
3616
|
+
preview.setAttribute("label", "Print preview of the current page");
|
|
3617
|
+
preview.setAttribute("empty-label", "Open a drawing to print it.");
|
|
3618
|
+
root.append(page(doc, "info", "Info", file, facts, notes), page(doc, "new", "New", templates), page(doc, "open", "Open", action(doc, "open", "Browse", "Open a .vsdx drawing or a legacy .vsd preview from this device."), input, el(doc, "p", "backstage-muted", `Recent drawings are not tracked. ${LOCAL}`)), page(doc, "save-as", "Save As", action(doc, "download", "Download a copy", "Save the drawing, with your edits, as a .vsdx file."), action(doc, "export-svg", "Export the current page as SVG", "An approximate picture with compatibility notes.")), page(doc, "print", "Print", action(doc, "print", "Print", "Print the current page."), preview), page(doc, "share", "Share", share(doc), action(doc, "email", "Email", "Send the drawing as an attachment.", LOCAL)), page(doc, "export", "Export", action(doc, "export-svg", "Export the current page as SVG", "An approximate picture with compatibility notes."), action(doc, "export-pdf", "Create PDF/XPS Document", "A fixed-layout document.", "Needs PDF output."), action(doc, "change-type", "Change File Type", "Save in another Visio format.", "Needs core format conversion.")), page(doc, "account", "Account", account(doc)), page(doc, "feedback", "Feedback", el(doc, "p", "backstage-muted", "Feedback is not collected by this viewer.")));
|
|
3696
3619
|
return root;
|
|
3697
3620
|
}
|
|
3698
3621
|
//#endregion
|
|
3699
3622
|
//#region src/viewer-backstage.ts
|
|
3700
|
-
/**
|
|
3701
|
-
* Visio's File backstage: opened from the File tab, closed with Back or Escape (focus returns to
|
|
3702
|
-
* File). Open, Save, Save As, Export, Print and Close are real; the rest is shown disabled.
|
|
3703
|
-
*/
|
|
3704
3623
|
var ViewerBackstage = class {
|
|
3705
3624
|
host;
|
|
3706
3625
|
#root;
|
|
3707
|
-
#
|
|
3626
|
+
#ribbon;
|
|
3708
3627
|
#input;
|
|
3709
3628
|
#state;
|
|
3629
|
+
#items = "";
|
|
3710
3630
|
#urls = /* @__PURE__ */ new Set();
|
|
3711
3631
|
constructor(host) {
|
|
3712
3632
|
this.host = host;
|
|
3713
|
-
this.#root = host.root.querySelector("
|
|
3714
|
-
this.#
|
|
3633
|
+
this.#root = host.root.querySelector("office-ui-backstage");
|
|
3634
|
+
this.#ribbon = host.root.querySelector("office-ui-ribbon");
|
|
3715
3635
|
this.#input = this.#root.querySelector(".backstage-file-input");
|
|
3716
3636
|
}
|
|
3717
3637
|
get open() {
|
|
3718
|
-
return
|
|
3638
|
+
return this.#root.open;
|
|
3719
3639
|
}
|
|
3720
3640
|
show(page = "info") {
|
|
3721
|
-
this.#
|
|
3722
|
-
this.#
|
|
3723
|
-
this.#
|
|
3724
|
-
this.#root.querySelector(`[data-backstage-item="${page}"]`)?.focus();
|
|
3641
|
+
this.#ribbon.setAttribute("file-expanded", "true");
|
|
3642
|
+
this.#root.show(page);
|
|
3643
|
+
if (page === "print") this.#preview();
|
|
3725
3644
|
}
|
|
3726
3645
|
hide() {
|
|
3727
|
-
|
|
3728
|
-
this.#root.hidden = true;
|
|
3729
|
-
this.#file.setAttribute("aria-expanded", "false");
|
|
3730
|
-
this.#file.focus();
|
|
3646
|
+
this.#root.close();
|
|
3731
3647
|
}
|
|
3732
3648
|
wire() {
|
|
3733
3649
|
const events = new ((this.host.root.ownerDocument.defaultView?.AbortController) ?? AbortController)();
|
|
3734
3650
|
const options = { signal: events.signal };
|
|
3735
|
-
this.#
|
|
3651
|
+
this.#ribbon.addEventListener("office-ribbon-file", () => this.open ? this.hide() : this.show(), options);
|
|
3652
|
+
this.#root.addEventListener("office-backstage-select", (event) => {
|
|
3653
|
+
const id = event.detail.id;
|
|
3654
|
+
if (id === "save") this.#download();
|
|
3655
|
+
else if (id === "close") this.#close();
|
|
3656
|
+
else if (id === "options") {
|
|
3657
|
+
this.hide();
|
|
3658
|
+
this.host.showOptions();
|
|
3659
|
+
} else if (id === "print") this.#preview();
|
|
3660
|
+
}, options);
|
|
3661
|
+
this.#root.addEventListener("office-backstage-close", () => {
|
|
3662
|
+
this.#ribbon.setAttribute("file-expanded", "false");
|
|
3663
|
+
this.#ribbon.focusFile();
|
|
3664
|
+
}, options);
|
|
3736
3665
|
this.#root.addEventListener("click", (event) => {
|
|
3737
|
-
const
|
|
3738
|
-
if (target.closest?.("[data-backstage=\"back\"]")) return this.hide();
|
|
3739
|
-
const item = target.closest?.("[data-backstage-item]")?.dataset.backstageItem;
|
|
3740
|
-
if (item === "save") return this.#download();
|
|
3741
|
-
if (item === "close") return this.#close();
|
|
3742
|
-
if (item) return this.#select(item);
|
|
3743
|
-
const action = target.closest?.("[data-backstage-action]");
|
|
3666
|
+
const action = event.target.closest?.("[data-backstage-action]");
|
|
3744
3667
|
if (action && !action.disabled) this.#run(action.dataset.backstageAction);
|
|
3745
3668
|
}, options);
|
|
3746
|
-
this.#root.addEventListener("keydown", (event) => {
|
|
3747
|
-
if (event.key !== "Escape") return;
|
|
3748
|
-
event.preventDefault();
|
|
3749
|
-
event.stopPropagation();
|
|
3750
|
-
this.hide();
|
|
3751
|
-
}, options);
|
|
3752
3669
|
this.#input.addEventListener("change", () => {
|
|
3753
3670
|
const file = this.#input.files?.[0];
|
|
3754
3671
|
this.#input.value = "";
|
|
@@ -3762,11 +3679,6 @@ var ViewerBackstage = class {
|
|
|
3762
3679
|
this.#urls.clear();
|
|
3763
3680
|
};
|
|
3764
3681
|
}
|
|
3765
|
-
#select(page) {
|
|
3766
|
-
for (const item of this.#root.querySelectorAll("[data-backstage-item]")) item.toggleAttribute("aria-current", item.dataset.backstageItem === page);
|
|
3767
|
-
for (const section of this.#root.querySelectorAll("[data-backstage-page]")) section.hidden = section.dataset.backstagePage !== page;
|
|
3768
|
-
if (page === "print") this.#preview();
|
|
3769
|
-
}
|
|
3770
3682
|
#run(action) {
|
|
3771
3683
|
if (action === "open") this.#input.click();
|
|
3772
3684
|
else if (action === "download") this.#download();
|
|
@@ -3816,14 +3728,11 @@ var ViewerBackstage = class {
|
|
|
3816
3728
|
this.host.announce(error instanceof Error ? error.message : String(error));
|
|
3817
3729
|
}
|
|
3818
3730
|
}
|
|
3819
|
-
/**
|
|
3731
|
+
/** The shared print preview shows an inert clone of the rendered page, never parsed markup. */
|
|
3820
3732
|
#preview() {
|
|
3821
|
-
const
|
|
3733
|
+
const preview = this.#root.querySelector("office-ui-print-preview");
|
|
3822
3734
|
const paper = this.host.viewport.querySelector("svg.paper");
|
|
3823
|
-
|
|
3824
|
-
const clone = target.querySelector("svg");
|
|
3825
|
-
clone?.removeAttribute("style");
|
|
3826
|
-
clone?.querySelectorAll("[tabindex]").forEach((node) => node.removeAttribute("tabindex"));
|
|
3735
|
+
preview.pages = paper ? [paper] : [];
|
|
3827
3736
|
}
|
|
3828
3737
|
#print() {
|
|
3829
3738
|
const doc = this.host.root.ownerDocument;
|
|
@@ -3867,13 +3776,24 @@ var ViewerBackstage = class {
|
|
|
3867
3776
|
this.#root.querySelector(".backstage-note-count").textContent = notes ? `${notes} compatibility notes describe what this viewer approximates or omits.` : "No compatibility notes.";
|
|
3868
3777
|
const busy = state.loading || state.edit.busy;
|
|
3869
3778
|
const saveReason = state.document?.format === "vsd" ? "Legacy VSD drawings are read only here." : "Open a .vsdx file to save a copy.";
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3779
|
+
const saveDisabled = !state.edit.sourceAvailable || busy;
|
|
3780
|
+
const download = this.#root.querySelector("[data-backstage-action=\"download\"]");
|
|
3781
|
+
download.disabled = saveDisabled;
|
|
3782
|
+
download.title = saveDisabled ? saveReason : "";
|
|
3783
|
+
const items = backstageItems({
|
|
3784
|
+
save: {
|
|
3785
|
+
disabled: saveDisabled,
|
|
3786
|
+
...saveDisabled ? { title: saveReason } : {}
|
|
3787
|
+
},
|
|
3788
|
+
close: { disabled: !state.document }
|
|
3789
|
+
});
|
|
3790
|
+
const key = JSON.stringify(items);
|
|
3791
|
+
if (key !== this.#items) {
|
|
3792
|
+
this.#items = key;
|
|
3793
|
+
this.#root.items = items;
|
|
3873
3794
|
}
|
|
3874
3795
|
for (const node of this.#root.querySelectorAll("[data-backstage-action=\"export-svg\"], [data-backstage-action=\"print\"]")) node.disabled = !page || busy;
|
|
3875
3796
|
this.#root.querySelector("[data-backstage-action=\"notes\"]").disabled = notes === 0;
|
|
3876
|
-
this.#root.querySelector("[data-backstage-item=\"close\"]").disabled = !state.document;
|
|
3877
3797
|
}
|
|
3878
3798
|
};
|
|
3879
3799
|
//#endregion
|
|
@@ -4215,6 +4135,396 @@ var ViewerPanZoom = class {
|
|
|
4215
4135
|
}
|
|
4216
4136
|
};
|
|
4217
4137
|
//#endregion
|
|
4138
|
+
//#region src/viewer-options.ts
|
|
4139
|
+
var disabled = (reason) => ({ disabled: `Not available yet. ${reason}` });
|
|
4140
|
+
/**
|
|
4141
|
+
* Visio's File > Options categories, in Visio's order. Only User name and Initials are honoured
|
|
4142
|
+
* today (they are the local profile, also used as the identity in a shared session); every other
|
|
4143
|
+
* setting is shown disabled with the capability it is waiting for.
|
|
4144
|
+
*/
|
|
4145
|
+
var VISIO_OPTION_CATEGORIES = [
|
|
4146
|
+
{
|
|
4147
|
+
id: "general",
|
|
4148
|
+
label: "General",
|
|
4149
|
+
description: "General options for working with Visio.",
|
|
4150
|
+
sections: [{
|
|
4151
|
+
id: "ui",
|
|
4152
|
+
title: "User Interface options",
|
|
4153
|
+
controls: [
|
|
4154
|
+
{
|
|
4155
|
+
kind: "toggle",
|
|
4156
|
+
key: "miniToolbar",
|
|
4157
|
+
label: "Show Mini Toolbar on selection",
|
|
4158
|
+
...disabled("Needs rich text formatting.")
|
|
4159
|
+
},
|
|
4160
|
+
{
|
|
4161
|
+
kind: "toggle",
|
|
4162
|
+
key: "livePreview",
|
|
4163
|
+
label: "Enable Live Preview",
|
|
4164
|
+
...disabled("Needs Quick Styles and themes.")
|
|
4165
|
+
},
|
|
4166
|
+
{
|
|
4167
|
+
kind: "select",
|
|
4168
|
+
key: "screenTips",
|
|
4169
|
+
label: "ScreenTip style",
|
|
4170
|
+
choices: [{
|
|
4171
|
+
value: "descriptions",
|
|
4172
|
+
label: "Show feature descriptions in ScreenTips"
|
|
4173
|
+
}],
|
|
4174
|
+
...disabled("ScreenTips always describe the command.")
|
|
4175
|
+
}
|
|
4176
|
+
]
|
|
4177
|
+
}, {
|
|
4178
|
+
id: "personalize",
|
|
4179
|
+
title: "Personalize your copy of Microsoft Office",
|
|
4180
|
+
description: "Stored on this device only. Shared sessions show this name to others.",
|
|
4181
|
+
controls: [{
|
|
4182
|
+
kind: "text",
|
|
4183
|
+
key: "userName",
|
|
4184
|
+
label: "User name",
|
|
4185
|
+
maxLength: 64
|
|
4186
|
+
}, {
|
|
4187
|
+
kind: "text",
|
|
4188
|
+
key: "userInitials",
|
|
4189
|
+
label: "Initials",
|
|
4190
|
+
maxLength: 2
|
|
4191
|
+
}]
|
|
4192
|
+
}]
|
|
4193
|
+
},
|
|
4194
|
+
{
|
|
4195
|
+
id: "proofing",
|
|
4196
|
+
label: "Proofing",
|
|
4197
|
+
description: "Change how Visio corrects and formats your text.",
|
|
4198
|
+
disabled: "Not available yet. Spelling and AutoCorrect need rich text editing.",
|
|
4199
|
+
sections: []
|
|
4200
|
+
},
|
|
4201
|
+
{
|
|
4202
|
+
id: "save",
|
|
4203
|
+
label: "Save",
|
|
4204
|
+
description: "Customize how drawings are saved.",
|
|
4205
|
+
disabled: "Not available yet. Drawings are saved as downloaded copies; AutoRecover is not kept.",
|
|
4206
|
+
sections: []
|
|
4207
|
+
},
|
|
4208
|
+
{
|
|
4209
|
+
id: "language",
|
|
4210
|
+
label: "Language",
|
|
4211
|
+
description: "Set the Office Language Preferences.",
|
|
4212
|
+
disabled: "Not available yet. The viewer is English only.",
|
|
4213
|
+
sections: []
|
|
4214
|
+
},
|
|
4215
|
+
{
|
|
4216
|
+
id: "advanced",
|
|
4217
|
+
label: "Advanced",
|
|
4218
|
+
description: "Advanced options for working with Visio.",
|
|
4219
|
+
sections: [{
|
|
4220
|
+
id: "editing",
|
|
4221
|
+
title: "Editing options",
|
|
4222
|
+
controls: [{
|
|
4223
|
+
kind: "number",
|
|
4224
|
+
key: "undoLevels",
|
|
4225
|
+
label: "Number of undo levels",
|
|
4226
|
+
min: 1,
|
|
4227
|
+
max: 99,
|
|
4228
|
+
...disabled("The history depth is fixed.")
|
|
4229
|
+
}, {
|
|
4230
|
+
kind: "toggle",
|
|
4231
|
+
key: "autoSizeText",
|
|
4232
|
+
label: "Auto-size text while editing",
|
|
4233
|
+
...disabled("Needs core text layout.")
|
|
4234
|
+
}]
|
|
4235
|
+
}]
|
|
4236
|
+
},
|
|
4237
|
+
{
|
|
4238
|
+
id: "ribbon",
|
|
4239
|
+
label: "Customize Ribbon",
|
|
4240
|
+
description: "Customize the Ribbon and keyboard shortcuts.",
|
|
4241
|
+
disabled: "Not available yet. The ribbon layout is fixed.",
|
|
4242
|
+
sections: []
|
|
4243
|
+
},
|
|
4244
|
+
{
|
|
4245
|
+
id: "qat",
|
|
4246
|
+
label: "Quick Access Toolbar",
|
|
4247
|
+
description: "Customize the Quick Access Toolbar.",
|
|
4248
|
+
disabled: "Not available yet. The Quick Access Toolbar shows Undo and Redo.",
|
|
4249
|
+
sections: []
|
|
4250
|
+
},
|
|
4251
|
+
{
|
|
4252
|
+
id: "add-ins",
|
|
4253
|
+
label: "Add-ins",
|
|
4254
|
+
description: "View and manage Microsoft Office Add-ins.",
|
|
4255
|
+
disabled: "Not available. Add-ins and macros never run in this viewer.",
|
|
4256
|
+
sections: []
|
|
4257
|
+
},
|
|
4258
|
+
{
|
|
4259
|
+
id: "trust",
|
|
4260
|
+
label: "Trust Center",
|
|
4261
|
+
description: "Help keep your documents safe and your computer secure and healthy.",
|
|
4262
|
+
disabled: "Not available. Documents stay local and active content is never run.",
|
|
4263
|
+
sections: []
|
|
4264
|
+
}
|
|
4265
|
+
];
|
|
4266
|
+
function profileValues(profile) {
|
|
4267
|
+
return {
|
|
4268
|
+
miniToolbar: false,
|
|
4269
|
+
livePreview: false,
|
|
4270
|
+
screenTips: "descriptions",
|
|
4271
|
+
undoLevels: 20,
|
|
4272
|
+
autoSizeText: false,
|
|
4273
|
+
userName: profile.displayName,
|
|
4274
|
+
userInitials: profile.initial ?? ""
|
|
4275
|
+
};
|
|
4276
|
+
}
|
|
4277
|
+
/** The Options dialog element. Built once and appended to the viewer's shadow root. */
|
|
4278
|
+
function createOptionsDialog(doc) {
|
|
4279
|
+
const dialog = doc.createElement("office-ui-options-dialog");
|
|
4280
|
+
dialog.setAttribute("heading", "Visio Options");
|
|
4281
|
+
dialog.categories = VISIO_OPTION_CATEGORIES;
|
|
4282
|
+
return dialog;
|
|
4283
|
+
}
|
|
4284
|
+
/**
|
|
4285
|
+
* File > Account and File > Options share one local profile. Edits in either are written to the
|
|
4286
|
+
* suite-wide profile store, mirrored into the other and reported through `onProfile`.
|
|
4287
|
+
*/
|
|
4288
|
+
var ViewerProfile = class {
|
|
4289
|
+
onProfile;
|
|
4290
|
+
#profile = readOfficeProfile();
|
|
4291
|
+
#dialog;
|
|
4292
|
+
#account;
|
|
4293
|
+
constructor(root, onProfile = () => {}) {
|
|
4294
|
+
this.onProfile = onProfile;
|
|
4295
|
+
this.#dialog = root.querySelector("office-ui-options-dialog");
|
|
4296
|
+
this.#account = root.querySelector("office-ui-account");
|
|
4297
|
+
this.#sync();
|
|
4298
|
+
}
|
|
4299
|
+
get profile() {
|
|
4300
|
+
return { ...this.#profile };
|
|
4301
|
+
}
|
|
4302
|
+
/** Open Visio Options, on General as Visio does. */
|
|
4303
|
+
showOptions(category = "general") {
|
|
4304
|
+
this.#dialog.values = profileValues(this.#profile);
|
|
4305
|
+
this.#dialog.category = category;
|
|
4306
|
+
this.#dialog.show();
|
|
4307
|
+
}
|
|
4308
|
+
wire() {
|
|
4309
|
+
const events = new ((this.#dialog.ownerDocument.defaultView?.AbortController) ?? AbortController)();
|
|
4310
|
+
const options = { signal: events.signal };
|
|
4311
|
+
this.#dialog.addEventListener("office-options-change", (event) => {
|
|
4312
|
+
const { values } = event.detail;
|
|
4313
|
+
const initial = String(values.userInitials ?? "").trim();
|
|
4314
|
+
this.#update({
|
|
4315
|
+
displayName: String(values.userName ?? "").trim(),
|
|
4316
|
+
avatarColor: this.#profile.avatarColor,
|
|
4317
|
+
...initial ? { initial } : {}
|
|
4318
|
+
});
|
|
4319
|
+
}, options);
|
|
4320
|
+
this.#account?.addEventListener("office-profile-change", (event) => this.#update(event.detail.profile), options);
|
|
4321
|
+
return () => events.abort();
|
|
4322
|
+
}
|
|
4323
|
+
#update(profile) {
|
|
4324
|
+
this.#profile = sanitizeOfficeProfile(profile);
|
|
4325
|
+
writeOfficeProfile(this.#profile);
|
|
4326
|
+
this.#sync();
|
|
4327
|
+
this.onProfile(this.profile);
|
|
4328
|
+
}
|
|
4329
|
+
#sync() {
|
|
4330
|
+
if (this.#account) this.#account.profile = this.#profile;
|
|
4331
|
+
this.#dialog.values = profileValues(this.#profile);
|
|
4332
|
+
}
|
|
4333
|
+
};
|
|
4334
|
+
//#endregion
|
|
4335
|
+
//#region src/viewer-share.ts
|
|
4336
|
+
/** Room names the core accepts: 1-64 letters, digits, hyphens or underscores. */
|
|
4337
|
+
var ROOM = /^[A-Za-z0-9_-]{1,64}$/;
|
|
4338
|
+
/**
|
|
4339
|
+
* File > Share: a live session over `ooxml-core/collab` (Yjs). Peers in other windows of this
|
|
4340
|
+
* browser share the whole VSDX package through a BroadcastChannel; remote changes arrive as one
|
|
4341
|
+
* undoable history step. The collab runtime loads only when sharing starts.
|
|
4342
|
+
*/
|
|
4343
|
+
var ViewerShare = class {
|
|
4344
|
+
controller;
|
|
4345
|
+
profile;
|
|
4346
|
+
loadCollab;
|
|
4347
|
+
#panel;
|
|
4348
|
+
#room;
|
|
4349
|
+
#start;
|
|
4350
|
+
#stop;
|
|
4351
|
+
#status;
|
|
4352
|
+
#people;
|
|
4353
|
+
#session = null;
|
|
4354
|
+
#teardown = null;
|
|
4355
|
+
#revision = 0;
|
|
4356
|
+
/** The package this window last published or adopted, to skip repeats of it. */
|
|
4357
|
+
#last = null;
|
|
4358
|
+
#adopting = false;
|
|
4359
|
+
#pending = null;
|
|
4360
|
+
#starting = false;
|
|
4361
|
+
constructor(root, controller, profile, loadCollab = () => import("ooxml-core/collab")) {
|
|
4362
|
+
this.controller = controller;
|
|
4363
|
+
this.profile = profile;
|
|
4364
|
+
this.loadCollab = loadCollab;
|
|
4365
|
+
this.#panel = root.querySelector(".share-panel");
|
|
4366
|
+
this.#room = this.#panel.querySelector(".share-room");
|
|
4367
|
+
this.#start = this.#panel.querySelector("[data-share=\"start\"]");
|
|
4368
|
+
this.#stop = this.#panel.querySelector("[data-share=\"stop\"]");
|
|
4369
|
+
this.#status = this.#panel.querySelector(".share-status");
|
|
4370
|
+
this.#people = this.#panel.querySelector("office-ui-presence");
|
|
4371
|
+
this.#room.value = `visio-${Math.random().toString(36).slice(2, 8)}`;
|
|
4372
|
+
}
|
|
4373
|
+
get active() {
|
|
4374
|
+
return this.#session !== null;
|
|
4375
|
+
}
|
|
4376
|
+
wire() {
|
|
4377
|
+
const events = new ((this.#panel.ownerDocument.defaultView?.AbortController) ?? AbortController)();
|
|
4378
|
+
this.#panel.addEventListener("click", (event) => {
|
|
4379
|
+
const action = event.target.closest?.("[data-share]");
|
|
4380
|
+
if (action?.dataset.share === "start") this.start(this.#room.value.trim());
|
|
4381
|
+
if (action?.dataset.share === "stop") this.stop();
|
|
4382
|
+
}, { signal: events.signal });
|
|
4383
|
+
return () => {
|
|
4384
|
+
events.abort();
|
|
4385
|
+
this.stop();
|
|
4386
|
+
};
|
|
4387
|
+
}
|
|
4388
|
+
async start(roomId) {
|
|
4389
|
+
if (this.#session || this.#starting) return;
|
|
4390
|
+
if (!ROOM.test(roomId)) return this.#say("Use 1 to 64 letters, digits, hyphens or underscores for the session name.");
|
|
4391
|
+
this.#starting = true;
|
|
4392
|
+
this.#render();
|
|
4393
|
+
try {
|
|
4394
|
+
const collab = await this.loadCollab();
|
|
4395
|
+
if (!collab.canBroadcast()) return this.#say("Sharing needs BroadcastChannel, which this browser does not provide.");
|
|
4396
|
+
this.#join(collab, roomId);
|
|
4397
|
+
} catch (error) {
|
|
4398
|
+
this.#say(error instanceof Error ? error.message : String(error));
|
|
4399
|
+
} finally {
|
|
4400
|
+
this.#starting = false;
|
|
4401
|
+
this.#render();
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
stop() {
|
|
4405
|
+
this.#teardown?.();
|
|
4406
|
+
this.#teardown = null;
|
|
4407
|
+
this.#session?.destroy();
|
|
4408
|
+
this.#session = null;
|
|
4409
|
+
this.#pending = null;
|
|
4410
|
+
this.#revision = 0;
|
|
4411
|
+
this.#last = null;
|
|
4412
|
+
this.#people.participants = [];
|
|
4413
|
+
this.#say("Not sharing.");
|
|
4414
|
+
this.#render();
|
|
4415
|
+
}
|
|
4416
|
+
#join(collab, roomId) {
|
|
4417
|
+
const adapter = collab.packageAdapter();
|
|
4418
|
+
const me = this.profile();
|
|
4419
|
+
const session = collab.createCollabSession({
|
|
4420
|
+
roomId: `visio-${roomId}`,
|
|
4421
|
+
provider: collab.transportProvider({ transport: collab.createBroadcastTransport({ roomId: `visio-${roomId}` }) }),
|
|
4422
|
+
user: {
|
|
4423
|
+
name: me.displayName || "Visio user",
|
|
4424
|
+
color: me.avatarColor
|
|
4425
|
+
},
|
|
4426
|
+
teardown: true
|
|
4427
|
+
});
|
|
4428
|
+
this.#session = session;
|
|
4429
|
+
const publish = () => {
|
|
4430
|
+
const bytes = this.#localBytes();
|
|
4431
|
+
if (!bytes || !session.canWrite()) return;
|
|
4432
|
+
adapter.write(session.doc, {
|
|
4433
|
+
bytes,
|
|
4434
|
+
revision: this.#revision
|
|
4435
|
+
}, collab.LOCAL_ORIGIN);
|
|
4436
|
+
this.#revision = adapter.read(session.doc).revision;
|
|
4437
|
+
this.#last = bytes;
|
|
4438
|
+
};
|
|
4439
|
+
const adopt = () => {
|
|
4440
|
+
if (adapter.isEmpty(session.doc)) return;
|
|
4441
|
+
const model = adapter.read(session.doc);
|
|
4442
|
+
if (model.revision < this.#revision) return;
|
|
4443
|
+
if (model.revision === this.#revision && same(model.bytes, this.#last)) return;
|
|
4444
|
+
this.#revision = model.revision;
|
|
4445
|
+
this.#last = model.bytes;
|
|
4446
|
+
this.#apply(model.bytes);
|
|
4447
|
+
};
|
|
4448
|
+
const map = session.doc.getMap("package");
|
|
4449
|
+
const observer = (_event, transaction) => {
|
|
4450
|
+
if (transaction.origin !== collab.LOCAL_ORIGIN) adopt();
|
|
4451
|
+
};
|
|
4452
|
+
map.observe(observer);
|
|
4453
|
+
const settle = () => adapter.isEmpty(session.doc) ? publish() : adopt();
|
|
4454
|
+
const stopReady = session.on("ready", settle);
|
|
4455
|
+
const stopPeers = session.on("peers", (peers) => this.#showPeers(peers));
|
|
4456
|
+
const stopEvents = this.controller.onEvent((name, detail) => {
|
|
4457
|
+
if (name === "document-change" && detail.kind !== "remote") publish();
|
|
4458
|
+
if (name === "document-load" && !this.#adopting) publish();
|
|
4459
|
+
});
|
|
4460
|
+
const stopState = this.controller.subscribe((state) => this.#flush(state));
|
|
4461
|
+
this.#teardown = () => {
|
|
4462
|
+
map.unobserve(observer);
|
|
4463
|
+
stopReady();
|
|
4464
|
+
stopPeers();
|
|
4465
|
+
stopEvents();
|
|
4466
|
+
stopState();
|
|
4467
|
+
};
|
|
4468
|
+
if (session.canWrite()) settle();
|
|
4469
|
+
this.#showPeers(session.peers());
|
|
4470
|
+
this.#say(`Sharing as ${me.displayName || "Visio user"} in session ${roomId}.`);
|
|
4471
|
+
}
|
|
4472
|
+
#localBytes() {
|
|
4473
|
+
const { edit, loading } = this.controller.state;
|
|
4474
|
+
if (!edit.sourceAvailable || edit.busy || loading) return null;
|
|
4475
|
+
try {
|
|
4476
|
+
return this.controller.exportVsdx().bytes;
|
|
4477
|
+
} catch {
|
|
4478
|
+
return null;
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
#apply(bytes) {
|
|
4482
|
+
const { edit, loading, document } = this.controller.state;
|
|
4483
|
+
if (edit.busy || loading) {
|
|
4484
|
+
this.#pending = bytes;
|
|
4485
|
+
return;
|
|
4486
|
+
}
|
|
4487
|
+
this.#pending = null;
|
|
4488
|
+
const done = (error) => {
|
|
4489
|
+
this.#adopting = false;
|
|
4490
|
+
if (error) this.#say(error instanceof Error ? error.message : String(error));
|
|
4491
|
+
};
|
|
4492
|
+
this.#adopting = true;
|
|
4493
|
+
(document && edit.sourceAvailable ? this.controller.applyRemoteSource(bytes) : this.controller.load(bytes)).then(() => done(), done);
|
|
4494
|
+
}
|
|
4495
|
+
#flush(state) {
|
|
4496
|
+
if (this.#pending && !state.edit.busy && !state.loading) this.#apply(this.#pending);
|
|
4497
|
+
}
|
|
4498
|
+
#showPeers(peers) {
|
|
4499
|
+
const me = this.profile();
|
|
4500
|
+
this.#people.participants = [{
|
|
4501
|
+
id: "self",
|
|
4502
|
+
name: me.displayName || "Visio user",
|
|
4503
|
+
color: me.avatarColor,
|
|
4504
|
+
self: true
|
|
4505
|
+
}, ...peers.map((peer) => ({
|
|
4506
|
+
id: String(peer.clientId),
|
|
4507
|
+
name: peer.userName,
|
|
4508
|
+
color: peer.userColor
|
|
4509
|
+
}))];
|
|
4510
|
+
}
|
|
4511
|
+
#say(message) {
|
|
4512
|
+
this.#status.textContent = message;
|
|
4513
|
+
}
|
|
4514
|
+
#render() {
|
|
4515
|
+
const active = this.active;
|
|
4516
|
+
this.#start.hidden = active;
|
|
4517
|
+
this.#stop.hidden = !active;
|
|
4518
|
+
this.#start.disabled = this.#starting;
|
|
4519
|
+
this.#room.disabled = active || this.#starting;
|
|
4520
|
+
}
|
|
4521
|
+
};
|
|
4522
|
+
function same(a, b) {
|
|
4523
|
+
if (!b || a.byteLength !== b.byteLength) return false;
|
|
4524
|
+
for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
|
|
4525
|
+
return true;
|
|
4526
|
+
}
|
|
4527
|
+
//#endregion
|
|
4218
4528
|
//#region src/edit-error.ts
|
|
4219
4529
|
/** Core error codes remain visible for a refused transaction; document content is plain text. */
|
|
4220
4530
|
function editErrorMessage(error) {
|
|
@@ -4445,12 +4755,6 @@ function wireStencil(pane, viewport, controller, announce) {
|
|
|
4445
4755
|
}
|
|
4446
4756
|
//#endregion
|
|
4447
4757
|
//#region src/viewer-ruler.ts
|
|
4448
|
-
/**
|
|
4449
|
-
* Visio's rulers: inch scales along the top and left of the drawing window with 0 at the page's
|
|
4450
|
-
* left and bottom edges (vertical values grow upward, as in Visio). Viewer-only presentation:
|
|
4451
|
-
* nothing is read from or written to the document beyond the rendered page box.
|
|
4452
|
-
*/
|
|
4453
|
-
var SIZE = 18;
|
|
4454
4758
|
/** Wraps the viewport in a grid with a corner, a top ruler and a left ruler. */
|
|
4455
4759
|
function createRulers(viewport) {
|
|
4456
4760
|
const doc = viewport.ownerDocument;
|
|
@@ -4458,79 +4762,35 @@ function createRulers(viewport) {
|
|
|
4458
4762
|
area.className = "canvas-area";
|
|
4459
4763
|
const corner = doc.createElement("div");
|
|
4460
4764
|
corner.className = "ruler-corner";
|
|
4461
|
-
|
|
4765
|
+
corner.setAttribute("aria-hidden", "true");
|
|
4766
|
+
const top = doc.createElement("office-ui-ruler");
|
|
4462
4767
|
top.className = "ruler ruler-h";
|
|
4463
|
-
const left = doc.createElement("
|
|
4768
|
+
const left = doc.createElement("office-ui-ruler");
|
|
4464
4769
|
left.className = "ruler ruler-v";
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
top,
|
|
4468
|
-
left
|
|
4469
|
-
]) ruler.setAttribute("aria-hidden", "true");
|
|
4770
|
+
left.setAttribute("orientation", "vertical");
|
|
4771
|
+
left.setAttribute("direction", "reverse");
|
|
4470
4772
|
viewport.replaceWith(area);
|
|
4471
4773
|
area.append(corner, top, left, viewport);
|
|
4472
4774
|
let visible = false;
|
|
4473
4775
|
let zoom = 1;
|
|
4474
4776
|
let frame = 0;
|
|
4475
|
-
const
|
|
4777
|
+
const place = () => {
|
|
4476
4778
|
frame = 0;
|
|
4477
4779
|
if (!visible) return;
|
|
4478
4780
|
const paper = viewport.querySelector("svg.paper");
|
|
4479
|
-
|
|
4480
|
-
const ink = (doc.defaultView?.getComputedStyle(area))?.getPropertyValue("--_vv-muted").trim() || "#605e5c";
|
|
4781
|
+
if (!paper) return;
|
|
4481
4782
|
const base = viewport.getBoundingClientRect();
|
|
4482
|
-
const page = paper
|
|
4483
|
-
const
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
canvas.height = Math.round(SIZE * ratio);
|
|
4489
|
-
if (!horizontal) [canvas.width, canvas.height] = [canvas.height, canvas.width];
|
|
4490
|
-
const context = canvas.getContext?.("2d");
|
|
4491
|
-
if (!context || !page) return;
|
|
4492
|
-
context.scale(ratio, ratio);
|
|
4493
|
-
context.strokeStyle = ink;
|
|
4494
|
-
context.fillStyle = ink;
|
|
4495
|
-
context.lineWidth = 1;
|
|
4496
|
-
context.font = "9px \"Segoe UI\", Arial, sans-serif";
|
|
4497
|
-
const origin = horizontal ? page.left - base.left : page.bottom - base.top;
|
|
4498
|
-
const step = inch / divisions;
|
|
4499
|
-
const first = Math.floor((horizontal ? -origin : origin - length) / step) - 1;
|
|
4500
|
-
const last = Math.ceil((horizontal ? length - origin : origin) / step) + 1;
|
|
4501
|
-
context.beginPath();
|
|
4502
|
-
for (let index = first; index <= last; index++) {
|
|
4503
|
-
const position = horizontal ? origin + index * step : origin - index * step;
|
|
4504
|
-
const tick = index % divisions === 0 ? SIZE : index % (divisions / 2) === 0 ? SIZE * .5 : SIZE * .3;
|
|
4505
|
-
const at = Math.round(position) + .5;
|
|
4506
|
-
if (horizontal) {
|
|
4507
|
-
context.moveTo(at, SIZE);
|
|
4508
|
-
context.lineTo(at, SIZE - tick);
|
|
4509
|
-
} else {
|
|
4510
|
-
context.moveTo(SIZE, at);
|
|
4511
|
-
context.lineTo(SIZE - tick, at);
|
|
4512
|
-
}
|
|
4513
|
-
if (index % divisions === 0) {
|
|
4514
|
-
const label = String(index / divisions);
|
|
4515
|
-
if (horizontal) context.fillText(label, at + 2, 9);
|
|
4516
|
-
else {
|
|
4517
|
-
context.save();
|
|
4518
|
-
context.translate(9, at - 2);
|
|
4519
|
-
context.rotate(-Math.PI / 2);
|
|
4520
|
-
context.fillText(label, 0, 0);
|
|
4521
|
-
context.restore();
|
|
4522
|
-
}
|
|
4523
|
-
}
|
|
4524
|
-
}
|
|
4525
|
-
context.stroke();
|
|
4526
|
-
};
|
|
4527
|
-
paint(top, true);
|
|
4528
|
-
paint(left, false);
|
|
4783
|
+
const page = paper.getBoundingClientRect();
|
|
4784
|
+
const scale = String(96 * zoom);
|
|
4785
|
+
top.setAttribute("scale", scale);
|
|
4786
|
+
left.setAttribute("scale", scale);
|
|
4787
|
+
top.setAttribute("origin", String(page.left - base.left));
|
|
4788
|
+
left.setAttribute("origin", String(page.bottom - base.top));
|
|
4529
4789
|
};
|
|
4530
4790
|
const schedule = () => {
|
|
4531
4791
|
if (!visible || frame) return;
|
|
4532
|
-
frame = doc.defaultView?.requestAnimationFrame?.(
|
|
4533
|
-
if (!frame)
|
|
4792
|
+
frame = doc.defaultView?.requestAnimationFrame?.(place) ?? 0;
|
|
4793
|
+
if (!frame) place();
|
|
4534
4794
|
};
|
|
4535
4795
|
return {
|
|
4536
4796
|
element: area,
|
|
@@ -4914,8 +5174,6 @@ var ViewerChrome = class {
|
|
|
4914
5174
|
this.#root.addEventListener("click", (event) => {
|
|
4915
5175
|
const button = event.target?.closest?.("button");
|
|
4916
5176
|
if (!button || button.disabled) return;
|
|
4917
|
-
const tab = button.dataset.tab;
|
|
4918
|
-
if (tab) this.showTab(tab);
|
|
4919
5177
|
const action = button.dataset.chrome;
|
|
4920
5178
|
if (action === "inspector" || action === "shapes") this.togglePane(action);
|
|
4921
5179
|
if (action === "notes") this.reveal("notes");
|
|
@@ -4931,33 +5189,13 @@ var ViewerChrome = class {
|
|
|
4931
5189
|
event.stopPropagation();
|
|
4932
5190
|
this.#controller.setPage(Number(index));
|
|
4933
5191
|
}, options);
|
|
4934
|
-
this.#root.addEventListener("
|
|
4935
|
-
const key = event;
|
|
4936
|
-
const button = event.target?.closest?.("button");
|
|
4937
|
-
if (!button?.dataset.tab || ![
|
|
4938
|
-
"ArrowLeft",
|
|
4939
|
-
"ArrowRight",
|
|
4940
|
-
"Home",
|
|
4941
|
-
"End"
|
|
4942
|
-
].includes(key.key)) return;
|
|
4943
|
-
key.preventDefault();
|
|
4944
|
-
const tabs = [...this.#root.querySelectorAll("[data-tab]")];
|
|
4945
|
-
const index = tabs.indexOf(button);
|
|
4946
|
-
const next = key.key === "Home" ? tabs[0] : key.key === "End" ? tabs.at(-1) : tabs[(index + (key.key === "ArrowRight" ? 1 : -1) + tabs.length) % tabs.length];
|
|
4947
|
-
if (!next?.dataset.tab) return;
|
|
4948
|
-
this.showTab(next.dataset.tab);
|
|
4949
|
-
next.focus();
|
|
4950
|
-
}, options);
|
|
5192
|
+
this.#root.addEventListener("office-ribbon-select", () => this.closeCompactTools(), options);
|
|
4951
5193
|
this.#notes.addEventListener("toggle", () => this.#syncNotes(), options);
|
|
4952
5194
|
return () => events.abort();
|
|
4953
5195
|
}
|
|
4954
5196
|
showTab(tab) {
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
button.setAttribute("aria-selected", String(selected));
|
|
4958
|
-
button.tabIndex = selected ? 0 : -1;
|
|
4959
|
-
}
|
|
4960
|
-
for (const panel of this.#root.querySelectorAll(".ribbon-content")) panel.hidden = panel.id !== `${tab}-panel`;
|
|
5197
|
+
const ribbon = this.#root.querySelector("office-ui-ribbon");
|
|
5198
|
+
if (ribbon) ribbon.selected = tab;
|
|
4961
5199
|
}
|
|
4962
5200
|
/** On phones, close the Tools sheet once a command runs. */
|
|
4963
5201
|
closeCompactTools() {
|
|
@@ -6204,6 +6442,7 @@ var VISIO_ICONS = {
|
|
|
6204
6442
|
function registerViewerControls(registry) {
|
|
6205
6443
|
for (const [name, path] of Object.entries(VISIO_ICONS)) if (!getIcon(name)) registerIcon(name, path);
|
|
6206
6444
|
registerControls(registry);
|
|
6445
|
+
definePresence(registry);
|
|
6207
6446
|
}
|
|
6208
6447
|
//#endregion
|
|
6209
6448
|
//#region src/export-svg.ts
|
|
@@ -6823,6 +7062,8 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
6823
7062
|
#rulers;
|
|
6824
7063
|
#canvas;
|
|
6825
7064
|
#panZoom;
|
|
7065
|
+
#profile;
|
|
7066
|
+
#share;
|
|
6826
7067
|
#backstage;
|
|
6827
7068
|
#fileName = "";
|
|
6828
7069
|
#loadToken = 0;
|
|
@@ -6831,7 +7072,6 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
6831
7072
|
#diagnostics;
|
|
6832
7073
|
#toolbar;
|
|
6833
7074
|
#chrome;
|
|
6834
|
-
#search;
|
|
6835
7075
|
#edit;
|
|
6836
7076
|
#layers;
|
|
6837
7077
|
#notes;
|
|
@@ -6857,7 +7097,7 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
6857
7097
|
workspace.before(ribbon, this.#findBar);
|
|
6858
7098
|
workspace.prepend(createShapesStrip(document), createShapesWindow(document));
|
|
6859
7099
|
workspace.append(createPanZoom(document));
|
|
6860
|
-
this.#root.append(createBackstage(document), ...createContextMenus(document));
|
|
7100
|
+
this.#root.append(createBackstage(document), createOptionsDialog(document), ...createContextMenus(document));
|
|
6861
7101
|
workspace.after(createPageTabs(document), createStatusBar(document));
|
|
6862
7102
|
this.#viewport = this.#root.querySelector(".viewport");
|
|
6863
7103
|
this.#rulers = createRulers(this.#viewport);
|
|
@@ -6866,7 +7106,6 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
6866
7106
|
this.#status = this.#root.querySelector("[data-status]");
|
|
6867
7107
|
this.#diagnostics = this.#root.querySelector("[data-diagnostics]");
|
|
6868
7108
|
this.#toolbar = this.#root.querySelector(".toolbar");
|
|
6869
|
-
this.#search = searchControls(this.#root);
|
|
6870
7109
|
this.#edit = new ViewerEditControls(this.#root, this.controller);
|
|
6871
7110
|
this.#layers = this.#root.querySelector(".layer-controls");
|
|
6872
7111
|
this.#notes = this.#root.querySelector(".notes ul");
|
|
@@ -6884,15 +7123,15 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
6884
7123
|
togglePanZoom: () => this.#panZoom.toggle(),
|
|
6885
7124
|
focusSearch: () => {
|
|
6886
7125
|
this.#chrome.closeCompactTools();
|
|
6887
|
-
this.#findBar.
|
|
6888
|
-
this.#search.input.focus();
|
|
6889
|
-
this.#search.input.select();
|
|
7126
|
+
this.#findBar.show();
|
|
6890
7127
|
},
|
|
6891
7128
|
announce: (message) => {
|
|
6892
7129
|
this.#announcement = message;
|
|
6893
7130
|
this.#status.textContent = message;
|
|
6894
7131
|
}
|
|
6895
7132
|
});
|
|
7133
|
+
this.#profile = new ViewerProfile(this.#root);
|
|
7134
|
+
this.#share = new ViewerShare(this.#root, this.controller, () => this.#profile.profile);
|
|
6896
7135
|
this.#backstage = new ViewerBackstage({
|
|
6897
7136
|
root: this.#root,
|
|
6898
7137
|
viewport: this.#viewport,
|
|
@@ -6905,6 +7144,7 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
6905
7144
|
this.controller.setDocument(null);
|
|
6906
7145
|
},
|
|
6907
7146
|
revealNotes: () => this.#chrome.reveal("notes"),
|
|
7147
|
+
showOptions: () => this.#profile.showOptions(),
|
|
6908
7148
|
announce: (message) => {
|
|
6909
7149
|
this.#announcement = message;
|
|
6910
7150
|
this.#status.textContent = message;
|
|
@@ -7082,19 +7322,19 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
7082
7322
|
const disposeTellMe = wireTellMe(this.#root);
|
|
7083
7323
|
const keyTips = attachKeyTips(this.#root);
|
|
7084
7324
|
const disposePanZoom = this.#panZoom.wire();
|
|
7325
|
+
const disposeProfile = this.#profile.wire();
|
|
7326
|
+
const disposeShare = this.#share.wire();
|
|
7085
7327
|
const disposeRulers = this.#rulers.wire();
|
|
7086
7328
|
const disposeStencil = wireStencil(this.#root.querySelector(".shapes-pane"), this.#viewport, this.controller, (message) => {
|
|
7087
7329
|
this.#announcement = message;
|
|
7088
7330
|
this.#status.textContent = message;
|
|
7089
7331
|
});
|
|
7090
|
-
const disposeFind = wireFindBar(this.#findBar, this
|
|
7332
|
+
const disposeFind = wireFindBar(this.#findBar, this.controller, () => this.#viewport.focus({ preventScroll: true }));
|
|
7091
7333
|
const disposeEdit = this.#edit.wire();
|
|
7092
7334
|
const disposeLayers = wireLayerControls(this.#layers, this.controller);
|
|
7093
7335
|
const disposeInputs = wireViewerInputs({
|
|
7094
7336
|
viewport: this.#viewport,
|
|
7095
|
-
|
|
7096
|
-
zoomSlider: this.#zoomSlider,
|
|
7097
|
-
searchInput: this.#search.input
|
|
7337
|
+
zoomSlider: this.#zoomSlider
|
|
7098
7338
|
}, this.controller, (mode) => this.#fit(mode));
|
|
7099
7339
|
return () => {
|
|
7100
7340
|
disposeChrome();
|
|
@@ -7104,6 +7344,8 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
7104
7344
|
disposeTellMe();
|
|
7105
7345
|
keyTips.dispose();
|
|
7106
7346
|
disposePanZoom();
|
|
7347
|
+
disposeProfile();
|
|
7348
|
+
disposeShare();
|
|
7107
7349
|
disposeRulers();
|
|
7108
7350
|
disposeStencil();
|
|
7109
7351
|
disposeFind();
|
|
@@ -7126,7 +7368,7 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
7126
7368
|
const inches = (value) => `${+value.toFixed(3)} in`;
|
|
7127
7369
|
this.#shapeStatus.setAttribute("value", inspected ? `Width: ${inches(inspected.width)} Height: ${inches(inspected.height)}` : "");
|
|
7128
7370
|
for (const button of this.#root.querySelectorAll("[data-action]")) button.disabled = !page;
|
|
7129
|
-
|
|
7371
|
+
renderFindBar(this.#findBar, state);
|
|
7130
7372
|
this.#edit.render(state);
|
|
7131
7373
|
if (changed) renderLayerControls(this.#layers, state);
|
|
7132
7374
|
else this.#layers.querySelector("[data-layer-reset=\"all\"]").disabled = state.layerVisibilityOverrides.length === 0;
|