Compare commits
5 commits
cd02dc2053
...
66366e6486
Author | SHA1 | Date | |
---|---|---|---|
|
66366e6486 | ||
|
94088768ba | ||
|
4b17c426ab | ||
|
93b4cd065a | ||
|
f06a7a6da1 |
31 changed files with 380 additions and 264 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
@ -40,7 +40,8 @@
|
|||
"masto": "^6.8.0",
|
||||
"nanostores": "^0.9.5",
|
||||
"solid-js": "^1.8.18",
|
||||
"solid-styled": "^0.11.1"
|
||||
"solid-styled": "^0.11.1",
|
||||
"stacktrace-js": "^2.0.2"
|
||||
},
|
||||
"packageManager": "bun@1.1.21"
|
||||
}
|
||||
|
|
|
@ -53,11 +53,13 @@ const App: Component = () => {
|
|||
);
|
||||
});
|
||||
|
||||
const UnexpectedError = lazy(() => import("./UnexpectedError.js"))
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
fallback={(err, reset) => {
|
||||
console.error(err);
|
||||
return <></>;
|
||||
return <UnexpectedError error={err} />;
|
||||
}}
|
||||
>
|
||||
<ThemeProvider theme={theme()}>
|
||||
|
|
40
src/UnexpectedError.tsx
Normal file
40
src/UnexpectedError.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { Button } from '@suid/material';
|
||||
import {Component, createResource} from 'solid-js'
|
||||
import { css } from 'solid-styled';
|
||||
|
||||
const UnexpectedError: Component<{error?: any}> = (props) => {
|
||||
|
||||
const [errorMsg] = createResource(() => props.error, async (err) => {
|
||||
if (err instanceof Error) {
|
||||
const mod = await import('stacktrace-js')
|
||||
const stacktrace = await mod.fromError(err)
|
||||
const strackMsg = stacktrace.map(entry => `${entry.functionName ?? "<unknown>"}@${entry.fileName}:(${entry.lineNumber}:${entry.columnNumber})`).join('\n')
|
||||
return `${err.name}: ${err.message}\n${strackMsg}`
|
||||
}
|
||||
|
||||
return err.toString()
|
||||
})
|
||||
|
||||
css`
|
||||
main {
|
||||
padding: calc(var(--safe-area-inset-top) + 20px) calc(var(--safe-area-inset-right) + 20px) calc(var(--safe-area-inset-bottom) + 20px) calc(var(--safe-area-inset-left) + 20px);
|
||||
}
|
||||
`
|
||||
|
||||
return <main>
|
||||
<h1>Oh, it is our fault.</h1>
|
||||
<p>There is an unexpected error in our app, and it's not your fault.</p>
|
||||
<p>You can reload to see if this guy is gone. If you meet this guy repeatly, please report to us.</p>
|
||||
<div>
|
||||
<Button onClick={() => window.location.reload()}>Reload</Button>
|
||||
</div>
|
||||
<details>
|
||||
<summary>{errorMsg.loading ? 'Generating ' : " "}Technical Infomation (Bring to us if you report the problem)</summary>
|
||||
<pre>
|
||||
{errorMsg()}
|
||||
</pre>
|
||||
</details>
|
||||
</main>
|
||||
}
|
||||
|
||||
export default UnexpectedError;
|
|
@ -44,12 +44,12 @@ const MastodonOAuth2Callback: Component = () => {
|
|||
setDocumentTitle(`Back from ${ins.title}...`);
|
||||
setSiteTitle(ins.title);
|
||||
|
||||
const srcset = []
|
||||
const srcset = [];
|
||||
if (ins.thumbnail.versions["@1x"]) {
|
||||
srcset.push(`${ins.thumbnail.versions["@1x"]} 1x`)
|
||||
srcset.push(`${ins.thumbnail.versions["@1x"]} 1x`);
|
||||
}
|
||||
if (ins.thumbnail.versions["@2x"]) {
|
||||
srcset.push(`${ins.thumbnail.versions["@2x"]} 2x`)
|
||||
srcset.push(`${ins.thumbnail.versions["@2x"]} 2x`);
|
||||
}
|
||||
|
||||
setSiteImg({
|
||||
|
@ -66,8 +66,8 @@ const MastodonOAuth2Callback: Component = () => {
|
|||
onGoingOAuth2Process,
|
||||
params.code,
|
||||
);
|
||||
$settings.setKey('onGoingOAuth2Process', undefined)
|
||||
navigate('/', {replace: true})
|
||||
$settings.setKey("onGoingOAuth2Process", undefined);
|
||||
navigate("/", { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -95,18 +95,27 @@ const MastodonOAuth2Callback: Component = () => {
|
|||
<div class={cards.layoutCentered}>
|
||||
<div class={cards.card} aria-busy="true" aria-describedby={progressId}>
|
||||
<LinearProgress
|
||||
class={[cards.cardNoPad, cards.cardGutSkip].join(' ')}
|
||||
class={[cards.cardNoPad, cards.cardGutSkip].join(" ")}
|
||||
id={progressId}
|
||||
aria-labelledby={titleId}
|
||||
/>
|
||||
<Show when={siteImg()} fallback={<i aria-busy="true" aria-label="Preparing image..." style={{"height": "235px", display: "block"}}></i>}>
|
||||
<Show
|
||||
when={siteImg()}
|
||||
fallback={
|
||||
<i
|
||||
aria-busy="true"
|
||||
aria-label="Preparing image..."
|
||||
style={{ height: "235px", display: "block" }}
|
||||
></i>
|
||||
}
|
||||
>
|
||||
<Img
|
||||
src={siteImg()?.src}
|
||||
srcset={siteImg()?.srcset}
|
||||
blurhash={siteImg()?.blurhash}
|
||||
class={[cards.cardNoPad, cards.cardGutSkip].join(' ')}
|
||||
class={[cards.cardNoPad, cards.cardGutSkip].join(" ")}
|
||||
alt={`Banner image for ${siteTitle()}`}
|
||||
style={{"height": "235px", "display": "block"}}
|
||||
style={{ height: "235px", display: "block" }}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
|
@ -114,7 +123,8 @@ const MastodonOAuth2Callback: Component = () => {
|
|||
Contracting {siteTitle}...
|
||||
</Title>
|
||||
<p>
|
||||
If this page stays too long, you can close this page and sign in again.
|
||||
If this page stays too long, you can close this page and sign in
|
||||
again.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -69,8 +69,8 @@ const SignIn: Component = () => {
|
|||
});
|
||||
|
||||
onMount(() => {
|
||||
$settings.setKey('onGoingOAuth2Process', undefined)
|
||||
})
|
||||
$settings.setKey("onGoingOAuth2Process", undefined);
|
||||
});
|
||||
|
||||
const onStartOAuth2 = async (e: Event) => {
|
||||
e.preventDefault();
|
||||
|
@ -107,7 +107,7 @@ const SignIn: Component = () => {
|
|||
for (const [k, v] of Object.entries(args)) {
|
||||
searches.set(k, v);
|
||||
}
|
||||
$settings.setKey("onGoingOAuth2Process", url)
|
||||
$settings.setKey("onGoingOAuth2Process", url);
|
||||
window.location.href = authStart.toString();
|
||||
} catch (e) {
|
||||
setServerUrlHelperText(
|
||||
|
|
|
@ -95,9 +95,13 @@ export const updateAcctInf = action(
|
|||
},
|
||||
);
|
||||
|
||||
export const signOut = action($accounts, "signOut", ($store, predicate: (acct: Account) => boolean) => {
|
||||
$store.set($store.get().filter(a => !predicate(a)));
|
||||
});
|
||||
export const signOut = action(
|
||||
$accounts,
|
||||
"signOut",
|
||||
($store, predicate: (acct: Account) => boolean) => {
|
||||
$store.set($store.get().filter((a) => !predicate(a)));
|
||||
},
|
||||
);
|
||||
|
||||
export type RegisteredApp = {
|
||||
site: string;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {render} from 'solid-js/web'
|
||||
import App from './App.js'
|
||||
import "./material/theme.css"
|
||||
import { render } from "solid-js/web";
|
||||
import App from "./App.js";
|
||||
import "./material/theme.css";
|
||||
|
||||
render(() => <App />, document.getElementById("root")!)
|
||||
render(() => <App />, document.getElementById("root")!);
|
||||
|
|
|
@ -12,9 +12,7 @@ type Timeline = {
|
|||
}): mastodon.Paginator<mastodon.v1.Status[], unknown>;
|
||||
};
|
||||
|
||||
export function useTimeline(
|
||||
timeline: Accessor<Timeline>,
|
||||
) {
|
||||
export function useTimeline(timeline: Accessor<Timeline>) {
|
||||
let minId: string | undefined;
|
||||
let maxId: string | undefined;
|
||||
let otl: Timeline | undefined;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.bottomSheet {
|
||||
composes: surface from 'material.module.css';
|
||||
composes: surface from "material.module.css";
|
||||
border: none;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
|
@ -22,7 +22,7 @@
|
|||
@media (max-width: 560px) {
|
||||
& {
|
||||
left: 0;
|
||||
top: 0;
|
||||
top: var(--safe-area-inset-top, 0);
|
||||
transform: none;
|
||||
bottom: 0;
|
||||
height: 100vh;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { createEffect, type ParentComponent } from "solid-js";
|
||||
import styles from './BottomSheet.module.css'
|
||||
import styles from "./BottomSheet.module.css";
|
||||
|
||||
export type BottomSheetProps = {
|
||||
open?: boolean;
|
||||
|
@ -20,7 +20,11 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
|
|||
}
|
||||
});
|
||||
|
||||
return <dialog class={styles.bottomSheet} ref={element!}>{props.children}</dialog>;
|
||||
return (
|
||||
<dialog class={styles.bottomSheet} ref={element!}>
|
||||
{props.children}
|
||||
</dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default BottomSheet;
|
||||
|
|
|
@ -9,12 +9,12 @@ import materialStyles from "./material.module.css";
|
|||
const Button: Component<JSX.ButtonHTMLAttributes<HTMLButtonElement>> = (
|
||||
props,
|
||||
) => {
|
||||
const [managed, passthough] = splitProps(props, ["class", 'type']);
|
||||
const [managed, passthough] = splitProps(props, ["class", "type"]);
|
||||
const classes = () =>
|
||||
managed.class
|
||||
? [materialStyles.button, managed.class].join(" ")
|
||||
: materialStyles.button;
|
||||
const type = () => managed.type ?? 'button'
|
||||
const type = () => managed.type ?? "button";
|
||||
return <button type={type()} class={classes()} {...passthough}></button>;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.card {
|
||||
composes: surface from 'material.module.css';
|
||||
composes: surface from "material.module.css";
|
||||
border-radius: 2px;
|
||||
box-shadow: var(--tutu-shadow-e2);
|
||||
transition: var(--tutu-transition-shadow);
|
||||
|
@ -28,7 +28,6 @@
|
|||
margin-bottom: var(--card-gut, 20px);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.layoutCentered {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.textfield {
|
||||
composes: touchTarget from 'material.module.css';
|
||||
composes: touchTarget from "material.module.css";
|
||||
|
||||
--border-color: var(--tutu-color-inactive-on-surface);
|
||||
--active-border-color: var(--tutu-color-primary);
|
||||
|
@ -11,7 +11,8 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
&.error, &:has(>input[aria-invalid="true"]) {
|
||||
&.error,
|
||||
&:has(> input[aria-invalid="true"]) {
|
||||
&:not(:focus-within) {
|
||||
--border-color: var(--tutu-color-error-on-surface);
|
||||
--label-color: var(--tutu-color-error-on-surface);
|
||||
|
@ -23,7 +24,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
position: relative;
|
||||
|
||||
& > label {
|
||||
|
@ -31,7 +31,10 @@
|
|||
left: 0;
|
||||
bottom: calc(10px + var(--bottom-height, 0px));
|
||||
color: var(--label-color);
|
||||
transition: bottom .2s ease-in-out, font-size .2s ease-in-out, color .2s ease-in-out;
|
||||
transition:
|
||||
bottom 0.2s ease-in-out,
|
||||
font-size 0.2s ease-in-out,
|
||||
color 0.2s ease-in-out;
|
||||
cursor: text;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
@ -40,13 +43,14 @@
|
|||
bottom: calc(100% - 0.8125rem);
|
||||
}
|
||||
|
||||
&:focus-within>label, &.float-label>label {
|
||||
&:focus-within > label,
|
||||
&.float-label > label {
|
||||
bottom: calc(100% - 0.8125rem);
|
||||
color: var(--active-label-color);
|
||||
}
|
||||
|
||||
&>input[type='text'],
|
||||
&>input[type='password'] {
|
||||
& > input[type="text"],
|
||||
& > input[type="password"] {
|
||||
border: none;
|
||||
outline: none;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
@ -54,7 +58,7 @@
|
|||
padding-top: 16px;
|
||||
padding-bottom: 8px;
|
||||
margin-bottom: 1px;
|
||||
transition: border-color .2s ease-in-out;
|
||||
transition: border-color 0.2s ease-in-out;
|
||||
|
||||
&:focus {
|
||||
border-bottom: 2px solid var(--active-border-color);
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
}
|
||||
|
||||
.button {
|
||||
composes: buttonText from './typography.module.css';
|
||||
composes: buttonText from "./typography.module.css";
|
||||
composes: touchTarget;
|
||||
|
||||
border: none;
|
||||
|
@ -18,7 +18,9 @@
|
|||
color: var(--tutu-color-primary);
|
||||
font-family: inherit;
|
||||
|
||||
&:focus,&:hover,&:focus-visible {
|
||||
&:focus,
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
background-color: var(--tutu-color-surface-dd);
|
||||
}
|
||||
|
||||
|
@ -31,15 +33,19 @@
|
|||
color: var(--tutu-color-on-primary);
|
||||
}
|
||||
|
||||
&:disabled, &[aria-disabled]:not([aria-disabled="false"]) {
|
||||
&:disabled,
|
||||
&[aria-disabled]:not([aria-disabled="false"]) {
|
||||
color: #9e9e9e;
|
||||
|
||||
&:focus,&:hover,&:focus-visible {
|
||||
&:focus,
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar &, .appbar & {
|
||||
.toolbar &,
|
||||
.appbar & {
|
||||
height: 100%;
|
||||
margin-block: 0;
|
||||
padding-block: 0;
|
||||
|
@ -49,7 +55,9 @@
|
|||
.appbar & {
|
||||
color: var(--tutu-color-on-primary);
|
||||
|
||||
&:focus,&:hover,&:focus-visible {
|
||||
&:focus,
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
background-color: var(--tutu-color-primary-ll);
|
||||
}
|
||||
|
||||
|
@ -62,4 +70,3 @@
|
|||
color: var(--tutu-color-on-surface);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,14 +3,15 @@ import { deepPurple, amber } from "@suid/material/colors";
|
|||
import { Accessor } from "solid-js";
|
||||
|
||||
export function useRootTheme(): Accessor<Theme> {
|
||||
return () => createTheme({
|
||||
return () =>
|
||||
createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: deepPurple[500]
|
||||
main: deepPurple[500],
|
||||
},
|
||||
secondary: {
|
||||
main: amber.A200
|
||||
}
|
||||
}
|
||||
})
|
||||
main: amber.A200,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
:root,
|
||||
[lang^="en"], [lang="en"] {
|
||||
[lang^="en"],
|
||||
[lang="en"] {
|
||||
--md-typography-type: "regular";
|
||||
--title-size: 1.25rem;
|
||||
--title-weight: 500;
|
||||
|
@ -19,9 +20,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
[lang^="zh"], [lang="zh"],
|
||||
[lang^="kr"], [lang="kr"],
|
||||
[lang^="ja"], [lang="ja"] {
|
||||
[lang^="zh"],
|
||||
[lang="zh"],
|
||||
[lang^="kr"],
|
||||
[lang="kr"],
|
||||
[lang^="ja"],
|
||||
[lang="ja"] {
|
||||
--md-typography-type: "dense";
|
||||
--title-size: 1.4375rem;
|
||||
--subheading-size: 1.1875rem;
|
||||
|
@ -95,7 +99,6 @@
|
|||
--tutu-anim-curve-sharp: cubic-bezier(0.4, 0, 0.6, 1);
|
||||
|
||||
@media (max-width: 300px) {
|
||||
|
||||
/* XS screen, like wearables */
|
||||
& {
|
||||
--tutu-transition-shadow: box-shadow 157.5ms var(--tutu-anim-curve-std);
|
||||
|
@ -103,7 +106,6 @@
|
|||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
|
||||
/* Mobile */
|
||||
& {
|
||||
--tutu-transition-shadow: box-shadow 225ms var(--tutu-anim-curve-std);
|
||||
|
@ -111,7 +113,6 @@
|
|||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
|
||||
/* Tablet */
|
||||
& {
|
||||
--tutu-transition-shadow: box-shadow 292.5ms var(--tutu-anim-curve-std);
|
||||
|
@ -125,7 +126,13 @@
|
|||
}
|
||||
|
||||
* {
|
||||
font-family: Roboto, "Noto Sans", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
font-family:
|
||||
Roboto,
|
||||
"Noto Sans",
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
sans-serif;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Dynamic } from "solid-js/web";
|
|||
import typography from "./typography.module.css";
|
||||
import { mergeClass } from "../utils";
|
||||
|
||||
type AnyElement = keyof JSX.IntrinsicElements | ParentComponent<any>
|
||||
type AnyElement = keyof JSX.IntrinsicElements | ParentComponent<any>;
|
||||
|
||||
type PropsOf<E extends AnyElement> =
|
||||
E extends ParentComponent<infer Props>
|
||||
|
@ -12,9 +12,7 @@ type PropsOf<E extends AnyElement> =
|
|||
? JSX.IntrinsicElements[E]
|
||||
: JSX.HTMLAttributes<HTMLElement>;
|
||||
|
||||
export type TypographyProps<
|
||||
E extends AnyElement,
|
||||
> = {
|
||||
export type TypographyProps<E extends AnyElement> = {
|
||||
ref?: Ref<E>;
|
||||
component?: E;
|
||||
class?: string;
|
||||
|
@ -33,7 +31,9 @@ type TypographyKind =
|
|||
| "caption"
|
||||
| "buttonText";
|
||||
|
||||
export function Typography<T extends AnyElement>(props: {typography: TypographyKind } & TypographyProps<T>) {
|
||||
export function Typography<T extends AnyElement>(
|
||||
props: { typography: TypographyKind } & TypographyProps<T>,
|
||||
) {
|
||||
const [managed, passthough] = splitProps(props, [
|
||||
"ref",
|
||||
"component",
|
||||
|
@ -50,38 +50,38 @@ export function Typography<T extends AnyElement>(props: {typography: TypographyK
|
|||
{...passthough}
|
||||
></Dynamic>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function Display4<E extends AnyElement>(props: TypographyProps<E>) {
|
||||
return <Typography typography={"display4"} {...props}></Typography>
|
||||
return <Typography typography={"display4"} {...props}></Typography>;
|
||||
}
|
||||
export function Display3<E extends AnyElement>(props: TypographyProps<E>) {
|
||||
return <Typography typography={"display3"} {...props}></Typography>
|
||||
return <Typography typography={"display3"} {...props}></Typography>;
|
||||
}
|
||||
export function Display2<E extends AnyElement>(props: TypographyProps<E>) {
|
||||
return <Typography typography={"display2"} {...props}></Typography>
|
||||
return <Typography typography={"display2"} {...props}></Typography>;
|
||||
}
|
||||
export function Display1<E extends AnyElement>(props: TypographyProps<E>) {
|
||||
return <Typography typography={"display1"} {...props}></Typography>
|
||||
return <Typography typography={"display1"} {...props}></Typography>;
|
||||
}
|
||||
export function Headline<E extends AnyElement>(props: TypographyProps<E>) {
|
||||
return <Typography typography={"headline"} {...props}></Typography>
|
||||
return <Typography typography={"headline"} {...props}></Typography>;
|
||||
}
|
||||
export function Title<E extends AnyElement>(props: TypographyProps<E>) {
|
||||
return <Typography typography={"title"} {...props}></Typography>
|
||||
return <Typography typography={"title"} {...props}></Typography>;
|
||||
}
|
||||
export function Subheading<E extends AnyElement>(props: TypographyProps<E>) {
|
||||
return <Typography typography={"subheading"} {...props}></Typography>
|
||||
return <Typography typography={"subheading"} {...props}></Typography>;
|
||||
}
|
||||
export function Body1<E extends AnyElement>(props: TypographyProps<E>) {
|
||||
return <Typography typography={"body1"} {...props}></Typography>
|
||||
return <Typography typography={"body1"} {...props}></Typography>;
|
||||
}
|
||||
export function Body2<E extends AnyElement>(props: TypographyProps<E>) {
|
||||
return <Typography typography={"body2"} {...props}></Typography>
|
||||
return <Typography typography={"body2"} {...props}></Typography>;
|
||||
}
|
||||
export function Caption<E extends AnyElement>(props: TypographyProps<E>) {
|
||||
return <Typography typography={"caption"} {...props}></Typography>
|
||||
return <Typography typography={"caption"} {...props}></Typography>;
|
||||
}
|
||||
export function ButtonText<E extends AnyElement>(props: TypographyProps<E>) {
|
||||
return <Typography typography={"buttonText"} {...props}></Typography>
|
||||
return <Typography typography={"buttonText"} {...props}></Typography>;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { createContext, useContext, type Accessor } from "solid-js";
|
||||
|
||||
export type HeroSource = {[key: string | symbol | number]: HTMLElement | undefined}
|
||||
export type HeroSource = {
|
||||
[key: string | symbol | number]: HTMLElement | undefined;
|
||||
};
|
||||
|
||||
const HeroSourceContext = createContext<Accessor<HeroSource>>(() => ({}))
|
||||
const HeroSourceContext = createContext<Accessor<HeroSource>>(() => ({}));
|
||||
|
||||
export const HeroSourceProvider = HeroSourceContext.Provider
|
||||
export const HeroSourceProvider = HeroSourceContext.Provider;
|
||||
|
||||
export function useHeroSource() {
|
||||
return useContext(HeroSourceContext)
|
||||
return useContext(HeroSourceContext);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import { persistentMap } from "@nanostores/persistent";
|
||||
|
||||
type Settings = {
|
||||
onGoingOAuth2Process?: string
|
||||
prefetchTootsDisabled?: boolean
|
||||
}
|
||||
onGoingOAuth2Process?: string;
|
||||
prefetchTootsDisabled?: boolean;
|
||||
};
|
||||
|
||||
export const $settings = persistentMap<Settings>("settings::", {}, {
|
||||
export const $settings = persistentMap<Settings>(
|
||||
"settings::",
|
||||
{},
|
||||
{
|
||||
encode: JSON.stringify,
|
||||
decode: JSON.parse
|
||||
})
|
||||
decode: JSON.parse,
|
||||
},
|
||||
);
|
||||
|
|
|
@ -23,11 +23,9 @@ const CompactToot: Component<CompactTootProps> = (props) => {
|
|||
>
|
||||
<Img
|
||||
src={toot().account.avatar}
|
||||
class={[
|
||||
tootStyle.tootAvatar,
|
||||
].join(" ")}
|
||||
class={[tootStyle.tootAvatar].join(" ")}
|
||||
/>
|
||||
<div class={[tootStyle.compactAuthorGroup].join(' ')}>
|
||||
<div class={[tootStyle.compactAuthorGroup].join(" ")}>
|
||||
<Body2
|
||||
ref={(e: { innerHTML: string }) => {
|
||||
appliedCustomEmoji(
|
||||
|
@ -48,7 +46,7 @@ const CompactToot: Component<CompactTootProps> = (props) => {
|
|||
ref={(e: { innerHTML: string }) => {
|
||||
appliedCustomEmoji(e, toot().content, toot().emojis);
|
||||
}}
|
||||
class={[tootStyle.compactTootContent].join(' ')}
|
||||
class={[tootStyle.compactTootContent].join(" ")}
|
||||
></div>
|
||||
</section>
|
||||
);
|
||||
|
|
|
@ -155,20 +155,20 @@ const Home: ParentComponent = (props) => {
|
|||
useDocumentTitle("Timelines");
|
||||
const now = createTimeSource();
|
||||
|
||||
const settings$ = useStore($settings)
|
||||
const settings$ = useStore($settings);
|
||||
const sessions = useSessions();
|
||||
const client = () => sessions()[0].client;
|
||||
const [profile] = useAcctProfile(client);
|
||||
|
||||
const [panelOffset, setPanelOffset] = createSignal(0);
|
||||
const prefetching = () => !settings$().prefetchTootsDisabled
|
||||
const prefetching = () => !settings$().prefetchTootsDisabled;
|
||||
const [currentFocusOn, setCurrentFocusOn] = createSignal<HTMLElement[]>([]);
|
||||
const [focusRange, setFocusRange] = createSignal([0, 0] as readonly [
|
||||
number,
|
||||
number,
|
||||
]);
|
||||
|
||||
const child = children(() => props.children)
|
||||
const child = children(() => props.children);
|
||||
|
||||
let scrollEventLockReleased = true;
|
||||
|
||||
|
@ -229,7 +229,7 @@ const Home: ParentComponent = (props) => {
|
|||
const onTabClick = (idx: number) => {
|
||||
const items = panelList.querySelectorAll(".tab-panel");
|
||||
if (items.length > idx) {
|
||||
items.item(idx).scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||
items.item(idx).scrollIntoView({ block: "start", behavior: "smooth" });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -269,7 +269,11 @@ const Home: ParentComponent = (props) => {
|
|||
<Scaffold
|
||||
topbar={
|
||||
<AppBar position="static">
|
||||
<Toolbar variant="dense" class="responsive" sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}>
|
||||
<Toolbar
|
||||
variant="dense"
|
||||
class="responsive"
|
||||
sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}
|
||||
>
|
||||
<Tabs onFocusChanged={setCurrentFocusOn} offset={panelOffset()}>
|
||||
<Tab focus={isTabFocus(0)} onClick={[onTabClick, 0]}>
|
||||
Home
|
||||
|
@ -282,7 +286,14 @@ const Home: ParentComponent = (props) => {
|
|||
</Tab>
|
||||
</Tabs>
|
||||
<ProfileMenuButton profile={profile()}>
|
||||
<MenuItem onClick={(e) => $settings.setKey("prefetchTootsDisabled", !$settings.get().prefetchTootsDisabled)}>
|
||||
<MenuItem
|
||||
onClick={(e) =>
|
||||
$settings.setKey(
|
||||
"prefetchTootsDisabled",
|
||||
!$settings.get().prefetchTootsDisabled,
|
||||
)
|
||||
}
|
||||
>
|
||||
<ListItemText>Prefetch Toots</ListItemText>
|
||||
<ListItemSecondaryAction>
|
||||
<Switch checked={prefetching()}></Switch>
|
||||
|
|
|
@ -11,7 +11,7 @@ const MediaAttachmentGrid: Component<{
|
|||
}> = (props) => {
|
||||
let rootRef: HTMLElement;
|
||||
const [viewerIndex, setViewerIndex] = createSignal<number>();
|
||||
const viewerOpened = () => typeof viewerIndex() !== "undefined"
|
||||
const viewerOpened = () => typeof viewerIndex() !== "undefined";
|
||||
const gridTemplateColumns = () => {
|
||||
const l = props.attachments.length;
|
||||
if (l < 2) {
|
||||
|
|
|
@ -36,7 +36,7 @@ function within(n: number, target: number, range: number) {
|
|||
}
|
||||
|
||||
function clamp(input: number, min: number, max: number) {
|
||||
return Math.min(Math.max(input, min), max)
|
||||
return Math.min(Math.max(input, min), max);
|
||||
}
|
||||
|
||||
const MediaViewer: ParentComponent<MediaViewerProps> = (props) => {
|
||||
|
@ -128,6 +128,13 @@ const MediaViewer: ParentComponent<MediaViewerProps> = (props) => {
|
|||
left: 0;
|
||||
z-index: 1;
|
||||
cursor: ${dragging() ? "grabbing" : "grab"};
|
||||
padding-left: var(--safe-area-inset-left, 0);
|
||||
padding-right: var(--safe-area-inset-right, 0);
|
||||
padding-bottom: var(--safe-area-inset-bottom, 0);
|
||||
|
||||
:global(> .MuiToolbar-root) {
|
||||
padding-top: var(--safe-area-inset-top, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.left-dock {
|
||||
|
@ -207,7 +214,13 @@ const MediaViewer: ParentComponent<MediaViewerProps> = (props) => {
|
|||
move: number,
|
||||
idx: number,
|
||||
) => {
|
||||
const { ref, top: otop, left: oleft, scale: oscale, osize: [owidth, oheight] } = state[idx];
|
||||
const {
|
||||
ref,
|
||||
top: otop,
|
||||
left: oleft,
|
||||
scale: oscale,
|
||||
osize: [owidth, oheight],
|
||||
} = state[idx];
|
||||
const [cx, cy] = center;
|
||||
const iy = clamp(cy - otop, 0, oheight),
|
||||
ix = clamp(cx - oleft, 0, owidth); // in image coordinate system
|
||||
|
|
|
@ -8,7 +8,13 @@ import {
|
|||
Menu,
|
||||
MenuItem,
|
||||
} from "@suid/material";
|
||||
import { Show, createSignal, createUniqueId, type ParentComponent } from "solid-js";
|
||||
import {
|
||||
ErrorBoundary,
|
||||
Show,
|
||||
createSignal,
|
||||
createUniqueId,
|
||||
type ParentComponent,
|
||||
} from "solid-js";
|
||||
import {
|
||||
Settings as SettingsIcon,
|
||||
Bookmark as BookmarkIcon,
|
||||
|
@ -64,7 +70,7 @@ const ProfileMenuButton: ParentComponent<{
|
|||
"aria-labelledby": buttonId,
|
||||
sx: {
|
||||
minWidth: "220px",
|
||||
}
|
||||
},
|
||||
}}
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
|
|
|
@ -211,7 +211,7 @@ const RegularToot: Component<TootCardProps> = (props) => {
|
|||
classList={{
|
||||
[tootStyle.toot]: true,
|
||||
[tootStyle.expanded]: managed.evaluated,
|
||||
[managed.class || ""]: true
|
||||
[managed.class || ""]: true,
|
||||
}}
|
||||
ref={rootRef!}
|
||||
lang={toot().language || managed.lang}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import type { Component } from "solid-js";
|
||||
|
||||
|
||||
const TootBottomSheet: Component = (props) => {
|
||||
return <></>
|
||||
}
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default TootBottomSheet
|
||||
export default TootBottomSheet;
|
||||
|
|
|
@ -38,7 +38,9 @@ const TootThread: Component<TootThreadProps> = (props) => {
|
|||
|
||||
css`
|
||||
article {
|
||||
transition: margin 90ms var(--tutu-anim-curve-sharp), var(--tutu-transition-shadow);
|
||||
transition:
|
||||
margin 90ms var(--tutu-anim-curve-sharp),
|
||||
var(--tutu-transition-shadow);
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -64,7 +66,10 @@ const TootThread: Component<TootThreadProps> = (props) => {
|
|||
`;
|
||||
|
||||
return (
|
||||
<article classList={{ "thread-line": !!inReplyTo(), "expanded": expanded() }} onClick={() => setExpanded((x) => !x)}>
|
||||
<article
|
||||
classList={{ "thread-line": !!inReplyTo(), expanded: expanded() }}
|
||||
onClick={() => setExpanded((x) => !x)}
|
||||
>
|
||||
<Show when={inReplyTo()}>
|
||||
<CompactToot
|
||||
status={inReplyTo()!}
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
&.toot {
|
||||
/* fix composition ordering: I think the css module processor should aware the overriding and behaves, but no */
|
||||
transition: margin-block 125ms var(--tutu-anim-curve-std),
|
||||
transition:
|
||||
margin-block 125ms var(--tutu-anim-curve-std),
|
||||
height 225ms var(--tutu-anim-curve-std),
|
||||
var(--tutu-transition-shadow);
|
||||
border-radius: 0;
|
||||
|
@ -80,7 +81,7 @@
|
|||
}
|
||||
|
||||
.tootContent {
|
||||
composes: cardNoPad from '../material/cards.module.css';
|
||||
composes: cardNoPad from "../material/cards.module.css";
|
||||
margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px);
|
||||
margin-right: var(--card-pad, 0);
|
||||
line-height: 1.5;
|
||||
|
@ -150,7 +151,7 @@
|
|||
}
|
||||
|
||||
.tootAttachmentGrp {
|
||||
composes: cardNoPad from '../material/cards.module.css';
|
||||
composes: cardNoPad from "../material/cards.module.css";
|
||||
margin-top: 1em;
|
||||
margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px);
|
||||
margin-right: var(--card-pad, 0);
|
||||
|
@ -168,7 +169,7 @@
|
|||
}
|
||||
|
||||
.tootBottomActionGrp {
|
||||
composes: cardGutSkip from '../material/cards.module.css';
|
||||
composes: cardGutSkip from "../material/cards.module.css";
|
||||
padding-block: calc((var(--card-gut) - 10px) / 2);
|
||||
|
||||
animation: 225ms var(--tutu-anim-curve-std) tootBottomExpanding;
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
import { createRenderEffect, createSignal, onCleanup } from "solid-js";
|
||||
|
||||
export function useDocumentTitle(newTitle?: string) {
|
||||
const capturedTitle = document.title
|
||||
const [title, setTitle] = createSignal(newTitle ?? capturedTitle)
|
||||
const capturedTitle = document.title;
|
||||
const [title, setTitle] = createSignal(newTitle ?? capturedTitle);
|
||||
|
||||
createRenderEffect(() => {
|
||||
document.title = title()
|
||||
})
|
||||
document.title = title();
|
||||
});
|
||||
|
||||
onCleanup(() => {
|
||||
document.title = capturedTitle
|
||||
})
|
||||
document.title = capturedTitle;
|
||||
});
|
||||
|
||||
return setTitle
|
||||
return setTitle;
|
||||
}
|
||||
|
||||
export function mergeClass(c1: string | undefined, c2: string | undefined) {
|
||||
if (!c1) {
|
||||
return c2
|
||||
return c2;
|
||||
}
|
||||
if (!c2) {
|
||||
return c1
|
||||
return c1;
|
||||
}
|
||||
return [c1, c2].join(' ')
|
||||
return [c1, c2].join(" ");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue