aboutsummaryrefslogtreecommitdiff
path: root/docker/crupest-nginx/sites/www/src
diff options
context:
space:
mode:
Diffstat (limited to 'docker/crupest-nginx/sites/www/src')
-rw-r--r--docker/crupest-nginx/sites/www/src/main.js89
-rw-r--r--docker/crupest-nginx/sites/www/src/main.ts96
-rw-r--r--docker/crupest-nginx/sites/www/src/mock-todos.ts126
-rw-r--r--docker/crupest-nginx/sites/www/src/style.css82
-rw-r--r--docker/crupest-nginx/sites/www/src/todos.ts29
5 files changed, 326 insertions, 96 deletions
diff --git a/docker/crupest-nginx/sites/www/src/main.js b/docker/crupest-nginx/sites/www/src/main.js
deleted file mode 100644
index 39d2ba8..0000000
--- a/docker/crupest-nginx/sites/www/src/main.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import "./style.css";
-
-const happy = "happy";
-const angry = "angry";
-
-function emotionOpposite(emotion) {
- if (emotion === happy) {
- return angry;
- } else if (emotion === angry) {
- return happy;
- }
-}
-
-function emotionElement(emotion) {
- return document.querySelector(`.slogan.${emotion}`);
-}
-
-function emotionElementHeight(emotion) {
- return emotionElement(emotion).clientHeight;
-}
-
-function setBodyPaddingForEmotion(emotion) {
- document.body.style.paddingTop = `${emotionElementHeight(emotion)}px`;
-}
-
-const sloganEmotionKey = "sloganEmotion";
-
-const savedEmotion = localStorage.getItem(sloganEmotionKey) ?? happy;
-if (savedEmotion !== happy && savedEmotion !== angry) {
- console.error(`Invalid saved emotion: ${savedEmotion}`);
-}
-
-setBodyPaddingForEmotion(savedEmotion);
-setTimeout(() => {
- document.body.style.transition = "padding-top 1s";
-})
-
-/** @type {HTMLDivElement} */
-const sloganContainer = document.querySelector(".slogan-container")
-
-setTimeout(() => {
- sloganContainer.dataset.sloganEmotion = savedEmotion;
- setBodyPaddingForEmotion(savedEmotion);
-}, 500);
-
-for (const emotion of [happy, angry]) {
- emotionElement(emotion).addEventListener("click", () => {
- const opposite = emotionOpposite(emotion);
- localStorage.setItem(sloganEmotionKey, opposite);
- sloganContainer.dataset.sloganEmotion = opposite;
- setBodyPaddingForEmotion(opposite);
- });
-}
-
-
-document.addEventListener("DOMContentLoaded", async () => {
- console.log("Try to fetch GitHub project(number: 2) of crupest.");
-
- const todoMessage = document.getElementById("todo-message");
- const todoContainer = document.getElementById("todo-container");
-
- const res = await fetch("/api/todos");
- const body = await res.json();
-
- if (res.status !== 200) {
- todoMessage.style.color = "red";
- todoMessage.textContent =
- "Failed to fetch TODOs. (Maybe due to rate limit. Please try later.)";
- console.log(
- `Failed to get GitHub project info. Status: ${res.status}. Body: ${body}`
- );
- } else {
- body.forEach((item) => {
- const { status, title, color } = item;
- const li = document.createElement("li");
- const statusSpan = document.createElement("span");
- const titleSpan = document.createElement("span");
- statusSpan.textContent = status;
- statusSpan.style.color = color;
- titleSpan.textContent = title;
- li.appendChild(statusSpan);
- li.append(" : ");
- li.append(titleSpan);
- todoContainer.appendChild(li);
- });
-
- todoMessage.parentElement.removeChild(todoMessage);
- }
-});
diff --git a/docker/crupest-nginx/sites/www/src/main.ts b/docker/crupest-nginx/sites/www/src/main.ts
new file mode 100644
index 0000000..17d7e12
--- /dev/null
+++ b/docker/crupest-nginx/sites/www/src/main.ts
@@ -0,0 +1,96 @@
+import "./style.css";
+
+import { fetchTodos } from "./todos";
+
+const happy = "happy" as const;
+const angry = "angry" as const;
+type Emotion = typeof happy | typeof angry;
+
+function emotionOpposite(emotion: Emotion): Emotion {
+ if (emotion === happy) {
+ return angry;
+ } else {
+ return happy;
+ }
+}
+
+function emotionElement(emotion: Emotion): HTMLDivElement {
+ return document.querySelector<HTMLDivElement>(`.slogan.${emotion}`)!;
+}
+
+function emotionElementHeight(emotion: Emotion): number {
+ return emotionElement(emotion).clientHeight;
+}
+
+function updateBodyTopPadding(emotion: Emotion): void {
+ document.body.style.paddingTop = `${emotionElementHeight(emotion)}px`;
+}
+
+const sloganEmotionKey = "sloganEmotion";
+
+const savedEmotion =
+ (localStorage.getItem(sloganEmotionKey) as Emotion | null) ?? happy;
+if (savedEmotion !== happy && savedEmotion !== angry) {
+ console.error(`Invalid saved emotion: ${savedEmotion}`);
+}
+
+updateBodyTopPadding(savedEmotion);
+// Then we add transition animation.
+setTimeout(() => {
+ document.body.style.transition = "padding-top 1s";
+});
+
+const sloganContainer = document.querySelector(
+ ".slogan-container"
+) as HTMLDivElement;
+
+setTimeout(() => {
+ sloganContainer.dataset.sloganEmotion = savedEmotion;
+}, 500);
+
+const sloganLoadedPromise = new Promise<void>((resolve) => {
+ setTimeout(() => {
+ resolve();
+ }, 1500);
+});
+
+for (const emotion of [happy, angry]) {
+ emotionElement(emotion).addEventListener("click", () => {
+ const opposite = emotionOpposite(emotion);
+ localStorage.setItem(sloganEmotionKey, opposite);
+ sloganContainer.dataset.sloganEmotion = opposite;
+ updateBodyTopPadding(opposite);
+ });
+}
+
+async function loadTodos(syncWith: Promise<unknown>): Promise<void> {
+ const todoMessage = document.getElementById("todo-message")!;
+ const todoContainer = document.getElementById("todo-container")!;
+
+ try {
+ const todosPromise = fetchTodos();
+ await syncWith; // Let's wait this first.
+ const todos = await todosPromise;
+ todos.forEach((item) => {
+ const { status, title, closed } = item;
+ const li = document.createElement("li");
+ li.dataset.status = closed ? "closed" : "open";
+ // The color from api server is kind of ugly at present.
+ // li.style.background = color;
+ const statusSpan = document.createElement("span");
+ const titleSpan = document.createElement("span");
+ statusSpan.textContent = status;
+ titleSpan.textContent = title;
+ li.appendChild(statusSpan);
+ li.append(" : ");
+ li.append(titleSpan);
+ todoContainer.appendChild(li);
+ });
+ todoMessage.parentElement!.removeChild(todoMessage);
+ } catch (e) {
+ todoMessage.style.color = "red";
+ todoMessage.textContent = (e as Error).message;
+ }
+}
+
+loadTodos(sloganLoadedPromise);
diff --git a/docker/crupest-nginx/sites/www/src/mock-todos.ts b/docker/crupest-nginx/sites/www/src/mock-todos.ts
new file mode 100644
index 0000000..aacb40e
--- /dev/null
+++ b/docker/crupest-nginx/sites/www/src/mock-todos.ts
@@ -0,0 +1,126 @@
+/** Grabbed at Tue, 18 Jul 2023 15:30:05 GMT, used as mock data. 🍻 */
+
+const todos = [
+ {
+ status: "Done",
+ title: "All BLOCKed by graduate paper.",
+ closed: true,
+ color: "green",
+ },
+ {
+ status: "Done",
+ title: "Slogan is not completely visible on phone.",
+ closed: true,
+ color: "green",
+ },
+ {
+ status: "Todo",
+ title: "Users api.",
+ closed: false,
+ color: "blue",
+ },
+ {
+ status: "Todo",
+ title: "Secrets api.",
+ closed: false,
+ color: "blue",
+ },
+ {
+ status: "Todo",
+ title: "Refactor aio python scripts.",
+ closed: false,
+ color: "blue",
+ },
+ {
+ status: "Todo",
+ title: "Nginx path redirection.",
+ closed: false,
+ color: "blue",
+ },
+ {
+ status: "Todo",
+ title: "Make services optional.",
+ closed: false,
+ color: "blue",
+ },
+ {
+ status: "Done",
+ title: "Optimize code-server.",
+ closed: true,
+ color: "green",
+ },
+ {
+ status: "Todo",
+ title: "No more alpine.",
+ closed: false,
+ color: "blue",
+ },
+ {
+ status: "Done",
+ title: "No netease music.",
+ closed: true,
+ color: "green",
+ },
+ {
+ status: "Done",
+ title: "Draft issue status in www TODOs.",
+ closed: true,
+ color: "green",
+ },
+ {
+ status: "Done",
+ title: "Re-bootstrap front end.",
+ closed: true,
+ color: "green",
+ },
+ {
+ status: "Todo",
+ title: "Clean react imports for new jsx usage.",
+ closed: false,
+ color: "blue",
+ },
+ {
+ status: "Done",
+ title: "i18next backend bug.",
+ closed: true,
+ color: "green",
+ },
+ {
+ status: "Done",
+ title: "Organize buttons.",
+ closed: true,
+ color: "green",
+ },
+ {
+ status: "Done",
+ title: "Fix dialog typo.",
+ closed: true,
+ color: "green",
+ },
+ {
+ status: "Todo",
+ title: "Organize OperationDialog.",
+ closed: false,
+ color: "blue",
+ },
+ {
+ status: "Todo",
+ title: "New palette api.",
+ closed: false,
+ color: "blue",
+ },
+ {
+ status: "Todo",
+ title: "No Docker!!!",
+ closed: false,
+ color: "blue",
+ },
+ {
+ status: "Done",
+ title: "Improve animation of slogan.",
+ closed: true,
+ color: "green",
+ },
+];
+
+export default todos;
diff --git a/docker/crupest-nginx/sites/www/src/style.css b/docker/crupest-nginx/sites/www/src/style.css
index f23bbc0..6937051 100644
--- a/docker/crupest-nginx/sites/www/src/style.css
+++ b/docker/crupest-nginx/sites/www/src/style.css
@@ -8,6 +8,11 @@ body {
box-sizing: border-box;
}
+.h-note {
+ font-size: 0.6em;
+ color: gray;
+}
+
@keyframes article-enter {
from {
opacity: 0;
@@ -20,17 +25,28 @@ body {
}
}
-#main-article {
- padding: 0 1em;
- animation: article-enter 1s;
+:root {
+ --main-article-horizontal-padding: 1em;
+ --main-article-horizontal-margin-shrink: -1em;
+
+ --im-happy: dodgerblue;
+ --im-angry: orangered;
+ --im-good: green;
+ --im-active: blue;
}
@media (min-width: 576px) {
- #main-article {
- padding: 0 2em;
+ :root {
+ --main-article-horizontal-padding: 2em;
+ --main-article-horizontal-margin-shrink: -2em;
}
}
+#main-article {
+ padding: 0 var(--main-article-horizontal-padding);
+ animation: article-enter 1s;
+}
+
.slogan-container {
width: 100%;
position: fixed;
@@ -48,11 +64,11 @@ body {
}
.slogan.happy {
- background-color: dodgerblue;
+ background-color: var(--im-happy);
}
.slogan.angry {
- background-color: orangered;
+ background-color: var(--im-angry);
}
.slogan-container[data-slogan-emotion="happy"] .slogan.happy {
@@ -70,6 +86,58 @@ body {
font-size: 1.2em;
}
+#todo-container {
+ list-style: none;
+ margin-block: 0;
+ padding-inline: 0;
+}
+
+#todo-container li {
+ margin: 0 var(--main-article-horizontal-margin-shrink);
+ padding: 0.6em 3em;
+ color: white;
+}
+
+@keyframes todo-enter-left {
+ from {
+ opacity: 0;
+ transform: translateX(-100%);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+@keyframes todo-enter-right {
+ from {
+ opacity: 0;
+ transform: translateX(100%);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+#todo-container li[data-status="closed"] {
+ background-color: var(--im-good);
+}
+
+#todo-container li[data-status="open"] {
+ background-color: var(--im-active);
+}
+
+#todo-container li:nth-child(odd) {
+ animation: todo-enter-left 1s;
+}
+
+#todo-container li:nth-child(even) {
+ animation: todo-enter-right 1s;
+}
+
.friend-link {
display: inline-block;
}
diff --git a/docker/crupest-nginx/sites/www/src/todos.ts b/docker/crupest-nginx/sites/www/src/todos.ts
new file mode 100644
index 0000000..5e36875
--- /dev/null
+++ b/docker/crupest-nginx/sites/www/src/todos.ts
@@ -0,0 +1,29 @@
+export interface Todo {
+ status: string;
+ title: string;
+ closed: boolean;
+ color: string;
+}
+
+export async function fetchTodos(): Promise<Todo[]> {
+ console.log("Try to fetch TODOs from server.");
+
+ if (process.env.NODE_ENV !== "production") {
+ console.log("YaY! We are developers. 🍻 Use mock TODOs. (After 2s)");
+ // await new Promise((resolve) => setTimeout(resolve, 2000));
+ return (await import("./mock-todos")).default;
+ } else {
+ const res = await fetch("/api/todos");
+ const body: Todo[] = await res.json();
+
+ if (res.status !== 200) {
+ console.error(
+ `Failed to get TODOs. Status: ${res.status}. Body: ${body}`
+ );
+ throw new Error(
+ "Failed to fetch TODOs. (Maybe due to rate limit. Please try later.)"
+ );
+ }
+ return body;
+ }
+}