()((set) => ({
11 | colorSchemeLight: themeFromSourceColor(argbFromHex('#42cef5')).schemes.light,
12 | colorSchemeDark: themeFromSourceColor(argbFromHex('#42cef5')).schemes.dark,
13 | changeColorPalette: (newColorHex: string) => set({
14 | colorSchemeLight: themeFromSourceColor(argbFromHex(newColorHex)).schemes.light,
15 | colorSchemeDark: themeFromSourceColor(argbFromHex(newColorHex)).schemes.dark
16 | })
17 | }))
--------------------------------------------------------------------------------
/src/main.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import "./main.css";
4 | import { BrowserRouter, Route, Routes } from "react-router";
5 | import MainScreen from "./screens/panel/main_panel";
6 | import PopUp from "./screens/popup/popup";
7 | import CalendarPopup from "./screens/popup/widgets/calendar/calendar_popup";
8 |
9 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
10 |
11 |
12 |
13 | // Panel
14 | } />
15 | // Popups
16 | } />
17 | } />
18 |
19 |
20 | ,
21 | );
22 |
--------------------------------------------------------------------------------
/src/screens/panel/main_panel.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Christopher Hartono
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License
15 | along with this program. If not, see .
16 | */
17 |
18 | import { hexFromArgb } from "@material/material-color-utilities";
19 | import { useMaterialColor } from "../../logic/color_theme";
20 | import Color from "color";
21 | import { centerWidgets, leftWidgets, rightWidgets } from "./panel_contents";
22 | import { Alignment } from "../utils/widgets";
23 |
24 | export default function MainScreen() {
25 | const theme = useMaterialColor((state) => state.colorSchemeLight);
26 |
27 | return (
28 |
37 |
38 |
39 |
40 |
41 | );
42 | }
43 |
44 | function LeftPanel() {
45 | return (
46 |
47 | {leftWidgets.map((widget, index) =>
48 | widget.build({ alignment: Alignment.start, key: index.toString() }),
49 | )}
50 |
51 | );
52 | }
53 |
54 | function CenterPanel() {
55 | return (
56 |
57 | {centerWidgets.map((widget, index) =>
58 | widget.build({ alignment: Alignment.center, key: index.toString() }),
59 | )}
60 |
61 | );
62 | }
63 |
64 | function RightPanel() {
65 | return (
66 |
67 | {rightWidgets.map((widget, index) =>
68 | widget.build({ alignment: Alignment.end, key: index.toString() }),
69 | )}
70 |
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/src/screens/panel/panel_contents.tsx:
--------------------------------------------------------------------------------
1 | import { Widget } from "../utils/widgets";
2 | import { Clock } from "./widgets/clock/clock_widget";
3 |
4 | export const leftWidgets: Widget[] = [];
5 | export const centerWidgets: Widget[] = [];
6 | export const rightWidgets: Widget[] = [new Clock()];
7 |
--------------------------------------------------------------------------------
/src/screens/panel/widgets/clock/clock_widget.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode, useEffect, useState } from "react";
2 | import { Alignment, GetAlignment, Widget } from "../../../utils/widgets";
3 | import { useMaterialColor } from "../../../../logic/color_theme";
4 | import { hexFromArgb } from "@material/material-color-utilities";
5 | import NumberFlow, { NumberFlowGroup } from "@number-flow/react";
6 | import {
7 | TransitionCurve,
8 | TransitionDuration,
9 | } from "../../../utils/transitions";
10 | import { hidePopup, PopupPosition, showPopup } from "../../../popup/popup";
11 |
12 | interface DateComponent {
13 | year: number;
14 | month: number;
15 | day: number;
16 | hour: number;
17 | minute: number;
18 | second: number;
19 | }
20 |
21 | export class Clock extends Widget {
22 | build({ alignment, key }: { alignment: Alignment; key: string }): ReactNode {
23 | const [time, setTime] = useState();
24 | const theme = useMaterialColor((state) => state.colorSchemeLight);
25 |
26 | useEffect(() => {
27 | const id = setInterval(() => {
28 | const date = new Date();
29 | setTime({
30 | year: date.getFullYear(),
31 | month: date.getMonth() + 1,
32 | day: date.getDate(),
33 | hour: date.getHours(),
34 | minute: date.getMinutes(),
35 | second: date.getSeconds(),
36 | });
37 | }, 1000);
38 | return () => clearInterval(id);
39 | }, []);
40 |
41 | return (
42 |
49 | showPopup({
50 | position: PopupPosition.Right,
51 | width: 400,
52 | height: 400,
53 | path: "calendar",
54 | })
55 | }
56 | onDoubleClick={() => hidePopup()}
57 | >
58 | {/* time */}
59 |
65 |
66 |
76 |
87 |
98 |
99 |
100 |
101 | {/* date */}
102 |
108 |
109 |
119 |
130 |
140 |
141 |
142 |
143 | );
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/screens/popup/popup.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Christopher Hartono
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License
15 | along with this program. If not, see .
16 | */
17 |
18 | import { invoke } from "@tauri-apps/api/core";
19 |
20 | export enum PopupPosition {
21 | Left = "left",
22 | Center = "center",
23 | Right = "right",
24 | }
25 |
26 | export interface PopupInitArgs {
27 | position: PopupPosition;
28 | width: number;
29 | height: number;
30 | path: string;
31 | }
32 |
33 | export function showPopup(args: PopupInitArgs) {
34 | invoke("show_popup", {
35 | popupWidth: args.width,
36 | popupHeight: args.height,
37 | popupPosition: args.position,
38 | path: args.path,
39 | });
40 | }
41 |
42 | export function hidePopup() {
43 | invoke("hide_popup");
44 | }
45 |
46 | export default function PopUp() {
47 | return (
48 |
49 |
Please implement the popup function!
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/src/screens/popup/widgets/calendar/calendar_popup.tsx:
--------------------------------------------------------------------------------
1 | export default function CalendarPopup() {
2 | return (
3 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/src/screens/utils/transitions.tsx:
--------------------------------------------------------------------------------
1 | export enum TransitionDuration {
2 | STANDARD = 300,
3 | STANDARD_DECELERATE = 250,
4 | STANDARD_ACCELERATE = 200,
5 | EMPHASIZED_DECELERATE = 400,
6 | EMPHASIZED_ACCELERATE = 200,
7 | }
8 |
9 | export enum TransitionCurve {
10 | STANDARD = "cubic-bezier(0.2, 0.0, 0, 1.0)",
11 | STANDARD_DECELERATE = "cubic-bezier(0, 0, 0, 1)",
12 | STANDARD_ACCELERATE = "cubic-bezier(0.3, 0, 1, 1)",
13 | EMPHASIZED_DECELERATE = "cubic-bezier(0.05, 0.7, 0.1, 1.0)",
14 | EMPHASIZED_ACCELERATE = "cubic-bezier(0.3, 0.0, 0.8, 0.15)",
15 | }
16 |
--------------------------------------------------------------------------------
/src/screens/utils/widgets.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 |
3 | export enum Alignment {
4 | start,
5 | center,
6 | end,
7 | }
8 |
9 | export class Widget {
10 | build({ alignment, key }: { alignment: Alignment; key: string }): ReactNode {
11 | return (
12 |
19 | Please implement build method!
20 |
21 | );
22 | }
23 | }
24 |
25 | export function GetAlignment(align: Alignment) {
26 | switch (align) {
27 | case Alignment.start:
28 | return "flex-start";
29 | case Alignment.center:
30 | return "center";
31 | case Alignment.end:
32 | return "flex-end";
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | import tailwindcss from '@tailwindcss/vite'
4 |
5 | // @ts-expect-error process is a nodejs global
6 | const host = process.env.TAURI_DEV_HOST;
7 |
8 | // https://vitejs.dev/config/
9 | export default defineConfig(async () => ({
10 | plugins: [
11 | react(),
12 | tailwindcss(),
13 | ],
14 |
15 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
16 | //
17 | // 1. prevent vite from obscuring rust errors
18 | clearScreen: false,
19 | // 2. tauri expects a fixed port, fail if that port is not available
20 | server: {
21 | port: 1420,
22 | strictPort: true,
23 | host: host || false,
24 | hmr: host
25 | ? {
26 | protocol: "ws",
27 | host,
28 | port: 1421,
29 | }
30 | : undefined,
31 | watch: {
32 | // 3. tell vite to ignore watching `src-tauri`
33 | ignored: ["**/src-tauri/**"],
34 | },
35 | },
36 | }));
37 |
--------------------------------------------------------------------------------