main
html 734 lines 19.5 KB
Raw
1 <html>
2 <head>
3 <script type="module">
4 import { store } from "/plugins/_desktop/webui/desktop-store.js";
5 </script>
6 </head>
7 <body>
8 <div x-data>
9 <template x-if="$store.desktop">
10 <div class="office-panel" x-create="$store.desktop.onMount($el, xAttrs($el) || {})" x-destroy="$store.desktop.cleanup()">
11 <div class="office-shell">
12 <div class="office-document-header" x-show="$store.desktop.hasActiveFile()" style="display: none;">
13 <div class="office-document-title" :title="$store.desktop.tabLabel($store.desktop.session)">
14 <x-icon class="office-document-icon" aria-hidden="true" :name="$store.desktop.tabIcon($store.desktop.session)"></x-icon>
15 <span class="office-document-name" x-text="$store.desktop.tabTitle($store.desktop.session)"></span>
16 <span class="office-document-dirty" x-show="$store.desktop.dirty" aria-hidden="true">*</span>
17 </div>
18
19 <button
20 type="button"
21 class="office-icon-button office-document-save-button"
22 title="Save"
23 aria-label="Save"
24 :class="{ 'is-primary': $store.desktop.dirty }"
25 :disabled="$store.desktop.saving"
26 @click="$store.desktop.save()"
27 >
28 <x-icon :class="{ spinning: $store.desktop.saving }" :name="$store.desktop.saving ? 'progress_activity' : 'save'"></x-icon>
29 </button>
30
31 <div class="office-file-actions" x-data="{ open: false }" @click.outside="open = false" @keydown.escape.window="open = false">
32 <button
33 type="button"
34 class="office-icon-button office-file-menu-button"
35 title="File actions"
36 aria-label="File actions"
37 aria-haspopup="menu"
38 :aria-expanded="open.toString()"
39 :disabled="$store.desktop.saving"
40 @click.stop="open = !open"
41 >
42 <x-icon name="more_vert"></x-icon>
43 </button>
44 <div class="office-new-menu office-file-menu" role="menu" x-show="open" @click.stop>
45 <button type="button" class="office-new-menu-item" role="menuitem" :disabled="$store.desktop.saving" @click="open = false; $store.desktop.renameActiveFile()">
46 <x-icon aria-hidden="true" name="edit"></x-icon>
47 <span>Rename</span>
48 </button>
49 <button type="button" class="office-new-menu-item" role="menuitem" :disabled="$store.desktop.loading" @click="open = false; $store.desktop.closeActiveFile()">
50 <x-icon aria-hidden="true" name="close"></x-icon>
51 <span>Close File</span>
52 </button>
53 </div>
54 </div>
55 </div>
56
57 <div class="office-state-line" x-show="$store.desktop.message || $store.desktop.error || $store.desktop.loading" style="display: none;">
58 <x-icon :class="{ spinning: $store.desktop.loading }" :name="$store.desktop.loading ? 'progress_activity' : ($store.desktop.error ? 'error' : 'check_circle')"></x-icon>
59 <span x-text="$store.desktop.error || $store.desktop.message || 'Working'"></span>
60 </div>
61
62 <div class="office-body">
63 <div class="office-editor-wrap" x-show="$store.desktop.session" style="display: none;">
64 <div class="office-editor-scroll" :class="{ 'is-desktop': $store.desktop.hasOfficialOffice() }" @click.self="$store.desktop.focusEditor()">
65 <template x-if="$store.desktop.hasOfficialOffice()">
66 <div
67 class="office-desktop-wrap"
68 data-office-desktop-host
69 x-init="$nextTick(() => $store.desktop.mountDesktopFrameHost($el))"
70 >
71 </div>
72 </template>
73 </div>
74 </div>
75
76 <div class="office-desktop-empty" x-show="$store.desktop.shouldShowDesktopEmptyState()" style="display: none;">
77 <x-icon aria-hidden="true" name="power_settings_new"></x-icon>
78 <span class="office-desktop-empty-title">Desktop is shut down</span>
79 <button type="button" class="office-icon-button office-command-button" @click="$store.desktop.restartDesktopSession()">
80 <x-icon aria-hidden="true" name="restart_alt"></x-icon>
81 <span class="office-button-label">Restart Desktop</span>
82 </button>
83 </div>
84 </div>
85 </div>
86 </div>
87 </template>
88 </div>
89
90 <style>
91 .office-panel,
92 .office-shell {
93 display: flex;
94 flex: 1 1 auto;
95 flex-direction: column;
96 width: 100%;
97 height: 100%;
98 min-width: 0;
99 min-height: 0;
100 background: var(--color-background);
101 color: var(--color-text);
102 }
103
104 .office-panel {
105 container-type: inline-size;
106 }
107
108 .modal-inner.office-modal {
109 box-sizing: border-box;
110 width: min(1120px, calc(100vw - 32px));
111 height: min(820px, calc(100vh - 32px));
112 min-width: min(720px, calc(100vw - 16px));
113 min-height: min(520px, calc(100vh - 16px));
114 max-width: none;
115 max-height: none;
116 resize: none;
117 overflow: hidden;
118 will-change: width, height, left, top;
119 }
120
121 .modal-inner.office-modal.is-resizing,
122 .modal-inner.office-modal.is-dragging {
123 user-select: none;
124 }
125
126 .modal-inner.office-modal.is-focus-mode {
127 border-radius: 6px;
128 }
129
130 .modal-inner.office-modal .modal-scroll {
131 display: flex;
132 flex: 1 1 auto;
133 min-height: 0;
134 max-height: none;
135 overflow: hidden;
136 padding: 0;
137 }
138
139 .modal-inner.office-modal .modal-header {
140 grid-template-columns: minmax(0, 1fr) auto auto;
141 }
142
143 .office-modal-input-shield {
144 position: absolute;
145 inset: 42px 0 0 0;
146 z-index: 4;
147 display: none;
148 background: transparent;
149 }
150
151 .office-modal-resizer {
152 position: absolute;
153 z-index: 5;
154 display: block;
155 touch-action: none;
156 }
157
158 .office-modal-resizer.is-right {
159 top: 42px;
160 right: -4px;
161 bottom: 12px;
162 width: 10px;
163 cursor: ew-resize;
164 }
165
166 .office-modal-resizer.is-bottom {
167 right: 12px;
168 bottom: -4px;
169 left: 0;
170 height: 10px;
171 cursor: ns-resize;
172 }
173
174 .office-modal-resizer.is-corner {
175 right: 0;
176 bottom: 0;
177 width: 22px;
178 height: 22px;
179 cursor: nwse-resize;
180 }
181
182 .office-modal-resizer.is-corner::after {
183 content: "";
184 position: absolute;
185 right: 6px;
186 bottom: 6px;
187 width: 9px;
188 height: 9px;
189 border-right: 2px solid color-mix(in srgb, var(--color-text) 42%, transparent);
190 border-bottom: 2px solid color-mix(in srgb, var(--color-text) 42%, transparent);
191 border-radius: 1px;
192 }
193
194 .modal-inner.office-modal.is-focus-mode .office-modal-resizer {
195 display: none;
196 }
197
198 .modal-inner.office-modal .modal-bd.office-modal-body,
199 .modal-inner.office-modal .modal-bd.office-modal-body > x-component,
200 .modal-inner.office-modal .modal-bd.office-modal-body > x-component > div[x-data],
201 .modal-inner.office-modal .modal-bd.office-modal-body > x-component > .office-panel,
202 .modal-inner.office-modal .modal-bd.office-modal-body > x-component > div[x-data] > .office-panel {
203 display: flex;
204 flex: 1 1 auto;
205 min-height: 0;
206 min-width: 0;
207 width: 100%;
208 height: 100%;
209 padding: 0;
210 }
211
212 .office-toolbar {
213 display: flex;
214 flex-direction: row;
215 align-items: stretch;
216 flex-wrap: nowrap;
217 min-height: 0;
218 padding: 6px 10px;
219 overflow: hidden;
220 border-bottom: 1px solid color-mix(in srgb, var(--color-border), transparent 20%);
221 background: color-mix(in srgb, var(--color-background), var(--color-panel) 48%);
222 }
223
224 .office-toolbar-row {
225 display: flex;
226 align-items: center;
227 flex: 0 0 auto;
228 flex-wrap: nowrap;
229 gap: 6px;
230 width: 100%;
231 min-width: 0;
232 min-height: 32px;
233 overflow-x: auto;
234 overflow-y: hidden;
235 scrollbar-width: thin;
236 }
237
238 .office-tool-group {
239 display: flex;
240 align-items: center;
241 flex-wrap: nowrap;
242 flex: 0 0 auto;
243 gap: 4px;
244 min-width: 0;
245 }
246
247 .office-editor-tools {
248 gap: 3px;
249 }
250
251 .office-tool-actions {
252 justify-content: flex-end;
253 }
254
255 .office-toolbar-spacer {
256 flex: 1 1 16px;
257 min-width: 8px;
258 }
259
260 .office-toolbar-divider {
261 flex: 0 0 auto;
262 width: 1px;
263 height: 22px;
264 margin-inline: 2px;
265 background: color-mix(in srgb, var(--color-border), transparent 18%);
266 }
267
268 .office-document-header {
269 display: flex;
270 align-items: center;
271 gap: 10px;
272 min-height: 38px;
273 padding: 5px 10px 5px 12px;
274 border-bottom: 1px solid color-mix(in srgb, var(--color-border), transparent 22%);
275 background: color-mix(in srgb, var(--color-panel), var(--color-background) 30%);
276 }
277
278 .office-document-title {
279 display: flex;
280 align-items: center;
281 gap: 7px;
282 min-width: 0;
283 flex: 1 1 auto;
284 color: var(--color-text);
285 }
286
287 .office-document-icon {
288 flex: 0 0 auto;
289 font-size: 19px;
290 line-height: 1;
291 }
292
293 .office-document-name {
294 min-width: 0;
295 overflow: hidden;
296 text-overflow: ellipsis;
297 white-space: nowrap;
298 font-size: 13px;
299 font-weight: 750;
300 letter-spacing: 0;
301 line-height: 1.2;
302 }
303
304 .office-document-dirty {
305 flex: 0 0 auto;
306 color: #2ca58d;
307 font-size: 14px;
308 font-weight: 800;
309 line-height: 1;
310 }
311
312 .office-file-actions {
313 position: relative;
314 display: inline-flex;
315 align-items: center;
316 flex: 0 0 auto;
317 }
318
319 .office-document-save-button,
320 .office-file-menu-button {
321 width: 30px;
322 height: 30px;
323 min-width: 30px;
324 }
325
326 .office-header-actions {
327 position: relative;
328 display: inline-flex;
329 align-items: center;
330 flex: 0 0 auto;
331 }
332
333 .office-header-new-button {
334 appearance: none;
335 display: inline-flex;
336 align-items: center;
337 justify-content: center;
338 gap: 4px;
339 height: 34px;
340 min-height: 34px;
341 padding: 0 9px 0 8px;
342 border: 1px solid transparent;
343 border-radius: 7px;
344 background: transparent;
345 color: var(--color-text);
346 cursor: pointer;
347 font: inherit;
348 font-size: 12px;
349 font-weight: 750;
350 letter-spacing: 0;
351 line-height: 1;
352 opacity: 0.82;
353 white-space: nowrap;
354 transition: background-color 0.16s ease, border-color 0.16s ease, opacity 0.16s ease;
355 }
356
357 .office-header-new-button:hover,
358 .office-header-actions.is-open .office-header-new-button {
359 opacity: 1;
360 border-color: color-mix(in srgb, var(--color-primary) 28%, var(--color-border));
361 background: color-mix(in srgb, var(--color-background-hover) 72%, transparent);
362 }
363
364 .office-header-new-button .material-symbols-outlined {
365 font-size: 18px;
366 line-height: 1;
367 }
368
369 .office-header-new-button .office-new-chevron {
370 margin-left: -2px;
371 font-size: 16px;
372 opacity: 0.8;
373 }
374
375 .office-new-menu {
376 position: absolute;
377 top: calc(100% + 6px);
378 right: 0;
379 z-index: 100;
380 min-width: 184px;
381 padding: 5px;
382 border: 1px solid color-mix(in srgb, var(--color-border), transparent 10%);
383 border-radius: 8px;
384 background: color-mix(in srgb, var(--color-panel), var(--color-background) 10%);
385 box-shadow: 0 14px 34px rgba(0, 0, 0, 0.34);
386 }
387
388 .right-canvas-desktop-actions .office-new-menu {
389 z-index: 4000;
390 }
391
392 .office-new-menu[hidden] {
393 display: none;
394 }
395
396 .office-new-menu-item {
397 appearance: none;
398 display: flex;
399 align-items: center;
400 gap: 8px;
401 width: 100%;
402 height: 32px;
403 padding: 0 8px;
404 border: 1px solid transparent;
405 border-radius: 6px;
406 background: transparent;
407 color: var(--color-text);
408 cursor: pointer;
409 font: inherit;
410 font-size: 12px;
411 font-weight: 650;
412 letter-spacing: 0;
413 line-height: 1;
414 text-align: left;
415 white-space: nowrap;
416 }
417
418 .office-new-menu-item:hover {
419 border-color: color-mix(in srgb, var(--color-primary) 22%, transparent);
420 background: color-mix(in srgb, var(--color-background-hover) 76%, transparent);
421 }
422
423 .office-new-menu-item:disabled {
424 cursor: default;
425 opacity: 0.46;
426 }
427
428 .office-new-menu-item .material-symbols-outlined {
429 flex: 0 0 auto;
430 width: 18px;
431 font-size: 18px;
432 line-height: 1;
433 text-align: center;
434 }
435
436 .office-icon-button,
437 .office-tab,
438 .office-tab-close {
439 border: 1px solid color-mix(in srgb, var(--color-border), transparent 12%);
440 border-radius: 8px;
441 background: color-mix(in srgb, var(--color-panel), var(--color-background) 16%);
442 color: inherit;
443 transition: border-color 120ms ease, background 120ms ease, transform 120ms ease;
444 }
445
446 .office-icon-button {
447 display: inline-grid;
448 place-items: center;
449 width: 32px;
450 height: 32px;
451 min-width: 32px;
452 padding: 0;
453 }
454
455 .office-command-button {
456 display: inline-flex;
457 align-items: center;
458 justify-content: center;
459 gap: 5px;
460 width: auto;
461 max-width: 126px;
462 padding: 0 8px;
463 white-space: nowrap;
464 }
465
466 .office-command-button .office-button-label {
467 min-width: 0;
468 overflow: hidden;
469 text-overflow: ellipsis;
470 font-size: 11px;
471 font-weight: 700;
472 line-height: 1;
473 }
474
475 .office-icon-button.is-primary {
476 border-color: color-mix(in srgb, #2c7be5, var(--color-border) 20%);
477 background: color-mix(in srgb, #2c7be5, var(--color-panel) 82%);
478 }
479
480 .office-icon-button.is-active {
481 border-color: color-mix(in srgb, #2ca58d, var(--color-border) 24%);
482 background: color-mix(in srgb, #2ca58d, var(--color-panel) 84%);
483 }
484
485 .office-icon-button:hover:not(:disabled),
486 .office-tab:hover,
487 .office-tab-close:hover {
488 border-color: color-mix(in srgb, #2c7be5, var(--color-border) 45%);
489 background: color-mix(in srgb, var(--color-panel), #2c7be5 8%);
490 }
491
492 .office-icon-button:disabled {
493 cursor: default;
494 opacity: 0.42;
495 }
496
497 .office-icon-button .material-symbols-outlined,
498 .office-tab-icon {
499 font-size: 19px;
500 line-height: 1;
501 }
502
503 .office-tabs {
504 display: flex;
505 gap: 6px;
506 min-height: 42px;
507 padding: 7px 10px;
508 overflow-x: auto;
509 border-bottom: 1px solid color-mix(in srgb, var(--color-border), transparent 22%);
510 background: color-mix(in srgb, var(--color-panel), var(--color-background) 28%);
511 }
512
513 .office-tab-shell {
514 display: grid;
515 grid-template-columns: minmax(0, 1fr) 28px;
516 align-items: center;
517 min-width: 150px;
518 max-width: 240px;
519 }
520
521 .office-tab-shell.is-system {
522 grid-template-columns: minmax(0, 1fr);
523 min-width: 172px;
524 }
525
526 .office-tab,
527 .office-tab-close {
528 height: 28px;
529 min-height: 28px;
530 border-radius: 7px;
531 }
532
533 .office-tab {
534 display: flex;
535 align-items: center;
536 gap: 6px;
537 min-width: 0;
538 border-top-right-radius: 0;
539 border-bottom-right-radius: 0;
540 padding: 0 8px;
541 text-align: left;
542 }
543
544 .office-tab-shell.is-system .office-tab {
545 border-radius: 7px;
546 }
547
548 .office-tab-close {
549 display: grid;
550 place-items: center;
551 border-left: 0;
552 border-top-left-radius: 0;
553 border-bottom-left-radius: 0;
554 padding: 0;
555 }
556
557 .office-tab-close .material-symbols-outlined {
558 font-size: 17px;
559 }
560
561 .office-tab-shell.is-active .office-tab,
562 .office-tab-shell.is-active .office-tab-close {
563 border-color: color-mix(in srgb, #2c7be5, var(--color-border) 36%);
564 background: color-mix(in srgb, #2c7be5, var(--color-panel) 88%);
565 }
566
567 .office-tab-shell.is-dirty .office-tab-title::after {
568 content: " *";
569 color: #2ca58d;
570 }
571
572 .office-tab-title {
573 min-width: 0;
574 overflow: hidden;
575 text-overflow: ellipsis;
576 white-space: nowrap;
577 font-size: 12px;
578 line-height: 1;
579 }
580
581 .office-state-line {
582 display: flex;
583 align-items: center;
584 flex: 0 0 auto;
585 gap: 8px;
586 width: 100%;
587 min-width: 0;
588 min-height: 34px;
589 box-sizing: border-box;
590 padding: 6px 12px;
591 border-bottom: 1px solid color-mix(in srgb, var(--color-border), transparent 28%);
592 color: var(--color-text-secondary);
593 font-size: 12px;
594 }
595
596 .office-state-line > .material-symbols-outlined {
597 flex: 0 0 auto;
598 }
599
600 .office-state-line > span:not(.material-symbols-outlined) {
601 min-width: 0;
602 overflow: hidden;
603 text-overflow: ellipsis;
604 white-space: nowrap;
605 }
606
607 .office-body {
608 position: relative;
609 display: flex;
610 flex: 1 1 auto;
611 min-height: 0;
612 overflow: hidden;
613 background: var(--color-background);
614 }
615
616 .office-body.is-source {
617 background: transparent;
618 }
619
620 .office-editor-wrap {
621 display: flex;
622 flex: 1 1 auto;
623 flex-direction: column;
624 min-width: 0;
625 min-height: 0;
626 }
627
628 .office-editor-scroll {
629 flex: 1 1 auto;
630 min-height: 0;
631 overflow: auto;
632 padding: 30px 24px;
633 }
634
635 .office-editor-scroll.is-source {
636 display: flex;
637 overflow: hidden;
638 padding: var(--spacing-md);
639 background: transparent;
640 }
641
642 .office-editor-scroll.is-desktop {
643 display: flex;
644 overflow: hidden;
645 padding: 0;
646 background: #1f2329;
647 }
648
649 .office-desktop-wrap {
650 display: flex;
651 flex: 1 1 auto;
652 width: 100%;
653 height: 100%;
654 min-width: 0;
655 min-height: 0;
656 aspect-ratio: auto;
657 background: #1f2329;
658 }
659
660 .office-desktop-empty {
661 display: grid;
662 flex: 1 1 auto;
663 place-items: center;
664 align-content: center;
665 gap: 12px;
666 min-width: 0;
667 min-height: 0;
668 padding: 24px;
669 color: var(--color-text-secondary);
670 text-align: center;
671 }
672
673 .office-desktop-empty > .material-symbols-outlined {
674 font-size: 32px;
675 color: color-mix(in srgb, var(--color-text) 68%, transparent);
676 }
677
678 .office-desktop-empty-title {
679 font-size: 13px;
680 font-weight: 700;
681 }
682
683 .office-desktop-frame {
684 flex: 1 1 auto;
685 width: 100%;
686 height: 100%;
687 min-height: 0;
688 aspect-ratio: auto;
689 border: 0;
690 background: #20242a;
691 }
692
693 .office-panel .spinning {
694 animation: office-spin 0.8s linear infinite;
695 }
696
697 @keyframes office-spin {
698 to { transform: rotate(360deg); }
699 }
700
701 @container (max-width: 680px) {
702 .office-toolbar {
703 padding-inline: 8px;
704 }
705
706 .office-toolbar-row {
707 gap: 5px;
708 }
709
710 .office-command-button {
711 width: 32px;
712 max-width: 32px;
713 padding-inline: 0;
714 }
715
716 .office-command-button .office-button-label {
717 position: absolute;
718 width: 1px;
719 height: 1px;
720 overflow: hidden;
721 clip: rect(0 0 0 0);
722 white-space: nowrap;
723 }
724
725 }
726
727 /* While the canvas is being resized, the left-edge drag sweeps the cursor across this
728 iframe, which captures the pointermove and freezes shrinking (widening drags away, so
729 it works). Disable hit-testing on the frame during the drag right-canvas-store.js
730 toggles `right-canvas-resizing` on <body> for the duration of the drag. */
731 body.right-canvas-resizing .office-desktop-frame { pointer-events: none; }
732 </style>
733 </body>
734 </html>