Compare commits

..

10 commits

Author SHA1 Message Date
thislight
67720a6f95
added createTimelineChunk 2024-10-13 16:09:11 +08:00
thislight
6592d71a46
createTimeline: fix viewport and dupelication 2024-10-13 15:52:42 +08:00
thislight
82a9c9b468
adjust thread visual 2024-10-12 19:48:34 +08:00
thislight
e16fdbe579
MediaAttachmentGrid: fix bad waterfall 2024-10-12 19:06:23 +08:00
thislight
196a7d10ac
PullDownToRefresh: fix the trigger 2024-10-12 19:05:52 +08:00
thislight
1a1fee8918
first attempt of createTimeline in TimelinePanel
- known bug: the paging is failed
2024-10-11 21:26:33 +08:00
thislight
d82b32732c
TootBottomSheet: minor clean up 2024-10-11 18:27:25 +08:00
thislight
7181fdf154
TrendTimelinePanel: improved error handling 2024-10-10 20:32:54 +08:00
thislight
a734c1dc5a
move TimelinePanel to separated files
- added TrendTimelinePanel for trending tab
2024-10-10 19:51:10 +08:00
thislight
675e45b44a
added createTimelineSnapshot 2024-10-10 19:51:10 +08:00
5 changed files with 85 additions and 128 deletions

View file

@ -13,31 +13,3 @@ You can debug on the Safari on iOS only if you have mac (and run macOS). The cer
## Hero Animation won't work (after hot reload) ## Hero Animation won't work (after hot reload)
That's a known issue. Hot reload won't refresh the module sets the hero cache. Refresh the whole page and it should work. That's a known issue. Hot reload won't refresh the module sets the hero cache. Refresh the whole page and it should work.
## The components don't react to the change as I setting the store, until the page reloaded
The `WritableAtom<unknwon>.set` might do an equals check. You must set a different object to ensure the atom sending a notify.
The code below may not notify the change:
```ts
export function updateAcctInf(idx: number) {
const o = $accounts.get();
// ...
o[idx].inf = inf;
$accounts.set(o);
}
```
Instead, set a new object:
```ts
export function updateAcctInf(idx: number) {
const o = $accounts.get();
// ...
o[idx] = Object.assign({}, o[idx], { inf });
$accounts.set(Array.from(o));
}
```
Ja, the code is weird, but that's the best we know. Anyway, you need new object on the path of your changed value.

View file

@ -2,9 +2,8 @@ import { Route, Router } from "@solidjs/router";
import { ThemeProvider } from "@suid/material"; import { ThemeProvider } from "@suid/material";
import { import {
Component, Component,
createEffect,
createMemo,
createRenderEffect, createRenderEffect,
createSignal,
ErrorBoundary, ErrorBoundary,
lazy, lazy,
onCleanup, onCleanup,
@ -13,8 +12,9 @@ import { useRootTheme } from "./material/mui.js";
import { import {
Provider as ClientProvider, Provider as ClientProvider,
createMastoClientFor, createMastoClientFor,
type Session,
} from "./masto/clients.js"; } from "./masto/clients.js";
import { $accounts, updateAcctInf } from "./accounts/stores.js"; import { $accounts } from "./accounts/stores.js";
import { useStore } from "@nanostores/solid"; import { useStore } from "@nanostores/solid";
import { DateFnScope, useLanguage } from "./platform/i18n.jsx"; import { DateFnScope, useLanguage } from "./platform/i18n.jsx";
@ -28,7 +28,6 @@ const TootBottomSheet = lazy(() => import("./timelines/TootBottomSheet.js"));
const MotionSettings = lazy(() => import("./settings/Motions.js")); const MotionSettings = lazy(() => import("./settings/Motions.js"));
const LanguageSettings = lazy(() => import("./settings/Language.js")); const LanguageSettings = lazy(() => import("./settings/Language.js"));
const RegionSettings = lazy(() => import("./settings/Region.jsx")); const RegionSettings = lazy(() => import("./settings/Region.jsx"));
const UnexpectedError = lazy(() => import("./UnexpectedError.js"));
const Routing: Component = () => { const Routing: Component = () => {
return ( return (
@ -57,30 +56,14 @@ const Routing: Component = () => {
const App: Component = () => { const App: Component = () => {
const theme = useRootTheme(); const theme = useRootTheme();
const accts = useStore($accounts); const accts = useStore($accounts);
const clientStore = createSignal<Session[]>([]);
const lang = useLanguage(); const lang = useLanguage();
const clients = createMemo(() => { createRenderEffect(() => {
return accts().map((x) => ({ const [, setClients] = clientStore;
account: x, setClients(
client: createMastoClientFor(x), accts().map((x) => ({ account: x, client: createMastoClientFor(x) })),
})); );
});
createEffect(() => {
const neededUpdates = accts()
.map((x, i) => [i, x] as const)
.filter(([, x]) => !x.inf);
if (neededUpdates.length > 0) {
// FIXME: we might need some kind of concurrent control
Promise.all(neededUpdates.map(([i]) => updateAcctInf(i))).then(
(x) => {
console.info("acct info updated for %d acct(s)", x.length);
},
(reason) => {
console.error("acct info update is fail", reason);
},
);
}
}); });
createRenderEffect(() => { createRenderEffect(() => {
@ -93,6 +76,8 @@ const App: Component = () => {
root.removeAttribute("lang"); root.removeAttribute("lang");
}); });
const UnexpectedError = lazy(() => import("./UnexpectedError.js"));
return ( return (
<ErrorBoundary <ErrorBoundary
fallback={(err, reset) => { fallback={(err, reset) => {
@ -102,7 +87,7 @@ const App: Component = () => {
> >
<ThemeProvider theme={theme()}> <ThemeProvider theme={theme()}>
<DateFnScope> <DateFnScope>
<ClientProvider value={clients}> <ClientProvider value={clientStore}>
<Routing /> <Routing />
</ClientProvider> </ClientProvider>
</DateFnScope> </DateFnScope>

View file

@ -59,32 +59,32 @@ async function oauth2TokenViaAuthCode(app: RegisteredApp, authCode: string) {
export async function acceptAccountViaAuthCode(site: string, authCode: string) { export async function acceptAccountViaAuthCode(site: string, authCode: string) {
const $store = $accounts; const $store = $accounts;
const app = $registeredApps.get()[site]; const app = $registeredApps.get()[site];
if (!app) { if (!app) {
throw TypeError("application not found"); throw TypeError("application not found");
} }
const token = await oauth2TokenViaAuthCode(app, authCode); const token = await oauth2TokenViaAuthCode(app, authCode);
const acct = { const acct = {
site: app.site, site: app.site,
accessToken: token.access_token, accessToken: token.access_token,
tokenType: token.token_type, tokenType: token.token_type,
scope: token.scope, scope: token.scope,
createdAt: token.created_at * 1000, createdAt: token.created_at * 1000,
}; };
const all = [...$store.get(), acct]; const all = [...$store.get(), acct];
$store.set(all); $store.set(all);
return acct; return acct;
} }
export async function updateAcctInf(idx: number) { export async function updateAcctInf(idx: number) {
const o = $accounts.get(); const o = $accounts.get();
const client = createMastoClientFor(o[idx]); const client = createMastoClientFor(o[idx]);
const inf = await client.v1.accounts.verifyCredentials(); const inf = await client.v1.accounts.verifyCredentials();
o[idx] = Object.assign({}, o[idx], { inf }); o[idx].inf = inf;
$accounts.set(Array.from(o)); $accounts.set(o);
return inf; return inf;
} }
export function signOut(predicate: (acct: Account) => boolean) { export function signOut(predicate: (acct: Account) => boolean) {
@ -131,57 +131,57 @@ async function getAppAccessToken(app: RegisteredApp) {
export async function getOrRegisterApp(site: string, redirectUrl: string) { export async function getOrRegisterApp(site: string, redirectUrl: string) {
const $store = $registeredApps; const $store = $registeredApps;
const all = $store.get(); const all = $store.get();
const savedApp = all[site]; const savedApp = all[site];
if (savedApp && savedApp.redirectUrl === redirectUrl) { if (savedApp && savedApp.redirectUrl === redirectUrl) {
const appAccessToken = await getAppAccessToken(savedApp); const appAccessToken = await getAppAccessToken(savedApp);
if (appAccessToken) { if (appAccessToken) {
const client = createRestAPIClient({ const client = createRestAPIClient({
url: site,
accessToken: appAccessToken,
});
try {
const verify = await client.v1.apps.verifyCredentials();
Object.assign(savedApp, {
vapidKey: verify.vapidKey,
});
const oauthClient = createOAuthAPIClient({
url: site, url: site,
accessToken: appAccessToken, accessToken: appAccessToken,
}); });
try { try {
await oauthClient.revoke({ const verify = await client.v1.apps.verifyCredentials();
clientId: savedApp.clientId, Object.assign(savedApp, {
clientSecret: savedApp.clientSecret, vapidKey: verify.vapidKey,
token: appAccessToken,
}); });
} catch {} const oauthClient = createOAuthAPIClient({
return savedApp; url: site,
} finally { accessToken: appAccessToken,
$store.set(all); });
try {
await oauthClient.revoke({
clientId: savedApp.clientId,
clientSecret: savedApp.clientSecret,
token: appAccessToken,
});
} catch {}
return savedApp;
} finally {
$store.set(all);
}
} }
} }
}
const client = createRestAPIClient({ const client = createRestAPIClient({
url: site, url: site,
}); });
const app = await client.v1.apps.create({ const app = await client.v1.apps.create({
clientName: "TuTu", clientName: "TuTu",
website: "https://github.com/thislight/tutu", website: "https://github.com/thislight/tutu",
redirectUris: redirectUrl, redirectUris: redirectUrl,
scopes: "read write push", scopes: "read write push",
}); });
if (!app.clientId || !app.clientSecret) { if (!app.clientId || !app.clientSecret) {
return null; return null;
} }
all[site] = { all[site] = {
site, site,
clientId: app.clientId, clientId: app.clientId,
clientSecret: app.clientSecret, clientSecret: app.clientSecret,
vapidKey: app.vapidKey ?? undefined, vapidKey: app.vapidKey ?? undefined,
redirectUrl: redirectUrl, redirectUrl: redirectUrl,
scope: "read write push", scope: "read write push",
}; };
$store.set(all); $store.set(all);
return all[site]; return all[site];
} }

View file

@ -49,12 +49,12 @@ export type Session = {
client: mastodon.rest.Client; client: mastodon.rest.Client;
}; };
const Context = /* @__PURE__ */ createContext<Accessor<readonly Readonly<Session>[]>>(); const Context = /* @__PURE__ */ createContext<Signal<Session[]>>();
export const Provider = Context.Provider; export const Provider = Context.Provider;
export function useSessions() { export function useSessions() {
const sessions = useSessionsRaw(); const [sessions] = useSessionsRw();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
@ -69,7 +69,7 @@ export function useSessions() {
return sessions; return sessions;
} }
function useSessionsRaw() { function useSessionsRw() {
const store = useContext(Context); const store = useContext(Context);
if (!store) { if (!store) {
throw new TypeError("sessions are not provided"); throw new TypeError("sessions are not provided");

View file

@ -118,12 +118,12 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
animation = element.animate( animation = element.animate(
{ {
top: [`${rect.top}px`, `${rect.top}px`], top: [rect.top, rect.top],
left: reserve left: reserve
? [`${rect.left}px`, `${window.innerWidth}px`] ? [`${rect.left}px`, `${window.innerWidth}px`]
: [`${window.innerWidth}px`, `${rect.left}px`], : [`${window.innerWidth}px`, `${rect.left}px`],
width: [`${rect.width}px`, `${rect.width}px`], width: [rect.width, rect.width],
height: [`${rect.height}px`, `${rect.height}px`], height: [rect.height, rect.height],
}, },
{ easing, duration }, { easing, duration },
); );
@ -151,12 +151,12 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
animation = element.animate( animation = element.animate(
{ {
left: [`${rect.left}px`, `${rect.left}px`], left: [rect.left, rect.left],
top: reserve top: reserve
? [`${rect.top}px`, `${window.innerHeight}px`] ? [`${rect.top}px`, `${window.innerHeight}px`]
: [`${window.innerHeight}px`, `${rect.top}px`], : [`${window.innerHeight}px`, `${rect.top}px`],
width: [`${rect.width}px`, `${rect.width}px`], width: [rect.width, rect.width],
height: [`${rect.height}px`, `${rect.height}px`], height: [rect.height, rect.height],
}, },
{ easing, duration }, { easing, duration },
); );