feat: PWA prompt
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
// import React from "react";
|
||||
import styled from "styled-components";
|
||||
import Modal from "../Modal";
|
||||
import Button from "../../component/styled/Button";
|
||||
|
||||
const Styled = styled.div`
|
||||
margin-top: 15px;
|
||||
pointer-events: all;
|
||||
width: 300px;
|
||||
padding: 16px 32px;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
box-shadow: 0px 25px 50px rgba(31, 41, 55, 0.25);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
.tip {
|
||||
line-height: 1.4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
color: #333;
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.desc {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
.btns {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
`;
|
||||
export default function Prompt({ handleInstall, closePrompt }) {
|
||||
return (
|
||||
<Modal mask={false}>
|
||||
<Styled>
|
||||
<div className="tip">
|
||||
<h2 className="title">Add this Web APP</h2>
|
||||
<p className="desc">Add to your PC and open like native APP</p>
|
||||
</div>
|
||||
<div className="btns">
|
||||
<Button className="main small" onClick={handleInstall}>
|
||||
Install
|
||||
</Button>
|
||||
<Button className="ghost small" onClick={closePrompt}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</Styled>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
// import { useGetServerQuery } from "../../../app/services/server";
|
||||
// import manifest from "./manifest.json";
|
||||
import Prompt from "./Prompt";
|
||||
export default function Manifest() {
|
||||
const deferredPromptRef = useRef(null);
|
||||
const [popup, setPopup] = useState(false);
|
||||
// const { data, isSuccess } = useGetServerQuery();
|
||||
useEffect(() => {
|
||||
const handleInstallPromotion = (e) => {
|
||||
// Prevent the mini-infobar from appearing on mobile
|
||||
e.preventDefault();
|
||||
// Stash the event so it can be triggered later.
|
||||
deferredPromptRef.current = e;
|
||||
// Update UI notify the user they can install the PWA
|
||||
setPopup(true);
|
||||
// Optionally, send analytics event that PWA install promo was shown.
|
||||
console.log(`'beforeinstallprompt' event was fired.`);
|
||||
};
|
||||
const handleInstalled = () => {
|
||||
deferredPromptRef.current = null;
|
||||
setPopup(false);
|
||||
};
|
||||
// if (isSuccess && data) {
|
||||
// console.log("server", data);
|
||||
// manifest.name = `${data.name}'s Chat`;
|
||||
// // const stringManifest = JSON.stringify(manifest);
|
||||
// // const blob = new Blob([stringManifest], { type: "application/json" });
|
||||
// // const manifestURL = URL.createObjectURL(blob);
|
||||
// let content = encodeURIComponent(JSON.stringify(manifest));
|
||||
// let manifestURL = "data:application/manifest+json," + content;
|
||||
// const manifestEle = document.querySelector("#my-manifest-placeholder");
|
||||
// if (manifestEle) {
|
||||
// manifestEle.setAttribute("href", manifestURL);
|
||||
// }
|
||||
|
||||
// }
|
||||
window.addEventListener("beforeinstallprompt", handleInstallPromotion);
|
||||
window.addEventListener("appinstalled", handleInstalled);
|
||||
return () => {
|
||||
window.removeEventListener("beforeinstallprompt", handleInstallPromotion);
|
||||
window.removeEventListener("appinstalled", handleInstalled);
|
||||
};
|
||||
}, []);
|
||||
const handleInstall = async () => {
|
||||
// Hide the app provided install promotion
|
||||
setPopup(false);
|
||||
if (!deferredPromptRef.current) return;
|
||||
// Show the install prompt
|
||||
deferredPromptRef.current.prompt();
|
||||
// Wait for the user to respond to the prompt
|
||||
const { outcome } = await deferredPromptRef.current.userChoice;
|
||||
// Optionally, send analytics event with outcome of user choice
|
||||
console.log(`User response to the install prompt: ${outcome}`);
|
||||
// We've used the prompt, and can't use it again, throw it away
|
||||
deferredPromptRef.current = null;
|
||||
};
|
||||
const handleClose = async () => {
|
||||
setPopup(false);
|
||||
};
|
||||
if (!popup) return null;
|
||||
return <Prompt handleInstall={handleInstall} closePrompt={handleClose} />;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "RustChat",
|
||||
"short_name": "Your private chat APP",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
export default function Modal({ id = "root-modal", children }) {
|
||||
export default function Modal({ id = "root-modal", mask = true, children }) {
|
||||
const [wrapper, setWrapper] = useState(null);
|
||||
// let eleRef = useRef(null);
|
||||
useEffect(() => {
|
||||
const modalRoot = document.getElementById(id);
|
||||
if (mask) {
|
||||
modalRoot.classList.add("mask");
|
||||
}
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.classList.add("wrapper");
|
||||
modalRoot.appendChild(wrapper);
|
||||
@@ -13,7 +16,7 @@ export default function Modal({ id = "root-modal", children }) {
|
||||
return () => {
|
||||
modalRoot.removeChild(wrapper);
|
||||
};
|
||||
}, [id]);
|
||||
}, [id, mask]);
|
||||
if (!wrapper) return null;
|
||||
console.log("create portal");
|
||||
return createPortal(children, wrapper);
|
||||
|
||||
+2
-2
@@ -23,6 +23,7 @@ import SettingPage from "./setting";
|
||||
import SettingChannelPage from "./settingChannel";
|
||||
import toast from "react-hot-toast";
|
||||
import ResourceManagement from "./resources";
|
||||
import Manifest from "../common/component/Manifest";
|
||||
|
||||
const PageRoutes = () => {
|
||||
const {
|
||||
@@ -128,10 +129,9 @@ export default function ReduxRoutes() {
|
||||
// };
|
||||
return (
|
||||
<Provider store={store}>
|
||||
{/* <PersistGate loading={null} persistor={persistStore(store)}> */}
|
||||
<Manifest />
|
||||
<Meta />
|
||||
<PageRoutes />
|
||||
{/* </PersistGate> */}
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user