mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 23:19:40 +01:00
feat: show custom pwa install notice
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
"@mdi/react": "^1.4.0",
|
"@mdi/react": "^1.4.0",
|
||||||
"@notesnook/desktop": "file:desktop",
|
"@notesnook/desktop": "file:desktop",
|
||||||
"@rebass/forms": "^4.0.6",
|
"@rebass/forms": "^4.0.6",
|
||||||
"@streetwritersco/tinymce-plugins": "^1.5.3",
|
"@streetwritersco/tinymce-plugins": "^1.5.5",
|
||||||
"@tinymce/tinymce-react": "^3.13.0",
|
"@tinymce/tinymce-react": "^3.13.0",
|
||||||
"async-mutex": "^0.3.2",
|
"async-mutex": "^0.3.2",
|
||||||
"axios": "^0.21.4",
|
"axios": "^0.21.4",
|
||||||
|
|||||||
@@ -260,6 +260,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="dialogContainer"></div>
|
<div id="dialogContainer"></div>
|
||||||
|
<div id="floatingViewContainer"></div>
|
||||||
<!--
|
<!--
|
||||||
This HTML file is a template.
|
This HTML file is a template.
|
||||||
If you open it directly in the browser, you will see an empty page.
|
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 { EVENTS } from "@notesnook/desktop/events";
|
||||||
import { render } from "react-dom";
|
import { render } from "react-dom";
|
||||||
import { AppEventManager } from "./common/app-events";
|
import { AppEventManager } from "./common/app-events";
|
||||||
|
import { showInstallNotice } from "./components/installnotice";
|
||||||
import { updateStatus } from "./hooks/use-status";
|
import { updateStatus } from "./hooks/use-status";
|
||||||
import { getCurrentHash, getCurrentPath, makeURL } from "./navigation";
|
import { getCurrentHash, getCurrentPath, makeURL } from "./navigation";
|
||||||
import * as serviceWorker from "./serviceWorkerRegistration";
|
import * as serviceWorker from "./serviceWorkerRegistration";
|
||||||
@@ -96,6 +97,7 @@ if (process.env.REACT_APP_PLATFORM !== "desktop") {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
window.addEventListener("beforeinstallprompt", () => showInstallNotice());
|
||||||
} else serviceWorker.unregister();
|
} else serviceWorker.unregister();
|
||||||
|
|
||||||
function shouldSkipInitiation() {
|
function shouldSkipInitiation() {
|
||||||
|
|||||||
@@ -7,20 +7,74 @@ export function getPlatform() {
|
|||||||
os = null;
|
os = null;
|
||||||
|
|
||||||
if (macosPlatforms.indexOf(platform) !== -1) {
|
if (macosPlatforms.indexOf(platform) !== -1) {
|
||||||
os = "macOS";
|
return "macOS";
|
||||||
} else if (iosPlatforms.indexOf(platform) !== -1) {
|
} else if (iosPlatforms.indexOf(platform) !== -1) {
|
||||||
os = "iOS";
|
return "iOS";
|
||||||
} else if (windowsPlatforms.indexOf(platform) !== -1) {
|
} else if (windowsPlatforms.indexOf(platform) !== -1) {
|
||||||
os = "Windows";
|
return "Windows";
|
||||||
} else if (/Android/.test(userAgent)) {
|
} else if (/Android/.test(userAgent)) {
|
||||||
os = "Android";
|
return "Android";
|
||||||
} else if (!os && /Linux/.test(platform)) {
|
} else if (!os && /Linux/.test(platform)) {
|
||||||
os = "Linux";
|
return "Linux";
|
||||||
}
|
}
|
||||||
|
|
||||||
return os;
|
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() {
|
export function isDesktop() {
|
||||||
return "api" in window;
|
return "api" in window;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
var isOpening = false;
|
var currentEvent = undefined;
|
||||||
|
|
||||||
function closeMenu() {
|
function closeMenu() {
|
||||||
if (isOpening) {
|
|
||||||
isOpening = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(
|
||||||
new CustomEvent("globalcontextmenu", { detail: { state: "close" } })
|
new CustomEvent("globalcontextmenu", { detail: { state: "close" } })
|
||||||
);
|
);
|
||||||
@@ -23,7 +18,13 @@ function onKeyDown(event) {
|
|||||||
if (event.keyCode === 27) closeMenu();
|
if (event.keyCode === 27) closeMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onWindowClick() {
|
function onWindowClick(event) {
|
||||||
|
if (
|
||||||
|
event === currentEvent.nativeEvent ||
|
||||||
|
event.target === currentEvent.target
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,9 +36,7 @@ function openMenu(e) {
|
|||||||
closeMenu();
|
closeMenu();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.type === "click") {
|
currentEvent = e;
|
||||||
isOpening = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.style.display = "block";
|
menu.style.display = "block";
|
||||||
|
|
||||||
@@ -66,7 +65,6 @@ function openMenu(e) {
|
|||||||
window.addEventListener("keydown", onKeyDown);
|
window.addEventListener("keydown", onKeyDown);
|
||||||
window.addEventListener("click", onWindowClick);
|
window.addEventListener("click", onWindowClick);
|
||||||
window.addEventListener("blur", onWindowClick);
|
window.addEventListener("blur", onWindowClick);
|
||||||
isOpening = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPosition(e) {
|
function getPosition(e) {
|
||||||
@@ -119,7 +117,7 @@ function useContextMenu() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useOpenContextMenu() {
|
export function useOpenContextMenu() {
|
||||||
return useCallback((event, items, data, withClick) => {
|
return useCallback((event, items, data) => {
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(
|
||||||
new CustomEvent("globalcontextmenu", {
|
new CustomEvent("globalcontextmenu", {
|
||||||
detail: {
|
detail: {
|
||||||
@@ -127,7 +125,6 @@ export function useOpenContextMenu() {
|
|||||||
items,
|
items,
|
||||||
data,
|
data,
|
||||||
internalEvent: event,
|
internalEvent: event,
|
||||||
withClick,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user