diff --git a/bun.lockb b/bun.lockb index b7b9998..42d6188 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/docs/optimizing.md b/docs/optimizing.md new file mode 100644 index 0000000..92f3005 --- /dev/null +++ b/docs/optimizing.md @@ -0,0 +1,26 @@ +# Optimizing Tutu + +Topic Index: + +- Time to first byte +- Time to first draw: [Load size](#load-size) +- CLS +- Framerate: [Algorithm](#algorithm) + +## Load size + +The baseline for the load size is lowest 3G download bandwidth in 2s, typically 1.1Mbps (or ~137 kilobytes/s) * 2s = 274 kilobytes. + +In another words there is 274 kilobytes budget for an interaction without further notice. Notice and progress are needed if the interaction needs than that. + +The service worker can use 1 chunk of size. + +## Algorithm + +Don't choose algorithm solely on the time complexity. GUI app needs smooth, not fast. The priority: + +- On the main thread: batching. Batching is usually required to spread the work to multiple frames. + - Think in Map-Reduce framework if you don't have any idea. +- On the worker thread: balance the speed and the memory usage. + - Arrays are usually faster and use less memory. + - Worker is always available on our target platforms, but workers introduce latency in the starting and the communication. diff --git a/manifest.config.ts b/manifest.config.ts new file mode 100644 index 0000000..c6ac14e --- /dev/null +++ b/manifest.config.ts @@ -0,0 +1,10 @@ +import { ManifestOptions } from "vite-plugin-pwa"; + +const manifest: Partial = { + name: "Tutu for Mastodon", + short_name: "Tutu", + description: "Tutu is an app to read, post, dog and cat on the mastodon.", + theme_color: "#673ab7" +}; + +export default manifest; diff --git a/package.json b/package.json index 8575c58..54e01ef 100644 --- a/package.json +++ b/package.json @@ -14,40 +14,41 @@ "author": "Rubicon", "license": "Apache-2.0", "devDependencies": { - "@suid/vite-plugin": "^0.3.0", - "@types/hammerjs": "^2.0.45", - "postcss": "^8.4.45", + "@suid/vite-plugin": "^0.3.1", + "@types/hammerjs": "^2.0.46", + "@vite-pwa/assets-generator": "^0.2.6", + "postcss": "^8.4.47", "prettier": "^3.3.3", - "typescript": "^5.6.2", - "vite": "^5.4.5", + "typescript": "^5.6.3", + "vite": "^5.4.9", "vite-plugin-package-version": "^1.1.0", "vite-plugin-pwa": "^0.20.5", "vite-plugin-solid": "^2.10.2", "vite-plugin-solid-styled": "^0.11.1", "workbox-build": "^7.1.1", - "wrangler": "^3.78.2" + "wrangler": "^3.81.0" }, "dependencies": { - "@formatjs/intl-localematcher": "^0.5.4", + "@formatjs/intl-localematcher": "^0.5.5", "@nanostores/persistent": "^0.10.2", - "@nanostores/solid": "^0.4.2", + "@nanostores/solid": "^0.5.0", "@solid-primitives/event-listener": "^2.3.3", "@solid-primitives/i18n": "^2.1.1", "@solid-primitives/intersection-observer": "^2.1.6", "@solid-primitives/map": "^0.4.13", "@solid-primitives/resize-observer": "^2.0.26", - "@solidjs/router": "^0.14.5", - "@suid/icons-material": "^0.8.0", - "@suid/material": "^0.17.0", + "@solidjs/router": "^0.14.10", + "@suid/icons-material": "^0.8.1", + "@suid/material": "^0.18.0", "blurhash": "^2.0.5", "colorjs.io": "^0.5.2", - "date-fns": "^3.6.0", + "date-fns": "^4.1.0", "fast-average-color": "^9.4.0", "hammerjs": "^2.0.8", "iso-639-1": "^3.1.3", - "masto": "^6.8.0", + "masto": "^6.10.0", "nanostores": "^0.11.3", - "solid-js": "^1.8.22", + "solid-js": "^1.9.2", "solid-styled": "^0.11.1", "stacktrace-js": "^2.0.2", "web-animations-js": "^2.3.2", diff --git a/public/logo.svg b/public/logo.svg new file mode 100644 index 0000000..c3b58f5 --- /dev/null +++ b/public/logo.svg @@ -0,0 +1,133 @@ + + + + + Tutu's Icon + + + + + + + + + + + + + + + + + + + + + Tutu's Icon + 2024/10/22 + + + Rubicon + + + + + Rubicon + + + + + + + + + + + + + + + + diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..c2a49f4 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / diff --git a/pwa-assets.config.ts b/pwa-assets.config.ts new file mode 100644 index 0000000..5766e34 --- /dev/null +++ b/pwa-assets.config.ts @@ -0,0 +1,12 @@ +import { + defineConfig, + minimal2023Preset as preset +} from '@vite-pwa/assets-generator/config' + +export default defineConfig({ + headLinkOptions: { + preset: '2023' + }, + preset, + images: ['public/logo.svg'] +}) \ No newline at end of file diff --git a/src/platform/i18n.tsx b/src/platform/i18n.tsx index b31d888..ff03e14 100644 --- a/src/platform/i18n.tsx +++ b/src/platform/i18n.tsx @@ -78,7 +78,7 @@ async function importDateFnLocale(tag: string): Promise { case "en_us": return (await import("date-fns/locale/en-US")).enUS; case "en_gb": - return (await import("date-fns/locale/en-GB")).enGB; + return enGB; case "zh_cn": return (await import("date-fns/locale/zh-CN")).zhCN; default: diff --git a/src/profiles/Profile.tsx b/src/profiles/Profile.tsx index fff795d..cdea9d5 100644 --- a/src/profiles/Profile.tsx +++ b/src/profiles/Profile.tsx @@ -126,7 +126,7 @@ const Profile: Component = () => { variant="dense" sx={{ display: "flex", - color: bannerSampledColors()?.text, + color: scrolledPastBanner() ? undefined : bannerSampledColors()?.text, paddingTop: "var(--safe-area-inset-top)", }} > diff --git a/src/timelines/TootList.tsx b/src/timelines/TootList.tsx index a72fb65..f0c510f 100644 --- a/src/timelines/TootList.tsx +++ b/src/timelines/TootList.tsx @@ -18,6 +18,10 @@ import PullDownToRefresh from "./PullDownToRefresh"; import TootComposer from "./TootComposer"; import Thread from "./Thread.jsx"; import { useDefaultSession } from "../masto/clients"; +import { useHeroSignal } from "../platform/anim"; +import { HERO as BOTTOM_SHEET_HERO } from "../material/BottomSheet"; +import { setCache as setTootBottomSheetCache } from "./TootBottomSheet"; +import { useNavigate } from "@solidjs/router"; const TootList: Component<{ ref?: Ref; @@ -26,7 +30,9 @@ const TootList: Component<{ onChangeToot: (id: string, value: mastodon.v1.Status) => void; }> = (props) => { const session = useDefaultSession(); + const [, setHeroSrc] = useHeroSignal(BOTTOM_SHEET_HERO); const [expandedThreadId, setExpandedThreadId] = createSignal(); + const navigate = useNavigate(); const onBookmark = async ( client: mastodon.rest.Client, @@ -65,6 +71,30 @@ const TootList: Component<{ ); }; + const openFullScreenToot = ( + toot: mastodon.v1.Status, + srcElement?: HTMLElement, + reply?: boolean, + ) => { + const p = session()?.account; + if (!p) return; + const inf = p.inf; + if (!inf) { + console.warn("no account info?"); + return; + } + setHeroSrc(srcElement); + const acct = `${inf.username}@${p.site}`; + setTootBottomSheetCache(acct, toot); + navigate(`/${encodeURIComponent(acct)}/toot/${toot.id}`, { + state: reply + ? { + tootReply: true, + } + : undefined, + }); + }; + return ( { @@ -82,14 +112,19 @@ const TootList: Component<{ toots={toots} onBoost={onBoost} onBookmark={onBookmark} - onReply={({ status }, element) => {}} + onReply={({ status }, element) => + openFullScreenToot(status, element, true) + } client={session()?.client!} isExpended={(status) => status.id === expandedThreadId()} onItemClick={(status, event) => { if (status.id !== expandedThreadId()) { setExpandedThreadId((x) => (x ? undefined : status.id)); } else { - // TODO: open full-screen toot + openFullScreenToot( + status, + event.currentTarget as HTMLElement, + ); } }} /> diff --git a/vite.config.ts b/vite.config.ts index bd0b6d1..ae43ff6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,6 +4,7 @@ import solidStyled from "vite-plugin-solid-styled"; import suid from "@suid/vite-plugin"; import { VitePWA } from "vite-plugin-pwa"; import version from "vite-plugin-package-version"; +import manifest from "./manifest.config"; export default defineConfig(({ mode }) => ({ plugins: [ @@ -23,9 +24,10 @@ export default defineConfig(({ mode }) => ({ }, srcDir: "src/serviceworker", filename: "main.ts", - manifest: { - theme_color: "#673ab7" - } + manifest: manifest, + pwaAssets: { + config: true, + }, }), version(), ],