mirror of https://github.com/msgbyte/tailchat
				
				
				
			feat: 增加安装应用按钮
							parent
							
								
									91fe01f247
								
							
						
					
					
						commit
						dcbc148eeb
					
				@ -0,0 +1,217 @@
 | 
			
		||||
// Fork from https://github.com/piro0919/use-pwa/blob/master/src/hooks/usePwa/index.ts
 | 
			
		||||
 | 
			
		||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
 | 
			
		||||
import { detect } from 'detect-browser';
 | 
			
		||||
 | 
			
		||||
type PromiseType<T extends Promise<any>> = T extends Promise<infer P>
 | 
			
		||||
  ? P
 | 
			
		||||
  : never;
 | 
			
		||||
 | 
			
		||||
type BeforeInstallPromptEvent = Event & {
 | 
			
		||||
  readonly platforms: Array<string>;
 | 
			
		||||
  readonly userChoice: Promise<{
 | 
			
		||||
    outcome: 'accepted' | 'dismissed';
 | 
			
		||||
    platform: string;
 | 
			
		||||
  }>;
 | 
			
		||||
  prompt(): Promise<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type PwaData = {
 | 
			
		||||
  appinstalled: boolean;
 | 
			
		||||
  canInstallprompt: boolean;
 | 
			
		||||
  enabledA2hs: boolean;
 | 
			
		||||
  enabledPwa: boolean;
 | 
			
		||||
  enabledUpdate: boolean;
 | 
			
		||||
  isLoading: boolean;
 | 
			
		||||
  isPwa: boolean;
 | 
			
		||||
  showInstallPrompt: () => void;
 | 
			
		||||
  unregister: () => Promise<boolean | undefined>;
 | 
			
		||||
  userChoice?: PromiseType<BeforeInstallPromptEvent['userChoice']>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function usePwa(): PwaData {
 | 
			
		||||
  const beforeinstallprompt = useRef<BeforeInstallPromptEvent>();
 | 
			
		||||
  const [appinstalled, setAppinstalled] = useState(false);
 | 
			
		||||
  const [canInstallprompt, setCanInstallprompt] = useState(false);
 | 
			
		||||
  const [enabledA2hs, setEnabledA2hs] = useState(false);
 | 
			
		||||
  const [enabledPwa, setEnabledPwa] = useState(false);
 | 
			
		||||
  const [isPwa, setIsPwa] = useState(false);
 | 
			
		||||
  const [enabledUpdate, setEnabledUpdate] = useState(false);
 | 
			
		||||
  const [userChoice, setUserChoice] = useState<PwaData['userChoice']>();
 | 
			
		||||
  const showInstallPrompt = useCallback(async () => {
 | 
			
		||||
    if (!beforeinstallprompt.current) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await beforeinstallprompt.current.prompt();
 | 
			
		||||
 | 
			
		||||
    if (!beforeinstallprompt.current) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const userChoice = await beforeinstallprompt.current.userChoice;
 | 
			
		||||
 | 
			
		||||
    setUserChoice(userChoice);
 | 
			
		||||
  }, []);
 | 
			
		||||
  const unregister = useCallback(async () => {
 | 
			
		||||
    if (!('serviceWorker' in window.navigator)) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const registration = await window.navigator.serviceWorker.getRegistration();
 | 
			
		||||
 | 
			
		||||
    if (!registration) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result = await registration.unregister();
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
  }, []);
 | 
			
		||||
  const handleBeforeInstallPrompt = useCallback(
 | 
			
		||||
    (event: BeforeInstallPromptEvent) => {
 | 
			
		||||
      beforeinstallprompt.current = event;
 | 
			
		||||
 | 
			
		||||
      setCanInstallprompt(true);
 | 
			
		||||
    },
 | 
			
		||||
    []
 | 
			
		||||
  );
 | 
			
		||||
  const handleAppinstalled = useCallback(() => {
 | 
			
		||||
    setAppinstalled(true);
 | 
			
		||||
  }, []);
 | 
			
		||||
  const [completed, setCompleted] = useState({
 | 
			
		||||
    appinstalled: false,
 | 
			
		||||
    beforeinstallprompt: false,
 | 
			
		||||
    enabledA2hs: false,
 | 
			
		||||
    enabledPwa: false,
 | 
			
		||||
    enabledUpdate: false,
 | 
			
		||||
    isPwa: false,
 | 
			
		||||
  });
 | 
			
		||||
  const isLoading = useMemo(
 | 
			
		||||
    () => !Object.values(completed).filter((value) => value).length,
 | 
			
		||||
    [completed]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    window.addEventListener(
 | 
			
		||||
      'beforeinstallprompt',
 | 
			
		||||
      handleBeforeInstallPrompt as any
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    setCompleted((prevCompleted) => ({
 | 
			
		||||
      ...prevCompleted,
 | 
			
		||||
      beforeinstallprompt: true,
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      window.removeEventListener(
 | 
			
		||||
        'beforeinstallprompt',
 | 
			
		||||
        handleBeforeInstallPrompt as any
 | 
			
		||||
      );
 | 
			
		||||
    };
 | 
			
		||||
  }, [handleBeforeInstallPrompt]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    window.addEventListener('appinstalled', handleAppinstalled);
 | 
			
		||||
 | 
			
		||||
    setCompleted((prevCompleted) => ({
 | 
			
		||||
      ...prevCompleted,
 | 
			
		||||
      appinstalled: true,
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      window.removeEventListener('appinstalled', handleAppinstalled);
 | 
			
		||||
    };
 | 
			
		||||
  }, [handleAppinstalled]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setEnabledPwa(
 | 
			
		||||
      'serviceWorker' in window.navigator &&
 | 
			
		||||
        'BeforeInstallPromptEvent' in window
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    setCompleted((prevCompleted) => ({
 | 
			
		||||
      ...prevCompleted,
 | 
			
		||||
      enabledPwa: true,
 | 
			
		||||
    }));
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setIsPwa(
 | 
			
		||||
      'standalone' in window.navigator ||
 | 
			
		||||
        window.matchMedia('(display-mode: standalone)').matches
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    setCompleted((prevCompleted) => ({
 | 
			
		||||
      ...prevCompleted,
 | 
			
		||||
      isPwa: true,
 | 
			
		||||
    }));
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    try {
 | 
			
		||||
      const browser = detect();
 | 
			
		||||
 | 
			
		||||
      if (!browser) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const userAgent = window.navigator.userAgent.toLowerCase();
 | 
			
		||||
      const isIos =
 | 
			
		||||
        userAgent.indexOf('iphone') >= 0 ||
 | 
			
		||||
        userAgent.indexOf('ipad') >= 0 ||
 | 
			
		||||
        (userAgent.indexOf('macintosh') >= 0 && 'ontouchend' in document);
 | 
			
		||||
      const { name } = browser;
 | 
			
		||||
 | 
			
		||||
      setEnabledA2hs(isIos && name === 'ios');
 | 
			
		||||
    } finally {
 | 
			
		||||
      setCompleted((prevCompleted) => ({
 | 
			
		||||
        ...prevCompleted,
 | 
			
		||||
        enabledA2hs: true,
 | 
			
		||||
      }));
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const callback = async () => {
 | 
			
		||||
      try {
 | 
			
		||||
        if (!('serviceWorker' in window.navigator)) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const registration =
 | 
			
		||||
          await window.navigator.serviceWorker.getRegistration();
 | 
			
		||||
 | 
			
		||||
        if (!registration) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        registration.onupdatefound = async () => {
 | 
			
		||||
          await registration.update();
 | 
			
		||||
 | 
			
		||||
          setEnabledUpdate(true);
 | 
			
		||||
        };
 | 
			
		||||
      } finally {
 | 
			
		||||
        setCompleted((prevCompleted) => ({
 | 
			
		||||
          ...prevCompleted,
 | 
			
		||||
          enabledUpdate: true,
 | 
			
		||||
        }));
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    callback();
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    appinstalled,
 | 
			
		||||
    canInstallprompt,
 | 
			
		||||
    enabledA2hs,
 | 
			
		||||
    enabledUpdate,
 | 
			
		||||
    enabledPwa,
 | 
			
		||||
    isLoading,
 | 
			
		||||
    isPwa,
 | 
			
		||||
    showInstallPrompt,
 | 
			
		||||
    unregister,
 | 
			
		||||
    userChoice,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
import { usePwa } from '@/hooks/usePwa';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { Icon } from 'tailchat-design';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 安装按钮
 | 
			
		||||
 */
 | 
			
		||||
export const InstallBtn: React.FC = React.memo(() => {
 | 
			
		||||
  const { canInstallprompt, showInstallPrompt } = usePwa();
 | 
			
		||||
 | 
			
		||||
  if (!canInstallprompt) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Icon
 | 
			
		||||
      className="text-3xl text-gray-600 dark:text-white cursor-pointer"
 | 
			
		||||
      icon="mdi:download"
 | 
			
		||||
      onClick={showInstallPrompt}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
InstallBtn.displayName = 'InstallBtn';
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue