From ef93da5b957dcfd75ac6c820ee35461399b58324 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 19 Jul 2023 01:37:02 +0800 Subject: A lot prettier. --- docker/crupest-nginx/sites/www/index.html | 101 ++++++++++-------- docker/crupest-nginx/sites/www/package.json | 8 +- docker/crupest-nginx/sites/www/pnpm-lock.yaml | 32 ++++++ docker/crupest-nginx/sites/www/src/main.js | 89 ---------------- docker/crupest-nginx/sites/www/src/main.ts | 96 +++++++++++++++++ docker/crupest-nginx/sites/www/src/mock-todos.ts | 126 +++++++++++++++++++++++ docker/crupest-nginx/sites/www/src/style.css | 82 +++++++++++++-- docker/crupest-nginx/sites/www/src/todos.ts | 29 ++++++ docker/crupest-nginx/sites/www/tsconfig.json | 19 ++++ 9 files changed, 439 insertions(+), 143 deletions(-) delete mode 100644 docker/crupest-nginx/sites/www/src/main.js create mode 100644 docker/crupest-nginx/sites/www/src/main.ts create mode 100644 docker/crupest-nginx/sites/www/src/mock-todos.ts create mode 100644 docker/crupest-nginx/sites/www/src/todos.ts create mode 100644 docker/crupest-nginx/sites/www/tsconfig.json (limited to 'docker/crupest-nginx/sites/www') diff --git a/docker/crupest-nginx/sites/www/index.html b/docker/crupest-nginx/sites/www/index.html index 0a7a99b..339d3c2 100644 --- a/docker/crupest-nginx/sites/www/index.html +++ b/docker/crupest-nginx/sites/www/index.html @@ -20,50 +20,61 @@

Hello! This is crupest.

-

Welcome to my home page! I'm very glad to meet you here.

-

If you have something interesting to share with me, feel free to reach me via email at crupest@outlook.com or, of course, I@crupest.life. You can also fire an issue in - any repo on GitHub. Here is the link to my GitHub profile, https://github.com/crupest.

-

Currently this page is hosted on my own server and my own apex domain (crupest.life). You can also share this - website link, aka, https://crupest.life.

-

This is just the home page. I also run some other services on my server under this domain:

-

Public:

- -

Private:

- -

If you wish to deploy similar services like me, you are in the right place. Take a look at https://github.com/crupest/crupest and there is - all you need to start with. Also, contact me if you run into some problem.

-

Friends of me:

- -

TODOs of me:

-

Fetching...

- +
+

Welcome to my home page! I'm very glad to meet you here.

+

If you have something interesting to share with me, feel free to reach me via email at crupest@outlook.com or, of course, I@crupest.life. You can also fire an issue in + any repo on GitHub. Here is the link to my GitHub profile, https://github.com/crupest.

+

Currently this page is hosted on my own server and my own apex domain (crupest.life). You can also share this + website link, aka, https://crupest.life.

+
+
+

TODOs of me grabbed from my Github + project

+

Fetching...

+
    +
+
+
+

Friends of me:

+ +
+
+

This is just the home page. I also run some other services on my server under this domain:

+

Public:

+ +

Private:

+ +

If you wish to deploy similar services like me, you are in the right place. Take a look at https://github.com/crupest/crupest and there is + all you need to start with. Also, contact me if you run into some problem.

+

That's all of me!

© 2022 crupest

@@ -75,7 +86,7 @@

- + \ No newline at end of file diff --git a/docker/crupest-nginx/sites/www/package.json b/docker/crupest-nginx/sites/www/package.json index 29d2d78..6927dfa 100644 --- a/docker/crupest-nginx/sites/www/package.json +++ b/docker/crupest-nginx/sites/www/package.json @@ -5,9 +5,13 @@ "source": "index.html", "scripts": { "start": "parcel", - "build": "parcel build" + "build": "tsc && parcel build" }, "devDependencies": { - "parcel": "latest" + "@tsconfig/recommended": "^1.0.2", + "@types/parcel-env": "^0.0.1", + "parcel": "latest", + "prettier": "3.0.0", + "typescript": "^5.1.6" } } \ No newline at end of file diff --git a/docker/crupest-nginx/sites/www/pnpm-lock.yaml b/docker/crupest-nginx/sites/www/pnpm-lock.yaml index a868dab..6de8ce2 100644 --- a/docker/crupest-nginx/sites/www/pnpm-lock.yaml +++ b/docker/crupest-nginx/sites/www/pnpm-lock.yaml @@ -5,9 +5,21 @@ settings: excludeLinksFromLockfile: false devDependencies: + '@tsconfig/recommended': + specifier: ^1.0.2 + version: 1.0.2 + '@types/parcel-env': + specifier: ^0.0.1 + version: 0.0.1 parcel: specifier: latest version: 2.9.3 + prettier: + specifier: 3.0.0 + version: 3.0.0 + typescript: + specifier: ^5.1.6 + version: 5.1.6 packages: @@ -1057,6 +1069,14 @@ packages: engines: {node: '>=10.13.0'} dev: true + /@tsconfig/recommended@1.0.2: + resolution: {integrity: sha512-dbHBtbWBOjq0/otpopAE02NT2Cm05Qe2JsEKeCf/wjSYbI2hz8nCqnpnOJWHATgjDz4fd3dchs3Wy1gQGjfN6w==} + dev: true + + /@types/parcel-env@0.0.1: + resolution: {integrity: sha512-8WmdiJ1uEBcW6AOWzQH7i0141ZXZr7B03YfTpguUDrTHXJHwYU9eEOckBRCZzYGrzb4pdoyBlaIMiTee04uqPQ==} + dev: true + /abortcontroller-polyfill@1.7.5: resolution: {integrity: sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==} dev: true @@ -1699,6 +1719,12 @@ packages: posthtml-render: 3.0.0 dev: true + /prettier@3.0.0: + resolution: {integrity: sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==} + engines: {node: '>=14'} + hasBin: true + dev: true + /react-error-overlay@6.0.9: resolution: {integrity: sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==} dev: true @@ -1797,6 +1823,12 @@ packages: engines: {node: '>=10'} dev: true + /typescript@5.1.6: + resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + /update-browserslist-db@1.0.11(browserslist@4.21.9): resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} hasBin: true 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(`.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((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): Promise { + 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 { + 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; + } +} diff --git a/docker/crupest-nginx/sites/www/tsconfig.json b/docker/crupest-nginx/sites/www/tsconfig.json new file mode 100644 index 0000000..9d1434c --- /dev/null +++ b/docker/crupest-nginx/sites/www/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/recommended/tsconfig.json", + "compilerOptions": { + "lib": [ + "ESNext", + "DOM", + "DOM.Iterable" + ], + "types": [ + "parcel-env" + ], + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true + } +} \ No newline at end of file -- cgit v1.2.3