Compare commits
No commits in common. "7eb55e2a140687e77b9a46787723600727a94e22" and "bea1d6abfa47b1cbfbca3b8a02a9d18c9a470d70" have entirely different histories.
7eb55e2a14
...
bea1d6abfa
11 changed files with 21 additions and 242 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
@ -1,26 +0,0 @@
|
||||||
# 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.
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { ManifestOptions } from "vite-plugin-pwa";
|
|
||||||
|
|
||||||
const manifest: Partial<ManifestOptions> = {
|
|
||||||
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;
|
|
29
package.json
29
package.json
|
@ -14,41 +14,40 @@
|
||||||
"author": "Rubicon",
|
"author": "Rubicon",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@suid/vite-plugin": "^0.3.1",
|
"@suid/vite-plugin": "^0.3.0",
|
||||||
"@types/hammerjs": "^2.0.46",
|
"@types/hammerjs": "^2.0.45",
|
||||||
"@vite-pwa/assets-generator": "^0.2.6",
|
"postcss": "^8.4.45",
|
||||||
"postcss": "^8.4.47",
|
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.2",
|
||||||
"vite": "^5.4.9",
|
"vite": "^5.4.5",
|
||||||
"vite-plugin-package-version": "^1.1.0",
|
"vite-plugin-package-version": "^1.1.0",
|
||||||
"vite-plugin-pwa": "^0.20.5",
|
"vite-plugin-pwa": "^0.20.5",
|
||||||
"vite-plugin-solid": "^2.10.2",
|
"vite-plugin-solid": "^2.10.2",
|
||||||
"vite-plugin-solid-styled": "^0.11.1",
|
"vite-plugin-solid-styled": "^0.11.1",
|
||||||
"workbox-build": "^7.1.1",
|
"workbox-build": "^7.1.1",
|
||||||
"wrangler": "^3.81.0"
|
"wrangler": "^3.78.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/intl-localematcher": "^0.5.5",
|
"@formatjs/intl-localematcher": "^0.5.4",
|
||||||
"@nanostores/persistent": "^0.10.2",
|
"@nanostores/persistent": "^0.10.2",
|
||||||
"@nanostores/solid": "^0.5.0",
|
"@nanostores/solid": "^0.4.2",
|
||||||
"@solid-primitives/event-listener": "^2.3.3",
|
"@solid-primitives/event-listener": "^2.3.3",
|
||||||
"@solid-primitives/i18n": "^2.1.1",
|
"@solid-primitives/i18n": "^2.1.1",
|
||||||
"@solid-primitives/intersection-observer": "^2.1.6",
|
"@solid-primitives/intersection-observer": "^2.1.6",
|
||||||
"@solid-primitives/map": "^0.4.13",
|
"@solid-primitives/map": "^0.4.13",
|
||||||
"@solid-primitives/resize-observer": "^2.0.26",
|
"@solid-primitives/resize-observer": "^2.0.26",
|
||||||
"@solidjs/router": "^0.14.10",
|
"@solidjs/router": "^0.14.5",
|
||||||
"@suid/icons-material": "^0.8.1",
|
"@suid/icons-material": "^0.8.0",
|
||||||
"@suid/material": "^0.18.0",
|
"@suid/material": "^0.17.0",
|
||||||
"blurhash": "^2.0.5",
|
"blurhash": "^2.0.5",
|
||||||
"colorjs.io": "^0.5.2",
|
"colorjs.io": "^0.5.2",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^3.6.0",
|
||||||
"fast-average-color": "^9.4.0",
|
"fast-average-color": "^9.4.0",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"iso-639-1": "^3.1.3",
|
"iso-639-1": "^3.1.3",
|
||||||
"masto": "^6.10.0",
|
"masto": "^6.8.0",
|
||||||
"nanostores": "^0.11.3",
|
"nanostores": "^0.11.3",
|
||||||
"solid-js": "^1.9.2",
|
"solid-js": "^1.8.22",
|
||||||
"solid-styled": "^0.11.1",
|
"solid-styled": "^0.11.1",
|
||||||
"stacktrace-js": "^2.0.2",
|
"stacktrace-js": "^2.0.2",
|
||||||
"web-animations-js": "^2.3.2",
|
"web-animations-js": "^2.3.2",
|
||||||
|
|
133
public/logo.svg
133
public/logo.svg
|
@ -1,133 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
width="512"
|
|
||||||
height="512"
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
version="1.1"
|
|
||||||
id="svg1"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
||||||
<title
|
|
||||||
id="title6">Tutu's Icon</title>
|
|
||||||
<defs
|
|
||||||
id="defs1">
|
|
||||||
<linearGradient
|
|
||||||
id="linearGradient4">
|
|
||||||
<stop
|
|
||||||
style="stop-color:#fac8a3;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop4" />
|
|
||||||
<stop
|
|
||||||
style="stop-color:#fac8a3;stop-opacity:1;"
|
|
||||||
offset="0.72011906"
|
|
||||||
id="stop6" />
|
|
||||||
<stop
|
|
||||||
style="stop-color:#f48d8a;stop-opacity:1;"
|
|
||||||
offset="1"
|
|
||||||
id="stop5" />
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient
|
|
||||||
xlink:href="#linearGradient4"
|
|
||||||
id="linearGradient5"
|
|
||||||
x1="244.74585"
|
|
||||||
y1="430.05423"
|
|
||||||
x2="281.31232"
|
|
||||||
y2="82.147469"
|
|
||||||
gradientUnits="userSpaceOnUse" />
|
|
||||||
<linearGradient
|
|
||||||
xlink:href="#linearGradient4"
|
|
||||||
id="linearGradient9"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="244.74585"
|
|
||||||
y1="430.05423"
|
|
||||||
x2="281.31232"
|
|
||||||
y2="82.147469" />
|
|
||||||
</defs>
|
|
||||||
<g
|
|
||||||
id="layer1"
|
|
||||||
transform="matrix(0,1.4786237,-1.4786237,0,642.78291,-98.6117)"
|
|
||||||
style="display:none">
|
|
||||||
<path
|
|
||||||
style="fill:url(#linearGradient5);stroke:#000000;stroke-width:2;stroke-dasharray:none"
|
|
||||||
d="m 142.83262,404.32618 c 82.40343,-32.96137 141.81198,28.9112 141.81198,28.9112 0,0 21.1442,-23.1068 65.90935,-81.91599 51.82799,-68.08783 41.673,-112.8227 41.673,-112.8227 0,0 2.09565,-72.92272 -51.48333,-125.1428 C 299.2268,72.892041 192.11851,96.043081 192.11851,96.043081 c 0,0 -84.44469,24.815289 -100.925375,105.021299 -16.480686,80.20601 51.639485,203.2618 51.639485,203.2618 z"
|
|
||||||
id="path2" />
|
|
||||||
<g
|
|
||||||
id="g6"
|
|
||||||
style="stroke-width:2;stroke-dasharray:none">
|
|
||||||
<path
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:2.3;stroke-dasharray:none"
|
|
||||||
d="m 210.50439,396.31338 c 0,0 -29.4511,-105.3688 -28.96641,-141.50635 0.34942,-26.05202 -0.47344,-34.47943 3.80644,-54.3634 5.96795,-27.72652 12.70659,-35.21402 12.70659,-35.21402"
|
|
||||||
id="path3" />
|
|
||||||
<path
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:1.8;stroke-dasharray:none"
|
|
||||||
d="m 185.10879,292.14608 c 2.88624,-0.57725 6.83376,-30.94178 44.70393,-42.39467"
|
|
||||||
id="path4" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g9"
|
|
||||||
transform="matrix(0,1.5553086,-1.5553086,0,668.42415,-94.877994)">
|
|
||||||
<path
|
|
||||||
style="fill:url(#linearGradient9);stroke:#000000;stroke-width:2;stroke-dasharray:none"
|
|
||||||
d="m 117.92992,367.86152 c 25.48298,38.39529 93.78536,33.94838 93.78536,33.94838 0,0 58.98001,-7.82594 105.93153,-57.60357 51.75,-54.86494 41.67301,-98.59259 41.67301,-98.59259 0,0 0.85581,-49.23349 -57.70901,-95.79319 -44.18497,-35.12756 -115.71798,-13.75528 -115.71798,-13.75528 0,0 -87.11283,22.14714 -94.699695,109.46821 -7.087568,81.5744 26.736785,122.32804 26.736785,122.32804 z"
|
|
||||||
id="path6" />
|
|
||||||
<g
|
|
||||||
id="g8"
|
|
||||||
style="stroke-width:2;stroke-dasharray:none"
|
|
||||||
transform="translate(-1.7787639,5.3362916)">
|
|
||||||
<path
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:2.3;stroke-dasharray:none"
|
|
||||||
d="m 210.50439,396.31338 c 0,0 -29.4511,-105.3688 -28.96641,-141.50635 0.34942,-26.05202 -0.47344,-34.47943 3.80644,-54.3634 5.96795,-27.72652 12.70659,-35.21402 12.70659,-35.21402"
|
|
||||||
id="path7" />
|
|
||||||
<path
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:1.8;stroke-dasharray:none"
|
|
||||||
d="m 185.10879,292.14608 c 2.88624,-0.57725 6.83376,-30.94178 44.70393,-42.39467"
|
|
||||||
id="path8" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<metadata
|
|
||||||
id="metadata6">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:title>Tutu's Icon</dc:title>
|
|
||||||
<dc:date>2024/10/22</dc:date>
|
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Rubicon</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:rights>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Rubicon</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:rights>
|
|
||||||
<cc:license
|
|
||||||
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
|
|
||||||
</cc:Work>
|
|
||||||
<cc:License
|
|
||||||
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
|
||||||
<cc:prohibits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 5.1 KiB |
|
@ -1,2 +0,0 @@
|
||||||
User-agent: *
|
|
||||||
Allow: /
|
|
|
@ -1,12 +0,0 @@
|
||||||
import {
|
|
||||||
defineConfig,
|
|
||||||
minimal2023Preset as preset
|
|
||||||
} from '@vite-pwa/assets-generator/config'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
headLinkOptions: {
|
|
||||||
preset: '2023'
|
|
||||||
},
|
|
||||||
preset,
|
|
||||||
images: ['public/logo.svg']
|
|
||||||
})
|
|
|
@ -78,7 +78,7 @@ async function importDateFnLocale(tag: string): Promise<Locale> {
|
||||||
case "en_us":
|
case "en_us":
|
||||||
return (await import("date-fns/locale/en-US")).enUS;
|
return (await import("date-fns/locale/en-US")).enUS;
|
||||||
case "en_gb":
|
case "en_gb":
|
||||||
return enGB;
|
return (await import("date-fns/locale/en-GB")).enGB;
|
||||||
case "zh_cn":
|
case "zh_cn":
|
||||||
return (await import("date-fns/locale/zh-CN")).zhCN;
|
return (await import("date-fns/locale/zh-CN")).zhCN;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -126,7 +126,7 @@ const Profile: Component = () => {
|
||||||
variant="dense"
|
variant="dense"
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
color: scrolledPastBanner() ? undefined : bannerSampledColors()?.text,
|
color: bannerSampledColors()?.text,
|
||||||
paddingTop: "var(--safe-area-inset-top)",
|
paddingTop: "var(--safe-area-inset-top)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -18,10 +18,6 @@ import PullDownToRefresh from "./PullDownToRefresh";
|
||||||
import TootComposer from "./TootComposer";
|
import TootComposer from "./TootComposer";
|
||||||
import Thread from "./Thread.jsx";
|
import Thread from "./Thread.jsx";
|
||||||
import { useDefaultSession } from "../masto/clients";
|
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<{
|
const TootList: Component<{
|
||||||
ref?: Ref<HTMLDivElement>;
|
ref?: Ref<HTMLDivElement>;
|
||||||
|
@ -30,9 +26,7 @@ const TootList: Component<{
|
||||||
onChangeToot: (id: string, value: mastodon.v1.Status) => void;
|
onChangeToot: (id: string, value: mastodon.v1.Status) => void;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const session = useDefaultSession();
|
const session = useDefaultSession();
|
||||||
const [, setHeroSrc] = useHeroSignal(BOTTOM_SHEET_HERO);
|
|
||||||
const [expandedThreadId, setExpandedThreadId] = createSignal<string>();
|
const [expandedThreadId, setExpandedThreadId] = createSignal<string>();
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const onBookmark = async (
|
const onBookmark = async (
|
||||||
client: mastodon.rest.Client,
|
client: mastodon.rest.Client,
|
||||||
|
@ -71,30 +65,6 @@ 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 (
|
return (
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
fallback={(err, reset) => {
|
fallback={(err, reset) => {
|
||||||
|
@ -112,19 +82,14 @@ const TootList: Component<{
|
||||||
toots={toots}
|
toots={toots}
|
||||||
onBoost={onBoost}
|
onBoost={onBoost}
|
||||||
onBookmark={onBookmark}
|
onBookmark={onBookmark}
|
||||||
onReply={({ status }, element) =>
|
onReply={({ status }, element) => {}}
|
||||||
openFullScreenToot(status, element, true)
|
|
||||||
}
|
|
||||||
client={session()?.client!}
|
client={session()?.client!}
|
||||||
isExpended={(status) => status.id === expandedThreadId()}
|
isExpended={(status) => status.id === expandedThreadId()}
|
||||||
onItemClick={(status, event) => {
|
onItemClick={(status, event) => {
|
||||||
if (status.id !== expandedThreadId()) {
|
if (status.id !== expandedThreadId()) {
|
||||||
setExpandedThreadId((x) => (x ? undefined : status.id));
|
setExpandedThreadId((x) => (x ? undefined : status.id));
|
||||||
} else {
|
} else {
|
||||||
openFullScreenToot(
|
// TODO: open full-screen toot
|
||||||
status,
|
|
||||||
event.currentTarget as HTMLElement,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -4,7 +4,6 @@ import solidStyled from "vite-plugin-solid-styled";
|
||||||
import suid from "@suid/vite-plugin";
|
import suid from "@suid/vite-plugin";
|
||||||
import { VitePWA } from "vite-plugin-pwa";
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
import version from "vite-plugin-package-version";
|
import version from "vite-plugin-package-version";
|
||||||
import manifest from "./manifest.config";
|
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => ({
|
export default defineConfig(({ mode }) => ({
|
||||||
plugins: [
|
plugins: [
|
||||||
|
@ -24,10 +23,9 @@ export default defineConfig(({ mode }) => ({
|
||||||
},
|
},
|
||||||
srcDir: "src/serviceworker",
|
srcDir: "src/serviceworker",
|
||||||
filename: "main.ts",
|
filename: "main.ts",
|
||||||
manifest: manifest,
|
manifest: {
|
||||||
pwaAssets: {
|
theme_color: "#673ab7"
|
||||||
config: true,
|
}
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
version(),
|
version(),
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in a new issue