Compare commits
	
		
			3 commits
		
	
	
		
			bea1d6abfa
			...
			7eb55e2a14
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 7eb55e2a14 | ||
|  | 29b8b5307e | ||
|  | cacca17dd8 | 
					 11 changed files with 242 additions and 21 deletions
				
			
		
							
								
								
									
										
											BIN
										
									
								
								bun.lockb
									
										
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bun.lockb
									
										
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										26
									
								
								docs/optimizing.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								docs/optimizing.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -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. | ||||
							
								
								
									
										10
									
								
								manifest.config.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								manifest.config.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| 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,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", | ||||
|  |  | |||
							
								
								
									
										133
									
								
								public/logo.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								public/logo.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,133 @@ | |||
| <?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> | ||||
| After Width: | Height: | Size: 5.1 KiB | 
							
								
								
									
										2
									
								
								public/robots.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								public/robots.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| User-agent: * | ||||
| Allow: / | ||||
							
								
								
									
										12
									
								
								pwa-assets.config.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								pwa-assets.config.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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'] | ||||
| }) | ||||
|  | @ -78,7 +78,7 @@ async function importDateFnLocale(tag: string): Promise<Locale> { | |||
|     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: | ||||
|  |  | |||
|  | @ -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)", | ||||
|             }} | ||||
|           > | ||||
|  |  | |||
|  | @ -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<HTMLDivElement>; | ||||
|  | @ -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<string>(); | ||||
|   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 ( | ||||
|     <ErrorBoundary | ||||
|       fallback={(err, reset) => { | ||||
|  | @ -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, | ||||
|                     ); | ||||
|                   } | ||||
|                 }} | ||||
|               /> | ||||
|  |  | |||
|  | @ -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(), | ||||
|   ], | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue