Compare commits
	
		
			2 commits
		
	
	
		
			b4f7a863a2
			...
			85ac9a236b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 85ac9a236b | ||
|  | 71b9a60b35 | 
					 16 changed files with 364 additions and 91 deletions
				
			
		|  | @ -28,8 +28,13 @@ jobs: | |||
|       - name: Install Dependencies | ||||
|         run: pnpm i | ||||
| 
 | ||||
|       - name: Build Dist (Staging) | ||||
|         run: pnpm dist -m staging | ||||
|         if: env.GITHUB_REF_NAME == 'master' | ||||
| 
 | ||||
|       - name: Build Dist | ||||
|         run: pnpm dist | ||||
|         if: env.GITHUB_REF_NAME != 'master' | ||||
| 
 | ||||
|       - name: Depoly to Preview | ||||
|         uses: https://github.com/cloudflare/wrangler-action@v3 | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ | |||
|     "prettier": "^3.3.2", | ||||
|     "typescript": "^5.5.2", | ||||
|     "vite": "^5.3.2", | ||||
|     "vite-plugin-package-version": "^1.1.0", | ||||
|     "vite-plugin-pwa": "^0.20.0", | ||||
|     "vite-plugin-solid": "^2.10.2", | ||||
|     "vite-plugin-solid-styled": "^0.11.1", | ||||
|  |  | |||
							
								
								
									
										12
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							|  | @ -69,6 +69,9 @@ importers: | |||
|       vite: | ||||
|         specifier: ^5.3.2 | ||||
|         version: 5.3.2(@types/node@20.14.10)(lightningcss@1.25.1)(terser@5.31.2) | ||||
|       vite-plugin-package-version: | ||||
|         specifier: ^1.1.0 | ||||
|         version: 1.1.0(vite@5.3.2(@types/node@20.14.10)(lightningcss@1.25.1)(terser@5.31.2)) | ||||
|       vite-plugin-pwa: | ||||
|         specifier: ^0.20.0 | ||||
|         version: 0.20.0(vite@5.3.2(@types/node@20.14.10)(lightningcss@1.25.1)(terser@5.31.2))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0) | ||||
|  | @ -2489,6 +2492,11 @@ packages: | |||
|   validate-html-nesting@1.2.2: | ||||
|     resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==} | ||||
| 
 | ||||
|   vite-plugin-package-version@1.1.0: | ||||
|     resolution: {integrity: sha512-TPoFZXNanzcaKCIrC3e2L/TVRkkRLB6l4RPN/S7KbG7rWfyLcCEGsnXvxn6qR7fyZwXalnnSN/I9d6pSFjHpEA==} | ||||
|     peerDependencies: | ||||
|       vite: '>=2.0.0-beta.69' | ||||
| 
 | ||||
|   vite-plugin-pwa@0.20.0: | ||||
|     resolution: {integrity: sha512-/kDZyqF8KqoXRpMUQtR5Atri/7BWayW8Gp7Kz/4bfstsV6zSFTxjREbXZYL7zSuRL40HGA+o2hvUAFRmC+bL7g==} | ||||
|     engines: {node: '>=16.0.0'} | ||||
|  | @ -5280,6 +5288,10 @@ snapshots: | |||
| 
 | ||||
|   validate-html-nesting@1.2.2: {} | ||||
| 
 | ||||
|   vite-plugin-package-version@1.1.0(vite@5.3.2(@types/node@20.14.10)(lightningcss@1.25.1)(terser@5.31.2)): | ||||
|     dependencies: | ||||
|       vite: 5.3.2(@types/node@20.14.10)(lightningcss@1.25.1)(terser@5.31.2) | ||||
| 
 | ||||
|   vite-plugin-pwa@0.20.0(vite@5.3.2(@types/node@20.14.10)(lightningcss@1.25.1)(terser@5.31.2))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0): | ||||
|     dependencies: | ||||
|       debug: 4.3.5 | ||||
|  |  | |||
							
								
								
									
										12
									
								
								src/App.css
									
										
									
									
									
								
							
							
						
						
									
										12
									
								
								src/App.css
									
										
									
									
									
								
							|  | @ -1,14 +1,4 @@ | |||
| html, | ||||
| body { | ||||
|   overflow: hidden; | ||||
|   height: 100vh; | ||||
|   height: 100dvh; | ||||
| } | ||||
| 
 | ||||
| #root { | ||||
|   overflow: hidden hidden; | ||||
|   height: 100vh; | ||||
|   height: 100dvh; | ||||
| :root { | ||||
|   background-color: var(--tutu-color-surface, transparent); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -22,11 +22,15 @@ const AccountMastodonOAuth2Callback = lazy( | |||
|   () => import("./accounts/MastodonOAuth2Callback.js"), | ||||
| ); | ||||
| const TimelineHome = lazy(() => import("./timelines/Home.js")); | ||||
| const Settings = lazy(() => import("./settings/Settings.js")); | ||||
| 
 | ||||
| const Routing: Component = () => { | ||||
|   return ( | ||||
|     <Router> | ||||
|       <Route path="/" component={TimelineHome}></Route> | ||||
|       <Route path="/" component={TimelineHome}> | ||||
|         <Route path=""></Route> | ||||
|         <Route path="/settings" component={Settings}></Route> | ||||
|       </Route> | ||||
|       <Route path={"/accounts"}> | ||||
|         <Route path={"/sign-in"} component={AccountSignIn} /> | ||||
|         <Route | ||||
|  |  | |||
|  | @ -1,6 +1,11 @@ | |||
| import { persistentAtom } from "@nanostores/persistent"; | ||||
| import { createOAuthAPIClient, createRestAPIClient } from "masto"; | ||||
| import { | ||||
|   createOAuthAPIClient, | ||||
|   createRestAPIClient, | ||||
|   type mastodon, | ||||
| } from "masto"; | ||||
| import { action } from "nanostores"; | ||||
| import { createMastoClientFor } from "../masto/clients"; | ||||
| 
 | ||||
| export type Account = { | ||||
|   site: string; | ||||
|  | @ -9,6 +14,8 @@ export type Account = { | |||
|   tokenType: string; | ||||
|   scope: string; | ||||
|   createdAt: number; | ||||
| 
 | ||||
|   inf?: mastodon.v1.AccountCredentials; | ||||
| }; | ||||
| 
 | ||||
| export const $accounts = persistentAtom<Account[]>("accounts", [], { | ||||
|  | @ -75,6 +82,23 @@ export const acceptAccountViaAuthCode = action( | |||
|   }, | ||||
| ); | ||||
| 
 | ||||
| export const updateAcctInf = action( | ||||
|   $accounts, | ||||
|   "updateAcctInf", | ||||
|   async ($store, idx: number) => { | ||||
|     const o = $store.get(); | ||||
|     const client = createMastoClientFor(o[idx]); | ||||
|     const inf = await client.v1.accounts.verifyCredentials(); | ||||
|     o[idx].inf = inf; | ||||
|     $store.set(o); | ||||
|     return inf; | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
| export const signOut = action($accounts, "signOut", ($store, predicate: (acct: Account) => boolean) => { | ||||
|   $store.set($store.get().filter(a => !predicate(a))); | ||||
| }); | ||||
| 
 | ||||
| export type RegisteredApp = { | ||||
|   site: string; | ||||
|   clientId: string; | ||||
|  |  | |||
|  | @ -1,10 +1,35 @@ | |||
| import { Accessor, createResource } from "solid-js"; | ||||
| import type { mastodon } from "masto"; | ||||
| import { useSessions } from "./clients"; | ||||
| import { updateAcctInf } from "../accounts/stores"; | ||||
| 
 | ||||
| export function useAcctProfile(client: Accessor<mastodon.rest.Client>) { | ||||
|   return createResource(client, (client) => { | ||||
|     return client.v1.accounts.verifyCredentials() | ||||
|   }, { | ||||
|     name: "MastodonAccountProfile" | ||||
|   }) | ||||
|   return createResource( | ||||
|     client, | ||||
|     (client) => { | ||||
|       return client.v1.accounts.verifyCredentials(); | ||||
|     }, | ||||
|     { | ||||
|       name: "MastodonAccountProfile", | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export function useSignedInProfiles() { | ||||
|   const sessions = useSessions(); | ||||
|   const [accessor, tools] = createResource(sessions, async (all) => { | ||||
|     return Promise.all( | ||||
|       all.map(async (x, i) => ({ ...x, inf: await updateAcctInf(i) })), | ||||
|     ); | ||||
|   }); | ||||
|   return [ | ||||
|     () => { | ||||
|       if (accessor.loading) { | ||||
|         accessor(); | ||||
|         return sessions().map((x) => ({ ...x, inf: x.account.inf })); | ||||
|       } | ||||
|       return accessor(); | ||||
|     }, | ||||
|     tools, | ||||
|   ] as const; | ||||
| } | ||||
|  |  | |||
|  | @ -69,7 +69,7 @@ export function useSessions() { | |||
|   return sessions; | ||||
| } | ||||
| 
 | ||||
| export function useSessionsRw() { | ||||
| function useSessionsRw() { | ||||
|   const store = useContext(Context); | ||||
|   if (!store) { | ||||
|     throw new TypeError("sessions are not provided"); | ||||
|  |  | |||
							
								
								
									
										32
									
								
								src/material/BottomSheet.module.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/material/BottomSheet.module.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | |||
| .bottomSheet { | ||||
|   composes: surface from 'material.module.css'; | ||||
|   border: none; | ||||
|   position: absolute; | ||||
|   left: 50%; | ||||
|   top: 50%; | ||||
|   transform: translate(-50%, -50%); | ||||
|   padding: 0; | ||||
|   width: 100%; | ||||
|   max-width: 560px; | ||||
|   border-radius: 2px; | ||||
|   overscroll-behavior: contain; | ||||
| 
 | ||||
|   box-shadow: var(--tutu-shadow-e16); | ||||
| 
 | ||||
|   :global(.MuiToolbar-root) > :global(.MuiButtonBase-root):first-child { | ||||
|     color: white; | ||||
|     margin-left: -0.5em; | ||||
|     margin-right: 24px; | ||||
|   } | ||||
| 
 | ||||
|   @media (max-width: 560px) { | ||||
|     & { | ||||
|       left: 0; | ||||
|       top: 0; | ||||
|       transform: none; | ||||
|       bottom: 0; | ||||
|       height: 100vh; | ||||
|       height: 100dvh; | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										26
									
								
								src/material/BottomSheet.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/material/BottomSheet.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | |||
| import { createEffect, type ParentComponent } from "solid-js"; | ||||
| import styles from './BottomSheet.module.css' | ||||
| 
 | ||||
| export type BottomSheetProps = { | ||||
|   open?: boolean; | ||||
| }; | ||||
| 
 | ||||
| const BottomSheet: ParentComponent<BottomSheetProps> = (props) => { | ||||
|   let element: HTMLDialogElement; | ||||
| 
 | ||||
|   createEffect(() => { | ||||
|     if (props.open) { | ||||
|       if (!element.open) { | ||||
|         element.showModal(); | ||||
|       } | ||||
|     } else { | ||||
|       if (element.open) { | ||||
|         element.close(); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   return <dialog class={styles.bottomSheet} ref={element!}>{props.children}</dialog>; | ||||
| }; | ||||
| 
 | ||||
| export default BottomSheet; | ||||
|  | @ -22,9 +22,6 @@ const Scaffold: ParentComponent<ScaffoldProps> = (props) => { | |||
|   css` | ||||
|     .scaffold-content { | ||||
|       --scaffold-topbar-height: ${(topbarSize.height?.toString() ?? 0) + "px"}; | ||||
| 
 | ||||
|       height: 100%; | ||||
|       width: 100%; | ||||
|     } | ||||
| 
 | ||||
|     .topbar { | ||||
|  |  | |||
							
								
								
									
										10
									
								
								src/overrides.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/overrides.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| /// <reference types="vite/client" />
 | ||||
| 
 | ||||
| interface ImportMetaEnv { | ||||
|   readonly BUILT_AT: string; | ||||
|   readonly PACKAGE_VERSION: string; | ||||
| } | ||||
| 
 | ||||
| interface ImportMeta { | ||||
|   readonly env: ImportMetaEnv; | ||||
| } | ||||
							
								
								
									
										137
									
								
								src/settings/Settings.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								src/settings/Settings.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,137 @@ | |||
| import { createResource, For, type ParentComponent } from "solid-js"; | ||||
| import Scaffold from "../material/Scaffold.js"; | ||||
| import { | ||||
|   AppBar, | ||||
|   Divider, | ||||
|   IconButton, | ||||
|   List, | ||||
|   ListItem, | ||||
|   ListItemButton, | ||||
|   ListItemSecondaryAction, | ||||
|   ListItemText, | ||||
|   ListSubheader, | ||||
|   NativeSelect, | ||||
|   Select, | ||||
|   Switch, | ||||
|   Toolbar, | ||||
| } from "@suid/material"; | ||||
| import { Close as CloseIcon } from "@suid/icons-material"; | ||||
| import { useNavigate } from "@solidjs/router"; | ||||
| import { Title } from "../material/typography.jsx"; | ||||
| import { useSessions } from "../masto/clients.js"; | ||||
| import { css } from "solid-styled"; | ||||
| import { useSignedInProfiles } from "../masto/acct.js"; | ||||
| import { signOut, type Account } from "../accounts/stores.js"; | ||||
| import { intlFormat } from "date-fns"; | ||||
| 
 | ||||
| const Settings: ParentComponent = () => { | ||||
|   const navigate = useNavigate(); | ||||
| 
 | ||||
|   const [profiles] = useSignedInProfiles(); | ||||
| 
 | ||||
|   const doSignOut = (acct: Account) => { | ||||
|     signOut((a) => a.site === acct.site && a.accessToken === acct.accessToken); | ||||
|   }; | ||||
| 
 | ||||
|   css` | ||||
|     ul { | ||||
|       padding: 0; | ||||
|     } | ||||
| 
 | ||||
|     .setting-list { | ||||
|       padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 16px); | ||||
|     } | ||||
|   `;
 | ||||
|   return ( | ||||
|     <Scaffold | ||||
|       topbar={ | ||||
|         <AppBar position="static"> | ||||
|           <Toolbar variant="dense"> | ||||
|             <IconButton onClick={[navigate, -1]}> | ||||
|               <CloseIcon /> | ||||
|             </IconButton> | ||||
|             <Title>Settings</Title> | ||||
|           </Toolbar> | ||||
|         </AppBar> | ||||
|       } | ||||
|     > | ||||
|       <List class="setting-list" use:solid-styled> | ||||
|         <li> | ||||
|           <ul> | ||||
|             <ListSubheader>Accounts</ListSubheader> | ||||
|             <ListItem> | ||||
|               <ListItemText>All Notifications</ListItemText> | ||||
|               <ListItemSecondaryAction> | ||||
|                 <Switch /> | ||||
|               </ListItemSecondaryAction> | ||||
|             </ListItem> | ||||
|             <ListItem> | ||||
|               <ListItemText>Sign in...</ListItemText> | ||||
|             </ListItem> | ||||
|           </ul> | ||||
|           <For each={profiles()}> | ||||
|             {({ account: acct, inf }) => ( | ||||
|               <ul data-site={acct.site} data-username={inf?.username}> | ||||
|                 <ListSubheader>{`@${inf?.username ?? "..."}@${new URL(acct.site).host}`}</ListSubheader> | ||||
|                 <ListItem> | ||||
|                   <ListItemText>Notifications</ListItemText> | ||||
|                   <ListItemSecondaryAction> | ||||
|                     <Switch /> | ||||
|                   </ListItemSecondaryAction> | ||||
|                 </ListItem> | ||||
|                 <ListItemButton onClick={[doSignOut, acct]}> | ||||
|                   <ListItemText>Sign out</ListItemText> | ||||
|                 </ListItemButton> | ||||
|               </ul> | ||||
|             )} | ||||
|           </For> | ||||
|         </li> | ||||
|         <li> | ||||
|           <ListSubheader>Reading</ListSubheader> | ||||
|           <ListItem> | ||||
|             <ListItemText secondary="Regular">Fonts</ListItemText> | ||||
|           </ListItem> | ||||
|           <ListItem> | ||||
|             <ListItemText secondary="Tutu will download toots before you scroll to the position."> | ||||
|               Prefetch Toots | ||||
|             </ListItemText> | ||||
|             <ListItemSecondaryAction> | ||||
|               <Switch /> | ||||
|             </ListItemSecondaryAction> | ||||
|           </ListItem> | ||||
|         </li> | ||||
|         <li> | ||||
|           <ListSubheader>Controls</ListSubheader> | ||||
|           <ListItem> | ||||
|             <ListItemText>Optimized UI</ListItemText> | ||||
|             <ListItemSecondaryAction> | ||||
|               <NativeSelect value="auto"> | ||||
|                 <option value="auto">Tutu Decides (Mouse)</option> | ||||
|                 <option value="mouse">Mouse</option> | ||||
|                 <option value="touch">Touch</option> | ||||
|                 <option value="controlpad">Control Pad</option> | ||||
|               </NativeSelect> | ||||
|             </ListItemSecondaryAction> | ||||
|           </ListItem> | ||||
|         </li> | ||||
|         <li> | ||||
|           <ListSubheader>This Application</ListSubheader> | ||||
|           <ListItem> | ||||
|             <ListItemText secondary="Comformtable tooting experience."> | ||||
|               About Tutu | ||||
|             </ListItemText> | ||||
|           </ListItem> | ||||
|           <ListItem> | ||||
|             <ListItemText | ||||
|               secondary={`Using v${import.meta.env.PACKAGE_VERSION} (built on ${intlFormat(import.meta.env.BUILT_AT)}, ${import.meta.env.MODE})`} | ||||
|             > | ||||
|               No updates | ||||
|             </ListItemText> | ||||
|           </ListItem> | ||||
|         </li> | ||||
|       </List> | ||||
|     </Scaffold> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export default Settings; | ||||
|  | @ -6,9 +6,11 @@ import { | |||
|   Show, | ||||
|   untrack, | ||||
|   onMount, | ||||
|   type ParentComponent, | ||||
|   children, | ||||
| } from "solid-js"; | ||||
| import { useDocumentTitle } from "../utils"; | ||||
| import {  useSessions } from "../masto/clients"; | ||||
| import { useSessions } from "../masto/clients"; | ||||
| import { type mastodon } from "masto"; | ||||
| import Scaffold from "../material/Scaffold"; | ||||
| import { | ||||
|  | @ -32,6 +34,7 @@ import Tab from "../material/Tab"; | |||
| import { Create as CreateTootIcon } from "@suid/icons-material"; | ||||
| import { useTimeline } from "../masto/timelines"; | ||||
| import { makeEventListener } from "@solid-primitives/event-listener"; | ||||
| import BottomSheet from "../material/BottomSheet"; | ||||
| 
 | ||||
| const TimelinePanel: Component<{ | ||||
|   client: mastodon.rest.Client; | ||||
|  | @ -145,7 +148,7 @@ const TimelinePanel: Component<{ | |||
|   ); | ||||
| }; | ||||
| 
 | ||||
| const Home: Component = () => { | ||||
| const Home: ParentComponent = (props) => { | ||||
|   let panelList: HTMLDivElement; | ||||
|   useDocumentTitle("Timelines"); | ||||
|   const now = createTimeSource(); | ||||
|  | @ -162,6 +165,8 @@ const Home: Component = () => { | |||
|     number, | ||||
|   ]); | ||||
| 
 | ||||
|   const child = children(() => props.children) | ||||
| 
 | ||||
|   let scrollEventLockReleased = true; | ||||
| 
 | ||||
|   const recalculateTabIndicator = () => { | ||||
|  | @ -221,7 +226,7 @@ const Home: Component = () => { | |||
|   const onTabClick = (idx: number) => { | ||||
|     const items = panelList.querySelectorAll(".tab-panel"); | ||||
|     if (items.length > idx) { | ||||
|       items.item(idx).scrollIntoView({ behavior: "smooth" }); | ||||
|       items.item(idx).scrollIntoView({ block: "nearest", behavior: "smooth" }); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|  | @ -235,10 +240,6 @@ const Home: Component = () => { | |||
|       max-height: calc(100dvh - var(--scaffold-topbar-height, 0px)); | ||||
|       scroll-snap-align: center; | ||||
| 
 | ||||
|       &:not(.active) { | ||||
|         overflow: hidden; | ||||
|       } | ||||
| 
 | ||||
|       @media (max-width: 600px) { | ||||
|         padding: 0; | ||||
|       } | ||||
|  | @ -261,71 +262,74 @@ const Home: Component = () => { | |||
|   `;
 | ||||
| 
 | ||||
|   return ( | ||||
|     <Scaffold | ||||
|       topbar={ | ||||
|         <AppBar position="static"> | ||||
|           <Toolbar variant="dense" class="responsive"> | ||||
|             <Tabs onFocusChanged={setCurrentFocusOn} offset={panelOffset()}> | ||||
|               <Tab focus={isTabFocus(0)} onClick={[onTabClick, 0]}> | ||||
|                 Home | ||||
|               </Tab> | ||||
|               <Tab focus={isTabFocus(1)} onClick={[onTabClick, 1]}> | ||||
|                 Trending | ||||
|               </Tab> | ||||
|               <Tab focus={isTabFocus(2)} onClick={[onTabClick, 2]}> | ||||
|                 Public | ||||
|               </Tab> | ||||
|             </Tabs> | ||||
|             <ProfileMenuButton profile={profile()}> | ||||
|               <MenuItem onClick={(e) => setPrefetching((x) => !x)}> | ||||
|                 <ListItemText>Prefetch Toots</ListItemText> | ||||
|                 <ListItemSecondaryAction> | ||||
|                   <Switch checked={prefetching()}></Switch> | ||||
|                 </ListItemSecondaryAction> | ||||
|               </MenuItem> | ||||
|             </ProfileMenuButton> | ||||
|           </Toolbar> | ||||
|         </AppBar> | ||||
|       } | ||||
|       fab={ | ||||
|         <Fab color="secondary"> | ||||
|           <CreateTootIcon /> | ||||
|         </Fab> | ||||
|       } | ||||
|     > | ||||
|       <TimeSourceProvider value={now}> | ||||
|         <div class="panel-list" ref={panelList!}> | ||||
|           <div class="tab-panel"> | ||||
|             <div> | ||||
|               <TimelinePanel | ||||
|                 client={client()} | ||||
|                 name="home" | ||||
|                 prefetch={prefetching()} | ||||
|               /> | ||||
|     <> | ||||
|       <Scaffold | ||||
|         topbar={ | ||||
|           <AppBar position="static"> | ||||
|             <Toolbar variant="dense" class="responsive"> | ||||
|               <Tabs onFocusChanged={setCurrentFocusOn} offset={panelOffset()}> | ||||
|                 <Tab focus={isTabFocus(0)} onClick={[onTabClick, 0]}> | ||||
|                   Home | ||||
|                 </Tab> | ||||
|                 <Tab focus={isTabFocus(1)} onClick={[onTabClick, 1]}> | ||||
|                   Trending | ||||
|                 </Tab> | ||||
|                 <Tab focus={isTabFocus(2)} onClick={[onTabClick, 2]}> | ||||
|                   Public | ||||
|                 </Tab> | ||||
|               </Tabs> | ||||
|               <ProfileMenuButton profile={profile()}> | ||||
|                 <MenuItem onClick={(e) => setPrefetching((x) => !x)}> | ||||
|                   <ListItemText>Prefetch Toots</ListItemText> | ||||
|                   <ListItemSecondaryAction> | ||||
|                     <Switch checked={prefetching()}></Switch> | ||||
|                   </ListItemSecondaryAction> | ||||
|                 </MenuItem> | ||||
|               </ProfileMenuButton> | ||||
|             </Toolbar> | ||||
|           </AppBar> | ||||
|         } | ||||
|         fab={ | ||||
|           <Fab color="secondary"> | ||||
|             <CreateTootIcon /> | ||||
|           </Fab> | ||||
|         } | ||||
|       > | ||||
|         <TimeSourceProvider value={now}> | ||||
|           <div class="panel-list" ref={panelList!}> | ||||
|             <div class="tab-panel"> | ||||
|               <div> | ||||
|                 <TimelinePanel | ||||
|                   client={client()} | ||||
|                   name="home" | ||||
|                   prefetch={prefetching()} | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="tab-panel"> | ||||
|             <div> | ||||
|               <TimelinePanel | ||||
|                 client={client()} | ||||
|                 name="trends" | ||||
|                 prefetch={prefetching()} | ||||
|               /> | ||||
|             <div class="tab-panel"> | ||||
|               <div> | ||||
|                 <TimelinePanel | ||||
|                   client={client()} | ||||
|                   name="trends" | ||||
|                   prefetch={prefetching()} | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="tab-panel"> | ||||
|             <div> | ||||
|               <TimelinePanel | ||||
|                 client={client()} | ||||
|                 name="public" | ||||
|                 prefetch={prefetching()} | ||||
|               /> | ||||
|             <div class="tab-panel"> | ||||
|               <div> | ||||
|                 <TimelinePanel | ||||
|                   client={client()} | ||||
|                   name="public" | ||||
|                   prefetch={prefetching()} | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div></div> | ||||
|           </div> | ||||
|           <div></div> | ||||
|         </div> | ||||
|       </TimeSourceProvider> | ||||
|     </Scaffold> | ||||
|         </TimeSourceProvider> | ||||
|         <BottomSheet open={!!child()}>{child()}</BottomSheet> | ||||
|       </Scaffold> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ import { | |||
|   Star as LikeIcon, | ||||
|   FeaturedPlayList as ListIcon, | ||||
| } from "@suid/icons-material"; | ||||
| import { A } from "@solidjs/router"; | ||||
| 
 | ||||
| const ProfileMenuButton: ParentComponent<{ | ||||
|   profile?: { displayName: string; avatar: string; username: string }; | ||||
|  | @ -107,7 +108,7 @@ const ProfileMenuButton: ParentComponent<{ | |||
|           {props.children} | ||||
|           <Divider /> | ||||
|         </Show> | ||||
|         <MenuItem> | ||||
|         <MenuItem component={A} href="/settings" onClick={onClose}> | ||||
|           <ListItemIcon> | ||||
|             <SettingsIcon /> | ||||
|           </ListItemIcon> | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import solid from "vite-plugin-solid"; | |||
| 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"; | ||||
| 
 | ||||
| export default defineConfig(({ mode }) => ({ | ||||
|   plugins: [ | ||||
|  | @ -17,7 +18,11 @@ export default defineConfig(({ mode }) => ({ | |||
|     VitePWA({ | ||||
|       registerType: "autoUpdate", | ||||
|     }), | ||||
|     version(), | ||||
|   ], | ||||
|   define: { | ||||
|     "import.meta.env.BUILT_AT": `"${new Date().toISOString()}"`, | ||||
|   }, | ||||
|   css: { | ||||
|     devSourcemap: true, | ||||
|   }, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue