mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-01 03:23:09 +03:00
grid: system notifications wip
This commit is contained in:
parent
952d5c0c38
commit
81ef91b24a
@ -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>}
|
||||
|
29
pkg/grid/src/components/DocketImage.tsx
Normal file
29
pkg/grid/src/components/DocketImage.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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, you’ll 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>
|
||||
);
|
||||
};
|
||||
|
@ -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 (
|
||||
|
@ -17,5 +17,5 @@ const api =
|
||||
} as unknown as Urbit
|
||||
: new Urbit('', '');
|
||||
|
||||
api.ship = 'marzod';
|
||||
api.ship = 'dopmet';
|
||||
export default api;
|
||||
|
@ -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;
|
||||
|
@ -40,6 +40,9 @@ module.exports = {
|
||||
300: "#FFDD66",
|
||||
400: "#FFC700",
|
||||
},
|
||||
orange: {
|
||||
100: '#FFF2EB'
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [
|
||||
|
@ -12,7 +12,7 @@ export default defineConfig(({ mode }) => ({
|
||||
: {
|
||||
proxy: {
|
||||
'^((?!/apps/grid).)*$': {
|
||||
target: 'http://localhost:8080'
|
||||
target: 'http://localhost:8083'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user