Skip to content

Commit

Permalink
feat: mobile design (RSSNext#1568)
Browse files Browse the repository at this point in the history
* init

Signed-off-by: Innei <i@innei.in>

* feat: feed column

Signed-off-by: Innei <i@innei.in>

* feat: float bar

Signed-off-by: Innei <tukon479@gmail.com>

* feat: profile button

Signed-off-by: Innei <tukon479@gmail.com>

* chore: comment

Signed-off-by: Innei <tukon479@gmail.com>

* feat: layout

Signed-off-by: Innei <tukon479@gmail.com>

* feat: split mobile entry content

Signed-off-by: Innei <tukon479@gmail.com>

* feat: add history stack

Signed-off-by: Innei <tukon479@gmail.com>

* feat: return back

Signed-off-by: Innei <tukon479@gmail.com>

* fix: return back position

Signed-off-by: Innei <tukon479@gmail.com>

* feat: entry list header

Signed-off-by: Innei <tukon479@gmail.com>

* feat: sync selector

Signed-off-by: Innei <tukon479@gmail.com>

* feat: modal stack mobile

Signed-off-by: Innei <tukon479@gmail.com>

* fix: modal selector

Signed-off-by: Innei <tukon479@gmail.com>

* cleanup

Signed-off-by: Innei <tukon479@gmail.com>

* fix: sheet and optimize entry readers

Signed-off-by: Innei <tukon479@gmail.com>

* fix: scroller

Signed-off-by: Innei <tukon479@gmail.com>

* feat: entry contents

Signed-off-by: Innei <tukon479@gmail.com>

* feat: entry content

Signed-off-by: Innei <tukon479@gmail.com>

* update

Signed-off-by: Innei <tukon479@gmail.com>

* feat: user profile sheet

Signed-off-by: Innei <tukon479@gmail.com>

* fix: entry content header

Signed-off-by: Innei <tukon479@gmail.com>

* fix: entry header

Signed-off-by: Innei <tukon479@gmail.com>

* chore: auto-fix linting and formatting issues

* fix: return back

Signed-off-by: Innei <tukon479@gmail.com>

* feat: image view

Signed-off-by: Innei <tukon479@gmail.com>

* feat: audio

Signed-off-by: Innei <tukon479@gmail.com>

* feat: inline player

Signed-off-by: Innei <tukon479@gmail.com>

* feat: pull to refresh

Signed-off-by: Innei <tukon479@gmail.com>

* cleanup

Signed-off-by: Innei <tukon479@gmail.com>

* update

Signed-off-by: Innei <tukon479@gmail.com>

* chore: auto-fix linting and formatting issues

* chore: fix import, remove no support display

* chore: auto-fix linting and formatting issues

* feat: achievement modal in mobile

Signed-off-by: Innei <tukon479@gmail.com>

* fix: subview mobile

Signed-off-by: Innei <tukon479@gmail.com>

* feat: power

Signed-off-by: Innei <tukon479@gmail.com>

* feat: wallet mobile design

Signed-off-by: Innei <tukon479@gmail.com>

* feat: pwa support (RSSNext#1575)

Co-authored-by: hyoban <hyoban@users.noreply.github.com>

* chore: auto-fix linting and formatting issues

* fix import

* discover

* fix: border color

Signed-off-by: Innei <tukon479@gmail.com>

* fix: modal async

Signed-off-by: Innei <tukon479@gmail.com>

* feat: mobile setting

Signed-off-by: Innei <tukon479@gmail.com>

* fix: entry header width

Signed-off-by: Innei <tukon479@gmail.com>

* fix: remove unused hide filter

* feat: settings

Signed-off-by: Innei <tukon479@gmail.com>

* feat: setting done

Signed-off-by: Innei <tukon479@gmail.com>

* action card

* fix discover feed form

* popular filter

* inbox table

* fix: picture masonry pull to refresh

Signed-off-by: Innei <tukon479@gmail.com>

* fix: scroll element

Signed-off-by: Innei <tukon479@gmail.com>

* fix: radio in mobile

Signed-off-by: Innei <tukon479@gmail.com>

* feat: to scientific notation

Signed-off-by: Innei <tukon479@gmail.com>

* fix: login

Signed-off-by: Innei <tukon479@gmail.com>

* ResponsiveSelect for action card

* chore: remove theme color

Signed-off-by: Innei <tukon479@gmail.com>

* type check

* type check

* fix: setting import circular

Signed-off-by: Innei <tukon479@gmail.com>

* refactor: remove hijack pushState

Signed-off-by: Innei <tukon479@gmail.com>

* fix: float bar

Signed-off-by: Innei <tukon479@gmail.com>

* fix: scroll end trigger

Signed-off-by: Innei <tukon479@gmail.com>

* fix: dark theme color

Signed-off-by: Innei <tukon479@gmail.com>

* fix: overscroll-behavior only in destop

Signed-off-by: Innei <tukon479@gmail.com>

* fix: ux

Signed-off-by: Innei <tukon479@gmail.com>

* fix: color

Signed-off-by: Innei <tukon479@gmail.com>

* fix: list scroll area

Signed-off-by: Innei <tukon479@gmail.com>

* update

Signed-off-by: Innei <tukon479@gmail.com>

* merge

* update

* update favicon link

* fix: reload prompt on mobile

* test

Signed-off-by: Innei <i@innei.in>

* test

Signed-off-by: Innei <i@innei.in>

* fix: ios

Signed-off-by: Innei <i@innei.in>

* try patch precaching

* try

* try

* build

* try

* try

* feat: ctx menu on safari

Signed-off-by: Innei <tukon479@gmail.com>

* try

* clear timeout

Signed-off-by: Innei <tukon479@gmail.com>

* try

* fix

* deny og

* fix patch

* pre cache exclude

* feat: add all

Signed-off-by: Innei <tukon479@gmail.com>

* i18n

Signed-off-by: Innei <tukon479@gmail.com>

* feat: slide

Signed-off-by: Innei <tukon479@gmail.com>

* feat: ico

Signed-off-by: Innei <tukon479@gmail.com>

* fix: build

Signed-off-by: Innei <tukon479@gmail.com>

* test network first

Signed-off-by: Innei <tukon479@gmail.com>

* test

Signed-off-by: Innei <tukon479@gmail.com>

* fix: icon padding

* test

Signed-off-by: Innei <tukon479@gmail.com>

* use react-ios-pwa-prompt

* fix: icon path

* update

* fix: prompt

Signed-off-by: Innei <tukon479@gmail.com>

* feat: redesign toc

Signed-off-by: Innei <tukon479@gmail.com>

* ctx

Signed-off-by: Innei <tukon479@gmail.com>

* ready to prod

Signed-off-by: Innei <tukon479@gmail.com>

* feat: startup screen

Signed-off-by: Innei <tukon479@gmail.com>

* feat: floatbar

Signed-off-by: Innei <tukon479@gmail.com>

* fix: switch view

Signed-off-by: Innei <tukon479@gmail.com>

* fix: keep scroll map

Signed-off-by: Innei <tukon479@gmail.com>

* enable hoverOnlyWhenSupported

* fix: count

Signed-off-by: Innei <tukon479@gmail.com>

* fix: hoverOnlyWhenSupported only in web

Signed-off-by: Innei <tukon479@gmail.com>

* fix

Signed-off-by: Innei <tukon479@gmail.com>

* chore: auto-fix linting and formatting issues

---------

Signed-off-by: Innei <i@innei.in>
Signed-off-by: Innei <tukon479@gmail.com>
Co-authored-by: Innei <Innei@users.noreply.github.com>
Co-authored-by: Stephen Zhou <38493346+hyoban@users.noreply.github.com>
Co-authored-by: hyoban <hyoban@users.noreply.github.com>
Co-authored-by: lawvs <18554747+lawvs@users.noreply.github.com>
Co-authored-by: lawvs <lawvs@users.noreply.github.com>
  • Loading branch information
6 people authored Nov 22, 2024
1 parent 78611bb commit edd4f9e
Show file tree
Hide file tree
Showing 229 changed files with 9,853 additions and 5,455 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ vite.config.*.mjs

.generated
.turbo

apps/renderer/dev-dist
tsconfig.tsbuildinfo
13 changes: 13 additions & 0 deletions apps/renderer/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,17 @@ declare global {
}
}

declare module "virtual:pwa-register/react" {
import type { Dispatch, SetStateAction } from "react"
import type { RegisterSWOptions } from "vite-plugin-pwa/types"

export function useRegisterSW(options?: RegisterSWOptions): {
needRefresh: [boolean, Dispatch<SetStateAction<boolean>>]
offlineReady: [boolean, Dispatch<SetStateAction<boolean>>]
updateServiceWorker: (reloadPage?: boolean) => Promise<void>
}
}

export {}

export { type RegisterSWOptions } from "vite-plugin-pwa/types"
17 changes: 14 additions & 3 deletions apps/renderer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
<meta name="referrer" content="no-referrer" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0, viewport-fit=cover"
/>

<meta name="theme-color" content="#ff5c00" />
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#121212" />
<!-- favicon -->
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<link rel="icon" href="/favicon.ico" sizes="48x48" />
<link rel="icon" href="/favicon.svg" sizes="any" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon-180x180.png" />
<title>Follow</title>

<meta property="og:type" content="website" />
Expand Down Expand Up @@ -58,6 +61,10 @@
const isElectron = navigator.userAgent.includes("Electron")
document.documentElement.dataset.buildType = isElectron ? "electron" : "web"
</script>
<script>
const isMobile = window.innerWidth < 1024
document.documentElement.dataset.viewport = isMobile ? "mobile" : "desktop"
</script>
</head>
<body>
<div id="root"></div>
Expand All @@ -77,6 +84,9 @@
inset: 0;
z-index: 1000;
}
[data-viewport="mobile"] #app-skeleton {
display: none;
}
[data-theme="light"] {
--background: 0 0% 100%;
}
Expand All @@ -89,6 +99,7 @@
height: 100%;
width: 100%;
}

.sidebar {
width: 16rem;
flex-shrink: 0;
Expand Down
4 changes: 4 additions & 0 deletions apps/renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"build:web": "cd ../.. && pnpm build:web",
"dev": "cd ../.. && pnpm dev:web",
"generate-pwa-assets": "pwa-assets-generator public/icon.svg",
"test": "vitest --typecheck",
"typecheck": "tsc --noEmit"
},
Expand Down Expand Up @@ -82,6 +83,7 @@
"react-hotkeys-hook": "4.6.1",
"react-i18next": "^15.1.0",
"react-intersection-observer": "9.13.1",
"react-ios-pwa-prompt": "^2.0.6",
"react-resizable-layout": "npm:@innei/react-resizable-layout@0.7.3-fork.1",
"react-router-dom": "6.27.0",
"react-selecto": "^1.26.3",
Expand All @@ -103,6 +105,7 @@
"unified": "11.0.5",
"unist-util-visit-parents": "^6.0.1",
"use-context-selector": "2.0.0",
"use-pull-to-refresh": "2.4.1",
"use-sync-external-store": "1.2.2",
"usehooks-ts": "3.1.0",
"vfile": "6.0.3",
Expand All @@ -125,6 +128,7 @@
"@types/node": "^22.8.7",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vite-pwa/assets-generator": "^0.2.6",
"fake-indexeddb": "6.0.0",
"happy-dom": "15.8.0",
"react": "^18.3.1",
Expand Down
Binary file added apps/renderer/public/apple-touch-icon-180x180.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/renderer/public/favicon.ico
Binary file not shown.
16 changes: 0 additions & 16 deletions apps/renderer/public/manifest.json

This file was deleted.

Binary file added apps/renderer/public/maskable-icon-512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/renderer/public/pwa-192x192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/renderer/public/pwa-512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/renderer/public/pwa-64x64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 54 additions & 0 deletions apps/renderer/pwa-assets.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { Preset } from "@vite-pwa/assets-generator/config"
import { defineConfig } from "@vite-pwa/assets-generator/config"

const minimal2023Preset: Preset = {
transparent: {
sizes: [64, 192, 512],
favicons: [[48, "favicon.ico"]],
padding: 0.05,
// rgba(255, 92, 0, 1)
resizeOptions: {
fit: "contain",
background: {
r: 255,
g: 92,
b: 0,
alpha: 1,
},
},
},
maskable: {
sizes: [512],
padding: 0,
resizeOptions: {
fit: "contain",
background: {
r: 255,
g: 92,
b: 0,
alpha: 1,
},
},
},
apple: {
sizes: [180],
padding: 0,
resizeOptions: {
fit: "contain",
background: {
r: 255,
g: 92,
b: 0,
alpha: 1,
},
},
},
}

export default defineConfig({
headLinkOptions: {
preset: "2023",
},
preset: minimal2023Preset,
images: ["public/logo.svg"],
})
14 changes: 14 additions & 0 deletions apps/renderer/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isMobile } from "@follow/components/hooks/useMobile.js"
import { IN_ELECTRON } from "@follow/shared/constants"
import { cn, getOS } from "@follow/utils/utils"
import { useEffect } from "react"
Expand All @@ -12,6 +13,7 @@ import { applyAfterReadyCallbacks } from "./initialize/queue"
import { removeAppSkeleton } from "./lib/app"
import { appLog } from "./lib/log"
import { Titlebar } from "./modules/app/Titlebar"
import { useRegisterFollowCommands } from "./modules/command/use-register-follow-commands"
import { RootProviders } from "./providers/root-providers"
import { handlers } from "./tipc"

Expand Down Expand Up @@ -53,6 +55,7 @@ function App() {
const AppLayer = () => {
const appIsReady = useAppIsReady()

useRegisterFollowCommands()
useEffect(() => {
removeAppSkeleton()

Expand All @@ -63,6 +66,17 @@ const AppLayer = () => {
appLog("App is ready", `${doneTime}ms`)

applyAfterReadyCallbacks()

if (isMobile()) {
const handler = (e: MouseEvent) => {
e.preventDefault()
}
document.addEventListener("contextmenu", handler)

return () => {
document.removeEventListener("contextmenu", handler)
}
}
}, [appIsReady])

return appIsReady ? <Outlet /> : <AppSkeleton />
Expand Down
4 changes: 3 additions & 1 deletion apps/renderer/src/atoms/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ const playerInitialValue: PlayerAtomValue = {
}

const jsonStorage = createJSONStorage<PlayerAtomValue>()
let hydrationDone = false
const patchedLocalStorage: SyncStorage<PlayerAtomValue> = {
setItem: jsonStorage.setItem,
getItem: (key, initialValue) => {
const value = jsonStorage.getItem(key, initialValue)
if (value) {
if (value && !hydrationDone) {
// patch status to `paused` when hydration
value.status = "paused"
hydrationDone = true
}
return value
},
Expand Down
2 changes: 2 additions & 0 deletions apps/renderer/src/atoms/settings/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const createDefaultSettings = (): GeneralSettings => ({
// App
appLaunchOnStartup: false,
language: "en",
// mobile app
startupScreen: "timeline",
// Data control
dataPersist: true,
sendAnonymousData: true,
Expand Down
83 changes: 83 additions & 0 deletions apps/renderer/src/components/common/ReloadPrompt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useEffect } from "react"
import { toast } from "sonner"
import { useRegisterSW } from "virtual:pwa-register/react"

// check for updates every hour
const period = 60 * 60 * 1000

export function ReloadPrompt() {
const {
// offlineReady: [offlineReady, setOfflineReady],
needRefresh: [needRefresh],
updateServiceWorker,
} = useRegisterSW({
onRegisteredSW(swUrl, r) {
if (period <= 0) return
if (r?.active?.state === "activated") {
registerPeriodicSync(period, swUrl, r)
} else if (r?.installing) {
r.installing.addEventListener("statechange", (e) => {
const sw = e.target as ServiceWorker
if (sw.state === "activated") registerPeriodicSync(period, swUrl, r)
})
}
},
})

// const close = useCallback(() => {
// setOfflineReady(false)
// setNeedRefresh(false)
// }, [setNeedRefresh, setOfflineReady])

// useEffect(() => {
// if (offlineReady) {
// toast.info("App is ready to work offline", {
// action: {
// label: "Close",
// onClick: close,
// },
// duration: Infinity,
// })
// }
// }, [offlineReady, close])

useEffect(() => {
const isPwa = window.matchMedia("(display-mode: standalone)").matches
if (!isPwa) return

if (needRefresh) {
toast.info("New version available", {
action: {
label: "Refresh",
onClick: () => {
updateServiceWorker(true)
},
},
duration: Infinity,
})
}
}, [needRefresh, updateServiceWorker])

return null
}

/**
* This function will register a periodic sync check every hour, you can modify the interval as needed.
*/
function registerPeriodicSync(period: number, swUrl: string, r: ServiceWorkerRegistration) {
if (period <= 0) return

setInterval(async () => {
if ("onLine" in navigator && !navigator.onLine) return

const resp = await fetch(swUrl, {
cache: "no-store",
headers: {
cache: "no-store",
"cache-control": "no-cache",
},
})

if (resp?.status === 200) await r.update()
}, period)
}
13 changes: 13 additions & 0 deletions apps/renderer/src/components/mobile/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { MotionButtonBase } from "@follow/components/ui/button/index.js"
import { cn } from "@follow/utils/utils"

export const HeaderTopReturnBackButton: Component<{ to?: string }> = ({ className, to }) => (
<MotionButtonBase
onClick={() => window.history.returnBack(to)}
className={cn("center size-8", className)}
>
<i className="i-mingcute-left-line size-6" />

<span className="sr-only">Back</span>
</MotionButtonBase>
)
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const ContextMenuContent = React.forwardRef<
ref={ref}
className={cn(
"z-[60] min-w-32 overflow-hidden rounded-md border border-border bg-theme-modal-background-opaque p-1 text-theme-foreground/90 shadow-lg dark:shadow-zinc-800/60",
"text-xs",
"text-xs motion-duration-150 motion-scale-in-75 lg:animate-none",
className,
)}
{...props}
Expand Down
11 changes: 10 additions & 1 deletion apps/renderer/src/components/ui/markdown/HTML.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { MemoedDangerousHTMLStyle } from "@follow/components/common/MemoedDanger
import katexStyle from "katex/dist/katex.min.css?raw"
import { createElement, Fragment, memo, useEffect, useMemo, useRef, useState } from "react"

import { ENTRY_CONTENT_RENDER_CONTAINER_ID } from "~/constants/dom"
import { parseHtml } from "~/lib/parse-html"
import { useWrappedElementSize } from "~/providers/wrapped-element-provider"

Expand Down Expand Up @@ -79,7 +80,15 @@ const HTMLImpl = <A extends keyof JSX.IntrinsicElements = "div">(props: HTMLProp
<MediaContainerWidthProvider width={containerWidth}>
<MediaInfoRecordProvider mediaInfo={mediaInfo}>
<MemoedDangerousHTMLStyle>{katexStyle}</MemoedDangerousHTMLStyle>
{createElement(as, { ...rest, ref: setRefElement }, markdownElement)}
{createElement(
as,
{
...rest,
id: ENTRY_CONTENT_RENDER_CONTAINER_ID,
ref: setRefElement,
},
markdownElement,
)}
</MediaInfoRecordProvider>
</MediaContainerWidthProvider>
{!!accessory && <Fragment key={shouldForceReMountKey}>{accessory}</Fragment>}
Expand Down
Loading

0 comments on commit edd4f9e

Please sign in to comment.