mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-22 22:49:45 +01:00
feat: show custom pwa install notice
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
"@mdi/react": "^1.4.0",
|
||||
"@notesnook/desktop": "file:desktop",
|
||||
"@rebass/forms": "^4.0.6",
|
||||
"@streetwritersco/tinymce-plugins": "^1.5.3",
|
||||
"@streetwritersco/tinymce-plugins": "^1.5.5",
|
||||
"@tinymce/tinymce-react": "^3.13.0",
|
||||
"async-mutex": "^0.3.2",
|
||||
"axios": "^0.21.4",
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div id="dialogContainer"></div>
|
||||
<div id="floatingViewContainer"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { Flex, Text } from "rebass";
|
||||
import { getPlatform } from "../../utils/platform";
|
||||
|
||||
function Banner() {
|
||||
const link = useMemo(() => {
|
||||
const os = getPlatform();
|
||||
if (os === "Android")
|
||||
return "https://play.google.com/store/apps/details?id=com.streetwriters.notesnook";
|
||||
if (os === "iOS")
|
||||
return "https://apps.apple.com/pk/app/notesnook-take-private-notes/id1544027013";
|
||||
return null;
|
||||
}, []);
|
||||
if (link === null) return null;
|
||||
return (
|
||||
<Flex alignItems="center" justifyContent="center" bg="primary" py={1}>
|
||||
<Text color="static" textAlign="center" fontSize="title">
|
||||
Use our <a href={link}>mobile app</a> for a better experience.
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
export default Banner;
|
||||
32
apps/web/src/components/dropdown-button/index.js
Normal file
32
apps/web/src/components/dropdown-button/index.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Button, Flex } from "rebass";
|
||||
import { useOpenContextMenu } from "../../utils/useContextMenu";
|
||||
import { ChevronDown } from "../icons";
|
||||
|
||||
export default function DropdownButton({ title, options }) {
|
||||
const openContextMenu = useOpenContextMenu();
|
||||
if (!options || !options.length) return null;
|
||||
return (
|
||||
<Flex>
|
||||
<Button
|
||||
sx={{ borderTopRightRadius: 0, borderBottomRightRadius: 0 }}
|
||||
onClick={options[0].onClick}
|
||||
>
|
||||
{options[0].title()}
|
||||
</Button>
|
||||
{options.length > 1 && (
|
||||
<Button
|
||||
px={1}
|
||||
sx={{
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopLeftRadius: 0,
|
||||
}}
|
||||
onClick={(event) =>
|
||||
openContextMenu(event, options.slice(1), { title })
|
||||
}
|
||||
>
|
||||
<ChevronDown color="static" size={18} />
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
127
apps/web/src/components/install-notice/index.js
Normal file
127
apps/web/src/components/install-notice/index.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import ReactDOM from "react-dom";
|
||||
import { Box, Button, Flex, Text } from "rebass";
|
||||
import { closeOpenedDialog } from "../../common/dialog-controller";
|
||||
import Config from "../../utils/config";
|
||||
import { getDownloadLink, getPlatform } from "../../utils/platform";
|
||||
import DropdownButton from "../dropdown-button";
|
||||
import ThemeProvider from "../theme-provider";
|
||||
|
||||
const nativeFeatures = [
|
||||
"Native high-performance encryption",
|
||||
"Automatic backups",
|
||||
"Pin notes in notifications drawer",
|
||||
"Share & append to notes from anywhere",
|
||||
"Quick note widgets",
|
||||
"App lock",
|
||||
];
|
||||
|
||||
const platform = getPlatform();
|
||||
const isMobile = platform === "Android" || platform === "iOS";
|
||||
const storeName = platform === "Android" ? "Play Store" : "App Store";
|
||||
function getOptions(onClose) {
|
||||
return getDownloadLink(platform).map((item) => ({
|
||||
key: item.type || item.link,
|
||||
title: () => {
|
||||
switch (platform) {
|
||||
case "iOS":
|
||||
case "Android":
|
||||
return `Install from ${storeName}`;
|
||||
default:
|
||||
return `Download (${item.type})`;
|
||||
}
|
||||
},
|
||||
onClick: () => {
|
||||
window.open(item.link, "_blank");
|
||||
onClose();
|
||||
Config.set("installNotice", false);
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
export default function InstallNotice({ onClose }) {
|
||||
return (
|
||||
<Flex
|
||||
flexDirection={"column"}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: ["initial", 2],
|
||||
right: [0, 2],
|
||||
left: [2, "initial"],
|
||||
bottom: [2, "initial"],
|
||||
zIndex: 2,
|
||||
bg: "background",
|
||||
borderRadius: "default",
|
||||
border: "1px solid var(--border)",
|
||||
width: ["95%", 400],
|
||||
}}
|
||||
p={2}
|
||||
>
|
||||
<Text variant={"title"}>Install Notesnook</Text>
|
||||
<Text variant={"body"}>
|
||||
For a more integrated user experience, try out Notesnook for {platform}.
|
||||
</Text>
|
||||
{isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 1fr",
|
||||
columnGap: 1,
|
||||
rowGap: 1,
|
||||
mt: 1,
|
||||
}}
|
||||
>
|
||||
{nativeFeatures.map((feature) => (
|
||||
<Flex
|
||||
p={1}
|
||||
sx={{
|
||||
borderRadius: "default",
|
||||
border: "1px solid var(--border)",
|
||||
}}
|
||||
>
|
||||
<Text variant="body" ml={1}>
|
||||
{feature}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Flex mt={[4, 1]} alignItems={"center"}>
|
||||
<DropdownButton title={"Options"} options={getOptions(onClose)} />
|
||||
<Button
|
||||
variant={"secondary"}
|
||||
ml={1}
|
||||
alignSelf={"start"}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
Config.set("installNotice", false);
|
||||
}}
|
||||
>
|
||||
Don't show again
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export function showInstallNotice() {
|
||||
if (!Config.get("installNotice", true)) return;
|
||||
|
||||
const root = document.getElementById("floatingViewContainer");
|
||||
|
||||
if (root) {
|
||||
return new Promise((resolve) => {
|
||||
const perform = (result) => {
|
||||
ReactDOM.unmountComponentAtNode(root);
|
||||
resolve(result);
|
||||
};
|
||||
ReactDOM.render(
|
||||
<ThemeProvider>
|
||||
<InstallNotice onClose={perform} />
|
||||
</ThemeProvider>,
|
||||
root
|
||||
);
|
||||
});
|
||||
}
|
||||
return Promise.reject("No element with id 'floatingViewContainer'");
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { EVENTS } from "@notesnook/desktop/events";
|
||||
import { render } from "react-dom";
|
||||
import { AppEventManager } from "./common/app-events";
|
||||
import { showInstallNotice } from "./components/installnotice";
|
||||
import { updateStatus } from "./hooks/use-status";
|
||||
import { getCurrentHash, getCurrentPath, makeURL } from "./navigation";
|
||||
import * as serviceWorker from "./serviceWorkerRegistration";
|
||||
@@ -96,6 +97,7 @@ if (process.env.REACT_APP_PLATFORM !== "desktop") {
|
||||
});
|
||||
},
|
||||
});
|
||||
window.addEventListener("beforeinstallprompt", () => showInstallNotice());
|
||||
} else serviceWorker.unregister();
|
||||
|
||||
function shouldSkipInitiation() {
|
||||
|
||||
@@ -7,20 +7,74 @@ export function getPlatform() {
|
||||
os = null;
|
||||
|
||||
if (macosPlatforms.indexOf(platform) !== -1) {
|
||||
os = "macOS";
|
||||
return "macOS";
|
||||
} else if (iosPlatforms.indexOf(platform) !== -1) {
|
||||
os = "iOS";
|
||||
return "iOS";
|
||||
} else if (windowsPlatforms.indexOf(platform) !== -1) {
|
||||
os = "Windows";
|
||||
return "Windows";
|
||||
} else if (/Android/.test(userAgent)) {
|
||||
os = "Android";
|
||||
return "Android";
|
||||
} else if (!os && /Linux/.test(platform)) {
|
||||
os = "Linux";
|
||||
return "Linux";
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
export function getDownloadLink(platform) {
|
||||
switch (platform) {
|
||||
case "iOS":
|
||||
return [
|
||||
{
|
||||
link: "https://apps.apple.com/pk/app/notesnook-take-private-notes/id1544027013",
|
||||
},
|
||||
];
|
||||
case "Android":
|
||||
return [
|
||||
{
|
||||
link: "https://play.google.com/store/apps/details?id=com.streetwriters.notesnook",
|
||||
},
|
||||
];
|
||||
case "macOS":
|
||||
return [
|
||||
{
|
||||
type: "x64",
|
||||
link: "https://github.com/streetwriters/notesnook/releases/latest/download/notesnook_x64.dmg",
|
||||
},
|
||||
{
|
||||
type: "arm64",
|
||||
link: "https://github.com/streetwriters/notesnook/releases/latest/download/notesnook_amd64.dmg",
|
||||
},
|
||||
];
|
||||
case "Windows":
|
||||
return [
|
||||
{
|
||||
type: ".exe",
|
||||
link: "https://github.com/streetwriters/notesnook/releases/latest/download/notesnook_x64.exe",
|
||||
},
|
||||
];
|
||||
case "Linux":
|
||||
return [
|
||||
{
|
||||
type: ".AppImage",
|
||||
link: "https://github.com/streetwriters/notesnook/releases/latest/download/notesnook_x86_64.AppImage",
|
||||
},
|
||||
{
|
||||
type: ".deb",
|
||||
link: "https://github.com/streetwriters/notesnook/releases/latest/download/notesnook_x86_64.deb",
|
||||
},
|
||||
{
|
||||
type: ".rpm",
|
||||
link: "https://github.com/streetwriters/notesnook/releases/latest/download/notesnook_x86_64.rpm",
|
||||
},
|
||||
];
|
||||
default:
|
||||
return [
|
||||
{ link: "https://github.com/streetwriters/notesnook/releases/latest/" },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export function isDesktop() {
|
||||
return "api" in window;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
var isOpening = false;
|
||||
|
||||
var currentEvent = undefined;
|
||||
function closeMenu() {
|
||||
if (isOpening) {
|
||||
isOpening = false;
|
||||
return;
|
||||
}
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("globalcontextmenu", { detail: { state: "close" } })
|
||||
);
|
||||
@@ -23,7 +18,13 @@ function onKeyDown(event) {
|
||||
if (event.keyCode === 27) closeMenu();
|
||||
}
|
||||
|
||||
function onWindowClick() {
|
||||
function onWindowClick(event) {
|
||||
if (
|
||||
event === currentEvent.nativeEvent ||
|
||||
event.target === currentEvent.target
|
||||
)
|
||||
return;
|
||||
|
||||
closeMenu();
|
||||
}
|
||||
|
||||
@@ -35,9 +36,7 @@ function openMenu(e) {
|
||||
closeMenu();
|
||||
return;
|
||||
}
|
||||
if (e.type === "click") {
|
||||
isOpening = true;
|
||||
}
|
||||
currentEvent = e;
|
||||
|
||||
menu.style.display = "block";
|
||||
|
||||
@@ -66,7 +65,6 @@ function openMenu(e) {
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
window.addEventListener("click", onWindowClick);
|
||||
window.addEventListener("blur", onWindowClick);
|
||||
isOpening = false;
|
||||
}
|
||||
|
||||
function getPosition(e) {
|
||||
@@ -119,7 +117,7 @@ function useContextMenu() {
|
||||
}
|
||||
|
||||
export function useOpenContextMenu() {
|
||||
return useCallback((event, items, data, withClick) => {
|
||||
return useCallback((event, items, data) => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("globalcontextmenu", {
|
||||
detail: {
|
||||
@@ -127,7 +125,6 @@ export function useOpenContextMenu() {
|
||||
items,
|
||||
data,
|
||||
internalEvent: event,
|
||||
withClick,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user