Skip to content

Commit a74f1e5

Browse files
authored
Init status bar & about modal (#54)
- Replace dark/light mode toggle via a button in the status bar - Add about modal - Move status text to its own line in the status bar ## Off-topic - Fix localhost links in manifest - Move `manifest.prod.xml` to `web/prod/` folder since PowerPoint picks that up otherwise.
1 parent d8a9857 commit a74f1e5

10 files changed

Lines changed: 325 additions & 92 deletions

File tree

web/manifest.xml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
<DefaultLocale>en-US</DefaultLocale>
1313
<DisplayName DefaultValue="Typst for PowerPoint" />
1414
<Description DefaultValue="Create and edit Typst equations in PowerPoint." />
15-
<IconUrl DefaultValue="https://localhost:3155/icon-32.png" />
16-
<HighResolutionIconUrl DefaultValue="https://localhost:3155/icon-80.png" />
15+
<IconUrl DefaultValue="https://localhost:3155/typst-powerpoint/icon-32.png" />
16+
<HighResolutionIconUrl DefaultValue="https://localhost:3155/typst-powerpoint/icon-80.png" />
1717
<SupportUrl DefaultValue="https://github.com/Splines/typst-powerpoint/issues" />
1818

1919
<Hosts>
@@ -27,7 +27,7 @@
2727
</Requirements>
2828

2929
<DefaultSettings>
30-
<SourceLocation DefaultValue="https://localhost:3155/powerpoint.html" />
30+
<SourceLocation DefaultValue="https://localhost:3155/typst-powerpoint/powerpoint.html" />
3131
</DefaultSettings>
3232

3333
<Permissions>ReadWriteDocument</Permissions>
@@ -101,13 +101,13 @@
101101

102102
<Resources>
103103
<bt:Images>
104-
<bt:Image id="Icon.16x16" DefaultValue="https://localhost:3155/icon-16.png" />
105-
<bt:Image id="Icon.32x32" DefaultValue="https://localhost:3155/icon-32.png" />
106-
<bt:Image id="Icon.80x80" DefaultValue="https://localhost:3155/icon-80.png" />
104+
<bt:Image id="Icon.16x16" DefaultValue="https://localhost:3155/typst-powerpoint/icon-16.png" />
105+
<bt:Image id="Icon.32x32" DefaultValue="https://localhost:3155/typst-powerpoint/icon-32.png" />
106+
<bt:Image id="Icon.80x80" DefaultValue="https://localhost:3155/typst-powerpoint/icon-80.png" />
107107
</bt:Images>
108108
<bt:Urls>
109109
<bt:Url id="GetStarted.LearnMoreUrl" DefaultValue="https://github.com/Splines/typst-powerpoint" />
110-
<bt:Url id="Taskpane.Url" DefaultValue="https://localhost:3155/powerpoint.html" />
110+
<bt:Url id="Taskpane.Url" DefaultValue="https://localhost:3155/typst-powerpoint/powerpoint.html" />
111111
</bt:Urls>
112112
<bt:ShortStrings>
113113
<bt:String id="GetStarted.Title" DefaultValue="Get started with Typst!" />

web/powerpoint.html

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<link href="data:image/x-icon;," rel="icon">
88
<link href="styles/main.css" rel="stylesheet">
99
<link href="styles/file-dropzone.css" rel="stylesheet">
10+
<link href="styles/status-bar.css" rel="stylesheet">
1011
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>
1112
</head>
1213

@@ -49,14 +50,6 @@
4950

5051
<button id="insertBtn" disabled></button>
5152
<button id="bulkUpdateBtn" style="display: none;">Update font size</button>
52-
<div id="status" class="status"></div>
53-
54-
<div id="themeToggle">
55-
<label>
56-
<input id="darkModeToggle" type="checkbox" alt="Toggle dark mode" checked>
57-
<span class="slider"></span>
58-
</label>
59-
</div>
6053

6154
<div class="file-dropzone">
6255
<div class="dropzone-container">
@@ -77,6 +70,107 @@
7770
</div>
7871
</div>
7972

73+
<!-- Status Bar -->
74+
<div id="statusBar" class="status-bar">
75+
<div id="status" class="status-text"></div>
76+
<div class="status-bar-controls">
77+
<button id="themeToggleBtn" class="icon-btn" aria-label="Toggle theme" title="Toggle dark/light mode">
78+
<svg class="icon-sun" height="18" viewBox="0 0 24 24" width="18">
79+
<circle cx="12" cy="12" fill="currentColor" r="5" />
80+
<line
81+
stroke="currentColor"
82+
stroke-width="2"
83+
x1="12"
84+
x2="12"
85+
y1="1"
86+
y2="3"
87+
/>
88+
<line
89+
stroke="currentColor"
90+
stroke-width="2"
91+
x1="12"
92+
x2="12"
93+
y1="21"
94+
y2="23"
95+
/>
96+
<line
97+
stroke="currentColor"
98+
stroke-width="2"
99+
x1="4.22"
100+
x2="5.64"
101+
y1="4.22"
102+
y2="5.64"
103+
/>
104+
<line
105+
stroke="currentColor"
106+
stroke-width="2"
107+
x1="18.36"
108+
x2="19.78"
109+
y1="18.36"
110+
y2="19.78"
111+
/>
112+
<line
113+
stroke="currentColor"
114+
stroke-width="2"
115+
x1="1"
116+
x2="3"
117+
y1="12"
118+
y2="12"
119+
/>
120+
<line
121+
stroke="currentColor"
122+
stroke-width="2"
123+
x1="21"
124+
x2="23"
125+
y1="12"
126+
y2="12"
127+
/>
128+
<line
129+
stroke="currentColor"
130+
stroke-width="2"
131+
x1="4.22"
132+
x2="5.64"
133+
y1="19.78"
134+
y2="18.36"
135+
/>
136+
<line
137+
stroke="currentColor"
138+
stroke-width="2"
139+
x1="18.36"
140+
x2="19.78"
141+
y1="5.64"
142+
y2="4.22"
143+
/>
144+
</svg>
145+
<svg class="icon-moon" height="18" viewBox="0 0 24 24" width="18">
146+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" fill="currentColor" />
147+
</svg>
148+
</button>
149+
<a id="aboutLink" class="about-link" href="#" title="About">About</a>
150+
</div>
151+
</div>
152+
153+
<!-- About Modal -->
154+
<div id="aboutModal" class="modal">
155+
<div class="modal-content">
156+
<button id="aboutModalClose" class="modal-close" aria-label="Close about dialog">
157+
&times;
158+
</button>
159+
<h2>About Typst for PowerPoint</h2>
160+
<div class="modal-links">
161+
<a href="https://github.com/splines/typst-powerpoint" rel="noopener noreferrer" target="_blank">
162+
🌐 Visit Website
163+
</a>
164+
<a href="https://github.com/splines/typst-powerpoint/issues" rel="noopener noreferrer" target="_blank">
165+
🐛 Report a Problem
166+
</a>
167+
<a href="privacy.html" target="_blank">
168+
🔒 Privacy Policy
169+
</a>
170+
</div>
171+
</div>
172+
</div>
173+
80174
<script type="module" defer src="src/main.js"></script>
81175
</body>
82176

web/src/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ export const DOM_IDS = {
4141
FILE_INFO: "fileInfo",
4242
FILE_NAME: "fileName",
4343
DROPZONE_LABEL: "dropzoneLabel",
44+
THEME_TOGGLE_BTN: "themeToggleBtn",
45+
ABOUT_LINK: "aboutLink",
46+
ABOUT_MODAL: "aboutModal",
47+
ABOUT_MODAL_CLOSE: "aboutModalClose",
4448
} as const;
4549

4650
/**

web/src/file/dragdrop.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,7 @@ async function handleDrop(event: DragEvent): Promise<void> {
7575
}
7676
}
7777
} catch (error) {
78-
// FileSystemFileHandle not supported or permission denied
79-
console.log("Could not get FileSystemFileHandle, using File object only:", error);
78+
console.error("Could not get FileSystemFileHandle, using File object only:", error);
8079
}
8180

8281
if (!file) {

web/src/main.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,45 @@ import { setupPreviewListeners, updateButtonState } from "./preview.js";
44
import { initializeDarkMode, setupDarkModeToggle } from "./theme.js";
55
import { handleSelectionChange } from "./selection.js";
66
import { generateFromFile, initializeDropzone } from "./file/file.js";
7+
import { DOM_IDS } from "./constants.js";
8+
import { getHTMLElement } from "./utils/dom.js";
79

810
Office.actions.associate("generateFromFile", (event: Office.AddinCommands.Event) => {
911
void generateFromFile(event);
1012
});
1113

14+
/**
15+
* Sets up the About modal functionality.
16+
*/
17+
function setupAboutModal() {
18+
const aboutLink = getHTMLElement(DOM_IDS.ABOUT_LINK);
19+
const aboutModal = getHTMLElement(DOM_IDS.ABOUT_MODAL);
20+
const closeBtn = getHTMLElement(DOM_IDS.ABOUT_MODAL_CLOSE);
21+
22+
aboutLink.addEventListener("click", (e) => {
23+
e.preventDefault();
24+
aboutModal.classList.add("active");
25+
});
26+
27+
closeBtn.addEventListener("click", () => {
28+
aboutModal.classList.remove("active");
29+
});
30+
31+
// Close modal when clicking outside of it
32+
aboutModal.addEventListener("click", (e) => {
33+
if (e.target === aboutModal) {
34+
aboutModal.classList.remove("active");
35+
}
36+
});
37+
38+
// Close modal on Escape key
39+
document.addEventListener("keydown", (e) => {
40+
if (e.key === "Escape" && aboutModal.classList.contains("active")) {
41+
aboutModal.classList.remove("active");
42+
}
43+
});
44+
}
45+
1246
/**
1347
* Main initialization function for Office Add-in.
1448
*/
@@ -21,6 +55,7 @@ await Office.onReady(async (info) => {
2155

2256
initializeDarkMode();
2357
setupDarkModeToggle();
58+
setupAboutModal();
2459

2560
initializeUIState();
2661
initializeDropzone();

web/src/preview.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ export async function updatePreview() {
134134
* Displays diagnostics in the UI.
135135
*/
136136
function displayDiagnostics(diagnostics: (string | DiagnosticMessage)[], content: HTMLElement, mathMode: boolean) {
137-
console.log("Displaying diagnostics:", diagnostics);
138137
content.innerHTML = "";
139138

140139
diagnostics.forEach((diag, index) => {

web/src/theme.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import { updatePreview } from "./preview.js";
2-
import { getInputElement } from "./utils/dom";
2+
import { getButtonElement } from "./utils/dom";
33
import { DOM_IDS, STORAGE_KEYS, THEMES } from "./constants.js";
44

55
/**
66
* Initializes dark mode based on stored preference (defaults to light mode).
77
*/
88
export function initializeDarkMode() {
99
const isDarkMode = isDarkModeEnabled();
10-
const darkModeToggle = getInputElement(DOM_IDS.DARK_MODE_TOGGLE);
11-
darkModeToggle.checked = !isDarkMode;
1210
applyTheme(isDarkMode);
1311
}
1412

@@ -36,9 +34,9 @@ function applyTheme(isDark: boolean) {
3634
* Sets up the dark mode toggle listener.
3735
*/
3836
export function setupDarkModeToggle() {
39-
const darkModeToggle = getInputElement(DOM_IDS.DARK_MODE_TOGGLE);
40-
darkModeToggle.addEventListener("change", (event) => {
41-
const isDark = !(event.target as HTMLInputElement).checked;
37+
const themeToggleBtn = getButtonElement(DOM_IDS.THEME_TOGGLE_BTN);
38+
themeToggleBtn.addEventListener("click", () => {
39+
const isDark = !document.documentElement.classList.contains("dark-mode");
4240
applyTheme(isDark);
4341
localStorage.setItem(STORAGE_KEYS.THEME, isDark ? THEMES.DARK : THEMES.LIGHT);
4442
void updatePreview();

web/styles/main.css

Lines changed: 1 addition & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
body {
2828
margin: 0;
2929
padding: 15px;
30+
padding-bottom: 60px; /* Space for sticky status bar */
3031
font-family: sans-serif;
3132
background: var(--bg-primary);
3233
color: var(--text-primary);
@@ -40,64 +41,6 @@ hr {
4041
margin: 12px 0;
4142
}
4243

43-
/* https://codepen.io/fydsa/pen/abwdpep */
44-
#themeToggle {
45-
--light: #E5E7EB;
46-
--dark: #28292c;
47-
--toggle-width: 40px;
48-
49-
position: relative;
50-
width: var(--toggle-width);
51-
margin-bottom: 5em;
52-
53-
input {
54-
position: absolute;
55-
display: none;
56-
}
57-
58-
label {
59-
position: absolute;
60-
width: 100%;
61-
height: calc(var(--toggle-width) * 0.5);
62-
background-color: var(--dark);
63-
border-radius: calc(var(--toggle-width) * 0.25);
64-
cursor: pointer;
65-
}
66-
67-
.slider {
68-
position: absolute;
69-
width: 100%;
70-
height: 100%;
71-
border-radius: calc(var(--toggle-width) * 0.240);
72-
transition: 0.3s;
73-
74-
&::before {
75-
content: "";
76-
position: absolute;
77-
top: calc(var(--toggle-width) * 0.065);
78-
left: calc(var(--toggle-width) * 0.08);
79-
width: calc(var(--toggle-width) * 0.375);
80-
height: calc(var(--toggle-width) * 0.375);
81-
border-radius: 50%;
82-
box-shadow: inset calc(var(--toggle-width) * 0.14) calc(var(--toggle-width) * -0.02) 0px 0px var(--light);
83-
background-color: var(--dark);
84-
transition: 0.3s;
85-
}
86-
}
87-
}
88-
89-
input:checked~.slider {
90-
background-color: var(--light);
91-
}
92-
93-
input:checked~.slider::before {
94-
transform: translateX(calc(var(--toggle-width) * 0.475));
95-
background-color: white;
96-
box-shadow: none;
97-
border: 1px solid lightgray;
98-
top: calc(var(--toggle-width) * 0.066 - 1px);
99-
}
100-
10144
.controls {
10245
display: flex;
10346
flex-wrap: wrap;
@@ -276,16 +219,6 @@ button {
276219
}
277220
}
278221

279-
.status {
280-
margin-top: 10px;
281-
font-size: 12px;
282-
color: var(--text-primary);
283-
284-
&.error {
285-
color: var(--error-color);
286-
}
287-
}
288-
289222
#previewContainer {
290223
margin-top: 10px;
291224
border: 1px dashed var(--border-color);

0 commit comments

Comments
 (0)