grid: system notifications wip

This commit is contained in:
Liam Fitzgerald 2021-08-25 09:17:12 +10:00
parent 952d5c0c38
commit 81ef91b24a
9 changed files with 187 additions and 18 deletions

View File

@ -1,5 +1,6 @@
import React from 'react';
import { Docket } from '@urbit/api/docket';
import {DocketImage} from './DocketImage';
interface DocketHeaderProps {
docket: Docket;
@ -12,18 +13,7 @@ export function DocketHeader(props: DocketHeaderProps) {
return (
<header className="grid grid-cols-[5rem,1fr] md:grid-cols-[8rem,1fr] auto-rows-min grid-flow-row-dense mb-5 sm:mb-8 gap-x-6 gap-y-4">
<div
className="flex-none row-span-1 md:row-span-2 relative w-20 h-20 md:w-32 md:h-32 bg-gray-200 rounded-xl"
style={{ backgroundColor: color }}
>
{image && (
<img
className="absolute top-1/2 left-1/2 h-[40%] w-[40%] object-contain transform -translate-x-1/2 -translate-y-1/2"
src={image}
alt=""
/>
)}
</div>
<DocketImage color={color} image={image} className="row-span-1 md:row-span-2" />
<div className="col-start-2">
<h1 className="h2">{title}</h1>
{info && <p className="h4 mt-2 text-gray-500">{info}</p>}

View File

@ -0,0 +1,29 @@
import React from 'react';
import { Docket } from '@urbit/api/docket';
import cn from 'classnames';
interface DocketImageProps extends Pick<Docket, 'color' | 'image'> {
small?: boolean;
className?: string;
}
export function DocketImage({ color, image, className = '', small = false }: DocketImageProps) {
const sizing = small
? 'w-4 h-4 md:w-6 md:h-6 rounded-md'
: 'w-12 h-12 md:w-20 md:h-20 rounded-xl';
return (
<div
className={cn(sizing, `flex-none relative bg-gray-200`, className)}
style={{ backgroundColor: color }}
>
{image && (
<img
className="absolute top-1/2 left-1/2 h-[40%] w-[40%] object-contain transform -translate-x-1/2 -translate-y-1/2"
src={image}
alt=""
/>
)}
</div>
);
}

View File

@ -1,5 +1,89 @@
import React, { useEffect } from 'react';
import React, { FC, useEffect, useState } from 'react';
import cn from 'classnames';
import { useLeapStore } from './Nav';
import { useBlockers, useLag } from '../state/kiln';
import { useCharge } from '../state/docket';
import { DocketImage } from '../components/DocketImage';
import api from '../state/api';
import { kilnSuspend } from '../../../npm/api/hood';
interface INotification {
title: string;
body: string;
actions: {
title: string;
role: 'primary' | 'destructive' | 'none';
link: string;
}[];
}
interface NotificationProps {
notification?: INotification;
read?: boolean;
className?: string;
children?: React.ReactNode;
}
const Notification: FC<NotificationProps> = ({ notification, className, children }) => (
<div className={cn('rounded-md flex flex-col p-4', className)}>{children}</div>
);
const LagNotification = () => (
<Notification read={false} className="bg-orange-100">
<p className="text-black leading-normal">
The runtime of this ship is out of date, and preventing a kernel upgrade. Please upgrade to
the latest runtime version. If you are hosted, please contact your hosting provider
</p>
</Notification>
);
const DeskIcon = ({ desk }) => {
const { title, image, color } = useCharge(desk);
return (
<div className="flex items-center space-x-2">
<DocketImage small color={color} image={image} />
<p className="text-black font-medium">{title}</p>
</div>
);
};
interface BlockNotificationProps {
desks: string[];
}
const BlockNotification: React.FC<BlockNotificationProps> = ({ desks }) => {
const count = desks.length;
const [dismissed, setDismissed] = useState(false);
const onArchive = async () => {
await Promise.all(desks.map((d) => api.poke(kilnSuspend(d))));
};
if (dismissed) {
return null;
}
return (
<Notification className="bg-orange-100 flex-col space-y-4 p-6">
<p className="text-black"> The following {desks.length} apps blocked a System Update: </p>
<div className="flex flex-col space-y-4">
{desks.map((desk) => (
<DeskIcon key={desk} desk={desk} />
))}
</div>
<p className="text-black">
In order to proceed with the System Update, youll need to temporarily archive these apps,
which will render them unusable, but with data intact.
<br />
<br />
Archived apps will automatically un-archive and resume operation when their developer
provides an app update.
</p>
<div className="flex space-x-4">
<button onClick={() => setDismissed(true)}>Dismiss</button>
<button onClick={onArchive}>Archive {count} apps and System Update</button>
</div>
</Notification>
);
};
export const Notifications = () => {
const select = useLeapStore((state) => state.select);
@ -8,13 +92,19 @@ export const Notifications = () => {
select('Notifications');
}, []);
const blockers = useBlockers();
const lag = useLag();
return (
<div className="p-4 md:p-8 space-y-8">
<h2 className="h4 text-gray-500">Recent Apps</h2>
{lag && <LagNotification />}
{blockers.length > 0 ? <BlockNotification desks={blockers} /> : null}
{/*<h2 className="h4 text-gray-500">Recent Apps</h2>
<div className="min-h-[150px] rounded-xl bg-gray-100" />
<hr className="-mx-4 md:-mx-8" />
<h2 className="h4 text-gray-500">Recent Developers</h2>
<div className="min-h-[150px] rounded-xl bg-gray-100" />
*/}
</div>
);
};

View File

@ -1,12 +1,14 @@
import { map, omit } from 'lodash-es';
import React, { FunctionComponent, useEffect } from 'react';
import { Route, RouteComponentProps } from 'react-router-dom';
import { KilnDiff } from '@urbit/api/hood';
import { MenuState, Nav } from '../nav/Nav';
import useDocketState, { useCharges } from '../state/docket';
import { useKilnState } from '../state/kiln';
import { RemoveApp } from '../tiles/RemoveApp';
import { SuspendApp } from '../tiles/SuspendApp';
import { Tile } from '../tiles/Tile';
import api from '../state/api';
type GridProps = RouteComponentProps<{
menu?: MenuState;
@ -18,10 +20,18 @@ export const Grid: FunctionComponent<GridProps> = ({ match }) => {
useEffect(() => {
const { fetchCharges, fetchAllies } = useDocketState.getState();
const { fetchVats } = useKilnState.getState();
const { fetchVats, fetchLag } = useKilnState.getState();
fetchCharges();
fetchAllies();
fetchVats();
fetchLag();
api.subscribe({ app: 'hood', path: '/kiln/vats', event: (data: KilnDiff) => {
console.log(data);
}, err: () => { }, quit: () => {} }).catch(e => {
console.log(e);
}).then(r => { console.log(r); });
}, []);
return (

View File

@ -17,5 +17,5 @@ const api =
} as unknown as Urbit
: new Urbit('', '');
api.ship = 'marzod';
api.ship = 'dopmet';
export default api;

View File

@ -1,20 +1,42 @@
import { getVats, Vats } from '@urbit/api';
import { getVats, Vats, scryLag, KilnDiff } from '@urbit/api';
import create from 'zustand';
import produce from 'immer';
import api from './api';
import {getBlockers} from '../../../npm/api/hood';
interface KilnState {
vats: Vats;
fetchVats: () => Promise<void>;
lag: boolean;
fetchLag: () => Promise<void>;
set: (s: KilnState) => void;
}
export const useKilnState = create<KilnState>((set) => ({
vats: {},
lag: true,
fetchVats: async () => {
const vats = await api.scry<Vats>(getVats);
console.log(vats);
set ({ vats });
},
fetchLag: async () => {
const lag = await api.scry<boolean>(scryLag);
set({ lag });
},
set: produce(set)
}));
const selBlockers = (s: KilnState) => getBlockers(s.vats)
export function useBlockers() {
return useKilnState(selBlockers);
}
const selLag = (s: KilnState) => s.lag;
export function useLag() {
return useKilnState(selLag);
}
window.kiln = useKilnState;
export default useKilnState;

View File

@ -40,6 +40,9 @@ module.exports = {
300: "#FFDD66",
400: "#FFC700",
},
orange: {
100: '#FFF2EB'
}
},
fontFamily: {
sans: [

View File

@ -12,7 +12,7 @@ export default defineConfig(({ mode }) => ({
: {
proxy: {
'^((?!/apps/grid).)*$': {
target: 'http://localhost:8080'
target: 'http://localhost:8083'
}
}
},

View File

@ -1,4 +1,6 @@
import { Poke, Scry } from '../lib';
import {Vats} from './types';
import _ from 'lodash';
export const getVats: Scry = {
app: 'hood',
@ -56,3 +58,26 @@ export function kilnRevive(
json: desk
};
}
export const scryLag: Scry = ({ app: 'hood', path: '/kiln/lag' });
export function getBlockers(vats: Vats): string[] {
let blockers: string[] = [];
const base = vats.base;
if(!base) {
return blockers;
}
const blockedOn = base.arak.next?.[0]?.weft?.kelvin;
if(!blockedOn) {
return blockers;
}
_.forEach(_.omit(vats, 'base'), (vat, desk) => {
// assuming only %zuse
const kelvins = _.map(vat.arak.next, n => n.weft.kelvin);
if(!(kelvins.includes(blockedOn))) {
blockers.push(desk);
}
});
return blockers;
}