mirror of
https://github.com/Vikeo/LifeTrinket.git
synced 2025-11-14 15:07:59 +00:00
show and hide things based on ios, add button for installing PWA
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "life-trinket",
|
"name": "life-trinket",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.9.95",
|
"version": "0.9.96",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20",
|
"node": ">=20",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { twc } from 'react-twc';
|
|||||||
import { useAnalytics } from '../../Hooks/useAnalytics';
|
import { useAnalytics } from '../../Hooks/useAnalytics';
|
||||||
import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
|
import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
|
||||||
import { PreStartMode } from '../../Types/Settings';
|
import { PreStartMode } from '../../Types/Settings';
|
||||||
|
import { InstallPWA } from '../Misc/InstallPWAButton';
|
||||||
import { Separator } from '../Misc/Separator';
|
import { Separator } from '../Misc/Separator';
|
||||||
import { Paragraph } from '../Misc/TextComponents';
|
import { Paragraph } from '../Misc/TextComponents';
|
||||||
import { ToggleButton } from '../Misc/ToggleButton';
|
import { ToggleButton } from '../Misc/ToggleButton';
|
||||||
@@ -201,25 +202,25 @@ export const SettingsDialog = ({
|
|||||||
is enabled.
|
is enabled.
|
||||||
</Description>
|
</Description>
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
<SettingContainer>
|
{(!window.isIOS || window.isIPad) && (
|
||||||
<ToggleContainer>
|
<SettingContainer>
|
||||||
<label>
|
<ToggleContainer>
|
||||||
Fullscreen on start <span className="text-xs">(Android only)</span>
|
<label>Fullscreen on start</label>
|
||||||
</label>
|
<ToggleButton
|
||||||
<ToggleButton
|
checked={settings.goFullscreenOnStart}
|
||||||
checked={settings.goFullscreenOnStart}
|
onChange={() => {
|
||||||
onChange={() => {
|
setSettings({
|
||||||
setSettings({
|
...settings,
|
||||||
...settings,
|
goFullscreenOnStart: !settings.goFullscreenOnStart,
|
||||||
goFullscreenOnStart: !settings.goFullscreenOnStart,
|
});
|
||||||
});
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</ToggleContainer>
|
||||||
</ToggleContainer>
|
<Description>
|
||||||
<Description>
|
Will enter fullscreen mode when starting a game if this is enabled.
|
||||||
Will enter fullscreen mode when starting a game if this is enabled.
|
</Description>
|
||||||
</Description>
|
</SettingContainer>
|
||||||
</SettingContainer>
|
)}
|
||||||
|
|
||||||
<SettingContainer>
|
<SettingContainer>
|
||||||
<ToggleContainer>
|
<ToggleContainer>
|
||||||
@@ -255,21 +256,45 @@ export const SettingsDialog = ({
|
|||||||
</div>
|
</div>
|
||||||
{!isPWA && (
|
{!isPWA && (
|
||||||
<>
|
<>
|
||||||
<Separator height="1px" />
|
{window.isIOS && (
|
||||||
<SettingContainer>
|
<>
|
||||||
<ToggleContainer>
|
<Separator height="1px" />
|
||||||
<Paragraph>
|
<SettingContainer>
|
||||||
<b>Tip:</b> You can{' '}
|
<ToggleContainer>
|
||||||
<b>add this webapp to your home page on iOS</b> or{' '}
|
<Paragraph>
|
||||||
<b>install it on Android</b> to have it act just like a normal
|
<b>Tip:</b> You can <b>add this webapp to your home page</b>{' '}
|
||||||
app!
|
to have it act just like a normal app!
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</ToggleContainer>
|
</ToggleContainer>
|
||||||
<Description className="mt-1">
|
<Description className="mt-1">
|
||||||
If you do, this app will work offline and the toolbar will be
|
If you do, this web app will work offline and the toolbar will
|
||||||
automatically hidden.
|
be automatically hidden.
|
||||||
</Description>
|
</Description>
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!window.isIOS && (
|
||||||
|
<>
|
||||||
|
<Separator height="1px" />
|
||||||
|
<SettingContainer>
|
||||||
|
<ToggleContainer>
|
||||||
|
<Paragraph>
|
||||||
|
<b>Tip:</b> You can <b>install this page as a PWA</b> to
|
||||||
|
have it act just like a normal app!
|
||||||
|
</Paragraph>
|
||||||
|
</ToggleContainer>
|
||||||
|
<Description className="mt-1">
|
||||||
|
If you do, this web app will work offline and the toolbar will
|
||||||
|
be automatically hidden. PWA stands for Progressive Web
|
||||||
|
Application
|
||||||
|
</Description>
|
||||||
|
</SettingContainer>
|
||||||
|
<div className="flex w-full justify-center">
|
||||||
|
<InstallPWA />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Separator height="1px" />
|
<Separator height="1px" />
|
||||||
|
|||||||
45
src/Components/Misc/InstallPWAButton.tsx
Normal file
45
src/Components/Misc/InstallPWAButton.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { BeforeInstallPromptEvent } from '../../global';
|
||||||
|
import { useAnalytics } from '../../Hooks/useAnalytics';
|
||||||
|
|
||||||
|
export const InstallPWA = () => {
|
||||||
|
const supportsPWARef = useRef<boolean>(false);
|
||||||
|
const [promptInstall, setPromptInstall] =
|
||||||
|
useState<BeforeInstallPromptEvent | null>(null);
|
||||||
|
|
||||||
|
const analytics = useAnalytics();
|
||||||
|
|
||||||
|
const handler = (e: BeforeInstallPromptEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
supportsPWARef.current = true;
|
||||||
|
setPromptInstall(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('beforeinstallprompt', handler);
|
||||||
|
|
||||||
|
return () => window.removeEventListener('transitionend', handler);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!supportsPWARef.current) {
|
||||||
|
return 'lull';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="mt-1 mb-1 bg-primary-main px-3 py-1 rounded-md duration-200 ease-in-out shadow-[1px_2px_4px_0px_rgba(0,0,0,0.3)] hover:bg-primary-dark font-bold"
|
||||||
|
aria-label="Install app"
|
||||||
|
title="Install app"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!promptInstall) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
analytics.trackEvent('install_pwa_prompt_shown');
|
||||||
|
promptInstall.prompt();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Install as a PWA
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -347,30 +347,35 @@ const PlayerMenu = ({
|
|||||||
>
|
>
|
||||||
<Exit size={iconSize} style={{ rotate: '180deg' }} />
|
<Exit size={iconSize} style={{ rotate: '180deg' }} />
|
||||||
</button>
|
</button>
|
||||||
<div
|
{(!window.isIOS || window.isIPad) && (
|
||||||
data-fullscreen={document.fullscreenElement ? true : false}
|
<div
|
||||||
className="flex
|
data-fullscreen={document.fullscreenElement ? true : false}
|
||||||
|
className="flex
|
||||||
data-[fullscreen=true]:bg-secondary-dark rounded-lg border border-transparent
|
data-[fullscreen=true]:bg-secondary-dark rounded-lg border border-transparent
|
||||||
data-[fullscreen=true]:border-primary-main"
|
data-[fullscreen=true]:border-primary-main"
|
||||||
>
|
>
|
||||||
<IconCheckbox
|
<IconCheckbox
|
||||||
className="p-1"
|
className="p-1"
|
||||||
name="fullscreen"
|
name="fullscreen"
|
||||||
checked={document.fullscreenElement ? true : false}
|
checked={document.fullscreenElement ? true : false}
|
||||||
icon={
|
icon={
|
||||||
<FullscreenOff
|
<FullscreenOff
|
||||||
size={iconSize}
|
size={iconSize}
|
||||||
className="text-primary-main"
|
className="text-primary-main"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
checkedIcon={
|
checkedIcon={
|
||||||
<FullscreenOn size={iconSize} className="text-primary-main" />
|
<FullscreenOn
|
||||||
}
|
size={iconSize}
|
||||||
onChange={toggleFullscreen}
|
className="text-primary-main"
|
||||||
aria-checked={document.fullscreenElement ? true : false}
|
/>
|
||||||
aria-label="Fullscreen"
|
}
|
||||||
/>
|
onChange={toggleFullscreen}
|
||||||
</div>
|
aria-checked={document.fullscreenElement ? true : false}
|
||||||
|
aria-label="Fullscreen"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
data-wake-lock-active={settings.keepAwake}
|
data-wake-lock-active={settings.keepAwake}
|
||||||
|
|||||||
21
src/global.d.ts
vendored
Normal file
21
src/global.d.ts
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
export {};
|
||||||
|
|
||||||
|
export interface BeforeInstallPromptEvent extends Event {
|
||||||
|
readonly platforms: string[];
|
||||||
|
readonly userChoice: Promise<{
|
||||||
|
outcome: 'accepted' | 'dismissed';
|
||||||
|
platform: string;
|
||||||
|
}>;
|
||||||
|
prompt(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
isIOS: boolean;
|
||||||
|
isIPad: boolean;
|
||||||
|
}
|
||||||
|
interface WindowEventMap {
|
||||||
|
beforeinstallprompt: BeforeInstallPromptEvent;
|
||||||
|
transitionend: BeforeInstallPromptEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,10 @@ import ReactDOM from 'react-dom/client';
|
|||||||
import App from './App.tsx';
|
import App from './App.tsx';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
|
window.isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
||||||
|
|
||||||
|
window.isIPad = /iPad/.test(navigator.userAgent);
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById('root') as HTMLElement
|
document.getElementById('root') as HTMLElement
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user