diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5fb03dec..b38daafa 100755 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -116,6 +116,13 @@ model Zipline { discordOnShortenAvatarUrl String? discordOnShortenContent String? discordOnShortenEmbed Json? + + pwaEnabled Boolean @default(false) + pwaTitle String @default("Zipline") + pwaShortName String @default("Zipline") + pwaDescription String @default("Zipline") + pwaThemeColor String @default("#000000") + pwaBackgroundColor String @default("#000000") } model User { diff --git a/public/favicon-128x128.png b/public/favicon-128x128.png new file mode 100644 index 00000000..d209dd19 Binary files /dev/null and b/public/favicon-128x128.png differ diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 00000000..76b02bee Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 00000000..3ffa4596 Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon-512x512.png b/public/favicon-512x512.png new file mode 100644 index 00000000..0a16f1e4 Binary files /dev/null and b/public/favicon-512x512.png differ diff --git a/public/favicon-64x64.png b/public/favicon-64x64.png new file mode 100644 index 00000000..dcc61459 Binary files /dev/null and b/public/favicon-64x64.png differ diff --git a/src/components/pages/serverSettings/index.tsx b/src/components/pages/serverSettings/index.tsx index 5f88e926..96e02b9c 100644 --- a/src/components/pages/serverSettings/index.tsx +++ b/src/components/pages/serverSettings/index.tsx @@ -14,6 +14,7 @@ import ServerSettingsRatelimit from './parts/ServerSettingsRatelimit'; import ServerSettingsTasks from './parts/ServerSettingsTasks'; import ServerSettingsUrls from './parts/ServerSettingsUrls'; import ServerSettingsWebsite from './parts/ServerSettingsWebsite'; +import ServerSettingsPWA from './parts/ServerSettingsPWA'; export default function DashboardSettings() { const { data, isLoading, error } = useSWR('/api/server/settings'); @@ -44,6 +45,10 @@ export default function DashboardSettings() { + + + + )} @@ -53,8 +58,6 @@ export default function DashboardSettings() {
Error loading server settings
) : ( <> - - )} diff --git a/src/components/pages/serverSettings/parts/ServerSettingsPWA.tsx b/src/components/pages/serverSettings/parts/ServerSettingsPWA.tsx new file mode 100644 index 00000000..1b170be5 --- /dev/null +++ b/src/components/pages/serverSettings/parts/ServerSettingsPWA.tsx @@ -0,0 +1,126 @@ +import { Response } from '@/lib/api/response'; +import { + Button, + ColorInput, + LoadingOverlay, + Paper, + SimpleGrid, + Switch, + TextInput, + Title, +} from '@mantine/core'; +import { useForm } from '@mantine/form'; +import { IconDeviceFloppy } from '@tabler/icons-react'; +import { useRouter } from 'next/router'; +import { useEffect } from 'react'; +import { settingsOnSubmit } from '../settingsOnSubmit'; + +export default function ServerSettingsPWA({ + swr: { data, isLoading }, +}: { + swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean }; +}) { + const router = useRouter(); + const form = useForm({ + initialValues: { + pwaEnabled: false, + pwaTitle: '', + pwaShortName: '', + pwaDescription: '', + pwaThemeColor: '', + pwaBackgroundColor: '', + }, + }); + + const onSubmit = async (values: typeof form.values) => { + const sendValues: Record = {}; + + sendValues.pwaTitle = values.pwaTitle.trim() === '' ? null : values.pwaTitle.trim(); + sendValues.pwaShortName = values.pwaShortName.trim() === '' ? null : values.pwaShortName.trim(); + sendValues.pwaDescription = values.pwaDescription.trim() === '' ? null : values.pwaDescription.trim(); + + return settingsOnSubmit( + router, + form, + )({ + ...sendValues, + pwaEnabled: values.pwaEnabled, + pwaThemeColor: values.pwaThemeColor, + pwaBackgroundColor: values.pwaBackgroundColor, + }); + }; + + useEffect(() => { + form.setValues({ + pwaEnabled: data?.pwaEnabled ?? false, + pwaTitle: data?.pwaTitle ?? '', + pwaShortName: data?.pwaShortName ?? '', + pwaDescription: data?.pwaDescription ?? '', + pwaThemeColor: data?.pwaThemeColor ?? '', + pwaBackgroundColor: data?.pwaBackgroundColor ?? '', + }); + }, [data]); + + return ( + + + + PWA + +
+ + + + + + + + + + + + + + + + +
+ ); +} diff --git a/src/lib/config/read.ts b/src/lib/config/read.ts index acee7f66..76385777 100755 --- a/src/lib/config/read.ts +++ b/src/lib/config/read.ts @@ -129,6 +129,14 @@ export const rawConfig: any = { key: undefined, cert: undefined, }, + pwa: { + enabled: undefined, + title: undefined, + shortName: undefined, + description: undefined, + backgroundColor: undefined, + themeColor: undefined, + }, }; export const PROP_TO_ENV = { @@ -256,6 +264,13 @@ export const DATABASE_TO_PROP = { discordOnShortenAvatarUrl: 'discord.onShorten.avatarUrl', discordOnShortenContent: 'discord.onShorten.content', discordOnShortenEmbed: 'discord.onShorten.embed', + + pwaEnabled: 'pwa.enabled', + pwaTitle: 'pwa.title', + pwaShortName: 'pwa.shortName', + pwaDescription: 'pwa.description', + pwaThemeColor: 'pwa.themeColor', + pwaBackgroundColor: 'pwa.backgroundColor', }; const logger = log('config').c('read'); diff --git a/src/lib/config/validate.ts b/src/lib/config/validate.ts index 3bf2722e..75d7d614 100755 --- a/src/lib/config/validate.ts +++ b/src/lib/config/validate.ts @@ -297,6 +297,14 @@ export const schema = z.object({ .nullable() .default(null), }), + pwa: z.object({ + enabled: z.boolean().default(true), + title: z.string().default('Zipline'), + shortName: z.string().default('Zipline'), + description: z.string().default('Zipline'), + themeColor: z.string().default('#000000'), + backgroundColor: z.string().default('#000000'), + }), }); export type Config = z.infer; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index ef11b905..093a1883 100755 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -12,7 +12,6 @@ import '@mantine/dropzone/styles.css'; import '@mantine/notifications/styles.css'; import 'mantine-datatable/styles.css'; -import '@/lib/theme/override.css'; import '@/components/render/code/HighlightCode.theme.css'; const fetcher = async (url: RequestInfo | URL) => { @@ -35,6 +34,7 @@ export default function App({ Component, pageProps }: AppProps) { Zipline + ({ + src: `/favicon-${size}x${size}.png`, + sizes: `${size}x${size}`, + type: 'image/png', + })); +} + +export const PATH = '/manifest.json'; +export default fastifyPlugin( + (server, _, done) => { + server.get(PATH, async (_, res) => { + if (!config.pwa.enabled) return res.notFound(); + + return { + name: config.pwa.title, + short_name: config.pwa.shortName, + description: config.pwa.description, + theme_color: config.pwa.themeColor, + background_color: config.pwa.backgroundColor, + + start_url: '/', + display: 'standalone', + icons: generateIcons([16, 32, 64, 128, 512]), + } satisfies MetadataRoute.Manifest; + }); + + done(); + }, + { name: PATH }, +);