mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-11-28 11:40:11 +03:00
Merge pull request #5660 from urbit/po/new-browser-api-toggles
grid: new browser api toggles/settings
This commit is contained in:
commit
618474dbae
47079
pkg/grid/package-lock.json
generated
47079
pkg/grid/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-checkbox": "^0.1.5",
|
||||
"@fingerprintjs/fingerprintjs": "^3.3.3",
|
||||
"@radix-ui/react-dialog": "^0.0.20",
|
||||
"@radix-ui/react-dropdown-menu": "^0.0.23",
|
||||
"@radix-ui/react-icons": "^1.1.0",
|
||||
|
@ -2,6 +2,8 @@ import React, { useEffect } from 'react';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import { BrowserRouter, Switch, Route, useHistory, useLocation } from 'react-router-dom';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import FingerprintJS from '@fingerprintjs/fingerprintjs';
|
||||
import { addNote } from '@urbit/api';
|
||||
import { Grid } from './pages/Grid';
|
||||
import useDocketState from './state/docket';
|
||||
import { PermalinkRoutes } from './pages/PermalinkRoutes';
|
||||
@ -10,23 +12,80 @@ import useContactState from './state/contact';
|
||||
import api from './state/api';
|
||||
import { useMedia } from './logic/useMedia';
|
||||
import { useHarkStore } from './state/hark';
|
||||
import { useSettingsState, useTheme } from './state/settings';
|
||||
import { useLocalState } from './state/local';
|
||||
import { useSettingsState, useBrowserSettings, useTheme } from './state/settings';
|
||||
import { useBrowserId, useLocalState } from './state/local';
|
||||
import { ErrorAlert } from './components/ErrorAlert';
|
||||
import { useErrorHandler } from './logic/useErrorHandler';
|
||||
|
||||
const getNoteRedirect = (path: string) => {
|
||||
if (path.startsWith('/desk/')) {
|
||||
const [, , desk] = path.split('/');
|
||||
return `/app/${desk}`;
|
||||
return `/apps/${desk}`;
|
||||
}
|
||||
|
||||
if (path.startsWith('/grid/')) {
|
||||
// Handle links to grid features (preferences, etc)
|
||||
const route = path
|
||||
.split('/')
|
||||
.filter((el) => el !== 'grid')
|
||||
.join('/');
|
||||
return route;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const getId = async () => {
|
||||
const fpPromise = FingerprintJS.load();
|
||||
const fp = await fpPromise;
|
||||
const result = await fp.get();
|
||||
return result.visitorId;
|
||||
};
|
||||
|
||||
const AppRoutes = () => {
|
||||
const { push } = useHistory();
|
||||
const { search } = useLocation();
|
||||
const handleError = useErrorHandler();
|
||||
const browserId = useBrowserId();
|
||||
const settings = useBrowserSettings();
|
||||
const { loaded } = useSettingsState.getState();
|
||||
|
||||
useEffect(() => {
|
||||
getId().then((value) => {
|
||||
useLocalState.setState({ browserId: value });
|
||||
});
|
||||
}, [browserId]);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if user has previously stored settings for this browser.
|
||||
// If not, send a notification prompting them to do so.
|
||||
// Set both settings to false so that they're not bugged again.
|
||||
if (browserId !== '' && loaded) {
|
||||
const thisBrowserSettings = settings.filter((el: any) => el.browserId === browserId)[0];
|
||||
if (!thisBrowserSettings) {
|
||||
api.poke(
|
||||
addNote(
|
||||
{
|
||||
path: '/browser-settings',
|
||||
place: { desk: 'garden', path: '/desk/garden' }
|
||||
},
|
||||
{
|
||||
title: [{ text: 'Browser Preferences' }],
|
||||
time: Date.now(),
|
||||
content: [
|
||||
{ text: "You haven't set your preferences for this browser, set them now?" }
|
||||
],
|
||||
link: '/grid/leap/system-preferences/interface',
|
||||
binned: '/desk/garden'
|
||||
}
|
||||
)
|
||||
);
|
||||
const newSettings = [{ browserId, protocolHandling: false, browserNotifications: false }];
|
||||
useSettingsState
|
||||
.getState()
|
||||
.putEntry('browserSettings', 'settings', JSON.stringify(newSettings));
|
||||
}
|
||||
}
|
||||
}, [browserId, loaded]);
|
||||
|
||||
useEffect(() => {
|
||||
const query = new URLSearchParams(search);
|
||||
|
@ -3,10 +3,11 @@ import cn from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { HarkLid, Vats, getVatPublisher } from '@urbit/api';
|
||||
import { Button } from '../../components/Button';
|
||||
import { useCurrentTheme, useProtocolHandling } from '../../state/local';
|
||||
import { useBrowserId, useCurrentTheme } from '../../state/local';
|
||||
import { getDarkColor } from '../../state/util';
|
||||
import useKilnState from '../../state/kiln';
|
||||
import {useHarkStore} from '../../state/hark';
|
||||
import { useHarkStore } from '../../state/hark';
|
||||
import { useProtocolHandling } from '../../state/settings';
|
||||
|
||||
const getCards = (vats: Vats, protocol: boolean): OnboardingCardProps[] => {
|
||||
const cards = [
|
||||
@ -52,7 +53,7 @@ const getCards = (vats: Vats, protocol: boolean): OnboardingCardProps[] => {
|
||||
// color: '#82A6CA'
|
||||
// }
|
||||
];
|
||||
if('registerProtocolHandler' in window.navigator && !protocol) {
|
||||
if ('registerProtocolHandler' in window.navigator && !protocol) {
|
||||
cards.push({
|
||||
title: 'Open Urbit-Native Links',
|
||||
body: 'Enable your Urbit to open links you find in the wild',
|
||||
@ -64,9 +65,10 @@ const getCards = (vats: Vats, protocol: boolean): OnboardingCardProps[] => {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return cards.filter(card => {
|
||||
return !Object.values(vats).find(vat => getVatPublisher(vat) == card.ship && vat?.arak?.rail?.desk === card.desk);
|
||||
return cards.filter((card) => {
|
||||
return !Object.values(vats).find(
|
||||
(vat) => getVatPublisher(vat) == card.ship && vat?.arak?.rail?.desk === card.desk
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@ -85,7 +87,7 @@ interface OnboardingCardProps {
|
||||
|
||||
const OnboardingCard = ({ title, button, href, body, color }: OnboardingCardProps) => (
|
||||
<div
|
||||
className="p-4 flex flex-col space-y-2 text-black bg-gray-100 justify-between rounded-xl"
|
||||
className="flex flex-col justify-between p-4 text-black bg-gray-100 space-y-2 rounded-xl"
|
||||
style={color ? { backgroundColor: color } : {}}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
@ -106,19 +108,22 @@ interface OnboardingNotificationProps {
|
||||
export const OnboardingNotification = ({ unread = false, lid }: OnboardingNotificationProps) => {
|
||||
const theme = useCurrentTheme();
|
||||
const vats = useKilnState((s) => s.vats);
|
||||
const protocolHandling = useProtocolHandling();
|
||||
const browserId = useBrowserId();
|
||||
const protocolHandling = useProtocolHandling(browserId);
|
||||
const cards = getCards(vats, protocolHandling);
|
||||
|
||||
if(cards.length === 0 && !('time' in lid)) {
|
||||
useHarkStore.getState().archiveNote({
|
||||
path: '/',
|
||||
place: {
|
||||
path: '/onboard',
|
||||
desk: window.desk
|
||||
}
|
||||
}, lid);
|
||||
if (cards.length === 0 && !('time' in lid)) {
|
||||
useHarkStore.getState().archiveNote(
|
||||
{
|
||||
path: '/',
|
||||
place: {
|
||||
path: '/onboard',
|
||||
desk: window.desk
|
||||
}
|
||||
},
|
||||
lid
|
||||
);
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1,11 +1,37 @@
|
||||
import React from 'react';
|
||||
import { Setting } from '../../components/Setting';
|
||||
import { useProtocolHandling, setLocalState } from '../../state/local';
|
||||
import {
|
||||
useBrowserNotifications,
|
||||
useBrowserSettings,
|
||||
useProtocolHandling,
|
||||
useSettingsState
|
||||
} from '../../state/settings';
|
||||
import { useBrowserId } from '../../state/local';
|
||||
|
||||
export function InterfacePrefs() {
|
||||
const protocolHandling = useProtocolHandling();
|
||||
const settings = useBrowserSettings();
|
||||
const browserId = useBrowserId();
|
||||
const protocolHandling = useProtocolHandling(browserId);
|
||||
const browserNotifications = useBrowserNotifications(browserId);
|
||||
const secure = window.location.protocol === 'https:' || window.location.hostname === 'localhost';
|
||||
const linkHandlingAllowed = secure && 'registerProtocolHandler' in window.navigator;
|
||||
const setProtocolHandling = (setting: boolean) => {
|
||||
const newSettings = [{ browserId, protocolHandling: setting, browserNotifications }];
|
||||
if (!settings.includes(newSettings)) {
|
||||
useSettingsState
|
||||
.getState()
|
||||
.putEntry('browserSettings', 'settings', JSON.stringify(newSettings));
|
||||
}
|
||||
};
|
||||
const setBrowserNotifications = (setting: boolean) => {
|
||||
const newSettings = [{ browserId, browserNotifications: setting, protocolHandling }];
|
||||
if (!settings.includes(newSettings)) {
|
||||
useSettingsState
|
||||
.getState()
|
||||
.putEntry('browserSettings', 'settings', JSON.stringify(newSettings));
|
||||
}
|
||||
};
|
||||
|
||||
const toggleProtoHandling = async () => {
|
||||
if (!protocolHandling && window?.navigator?.registerProtocolHandler) {
|
||||
try {
|
||||
@ -14,42 +40,64 @@ export function InterfacePrefs() {
|
||||
'/apps/grid/perma?ext=%s',
|
||||
'Urbit Links'
|
||||
);
|
||||
setLocalState((draft) => {
|
||||
draft.protocolHandling = true;
|
||||
});
|
||||
setProtocolHandling(true);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} else if (protocolHandling && window.navigator?.unregisterProtocolHandler) {
|
||||
try {
|
||||
window.navigator.unregisterProtocolHandler('web+urbitgraph', '/apps/grid/perma?ext=%s');
|
||||
setLocalState((draft) => {
|
||||
draft.protocolHandling = false;
|
||||
});
|
||||
setProtocolHandling(false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const toggleNotifications = async () => {
|
||||
if (!browserNotifications) {
|
||||
Notification.requestPermission();
|
||||
setBrowserNotifications(true);
|
||||
} else {
|
||||
setBrowserNotifications(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="h3 mb-7">Interface Settings</h2>
|
||||
<Setting
|
||||
on={protocolHandling}
|
||||
toggle={toggleProtoHandling}
|
||||
name="Handle Urbit links"
|
||||
disabled={!linkHandlingAllowed}
|
||||
>
|
||||
<p>
|
||||
Automatically open urbit links with this urbit
|
||||
{!linkHandlingAllowed && (
|
||||
<>
|
||||
, <strong className="text-orange-500">requires HTTPS</strong>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</Setting>
|
||||
<div className="space-y-3">
|
||||
<Setting
|
||||
on={protocolHandling}
|
||||
toggle={toggleProtoHandling}
|
||||
name="Handle Urbit links"
|
||||
disabled={!linkHandlingAllowed}
|
||||
>
|
||||
<p>
|
||||
Automatically open urbit links when using this browser.
|
||||
{!linkHandlingAllowed && (
|
||||
<>
|
||||
, <strong className="text-orange-500">requires HTTPS</strong>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</Setting>
|
||||
<Setting
|
||||
on={browserNotifications}
|
||||
toggle={toggleNotifications}
|
||||
name="Show desktop notifications"
|
||||
disabled={!secure}
|
||||
>
|
||||
<p>
|
||||
Show desktop notifications in this browser.
|
||||
{!secure && (
|
||||
<>
|
||||
, <strong className="text-orange-500">requires HTTPS</strong>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</Setting>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -9,9 +9,6 @@ const selDnd = (s: SettingsState) => s.display.doNotDisturb;
|
||||
async function toggleDnd() {
|
||||
const state = useSettingsState.getState();
|
||||
const curr = selDnd(state);
|
||||
if (curr) {
|
||||
Notification.requestPermission();
|
||||
}
|
||||
await state.putEntry('display', 'doNotDisturb', !curr);
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import produce from 'immer';
|
||||
import { clearStorageMigration, createStorageKey, storageVersion } from './util';
|
||||
|
||||
interface LocalState {
|
||||
protocolHandling: boolean;
|
||||
browserId: string;
|
||||
currentTheme: 'light' | 'dark';
|
||||
set: (f: (s: LocalState) => void) => void;
|
||||
}
|
||||
@ -14,7 +14,7 @@ export const useLocalState = create<LocalState>(
|
||||
(set, get) => ({
|
||||
set: (f) => set(produce(get(), f)),
|
||||
currentTheme: 'light',
|
||||
protocolHandling: false
|
||||
browserId: ''
|
||||
}),
|
||||
{
|
||||
name: createStorageKey('local'),
|
||||
@ -24,9 +24,9 @@ export const useLocalState = create<LocalState>(
|
||||
)
|
||||
);
|
||||
|
||||
const selProtocolHandling = (s: LocalState) => s.protocolHandling;
|
||||
export function useProtocolHandling() {
|
||||
return useLocalState(selProtocolHandling);
|
||||
const selBrowserId = (s: LocalState) => s.browserId;
|
||||
export function useBrowserId() {
|
||||
return useLocalState(selBrowserId);
|
||||
}
|
||||
|
||||
const selCurrentTheme = (s: LocalState) => s.currentTheme;
|
||||
|
@ -25,6 +25,9 @@ interface BaseSettingsState {
|
||||
order: string[];
|
||||
};
|
||||
loaded: boolean;
|
||||
browserSettings: {
|
||||
settings: string;
|
||||
};
|
||||
putEntry: (bucket: string, key: string, value: Value) => Promise<void>;
|
||||
fetchAll: () => Promise<void>;
|
||||
[ref: string]: unknown;
|
||||
@ -79,6 +82,9 @@ export const useSettingsState = createState<BaseSettingsState>(
|
||||
tiles: {
|
||||
order: []
|
||||
},
|
||||
browserSettings: {
|
||||
settings: ''
|
||||
},
|
||||
loaded: false,
|
||||
putEntry: async (bucket, key, val) => {
|
||||
const poke = doPutEntry(window.desk, bucket, key, val);
|
||||
@ -110,3 +116,24 @@ const selTheme = (s: SettingsState) => s.display.theme;
|
||||
export function useTheme() {
|
||||
return useSettingsState(selTheme);
|
||||
}
|
||||
|
||||
const selBrowserSettings = (s: SettingsState) => s.browserSettings.settings;
|
||||
export function useBrowserSettings() {
|
||||
const settings = useSettingsState(selBrowserSettings);
|
||||
console.log({ settings });
|
||||
return settings !== '' ? JSON.parse(settings) : [];
|
||||
}
|
||||
|
||||
export function useProtocolHandling(browserId: string) {
|
||||
const settings = useBrowserSettings();
|
||||
const { protocolHandling = false } =
|
||||
settings.filter((el: any) => el.browserId === browserId)[0] ?? false;
|
||||
return protocolHandling;
|
||||
}
|
||||
|
||||
export function useBrowserNotifications(browserId: string) {
|
||||
const settings = useBrowserSettings();
|
||||
const { browserNotifications = false } =
|
||||
settings.filter((el: any) => el.browserId === browserId)[0] ?? false;
|
||||
return browserNotifications;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user