Add support for database backups and enhance UI elements

Updates UI components and gitignore rules for database backups, adds
Facebook links to news org window, and improves desktop window title
handling.
This commit is contained in:
吳元皓 2025-05-15 10:54:39 +08:00
parent 67f574fc35
commit 94fbf1551d
8 changed files with 93 additions and 72 deletions

3
.gitignore vendored
View File

@ -29,3 +29,6 @@ __pycache__
# Sentry Config File # Sentry Config File
.env.sentry-build-plugin .env.sentry-build-plugin
*.sql
!database/*.sql

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { GlobeAltIcon } from "@heroicons/vue/24/outline"; import { GlobeAltIcon } from "@heroicons/vue/24/outline";
import { Facebook } from "lucide-vue-next";
import { gsap } from "gsap"; import { gsap } from "gsap";
import { ScrambleTextPlugin } from "gsap/dist/ScrambleTextPlugin"; import { ScrambleTextPlugin } from "gsap/dist/ScrambleTextPlugin";
gsap.registerPlugin(ScrambleTextPlugin); gsap.registerPlugin(ScrambleTextPlugin);
@ -17,7 +18,7 @@ const {
data: fetchNewsOrgInfo, data: fetchNewsOrgInfo,
pending, pending,
error, error,
} = useFetch("/api/cached/getData/fetchNewsOrgInfo", { } = useFetch("/api/cached/getData/fetchNewsOrgInfo/2293", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -55,19 +56,24 @@ onMounted(() => {
<span class="text-ms m-1 mt-5 text-left text-wrap">{{ <span class="text-ms m-1 mt-5 text-left text-wrap">{{
fetchNewsOrgInfo?.description fetchNewsOrgInfo?.description
}}</span> }}</span>
<div
class="gap-[3px] flex flex-row text-center align-center justify-center"
>
<a
:href="fetchNewsOrgInfo?.website"
target="_blank"
class="text-gray-800 hover:text-gray-500 transiton-all duration-150 flex flex-row"
><GlobeAltIcon class="w-6 h-6" />網站</a
>
<a
:href="fetchNewsOrgInfo?.facebook"
target="_blank"
class="text-gray-800 hover:text-gray-500 transiton-all duration-150 flex flex-row"
><Facebook class="w-6 h-6" />Facebook
</a>
</div>
</div> </div>
</div> </div>
<div
class="gap-[3px] flex flex-row text-center align-center justify-center"
>
<a
:href="fetchNewsOrgInfo?.website"
target="_blank"
class="text-blue-200 hover:text-blue-300 transiton-all duration-100 flex flex-row"
><GlobeAltIcon class="w-6 h-6" />網站</a
>
</div>
</div> </div>
</div> </div>
</template> </template>

15
database/README.md Normal file
View File

@ -0,0 +1,15 @@
# Database contents
This is where I put the database schemas, that contains everything (the user account & password info is NOT avaiable in the backup.)
## How to import
Import the schema:
```bash
psql -d database -f database_dump.sql
```
## Hoe to create a database_dump
Type or copy This
```bash
pg_dump -U your_username -d your_database --schema-only > schema.sql
```

View File

@ -51,6 +51,7 @@
"Welcome": "Welcome", "Welcome": "Welcome",
"loading": "Loading...", "loading": "Loading...",
"app": { "app": {
"launchtext": "Cooking up the desktop...",
"hotnews": "Hot News", "hotnews": "Hot News",
"news": "News", "news": "News",
"sources": "Sources", "sources": "Sources",

View File

@ -51,6 +51,7 @@
"Welcome": "歡迎", "Welcome": "歡迎",
"loading": "載入中...", "loading": "載入中...",
"app": { "app": {
"launchtext": "系統開機中...",
"hotnews": "熱門新聞", "hotnews": "熱門新聞",
"news": "新聞", "news": "新聞",
"sources": "資料來源", "sources": "資料來源",

View File

@ -165,21 +165,21 @@ const associAppWindow = [
/* /*
const keyboardShortcuts = { const keyboardShortcuts = {
'Meta+k': { 'Meta+k': {
action: () => toggleMenu(), action: () => toggleMenu(),
description: 'Toggle menu' description: 'Toggle menu'
}, },
'Meta+q': { 'Meta+q': {
action: () => router.push(localePath("/home")), action: () => router.push(localePath("/home")),
description: 'Quit to home' description: 'Quit to home'
}, },
'Meta+w': { 'Meta+w': {
action: () => closeWindow(activeWindows.value[activeWindows.value.length - 1]?.id), action: () => closeWindow(activeWindows.value[activeWindows.value.length - 1]?.id),
description: 'Close current window' description: 'Close current window'
}, },
'Meta+t': { 'Meta+t': {
action: () => findAndOpenWindow('tty'), action: () => findAndOpenWindow('tty'),
description: 'Open terminal' description: 'Open terminal'
}, },
} }
@ -242,16 +242,17 @@ onMounted(() => {
globalWindowVal.value.set(window.name, { globalWindowVal.value.set(window.name, {
id: window.id, id: window.id,
title: window.title, title: window.title,
windowCount: 1 windowCount: 1,
}); });
}); });
}) });
const openWindow = (windowName?: string) => { const openWindow = (windowName?: string) => {
if (windowName === "leave") { if (windowName === "leave") {
if (confirm("Are you sure?")) { if (confirm("Are you sure?")) {
router.push(localePath("/home")); router.push(localePath("/home"));
} else { } else {
return return;
} }
} else { } else {
if (windowName) findAndOpenWindow(windowName); if (windowName) findAndOpenWindow(windowName);
@ -316,18 +317,22 @@ const findAndOpenWindow = (windowName: string) => {
}); });
currentOpenAppId.value++; currentOpenAppId.value++;
// Add to navbar // Add to navbar
const windowNameVal2 = globalWindowVal.value.get(windowName).windowCount === 1 ? windowName : windowName + "(" + globalWindowVal.value.get(windowName).windowCount + ")" const windowNameVal2 =
console.log(globalWindowVal.value.get(windowName)) globalWindowVal.value.get(windowName).windowCount === 1
console.log(windowNameVal2) ? windowName
: windowName +
"(" +
globalWindowVal.value.get(windowName).windowCount +
")";
currentNavBar.value.push({ currentNavBar.value.push({
name: windowNameVal2, name: windowNameVal2,
icon: "anything", icon: "anything",
action: "idk", action: "idk",
flash: true, flash: true,
windowAssociated: abosluteId, windowAssociated: abosluteId,
minimized: false, minimized: false,
}); });
globalWindowVal.value.get(windowName).windowCount++ globalWindowVal.value.get(windowName).windowCount++;
} }
}; };
@ -338,7 +343,7 @@ const obtainTopWindowPosition = (windowId: string) => {
); );
if (windowIndex !== -1) { if (windowIndex !== -1) {
const [window] = activeWindows.value.splice(windowIndex, 1); const [window] = activeWindows.value.splice(windowIndex, 1);
titleAppName.value = window.name; titleAppName.value = window.title;
activeWindows.value.push(window); activeWindows.value.push(window);
} }
} }
@ -349,8 +354,8 @@ const closeWindow = (windowId: string, windowAID: string) => {
(window) => window.id !== windowId, (window) => window.id !== windowId,
); );
currentNavBar.value = currentNavBar.value.filter( currentNavBar.value = currentNavBar.value.filter(
(window) => window.windowAssociated !== windowAID (window) => window.windowAssociated !== windowAID,
) );
console.log("activeWindows.value", activeWindows.value); console.log("activeWindows.value", activeWindows.value);
}; };
@ -366,9 +371,13 @@ const maxWindow = (windowId: string) => {};
// Title // Title
useSeoMeta({ useSeoMeta({
title: titleAppName.value + " - Desktop", title: "Desktop",
});
watchEffect(() => {
useSeoMeta({
title: titleAppName.value + " - Desktop",
});
}); });
// Booting animation // Booting animation
onMounted(() => { onMounted(() => {
// booting animation bypass // booting animation bypass
@ -410,9 +419,7 @@ watchEffect((cleanupFn) => {
class="w-3/5 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" class="w-3/5 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
/> />
<br /> <br />
<span class="text-xl text-bold mt-3" <span class="text-xl text-bold mt-3">{{ t("app.launchtext") }}</span>
>Launching your fancy desktop...</span
>
</div> </div>
</div> </div>
<div <div
@ -432,24 +439,24 @@ watchEffect((cleanupFn) => {
<button <button
class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100" class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100"
></button> ></button>
<div class="overflow-hidden overflow-x-auto overflow-y-hidden scrollbar-thin scrollbar-track-transparent scrollbar-thumb-white flex-nowrap whitespace-nowrap min-w-0"> <div
<div class="flex flex-row flex-shrink-0 min-w-0"> class="overflow-hidden overflow-x-auto overflow-y-hidden scrollbar-thin scrollbar-track-transparent scrollbar-thumb-white flex-nowrap whitespace-nowrap min-w-0"
<div
v-for="item in currentNavBar"
:key="item.name"
class="flex flex-row items-center gap-x-2 hover:bg-gray-100 transition-all duration-150 px-4 py-2 cursor-pointer group rounded-xl"
> >
<button <div class="flex flex-row flex-shrink-0 min-w-0">
@click="unMinWindow(item.windowAssociated)" <div
class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100" v-for="item in currentNavBar"
> :key="item.name"
<component :is="item.icon" v-if="item.icon"></component> class="flex flex-row items-center gap-x-2 hover:bg-gray-100 transition-all duration-150 px-4 py-1 cursor-pointer group rounded-xl"
<span>{{ item.name }}</span> >
</button> <button
@click="unMinWindow(item.windowAssociated)"
class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100"
>
<span>{{ item.name }}</span>
</button>
</div>
</div>
</div> </div>
</div>
</div>
</div> </div>
<div class="flex flex-row gap-5"> <div class="flex flex-row gap-5">
<button <button

View File

@ -1,12 +1,13 @@
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, "slug");
const body = await readBody(event); const body = await readBody(event);
return { return {
body: body, body: body,
title: "News Org 1", title: "News Org 1",
slug: "taisounds", slug: "taisounds",
website: "https://www.taisounds.com.tw", website: "https://yuanhau.com",
description: "wah wah wah wah wah wah I dont fucking care", description: "wah wah wah wah wah wah I dont fucking care",
facebook: "https://www.facebook.com/taisounds", facebook: "https://www.facebook.csdkc",
logoUrl: logoUrl:
"https://cdn.discordapp.com/avatars/918723093646684180/4eecc27ac05ee8a701fa167808610c7a.jpg", "https://cdn.discordapp.com/avatars/918723093646684180/4eecc27ac05ee8a701fa167808610c7a.jpg",
}; };

View File

@ -1,13 +0,0 @@
export default defineEventHandler(async (event) => {
const body = await readBody(event);
return {
0: {
id: "1",
image: "whatever",
tags: [],
title: "三立新聞",
lean: "left",
score: "40",
},
};
});