Skip to content
This repository was archived by the owner on Jan 16, 2026. It is now read-only.

Commit ae061af

Browse files
committed
Worksheet update.
1 parent 3c89b00 commit ae061af

19 files changed

Lines changed: 4567 additions & 2280 deletions

package-lock.json

Lines changed: 3373 additions & 1793 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,27 +57,35 @@
5757
"@toast-ui/editor": "^3.2.2",
5858
"beercss": "^3.13.1",
5959
"easy-qrcode": "^0.1.0",
60-
"firebase": "^12.6.0",
60+
"firebase": "^12.7.0",
6161
"flatpickr": "^4.6.13",
6262
"fuse.js": "^7.1.0",
6363
"idb": "^8.0.3",
64+
"katex": "^0.16.27",
6465
"material-dynamic-colors": "^1.1.2",
6566
"material-file-icons": "^2.4.0",
66-
"swapy": "^1.0.5"
67+
"rehype-katex": "^7.0.1",
68+
"rehype-stringify": "^10.0.1",
69+
"remark-math": "^6.0.0",
70+
"remark-parse": "^11.0.0",
71+
"remark-rehype": "^11.1.2",
72+
"swapy": "^1.0.5",
73+
"unified": "^11.0.5"
6774
},
6875
"devDependencies": {
6976
"@types/firebase": "^3.2.3",
7077
"compression-webpack-plugin": "^11.1.0",
7178
"copy-webpack-plugin": "^13.0.1",
7279
"css-loader": "^7.1.2",
73-
"esbuild": "^0.27.1",
74-
"esbuild-loader": "^4.4.0",
80+
"depcheck": "^1.4.7",
81+
"esbuild": "^0.27.2",
82+
"esbuild-loader": "^4.4.2",
7583
"fork-ts-checker-webpack-plugin": "^9.1.0",
7684
"glob-all": "^3.3.1",
7785
"html-webpack-plugin": "^5.6.5",
7886
"mini-css-extract-plugin": "^2.9.4",
7987
"purgecss-webpack-plugin": "^7.0.2",
80-
"webpack": "^5.103.0",
88+
"webpack": "^5.104.1",
8189
"webpack-cli": "^6.0.1",
8290
"webpack-dev-server": "^5.2.2",
8391
"workbox-webpack-plugin": "^7.4.0"

src/components/common/dialogs/dialog-component.ts

Lines changed: 110 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,48 @@ type DialogPosition = "left" | "right" | "top" | "bottom" | "max";
22
type DialogWidth = "small-width" | "medium-width" | "large-width";
33

44
interface DialogOptions {
5-
id?: string | undefined;
6-
title?: string | undefined;
7-
position?: DialogPosition | null | undefined;
8-
width?: DialogWidth | null | undefined;
9-
onClose?: (() => void) | undefined;
5+
id?: string;
6+
title?: string;
7+
position?: DialogPosition | null;
8+
width?: DialogWidth | null;
9+
onClose?: () => void;
1010
autoRemove?: boolean;
11-
headerContent?: string | undefined;
12-
bodyContent?: string | undefined;
13-
footerContent?: string | undefined;
11+
headerContent?: string;
12+
bodyContent?: string;
13+
footerContent?: string;
1414
isModal?: boolean;
15+
draggable?: boolean;
1516
}
1617

1718
export class DialogComponent {
1819
protected readonly bodyElement: HTMLElement;
1920
private readonly dialog: HTMLDialogElement;
20-
private options: DialogOptions;
21+
private options: Required<Omit<DialogOptions, "id" | "onClose" | "headerContent" | "bodyContent" | "footerContent" | "isModal">> &
22+
Pick<DialogOptions, "id" | "onClose" | "headerContent" | "bodyContent" | "footerContent" | "isModal">;
2123
private readonly headerElement: HTMLElement;
2224
private readonly footerElement: HTMLElement;
25+
private isDragging = false;
26+
private dragOffsetX = 0;
27+
private dragOffsetY = 0;
2328

2429
constructor(options: DialogOptions = {}) {
25-
this.headerElement = document.createElement("header");
30+
this.headerElement = document.createElement("div");
2631
this.bodyElement = document.createElement("div");
27-
this.footerElement = document.createElement("footer");
32+
this.footerElement = document.createElement("div");
2833

2934
this.options = {
30-
id: options.id,
3135
title: options.title ?? "Dialog",
3236
position: options.position ?? null,
3337
width: options.width ?? null,
34-
onClose: options.onClose,
3538
autoRemove: options.autoRemove ?? true,
36-
headerContent: options.headerContent,
37-
bodyContent: options.bodyContent,
38-
footerContent: options.footerContent,
39-
isModal: options.isModal ?? false
39+
isModal: options.isModal ?? false,
40+
draggable: options.draggable ?? false,
41+
42+
...(options.id !== undefined && { id: options.id }),
43+
...(options.onClose && { onClose: options.onClose }),
44+
...(options.headerContent && { headerContent: options.headerContent }),
45+
...(options.bodyContent && { bodyContent: options.bodyContent }),
46+
...(options.footerContent && { footerContent: options.footerContent }),
4047
};
4148

4249
this.dialog = document.createElement("dialog");
@@ -58,6 +65,32 @@ export class DialogComponent {
5865
document.body.appendChild(this.dialog);
5966
ui(this.dialog);
6067
document.addEventListener("keydown", this.handleEscape);
68+
if (this.options.draggable) {
69+
setTimeout(() => {
70+
const el = this.element;
71+
72+
// Read once
73+
const rect = el.getBoundingClientRect();
74+
75+
requestAnimationFrame(() => {
76+
// Freeze BeerCSS animation immediately
77+
el.style.transition = "none";
78+
el.style.animation = "none";
79+
80+
// Lock position exactly where BeerCSS put it
81+
el.style.position = "fixed";
82+
el.style.left = "0";
83+
el.style.top = "0";
84+
el.style.transform = `translate3d(${rect.left}px, ${rect.top}px, 0)`;
85+
86+
// Force style flush
87+
el.getBoundingClientRect();
88+
89+
el.style.transition = "transform var(--speed1) ease-out";
90+
el.style.transform = `translate3d(${rect.left}px, ${rect.top}px, 0)`;
91+
});
92+
}, 300);
93+
}
6194
}
6295

6396
public get element(): HTMLDialogElement {
@@ -76,34 +109,34 @@ export class DialogComponent {
76109

77110
if (this.options.autoRemove) {
78111
setTimeout(() => this.dialog.remove(), 200);
112+
this.removeOwnOverlay();
79113
}
80114

81115
this.options.onClose?.();
82116
}
83117

84-
85118
private createDialogContent(): void {
86119
const dialogContent = document.createElement("div");
87120

88121
if (this.options.headerContent) {
89122
this.headerElement.innerHTML = this.options.headerContent;
90-
this.headerElement.className = "dialog-header";
123+
this.headerElement.className = "dialog-header right-align";
91124
} else {
92125
this.createDefaultHeader();
93126
}
94127
dialogContent.appendChild(this.headerElement);
95128

96129
if (this.options.bodyContent) {
97130
this.bodyElement.innerHTML = this.options.bodyContent;
98-
this.bodyElement.className = "dialog-body";
131+
this.bodyElement.className = "dialog-body top-padding";
99132
dialogContent.appendChild(this.bodyElement);
100133
} else {
101134
this.createDefaultBody();
102135
}
103136

104137
if (this.options.footerContent) {
105138
this.footerElement.innerHTML = this.options.footerContent;
106-
this.footerElement.className = "dialog-footer bottom fixed surface-container-high";
139+
this.footerElement.className = "dialog-footer right-align";
107140
dialogContent.appendChild(this.footerElement);
108141
} else {
109142
this.createDefaultFooter();
@@ -112,25 +145,35 @@ export class DialogComponent {
112145
this.dialog.appendChild(dialogContent);
113146
}
114147

148+
private removeOwnOverlay(): void {
149+
const prev = this.dialog.previousElementSibling;
150+
if (prev instanceof HTMLElement && prev.classList.contains("overlay")) {
151+
prev.remove();
152+
}
153+
}
154+
115155
private createDefaultHeader(): void {
116156
const nav = document.createElement("nav");
117-
nav.className = "row";
157+
nav.className = "row tiny-space dialog-header";
158+
159+
if (this.options.draggable) {
160+
const handle = document.createElement("i");
161+
handle.className = "handle";
162+
handle.innerHTML = "drag_indicator";
163+
handle.addEventListener("mousedown", this.startDrag);
164+
nav.appendChild(handle);
165+
}
118166

119167
const header = document.createElement("h5");
120168
header.className = "max";
121-
if (this.options.title) {
122-
header.innerText = this.options.title;
123-
}
169+
header.innerText = this.options.title;
124170

125171
const closeButton = document.createElement("button");
126172
closeButton.className = "circle transparent";
127-
closeButton.id = "close-button";
128173
closeButton.innerHTML = "<i>close</i>";
129174
closeButton.addEventListener("click", () => this.close());
130175

131-
nav.appendChild(header);
132-
nav.appendChild(closeButton);
133-
176+
nav.append(header, closeButton);
134177
this.headerElement.appendChild(nav);
135178
}
136179

@@ -155,12 +198,49 @@ export class DialogComponent {
155198
}
156199
};
157200

201+
private startDrag = (e: MouseEvent) => {
202+
if (!this.options.draggable || e.button !== 0) return;
203+
204+
e.preventDefault();
205+
e.stopPropagation();
206+
207+
this.isDragging = true;
208+
this.element.classList.add("dragging");
209+
210+
const rect = this.element.getBoundingClientRect();
211+
this.dragOffsetX = e.clientX - rect.left;
212+
this.dragOffsetY = e.clientY - rect.top;
213+
214+
document.addEventListener("mousemove", this.onDrag);
215+
document.addEventListener("mouseup", this.stopDrag);
216+
};
217+
218+
219+
private onDrag = (e: MouseEvent) => {
220+
if (!this.isDragging || e.buttons !== 1) return;
221+
222+
const x = e.clientX - this.dragOffsetX;
223+
const y = e.clientY - this.dragOffsetY;
224+
225+
this.element.style.transform = `translate3d(${x}px, ${y}px, 0)`;
226+
};
227+
228+
229+
private stopDrag = () => {
230+
this.isDragging = false;
231+
this.element.classList.remove("dragging");
232+
document.body.classList.remove("no-select");
233+
234+
document.removeEventListener("mousemove", this.onDrag);
235+
document.removeEventListener("mouseup", this.stopDrag);
236+
};
237+
238+
158239
public handleResize = () => {
159240
if (window.innerWidth < 600) {
160241
this.dialog.classList.add("max");
161242
} else {
162243
this.dialog.classList.remove("max");
163244
}
164245
};
165-
166246
}

src/components/common/dialogs/select-outcomes-dialog.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,12 @@ export class SelectOutcomesDialog extends DialogComponent {
4949
<label for="search">Search outcomes</label>
5050
</div>
5151
<p>Select one or more outcomes below:</p>
52-
<div id="results"></div>
53-
</div>`,
54-
footerContent: `
55-
<nav class="no-margin bottom right-align surface-container-high">
56-
<button class="button border" id="cancel-btn">Cancel</button>
57-
<button class="button primary" id="done-btn">Done</button>
58-
</nav>`,
59-
isModal: true,
52+
<div style="max-height: 400px; overflow-y: scroll;" class="small-round" id="results"></div>
53+
</div>
54+
<nav class="no-margin bottom right-align surface-container-high">
55+
<button class="button border" id="cancel-btn">Cancel</button>
56+
<button class="button primary" id="done-btn">Done</button>
57+
</nav>`,
6058
});
6159
}
6260

src/components/common/snackbar/snackbar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class SnackbarComponent {
3131
duration: options.duration ?? 6000,
3232
action: options.action,
3333
icon: options.icon ?? "info",
34-
onClose: options.onClose
34+
onClose: options.onClose ?? onclose
3535
};
3636

3737
this.snackbar = document.createElement("div");

0 commit comments

Comments
 (0)