first prototype of StackedRouter
This commit is contained in:
		
							parent
							
								
									607fa64c05
								
							
						
					
					
						commit
						0710aaf4f3
					
				
					 21 changed files with 442 additions and 109 deletions
				
			
		
							
								
								
									
										30
									
								
								src/App.tsx
									
										
									
									
									
								
							
							
						
						
									
										30
									
								
								src/App.tsx
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -27,6 +27,7 @@ import {
 | 
			
		|||
import { Service } from "./serviceworker/services.js";
 | 
			
		||||
import { makeEventListener } from "@solid-primitives/event-listener";
 | 
			
		||||
import { ServiceWorkerProvider } from "./platform/host.js";
 | 
			
		||||
import StackedRouter from "./platform/StackedRouter.js";
 | 
			
		||||
 | 
			
		||||
const AccountSignIn = lazy(() => import("./accounts/SignIn.js"));
 | 
			
		||||
const AccountMastodonOAuth2Callback = lazy(
 | 
			
		||||
| 
						 | 
				
			
			@ -37,24 +38,21 @@ const Settings = lazy(() => import("./settings/Settings.js"));
 | 
			
		|||
const TootBottomSheet = lazy(() => import("./timelines/TootBottomSheet.js"));
 | 
			
		||||
const MotionSettings = lazy(() => import("./settings/Motions.js"));
 | 
			
		||||
const LanguageSettings = lazy(() => import("./settings/Language.js"));
 | 
			
		||||
const RegionSettings = lazy(() => import("./settings/Region.jsx"));
 | 
			
		||||
const RegionSettings = lazy(() => import("./settings/Region.js"));
 | 
			
		||||
const UnexpectedError = lazy(() => import("./UnexpectedError.js"));
 | 
			
		||||
const Profile = lazy(() => import("./profiles/Profile.js"));
 | 
			
		||||
 | 
			
		||||
const Routing: Component = () => {
 | 
			
		||||
  return (
 | 
			
		||||
    <Router>
 | 
			
		||||
      <Route path="/" component={TimelineHome}>
 | 
			
		||||
        <Route path=""></Route>
 | 
			
		||||
        <Route path="/settings" component={Settings}>
 | 
			
		||||
          <Route path=""></Route>
 | 
			
		||||
          <Route path="/language" component={LanguageSettings}></Route>
 | 
			
		||||
          <Route path="/region" component={RegionSettings}></Route>
 | 
			
		||||
          <Route path="/motions" component={MotionSettings}></Route>
 | 
			
		||||
        </Route>
 | 
			
		||||
        <Route path="/:acct/toot/:id" component={TootBottomSheet}></Route>
 | 
			
		||||
        <Route path="/:acct/profile/:id" component={Profile}></Route>
 | 
			
		||||
      </Route>
 | 
			
		||||
    <StackedRouter>
 | 
			
		||||
      <Route path="/" component={TimelineHome} />
 | 
			
		||||
      <Route path="/settings" component={Settings} />
 | 
			
		||||
      <Route path="/settings/language" component={LanguageSettings} />
 | 
			
		||||
      <Route path="/settings/region" component={RegionSettings} />
 | 
			
		||||
      <Route path="/motions" component={MotionSettings} />
 | 
			
		||||
      <Route path="/:acct/toot/:id" component={TootBottomSheet} />
 | 
			
		||||
      <Route path="/:acct/profile/:id" component={Profile} />
 | 
			
		||||
 | 
			
		||||
      <Route path={"/accounts"}>
 | 
			
		||||
        <Route path={"/sign-in"} component={AccountSignIn} />
 | 
			
		||||
        <Route
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +60,7 @@ const Routing: Component = () => {
 | 
			
		|||
          component={AccountMastodonOAuth2Callback}
 | 
			
		||||
        />
 | 
			
		||||
      </Route>
 | 
			
		||||
    </Router>
 | 
			
		||||
    </StackedRouter>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +68,9 @@ const App: Component = () => {
 | 
			
		|||
  const theme = useRootTheme();
 | 
			
		||||
  const accts = useStore($accounts);
 | 
			
		||||
  const lang = useLanguage();
 | 
			
		||||
  const [serviceWorker, setServiceWorker] = createSignal<ServiceWorker>();
 | 
			
		||||
  const [serviceWorker, setServiceWorker] = createSignal<
 | 
			
		||||
    ServiceWorker | undefined
 | 
			
		||||
  >(undefined, { name: "serviceWorker" });
 | 
			
		||||
  const dispatcher = new ResultDispatcher();
 | 
			
		||||
 | 
			
		||||
  let checkAge = 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
import { useNavigate, useSearchParams } from "@solidjs/router";
 | 
			
		||||
import { useSearchParams } from "@solidjs/router";
 | 
			
		||||
import {
 | 
			
		||||
  Component,
 | 
			
		||||
  Show,
 | 
			
		||||
| 
						 | 
				
			
			@ -14,6 +14,7 @@ import { LinearProgress } from "@suid/material";
 | 
			
		|||
import Img from "../material/Img";
 | 
			
		||||
import { createRestAPIClient } from "masto";
 | 
			
		||||
import { Title } from "../material/typography";
 | 
			
		||||
import { useNavigator } from "../platform/StackedRouter";
 | 
			
		||||
 | 
			
		||||
type OAuth2CallbackParams = {
 | 
			
		||||
  code?: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -25,7 +26,7 @@ const MastodonOAuth2Callback: Component = () => {
 | 
			
		|||
  const progressId = createUniqueId();
 | 
			
		||||
  const titleId = createUniqueId();
 | 
			
		||||
  const [params] = useSearchParams<OAuth2CallbackParams>();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const { push: navigate } = useNavigator();
 | 
			
		||||
  const setDocumentTitle = useDocumentTitle("Back from Mastodon...");
 | 
			
		||||
  const [siteImg, setSiteImg] = createSignal<{
 | 
			
		||||
    src: string;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,8 @@ import {
 | 
			
		|||
} from "solid-js";
 | 
			
		||||
import { Account } from "../accounts/stores";
 | 
			
		||||
import { createRestAPIClient, mastodon } from "masto";
 | 
			
		||||
import { useLocation, useNavigate } from "@solidjs/router";
 | 
			
		||||
import { useLocation } from "@solidjs/router";
 | 
			
		||||
import { useNavigator } from "../platform/StackedRouter";
 | 
			
		||||
 | 
			
		||||
const restfulCache: Record<string, mastodon.rest.Client> = {};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -56,12 +57,12 @@ export const Provider = Context.Provider;
 | 
			
		|||
 | 
			
		||||
export function useSessions() {
 | 
			
		||||
  const sessions = useSessionsRaw();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const {push} = useNavigator();
 | 
			
		||||
  const location = useLocation();
 | 
			
		||||
 | 
			
		||||
  createRenderEffect(() => {
 | 
			
		||||
    if (sessions().length > 0) return;
 | 
			
		||||
    navigate(
 | 
			
		||||
    push(
 | 
			
		||||
      "/accounts/sign-in?back=" + encodeURIComponent(location.pathname),
 | 
			
		||||
      { replace: true },
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,22 +25,6 @@
 | 
			
		|||
 | 
			
		||||
  box-shadow: var(--tutu-shadow-e16);
 | 
			
		||||
 | 
			
		||||
  .MuiToolbar-root {
 | 
			
		||||
    >.MuiButtonBase-root {
 | 
			
		||||
 | 
			
		||||
      &:first-child {
 | 
			
		||||
        margin-left: -0.5em;
 | 
			
		||||
        margin-right: 24px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      &:last-child {
 | 
			
		||||
        margin-right: -0.5em;
 | 
			
		||||
        margin-left: 24px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @media (max-width: 560px) {
 | 
			
		||||
    & {
 | 
			
		||||
      left: 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,22 +1,42 @@
 | 
			
		|||
 | 
			
		||||
.Scaffold__topbar {
 | 
			
		||||
.Scaffold>.topbar {
 | 
			
		||||
  position: sticky;
 | 
			
		||||
  top: 0px;
 | 
			
		||||
  z-index: var(--tutu-zidx-nav, auto);
 | 
			
		||||
 | 
			
		||||
  .MuiToolbar-root {
 | 
			
		||||
    >.MuiButtonBase-root {
 | 
			
		||||
      &:first-child {
 | 
			
		||||
        margin-left: -0.5em;
 | 
			
		||||
        margin-right: 24px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      &:last-child {
 | 
			
		||||
        margin-right: -0.5em;
 | 
			
		||||
        margin-left: 24px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.Scaffold__fab-dock {
 | 
			
		||||
.Scaffold>.fab-dock {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  bottom: 40px;
 | 
			
		||||
  right: 40px;
 | 
			
		||||
  z-index: var(--tutu-zidx-nav, auto);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.Scaffold__bottom-dock {
 | 
			
		||||
.Scaffold>.bottom-dock {
 | 
			
		||||
  position: sticky;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  z-index: var(--tutu-zidx-nav, auto);
 | 
			
		||||
  padding-bottom: var(--safe-area-inset-bottom, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.Scaffold {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  background-color: var(--tutu-color-surface);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -28,42 +28,48 @@ const Scaffold: Component<ScaffoldProps> = (props) => {
 | 
			
		|||
    "bottom",
 | 
			
		||||
    "children",
 | 
			
		||||
    "ref",
 | 
			
		||||
    "class",
 | 
			
		||||
  ]);
 | 
			
		||||
  const [topbarElement, setTopbarElement] = createSignal<HTMLElement>();
 | 
			
		||||
 | 
			
		||||
  const topbarSize = createElementSize(topbarElement);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
    <div
 | 
			
		||||
      class={`Scaffold ${managed.class || ""}`}
 | 
			
		||||
      ref={(e) => {
 | 
			
		||||
        createRenderEffect(() => {
 | 
			
		||||
          e.style.setProperty(
 | 
			
		||||
            "--scaffold-topbar-height",
 | 
			
		||||
            (topbarSize.height?.toString() ?? 0) + "px",
 | 
			
		||||
          );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (managed.ref) {
 | 
			
		||||
          (managed.ref as (val: typeof e) => void)(e);
 | 
			
		||||
        }
 | 
			
		||||
      }}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    >
 | 
			
		||||
      <Show when={props.topbar}>
 | 
			
		||||
        <div class="Scaffold__topbar" ref={setTopbarElement} role="presentation">
 | 
			
		||||
        <div class="topbar" ref={setTopbarElement} role="presentation">
 | 
			
		||||
          {props.topbar}
 | 
			
		||||
        </div>
 | 
			
		||||
      </Show>
 | 
			
		||||
      <Show when={props.fab}>
 | 
			
		||||
        <div class="Scaffold__fab-dock" role="presentation">{props.fab}</div>
 | 
			
		||||
        <div class="fab-dock" role="presentation">
 | 
			
		||||
          {props.fab}
 | 
			
		||||
        </div>
 | 
			
		||||
      </Show>
 | 
			
		||||
      <div
 | 
			
		||||
        ref={(e) => {
 | 
			
		||||
          createRenderEffect(() => {
 | 
			
		||||
            e.style.setProperty(
 | 
			
		||||
              "--scaffold-topbar-height",
 | 
			
		||||
              (topbarSize.height?.toString() ?? 0) + "px",
 | 
			
		||||
            );
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          if (managed.ref) {
 | 
			
		||||
            (managed.ref as (val: typeof e) => void)(e);
 | 
			
		||||
          }
 | 
			
		||||
        }}
 | 
			
		||||
        {...rest}
 | 
			
		||||
      >
 | 
			
		||||
        {managed.children}
 | 
			
		||||
      </div>
 | 
			
		||||
      {managed.children}
 | 
			
		||||
 | 
			
		||||
      <Show when={props.bottom}>
 | 
			
		||||
        <div class="Scaffold__bottom-dock" role="presentation">{props.bottom}</div>
 | 
			
		||||
        <div class="bottom-dock" role="presentation">
 | 
			
		||||
          {props.bottom}
 | 
			
		||||
        </div>
 | 
			
		||||
      </Show>
 | 
			
		||||
    </>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										19
									
								
								src/platform/A.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/platform/A.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
import { type JSX } from "solid-js";
 | 
			
		||||
import { useNavigator } from "./StackedRouter";
 | 
			
		||||
 | 
			
		||||
function handleClick(
 | 
			
		||||
  push: (name: string, state: unknown) => void,
 | 
			
		||||
  event: MouseEvent & { currentTarget: HTMLAnchorElement },
 | 
			
		||||
) {
 | 
			
		||||
  const target = event.currentTarget;
 | 
			
		||||
  event.preventDefault();
 | 
			
		||||
  event.stopPropagation();
 | 
			
		||||
  push(target.href, { state: target.getAttribute("state") || undefined });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const A = (oprops: JSX.HTMLElementTags["a"]) => {
 | 
			
		||||
  const { push } = useNavigator();
 | 
			
		||||
  return <a onClick={[handleClick, push]} {...oprops}></a>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default A;
 | 
			
		||||
							
								
								
									
										24
									
								
								src/platform/BackButton.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/platform/BackButton.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
import type { IconButtonProps } from "@suid/material/IconButton";
 | 
			
		||||
import IconButton from "@suid/material/IconButton";
 | 
			
		||||
import { Show, type Component } from "solid-js";
 | 
			
		||||
import { useCurrentFrame, useNavigator } from "./StackedRouter";
 | 
			
		||||
import { ArrowBack, Close } from "@suid/icons-material";
 | 
			
		||||
 | 
			
		||||
export type BackButtonProps = Omit<IconButtonProps, "onClick" | "children">;
 | 
			
		||||
 | 
			
		||||
const BackButton: Component<BackButtonProps> = (props) => {
 | 
			
		||||
  const currentFrame = useCurrentFrame();
 | 
			
		||||
  const { pop } = useNavigator();
 | 
			
		||||
 | 
			
		||||
  const hasPrevSubPage = () => currentFrame().index > 1;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <IconButton onClick={[pop, 1]} {...props}>
 | 
			
		||||
      <Show when={hasPrevSubPage()} fallback={<Close />}>
 | 
			
		||||
        <ArrowBack />
 | 
			
		||||
      </Show>
 | 
			
		||||
    </IconButton>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default BackButton;
 | 
			
		||||
							
								
								
									
										52
									
								
								src/platform/StackedRouter.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/platform/StackedRouter.css
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,52 @@
 | 
			
		|||
.StackedPage {
 | 
			
		||||
  container: StackedPage / size;
 | 
			
		||||
  display: contents;
 | 
			
		||||
  max-width: 100vw;
 | 
			
		||||
  max-width: 100dvw;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dialog.StackedPage {
 | 
			
		||||
  border: none;
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  overscroll-behavior: none;
 | 
			
		||||
  width: 560px;
 | 
			
		||||
  max-height: 100vh;
 | 
			
		||||
  max-height: 100dvh;
 | 
			
		||||
  background: none;
 | 
			
		||||
  display: none;
 | 
			
		||||
 | 
			
		||||
  contain: strict;
 | 
			
		||||
  contain-intrinsic-size: auto 560px auto 100vh;
 | 
			
		||||
  contain-intrinsic-size: auto 560px auto 100dvh;
 | 
			
		||||
  content-visibility: auto;
 | 
			
		||||
 | 
			
		||||
  box-shadow: var(--tutu-shadow-e16);
 | 
			
		||||
 | 
			
		||||
  @media (min-width: 560px) {
 | 
			
		||||
    & {
 | 
			
		||||
      left: 50%;
 | 
			
		||||
      transform: translateX(-50%);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @media (max-width: 560px) {
 | 
			
		||||
    & {
 | 
			
		||||
      width: 100vw;
 | 
			
		||||
      width: 100dvw;
 | 
			
		||||
      height: 100vh;
 | 
			
		||||
      height: 100dvh;
 | 
			
		||||
      contain-intrinsic-size: 100vw 100vh;
 | 
			
		||||
      contain-intrinsic-size: 100dvw 100dvh;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &[open] {
 | 
			
		||||
    display: contents;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &::backdrop {
 | 
			
		||||
    background: none;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										228
									
								
								src/platform/StackedRouter.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								src/platform/StackedRouter.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,228 @@
 | 
			
		|||
import { StaticRouter, type RouterProps } from "@solidjs/router";
 | 
			
		||||
import {
 | 
			
		||||
  Component,
 | 
			
		||||
  createContext,
 | 
			
		||||
  createEffect,
 | 
			
		||||
  createRenderEffect,
 | 
			
		||||
  createUniqueId,
 | 
			
		||||
  Index,
 | 
			
		||||
  onMount,
 | 
			
		||||
  Show,
 | 
			
		||||
  untrack,
 | 
			
		||||
  useContext,
 | 
			
		||||
  type Accessor,
 | 
			
		||||
} from "solid-js";
 | 
			
		||||
import { createStore, unwrap } from "solid-js/store";
 | 
			
		||||
import { insert, render } from "solid-js/web";
 | 
			
		||||
import "./StackedRouter.css";
 | 
			
		||||
import { animateSlideInFromRight } from "./anim";
 | 
			
		||||
import { ANIM_CURVE_DECELERATION, ANIM_CURVE_STD } from "../material/theme";
 | 
			
		||||
 | 
			
		||||
export type StackedRouterProps = Omit<RouterProps, "url">;
 | 
			
		||||
 | 
			
		||||
export type StackFrame = {
 | 
			
		||||
  path: string;
 | 
			
		||||
  rootId: string;
 | 
			
		||||
  state: unknown;
 | 
			
		||||
  beforeShow?: (element: HTMLElement) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type NewFrameOptions<T> = (T extends undefined
 | 
			
		||||
  ? {
 | 
			
		||||
      state?: T;
 | 
			
		||||
    }
 | 
			
		||||
  : { state: T }) & {
 | 
			
		||||
  replace?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type FramePusher<T, K extends keyof T = keyof T> = T[K] extends
 | 
			
		||||
  | undefined
 | 
			
		||||
  | any
 | 
			
		||||
  ? (path: K, state?: Readonly<NewFrameOptions<T[K]>>) => Readonly<StackFrame>
 | 
			
		||||
  : (path: K, state: Readonly<NewFrameOptions<T[K]>>) => Readonly<StackFrame>;
 | 
			
		||||
 | 
			
		||||
export type Navigator<PushGuide = Record<string, any>> = {
 | 
			
		||||
  frames: readonly StackFrame[];
 | 
			
		||||
  push: FramePusher<PushGuide>;
 | 
			
		||||
  pop: (depth?: number) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const NavigatorContext = /* @__PURE__ */ createContext<Navigator>();
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the navigator of the {@link StackedRouter}.
 | 
			
		||||
 *
 | 
			
		||||
 * This function returns a {@link Navigator} without available
 | 
			
		||||
 * push guide. Push guide is a record type contains available
 | 
			
		||||
 * path and its state. If you need push guide, you may want to
 | 
			
		||||
 * define your own function (like `useAppNavigator`) and cast the
 | 
			
		||||
 * navigator to the type you need.
 | 
			
		||||
 */
 | 
			
		||||
export function useNavigator() {
 | 
			
		||||
  const navigator = useContext(NavigatorContext);
 | 
			
		||||
 | 
			
		||||
  if (!navigator) {
 | 
			
		||||
    throw new TypeError("not in available scope of StackedRouter");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return navigator;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type CurrentFrame = {
 | 
			
		||||
  index: number;
 | 
			
		||||
  frame: Readonly<StackFrame>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const CurrentFrameContext =
 | 
			
		||||
  /* @__PURE__ */ createContext<Accessor<Readonly<CurrentFrame>>>();
 | 
			
		||||
 | 
			
		||||
export function useCurrentFrame() {
 | 
			
		||||
  const frame = useContext(CurrentFrameContext);
 | 
			
		||||
 | 
			
		||||
  if (!frame) {
 | 
			
		||||
    throw new TypeError("not in available scope of StackedRouter");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return frame;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onDialogClick(
 | 
			
		||||
  onClose: () => void,
 | 
			
		||||
  event: MouseEvent & { currentTarget: HTMLDialogElement },
 | 
			
		||||
) {
 | 
			
		||||
  if (event.target !== event.currentTarget) return;
 | 
			
		||||
  const rect = event.currentTarget.getBoundingClientRect();
 | 
			
		||||
  const isNotInDialog =
 | 
			
		||||
    event.clientY < rect.top ||
 | 
			
		||||
    event.clientY > rect.bottom ||
 | 
			
		||||
    event.clientX < rect.left ||
 | 
			
		||||
    event.clientX > rect.right;
 | 
			
		||||
  if (isNotInDialog) {
 | 
			
		||||
    onClose();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The router that stacks the pages.
 | 
			
		||||
 */
 | 
			
		||||
const StackedRouter: Component<StackedRouterProps> = (oprops) => {
 | 
			
		||||
  const [stack, mutStack] = createStore([] as StackFrame[], { name: "stack" });
 | 
			
		||||
 | 
			
		||||
  const pushFrame = (path: string, opts?: Readonly<NewFrameOptions<any>>) =>
 | 
			
		||||
    untrack(() => {
 | 
			
		||||
      const frame = {
 | 
			
		||||
        path,
 | 
			
		||||
        state: opts?.state,
 | 
			
		||||
        rootId: createUniqueId(),
 | 
			
		||||
      };
 | 
			
		||||
      mutStack(opts?.replace ? stack.length - 1 : stack.length, frame);
 | 
			
		||||
      return frame;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  const popFrame = (depth: number = 1) =>
 | 
			
		||||
    untrack(() => {
 | 
			
		||||
      if (import.meta.env.DEV) {
 | 
			
		||||
        if (depth < 0) {
 | 
			
		||||
          console.warn("the depth to pop should not < 0, now is", depth);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (stack.length > 1) {
 | 
			
		||||
        const lastFrame = stack[stack.length - 1];
 | 
			
		||||
        const element = document.getElementById(lastFrame.rootId)!;
 | 
			
		||||
        requestAnimationFrame(() => {
 | 
			
		||||
          const animation = element.animate(
 | 
			
		||||
            {
 | 
			
		||||
              opacity: [0.5, 0],
 | 
			
		||||
            },
 | 
			
		||||
            { easing: ANIM_CURVE_STD, duration: 220 },
 | 
			
		||||
          );
 | 
			
		||||
          animation.addEventListener("finish", () =>
 | 
			
		||||
            mutStack((o) => o.toSpliced(o.length - depth, depth)),
 | 
			
		||||
          );
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        mutStack((o) => {
 | 
			
		||||
          return o.toSpliced(o.length - depth, depth);
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  /* createEffect(() => {
 | 
			
		||||
    const length = stack.length;
 | 
			
		||||
    console.debug("stack is changed", length, unwrap(stack));
 | 
			
		||||
  }); */
 | 
			
		||||
 | 
			
		||||
  createRenderEffect(() => {
 | 
			
		||||
    if (stack.length === 0) {
 | 
			
		||||
      pushFrame("/", undefined);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const onBeforeDialogMount = (element: HTMLDialogElement) => {
 | 
			
		||||
    createEffect(() => {
 | 
			
		||||
      requestAnimationFrame(() => {
 | 
			
		||||
        element.showModal();
 | 
			
		||||
        if (window.innerWidth <= 560) {
 | 
			
		||||
          animateSlideInFromRight(element, { easing: ANIM_CURVE_DECELERATION });
 | 
			
		||||
        } else {
 | 
			
		||||
          element.animate(
 | 
			
		||||
            {
 | 
			
		||||
              opacity: [0.5, 1],
 | 
			
		||||
            },
 | 
			
		||||
            { easing: ANIM_CURVE_STD, duration: 220 },
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <NavigatorContext.Provider
 | 
			
		||||
      value={{
 | 
			
		||||
        push: pushFrame,
 | 
			
		||||
        pop: popFrame,
 | 
			
		||||
        frames: stack,
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <Index each={stack}>
 | 
			
		||||
        {(frame, index) => {
 | 
			
		||||
          const currentFrame = () => {
 | 
			
		||||
            return {
 | 
			
		||||
              index,
 | 
			
		||||
              frame: frame(),
 | 
			
		||||
            };
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          return (
 | 
			
		||||
            <CurrentFrameContext.Provider value={currentFrame}>
 | 
			
		||||
              <Show
 | 
			
		||||
                when={index !== 0}
 | 
			
		||||
                fallback={
 | 
			
		||||
                  <div
 | 
			
		||||
                    class="StackedPage"
 | 
			
		||||
                    id={frame().rootId}
 | 
			
		||||
                    role="presentation"
 | 
			
		||||
                  >
 | 
			
		||||
                    <StaticRouter url={frame().path} {...oprops} />
 | 
			
		||||
                  </div>
 | 
			
		||||
                }
 | 
			
		||||
              >
 | 
			
		||||
                <dialog
 | 
			
		||||
                  ref={onBeforeDialogMount}
 | 
			
		||||
                  class="StackedPage"
 | 
			
		||||
                  onCancel={[popFrame, 1]}
 | 
			
		||||
                  onClick={[onDialogClick, popFrame]}
 | 
			
		||||
                  id={frame().rootId}
 | 
			
		||||
                >
 | 
			
		||||
                  <StaticRouter url={frame().path} {...oprops} />
 | 
			
		||||
                </dialog>
 | 
			
		||||
              </Show>
 | 
			
		||||
            </CurrentFrameContext.Provider>
 | 
			
		||||
          );
 | 
			
		||||
        }}
 | 
			
		||||
      </Index>
 | 
			
		||||
    </NavigatorContext.Provider>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default StackedRouter;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,6 @@
 | 
			
		|||
.Profile {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
 | 
			
		||||
  .intro {
 | 
			
		||||
    background-color: var(--tutu-color-surface-d);
 | 
			
		||||
    color: var(--tutu-color-on-surface);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,7 +45,7 @@ import {
 | 
			
		|||
  Verified,
 | 
			
		||||
} from "@suid/icons-material";
 | 
			
		||||
import { Body2, Title } from "../material/typography";
 | 
			
		||||
import { useNavigate, useParams } from "@solidjs/router";
 | 
			
		||||
import { useParams } from "@solidjs/router";
 | 
			
		||||
import { useSessionForAcctStr } from "../masto/clients";
 | 
			
		||||
import { resolveCustomEmoji } from "../masto/toot";
 | 
			
		||||
import { FastAverageColor } from "fast-average-color";
 | 
			
		||||
| 
						 | 
				
			
			@ -57,9 +57,10 @@ import TootFilterButton from "./TootFilterButton";
 | 
			
		|||
import Menu, { createManagedMenuState } from "../material/Menu";
 | 
			
		||||
import { share } from "../platform/share";
 | 
			
		||||
import "./Profile.css";
 | 
			
		||||
import { useNavigator } from "../platform/StackedRouter";
 | 
			
		||||
 | 
			
		||||
const Profile: Component = () => {
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const { pop } = useNavigator();
 | 
			
		||||
  const params = useParams<{ acct: string; id: string }>();
 | 
			
		||||
  const acctText = () => decodeURIComponent(params.acct);
 | 
			
		||||
  const session = useSessionForAcctStr(acctText);
 | 
			
		||||
| 
						 | 
				
			
			@ -209,11 +210,7 @@ const Profile: Component = () => {
 | 
			
		|||
              paddingTop: "var(--safe-area-inset-top)",
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <IconButton
 | 
			
		||||
              color="inherit"
 | 
			
		||||
              onClick={[navigate, -1]}
 | 
			
		||||
              aria-label="Close"
 | 
			
		||||
            >
 | 
			
		||||
            <IconButton color="inherit" onClick={[pop, 1]} aria-label="Close">
 | 
			
		||||
              <Close />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <Title
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,10 +24,10 @@ import { Title } from "../material/typography";
 | 
			
		|||
import type { Template } from "@solid-primitives/i18n";
 | 
			
		||||
import { useStore } from "@nanostores/solid";
 | 
			
		||||
import { $settings } from "./stores";
 | 
			
		||||
import { useNavigate } from "@solidjs/router";
 | 
			
		||||
import { useNavigator } from "../platform/StackedRouter";
 | 
			
		||||
 | 
			
		||||
const ChooseLang: Component = () => {
 | 
			
		||||
  const navigate = useNavigate()
 | 
			
		||||
  const { pop } = useNavigator();
 | 
			
		||||
  const [t] = createTranslator(
 | 
			
		||||
    () => import("./i18n/lang-names.json"),
 | 
			
		||||
    (code) =>
 | 
			
		||||
| 
						 | 
				
			
			@ -37,9 +37,9 @@ const ChooseLang: Component = () => {
 | 
			
		|||
        };
 | 
			
		||||
      }>,
 | 
			
		||||
  );
 | 
			
		||||
  const settings = useStore($settings)
 | 
			
		||||
  const settings = useStore($settings);
 | 
			
		||||
 | 
			
		||||
  const code = () => settings().language
 | 
			
		||||
  const code = () => settings().language;
 | 
			
		||||
 | 
			
		||||
  const unsupportedLangCodes = createMemo(() => {
 | 
			
		||||
    return iso639_1.getAllCodes().filter((x) => !["zh", "en"].includes(x));
 | 
			
		||||
| 
						 | 
				
			
			@ -48,8 +48,8 @@ const ChooseLang: Component = () => {
 | 
			
		|||
  const matchedLangCode = createMemo(() => autoMatchLangTag());
 | 
			
		||||
 | 
			
		||||
  const onCodeChange = (code?: string) => {
 | 
			
		||||
    $settings.setKey("language", code)
 | 
			
		||||
  }
 | 
			
		||||
    $settings.setKey("language", code);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Scaffold
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +59,7 @@ const ChooseLang: Component = () => {
 | 
			
		|||
            variant="dense"
 | 
			
		||||
            sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}
 | 
			
		||||
          >
 | 
			
		||||
            <IconButton color="inherit" onClick={[navigate, -1]} disableRipple>
 | 
			
		||||
            <IconButton color="inherit" onClick={[pop, 1]} disableRipple>
 | 
			
		||||
              <ArrowBack />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <Title>{t("Choose Language")}</Title>
 | 
			
		||||
| 
						 | 
				
			
			@ -96,7 +96,10 @@ const ChooseLang: Component = () => {
 | 
			
		|||
                <ListItemText>{t(`lang.${c}`)}</ListItemText>
 | 
			
		||||
                <ListItemSecondaryAction>
 | 
			
		||||
                  <Radio
 | 
			
		||||
                    checked={code() === c || (code() === undefined && matchedLangCode() == c)}
 | 
			
		||||
                    checked={
 | 
			
		||||
                      code() === c ||
 | 
			
		||||
                      (code() === undefined && matchedLangCode() == c)
 | 
			
		||||
                    }
 | 
			
		||||
                  />
 | 
			
		||||
                </ListItemSecondaryAction>
 | 
			
		||||
              </ListItemButton>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,14 +13,14 @@ import {
 | 
			
		|||
  Toolbar,
 | 
			
		||||
} from "@suid/material";
 | 
			
		||||
import { Title } from "../material/typography";
 | 
			
		||||
import { useNavigate } from "@solidjs/router";
 | 
			
		||||
import { ArrowBack } from "@suid/icons-material";
 | 
			
		||||
import { createTranslator } from "../platform/i18n";
 | 
			
		||||
import { useStore } from "@nanostores/solid";
 | 
			
		||||
import { $settings } from "./stores";
 | 
			
		||||
import { useNavigator } from "../platform/StackedRouter";
 | 
			
		||||
 | 
			
		||||
const Motions: Component = () => {
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const {pop} = useNavigator();
 | 
			
		||||
  const [t] = createTranslator(
 | 
			
		||||
    (code) =>
 | 
			
		||||
      import(`./i18n/${code}.json`) as Promise<{
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +36,7 @@ const Motions: Component = () => {
 | 
			
		|||
            variant="dense"
 | 
			
		||||
            sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}
 | 
			
		||||
          >
 | 
			
		||||
            <IconButton color="inherit" onClick={[navigate, -1]} disableRipple>
 | 
			
		||||
            <IconButton color="inherit" onClick={[pop, 1]} disableRipple>
 | 
			
		||||
              <ArrowBack />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <Title>{t("motions")}</Title>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,12 +20,12 @@ import {
 | 
			
		|||
} from "../platform/i18n";
 | 
			
		||||
import { Title } from "../material/typography";
 | 
			
		||||
import type { Template } from "@solid-primitives/i18n";
 | 
			
		||||
import { useNavigate } from "@solidjs/router";
 | 
			
		||||
import { $settings } from "./stores";
 | 
			
		||||
import { useStore } from "@nanostores/solid";
 | 
			
		||||
import { useNavigator } from "../platform/StackedRouter";
 | 
			
		||||
 | 
			
		||||
const ChooseRegion: Component = () => {
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const {pop} = useNavigator();
 | 
			
		||||
  const [t] = createTranslator(
 | 
			
		||||
    () => import("./i18n/lang-names.json"),
 | 
			
		||||
    (code) =>
 | 
			
		||||
| 
						 | 
				
			
			@ -54,7 +54,7 @@ const ChooseRegion: Component = () => {
 | 
			
		|||
            variant="dense"
 | 
			
		||||
            sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}
 | 
			
		||||
          >
 | 
			
		||||
            <IconButton color="inherit" onClick={[navigate, -1]} disableRipple>
 | 
			
		||||
            <IconButton color="inherit" onClick={[pop, 1]} disableRipple>
 | 
			
		||||
              <ArrowBack />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <Title>{t("Choose Region")}</Title>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,7 @@ import {
 | 
			
		|||
  Refresh as RefreshIcon,
 | 
			
		||||
  Translate as TranslateIcon,
 | 
			
		||||
} from "@suid/icons-material";
 | 
			
		||||
import { A, useNavigate } from "@solidjs/router";
 | 
			
		||||
import A from "../platform/A.js";
 | 
			
		||||
import { Title } from "../material/typography.jsx";
 | 
			
		||||
import { css } from "solid-styled";
 | 
			
		||||
import { signOut, type Account } from "../accounts/stores.js";
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +47,7 @@ import { type Template } from "@solid-primitives/i18n";
 | 
			
		|||
import BottomSheet from "../material/BottomSheet.jsx";
 | 
			
		||||
import { useServiceWorker } from "../platform/host.js";
 | 
			
		||||
import { useSessions } from "../masto/clients.js";
 | 
			
		||||
import { useNavigator } from "../platform/StackedRouter.jsx";
 | 
			
		||||
 | 
			
		||||
type Inset = {
 | 
			
		||||
  top?: number;
 | 
			
		||||
| 
						 | 
				
			
			@ -170,7 +171,7 @@ const Settings: ParentComponent = (props) => {
 | 
			
		|||
      }>,
 | 
			
		||||
    () => import(`./i18n/lang-names.json`),
 | 
			
		||||
  );
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const {pop} = useNavigator();
 | 
			
		||||
  const settings$ = useStore($settings);
 | 
			
		||||
  const { needRefresh, offlineReady } = useServiceWorker();
 | 
			
		||||
  const dateFnLocale = useDateFnLocale();
 | 
			
		||||
| 
						 | 
				
			
			@ -200,7 +201,7 @@ const Settings: ParentComponent = (props) => {
 | 
			
		|||
            variant="dense"
 | 
			
		||||
            sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}
 | 
			
		||||
          >
 | 
			
		||||
            <IconButton color="inherit" onClick={[navigate, -1]} disableRipple>
 | 
			
		||||
            <IconButton color="inherit" onClick={[pop, 11]} disableRipple>
 | 
			
		||||
              <CloseIcon />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <Title>{t("Settings")}</Title>
 | 
			
		||||
| 
						 | 
				
			
			@ -208,7 +209,7 @@ const Settings: ParentComponent = (props) => {
 | 
			
		|||
        </AppBar>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <BottomSheet open={!!subpage()} onClose={() => navigate(-1)}>
 | 
			
		||||
      <BottomSheet open={!!subpage()} onClose={() => pop(1)}>
 | 
			
		||||
        {subpage()}
 | 
			
		||||
      </BottomSheet>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,11 +29,11 @@ import BottomSheet, {
 | 
			
		|||
import { $settings } from "../settings/stores";
 | 
			
		||||
import { useStore } from "@nanostores/solid";
 | 
			
		||||
import { HeroSourceProvider, type HeroSource } from "../platform/anim";
 | 
			
		||||
import { useNavigate } from "@solidjs/router";
 | 
			
		||||
import { setCache as setTootBottomSheetCache } from "./TootBottomSheet";
 | 
			
		||||
import TrendTimelinePanel from "./TrendTimelinePanel";
 | 
			
		||||
import TimelinePanel from "./TimelinePanel";
 | 
			
		||||
import { useSessions } from "../masto/clients";
 | 
			
		||||
import { useNavigator } from "../platform/StackedRouter";
 | 
			
		||||
 | 
			
		||||
const Home: ParentComponent = (props) => {
 | 
			
		||||
  let panelList: HTMLDivElement;
 | 
			
		||||
| 
						 | 
				
			
			@ -53,7 +53,7 @@ const Home: ParentComponent = (props) => {
 | 
			
		|||
    const all = profiles();
 | 
			
		||||
    return all?.[0]?.client;
 | 
			
		||||
  };
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const {pop, push} = useNavigator();
 | 
			
		||||
 | 
			
		||||
  const [heroSrc, setHeroSrc] = createSignal<HeroSource>({});
 | 
			
		||||
  const [panelOffset, setPanelOffset] = createSignal(0);
 | 
			
		||||
| 
						 | 
				
			
			@ -151,7 +151,7 @@ const Home: ParentComponent = (props) => {
 | 
			
		|||
    );
 | 
			
		||||
    const acct = `${inf.username}@${p.account.site}`;
 | 
			
		||||
    setTootBottomSheetCache(acct, toot);
 | 
			
		||||
    navigate(`/${encodeURIComponent(acct)}/toot/${toot.id}`, {
 | 
			
		||||
    push(`/${encodeURIComponent(acct)}/toot/${toot.id}`, {
 | 
			
		||||
      state: reply
 | 
			
		||||
        ? {
 | 
			
		||||
            tootReply: true,
 | 
			
		||||
| 
						 | 
				
			
			@ -276,7 +276,7 @@ const Home: ParentComponent = (props) => {
 | 
			
		|||
            </Show>
 | 
			
		||||
          </TimeSourceProvider>
 | 
			
		||||
          <Suspense>
 | 
			
		||||
            <BottomSheet open={!!child()} onClose={() => navigate(-1)}>
 | 
			
		||||
            <BottomSheet open={!!child()} onClose={() => pop(1)}>
 | 
			
		||||
              {child()}
 | 
			
		||||
            </BottomSheet>
 | 
			
		||||
          </Suspense>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,7 +21,7 @@ import {
 | 
			
		|||
  Star as LikeIcon,
 | 
			
		||||
  FeaturedPlayList as ListIcon,
 | 
			
		||||
} from "@suid/icons-material";
 | 
			
		||||
import { A } from "@solidjs/router";
 | 
			
		||||
import A from "../platform/A";
 | 
			
		||||
 | 
			
		||||
const ProfileMenuButton: ParentComponent<{
 | 
			
		||||
  profile?: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,11 @@
 | 
			
		|||
.TootBottomSheet {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  height: calc(100% - var(--scaffold-topbar-height, 0px));
 | 
			
		||||
 | 
			
		||||
  .Scrollable {
 | 
			
		||||
    padding-bottom: var(--safe-area-inset-bottom, 0);
 | 
			
		||||
    overflow-y: auto;
 | 
			
		||||
    overscroll-behavior-y: contain;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    height: calc(100% - var(--scaffold-topbar-height, 0px));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .progress-line {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,9 @@
 | 
			
		|||
import { useLocation, useNavigate, useParams } from "@solidjs/router";
 | 
			
		||||
import { useLocation, useParams } from "@solidjs/router";
 | 
			
		||||
import {
 | 
			
		||||
  catchError,
 | 
			
		||||
  createEffect,
 | 
			
		||||
  createRenderEffect,
 | 
			
		||||
  createResource,
 | 
			
		||||
  createSignal,
 | 
			
		||||
  Show,
 | 
			
		||||
  type Component,
 | 
			
		||||
} from "solid-js";
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +24,8 @@ import { useDocumentTitle } from "../utils";
 | 
			
		|||
import { createTimelineControlsForArray } from "../masto/timelines";
 | 
			
		||||
import TootList from "./TootList";
 | 
			
		||||
import "./TootBottomSheet.css";
 | 
			
		||||
import { useNavigator } from "../platform/StackedRouter";
 | 
			
		||||
import BackButton from "../platform/BackButton";
 | 
			
		||||
 | 
			
		||||
let cachedEntry: [string, mastodon.v1.Status] | undefined;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +44,7 @@ const TootBottomSheet: Component = (props) => {
 | 
			
		|||
  const location = useLocation<{
 | 
			
		||||
    tootReply?: boolean;
 | 
			
		||||
  }>();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const { pop, push } = useNavigator();
 | 
			
		||||
  const time = createTimeSource();
 | 
			
		||||
  const acctText = () => decodeURIComponent(params.acct);
 | 
			
		||||
  const session = useSessionForAcctStr(acctText);
 | 
			
		||||
| 
						 | 
				
			
			@ -186,7 +187,7 @@ const TootBottomSheet: Component = (props) => {
 | 
			
		|||
          target.dataset.client || `@${new URL(target.href).origin}`,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        navigate(`/${acct}/profile/${target.dataset.acctId}`);
 | 
			
		||||
        push(`/${acct}/profile/${target.dataset.acctId}`);
 | 
			
		||||
 | 
			
		||||
        return;
 | 
			
		||||
      } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -228,9 +229,7 @@ const TootBottomSheet: Component = (props) => {
 | 
			
		|||
            variant="dense"
 | 
			
		||||
            sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}
 | 
			
		||||
          >
 | 
			
		||||
            <IconButton color="inherit" onClick={[navigate, -1]} disableRipple>
 | 
			
		||||
              <CloseIcon />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <BackButton color="inherit" />
 | 
			
		||||
            <Title component="div" class="name" use:solid-styled>
 | 
			
		||||
              <span
 | 
			
		||||
                ref={(e: HTMLElement) =>
 | 
			
		||||
| 
						 | 
				
			
			@ -246,9 +245,7 @@ const TootBottomSheet: Component = (props) => {
 | 
			
		|||
      }
 | 
			
		||||
      class="TootBottomSheet"
 | 
			
		||||
    >
 | 
			
		||||
      <div
 | 
			
		||||
        class="Scrollable"
 | 
			
		||||
      >
 | 
			
		||||
      <div class="Scrollable">
 | 
			
		||||
        <TimeSourceProvider value={time}>
 | 
			
		||||
          <TootList
 | 
			
		||||
            threads={ancestors.list}
 | 
			
		||||
| 
						 | 
				
			
			@ -288,9 +285,7 @@ const TootBottomSheet: Component = (props) => {
 | 
			
		|||
          </Show>
 | 
			
		||||
 | 
			
		||||
          <Show when={tootContextErrorUncaught.loading}>
 | 
			
		||||
            <div
 | 
			
		||||
              class="progress-line"
 | 
			
		||||
            >
 | 
			
		||||
            <div class="progress-line">
 | 
			
		||||
              <CircularProgress style="width: 1.5em; height: 1.5em;" />
 | 
			
		||||
            </div>
 | 
			
		||||
          </Show>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,7 @@ import RegularToot, {
 | 
			
		|||
} from "./RegularToot";
 | 
			
		||||
import cardStyle from "../material/cards.module.css";
 | 
			
		||||
import type { ThreadNode } from "../masto/timelines";
 | 
			
		||||
import { useNavigator } from "../platform/StackedRouter";
 | 
			
		||||
 | 
			
		||||
function positionTootInThread(index: number, threadLength: number) {
 | 
			
		||||
  if (index === 0) {
 | 
			
		||||
| 
						 | 
				
			
			@ -40,7 +41,7 @@ const TootList: Component<{
 | 
			
		|||
  const session = useDefaultSession();
 | 
			
		||||
  const heroSrc = useHeroSource();
 | 
			
		||||
  const [expandedThreadId, setExpandedThreadId] = createSignal<string>();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const {push} = useNavigator();
 | 
			
		||||
 | 
			
		||||
  const onBookmark = async (status: mastodon.v1.Status) => {
 | 
			
		||||
    const client = session()?.client;
 | 
			
		||||
| 
						 | 
				
			
			@ -115,7 +116,7 @@ const TootList: Component<{
 | 
			
		|||
 | 
			
		||||
    const acct = `${inf.username}@${p.site}`;
 | 
			
		||||
    setTootBottomSheetCache(acct, toot);
 | 
			
		||||
    navigate(`/${encodeURIComponent(acct)}/toot/${toot.id}`, {
 | 
			
		||||
    push(`/${encodeURIComponent(acct)}/toot/${toot.id}`, {
 | 
			
		||||
      state: reply
 | 
			
		||||
        ? {
 | 
			
		||||
            tootReply: true,
 | 
			
		||||
| 
						 | 
				
			
			@ -146,7 +147,7 @@ const TootList: Component<{
 | 
			
		|||
          target.dataset.client || `@${new URL(target.href).origin}`,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        navigate(`/${acct}/profile/${target.dataset.acctId}`);
 | 
			
		||||
        push(`/${acct}/profile/${target.dataset.acctId}`);
 | 
			
		||||
 | 
			
		||||
        return;
 | 
			
		||||
      } else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue