feat: pwa

This commit is contained in:
diced 2024-09-22 15:10:36 -07:00
parent fbb1f54b27
commit a62e03b439
No known key found for this signature in database
GPG key ID: 370BD1BA142842D1
13 changed files with 204 additions and 3 deletions

View file

@ -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 {

BIN
public/favicon-128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
public/favicon-512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
public/favicon-64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -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<Response['/api/server/settings']>('/api/server/settings');
@ -44,6 +45,10 @@ export default function DashboardSettings() {
<ServerSettingsRatelimit swr={{ data, isLoading }} />
<ServerSettingsWebsite swr={{ data, isLoading }} />
<ServerSettingsOauth swr={{ data, isLoading }} />
<ServerSettingsPWA swr={{ data, isLoading }} />
<ServerSettingsHttpWebhook swr={{ data, isLoading }} />
</>
)}
</SimpleGrid>
@ -53,8 +58,6 @@ export default function DashboardSettings() {
<div>Error loading server settings</div>
) : (
<>
<ServerSettingsHttpWebhook swr={{ data, isLoading }} />
<ServerSettingsDiscord swr={{ data, isLoading }} />
</>
)}

View file

@ -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<string, any> = {};
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 (
<Paper withBorder p='sm' pos='relative'>
<LoadingOverlay visible={isLoading} />
<Title order={2}>PWA</Title>
<form onSubmit={form.onSubmit(onSubmit)}>
<Switch
mt='md'
label='PWA Enabled'
description='Allow users to install the Zipline PWA on their devices. After enabling, refresh the page to see the download button.'
{...form.getInputProps('pwaEnabled', { type: 'checkbox' })}
/>
<SimpleGrid mt='md' cols={{ base: 1, md: 2 }} spacing='lg'>
<TextInput
label='Title'
description='The title for the PWA'
placeholder='Zipline'
disabled={!form.values.pwaEnabled}
{...form.getInputProps('pwaTitle')}
/>
<TextInput
label='Short Name'
description='The short name for the PWA'
placeholder='Zipline'
disabled={!form.values.pwaEnabled}
{...form.getInputProps('pwaShortName')}
/>
<TextInput
label='Description'
description='The description for the PWA'
placeholder='Zipline'
disabled={!form.values.pwaEnabled}
{...form.getInputProps('pwaDescription')}
/>
<ColorInput
label='Theme Color'
description='The theme color for the PWA'
placeholder='#000000'
disabled={!form.values.pwaEnabled}
{...form.getInputProps('pwaThemeColor')}
/>
<ColorInput
label='Background Color'
description='The background color for the PWA'
placeholder='#ffffff'
disabled={!form.values.pwaEnabled}
{...form.getInputProps('pwaBackgroundColor')}
/>
</SimpleGrid>
<Button type='submit' mt='md' loading={isLoading} leftSection={<IconDeviceFloppy size='1rem' />}>
Save
</Button>
</form>
</Paper>
);
}

View file

@ -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');

View file

@ -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<typeof schema>;

View file

@ -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) {
<Head>
<title>Zipline</title>
<meta name='viewport' content='minimum-scale=1, initial-scale=1, width=device-width' />
<link rel='manifest' href='/manifest.json' />
</Head>
<SWRConfig

View file

@ -220,6 +220,13 @@ export default fastifyPlugin(
discordOnShortenAvatarUrl: z.string().url().nullable(),
discordOnShortenContent: z.string().nullable(),
discordOnShortenEmbed: discordEmbed,
pwaEnabled: z.boolean(),
pwaTitle: z.string(),
pwaShortName: z.string(),
pwaDescription: z.string(),
pwaThemeColor: z.string().regex(/^#?([a-f0-9]{6}|[a-f0-9]{3})$/),
pwaBackgroundColor: z.string().regex(/^#?([a-f0-9]{6}|[a-f0-9]{3})/),
})
.partial()
.refine(

View file

@ -0,0 +1,35 @@
import { config } from '@/lib/config';
import fastifyPlugin from 'fastify-plugin';
import type { MetadataRoute } from 'next';
function generateIcons(sizes: number[]): MetadataRoute.Manifest['icons'] {
return sizes.map((size) => ({
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 },
);