mirror of
https://github.com/diced/zipline.git
synced 2025-05-10 18:05:54 +02:00
feat: pwa
This commit is contained in:
parent
fbb1f54b27
commit
a62e03b439
13 changed files with 204 additions and 3 deletions
|
@ -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
BIN
public/favicon-128x128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5 KiB |
BIN
public/favicon-16x16.png
Normal file
BIN
public/favicon-16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 582 B |
BIN
public/favicon-32x32.png
Normal file
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
BIN
public/favicon-512x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
public/favicon-64x64.png
Normal file
BIN
public/favicon-64x64.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
|
@ -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 }} />
|
||||
</>
|
||||
)}
|
||||
|
|
126
src/components/pages/serverSettings/parts/ServerSettingsPWA.tsx
Normal file
126
src/components/pages/serverSettings/parts/ServerSettingsPWA.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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');
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
35
src/server/routes/manifest.ts
Normal file
35
src/server/routes/manifest.ts
Normal 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 },
|
||||
);
|
Loading…
Add table
Add a link
Reference in a new issue