diff --git a/ui/package-lock.json b/ui/package-lock.json index e45681e..6ade055 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -18,8 +18,9 @@ "@radix-ui/react-portal": "^0.0.15", "@radix-ui/react-radio-group": "^0.1.5", "@radix-ui/react-toggle": "^0.0.10", - "@tanstack/react-query": "^4.29.5", - "@tanstack/react-query-persist-client": "^4.29.5", + "@tanstack/react-query": "^4.29.12", + "@tanstack/react-query-devtools": "^4.29.12", + "@tanstack/react-query-persist-client": "^4.29.12", "@tlon/sigil-js": "^1.4.4", "@tloncorp/mock-http-api": "^1.2.0", "@types/lodash": "^4.14.172", @@ -2033,21 +2034,36 @@ "tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1" } }, + "node_modules/@tanstack/match-sorter-utils": { + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz", + "integrity": "sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==", + "dependencies": { + "remove-accents": "0.4.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kentcdodds" + } + }, "node_modules/@tanstack/query-core": { - "version": "4.29.7", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.7.tgz", - "integrity": "sha512-GXG4b5hV2Loir+h2G+RXhJdoZhJLnrBWsuLB2r0qBRyhWuXq9w/dWxzvpP89H0UARlH6Mr9DiVj4SMtpkF/aUA==", + "version": "4.29.11", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.11.tgz", + "integrity": "sha512-8C+hF6SFAb/TlFZyS9FItgNwrw4PMa7YeX+KQYe2ZAiEz6uzg6yIr+QBzPkUwZ/L0bXvGd1sufTm3wotoz+GwQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/query-persist-client-core": { - "version": "4.29.7", - "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-4.29.7.tgz", - "integrity": "sha512-/QahvSq9/f8hetCsCd9MaOy6fAoPn0YDGDcl6TTobqdr9kHMgrM9laP9yKJFg2hm5/jIsrCMDO/iCnxBiUhrqw==", + "version": "4.29.11", + "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-4.29.11.tgz", + "integrity": "sha512-CSmMZchr+446r79NJ/pjD2yfjqNqFV7k8BnqOq4yTZvXsaQLEIn3tsaU45IsPgs4N7g9OBfPUPDdapSQvck2WQ==", "dependencies": { - "@tanstack/query-core": "4.29.7" + "@tanstack/query-core": "4.29.11" }, "funding": { "type": "github", @@ -2055,11 +2071,11 @@ } }, "node_modules/@tanstack/react-query": { - "version": "4.29.7", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.7.tgz", - "integrity": "sha512-ijBWEzAIo09fB1yd22slRZzprrZ5zMdWYzBnCg5qiXuFbH78uGN1qtGz8+Ed4MuhaPaYSD+hykn+QEKtQviEtg==", + "version": "4.29.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.12.tgz", + "integrity": "sha512-zhcN6+zF6cxprxhTHQajHGlvxgK8npnp9uLe9yaWhGc6sYcPWXzyO4raL4HomUzQOPzu3jLvkriJQ7BOrDM8vA==", "dependencies": { - "@tanstack/query-core": "4.29.7", + "@tanstack/query-core": "4.29.11", "use-sync-external-store": "^1.2.0" }, "funding": { @@ -2080,19 +2096,38 @@ } } }, - "node_modules/@tanstack/react-query-persist-client": { - "version": "4.29.7", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-4.29.7.tgz", - "integrity": "sha512-KYUeESnthjjcfakpAei9Cz5gsIm1uDAVHrKcIAoARQwksk4j0KAo9ieExoIhL9v4mpTOlE9GsuZ/y06ANmaVaQ==", + "node_modules/@tanstack/react-query-devtools": { + "version": "4.29.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.29.12.tgz", + "integrity": "sha512-ug4YGQhMhh6QI8/sWJhjXxuvdeehxf1cyxpTifGMH5qreQ5ECHT6vzqG/aKvADQDzqLBGrF0q4wTDnRRYvvtrA==", "dependencies": { - "@tanstack/query-persist-client-core": "4.29.7" + "@tanstack/match-sorter-utils": "^8.7.0", + "superjson": "^1.10.0", + "use-sync-external-store": "^1.2.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-query": "4.29.7" + "@tanstack/react-query": "4.29.12", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tanstack/react-query-persist-client": { + "version": "4.29.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-4.29.12.tgz", + "integrity": "sha512-rh6zZJB+3j8lr+YsEkVadnqmUELmqNFZQzGGsHS5col/YOjYsMe9ppqaUjIMJ2aXnFXye50sbe4KxHhSGoaNVw==", + "dependencies": { + "@tanstack/query-persist-client-core": "4.29.11" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "4.29.12" } }, "node_modules/@tlon/sigil-js": { @@ -3245,6 +3280,20 @@ "safe-buffer": "~5.1.1" } }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/core-js": { "version": "3.21.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz", @@ -5779,6 +5828,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-what": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.12.tgz", + "integrity": "sha512-w7JwFt3gIzbzpp+I//2Ov2UeTGgKM10PxJRD6yf7yGruT3415CdT8TDR6F9/mBAakZKsIaysQVVvxryctQUbnw==", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "dev": true, @@ -7488,6 +7548,11 @@ "url": "https://github.com/sponsors/mysticatea" } }, + "node_modules/remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, "node_modules/rename-keys": { "version": "1.2.0", "license": "MIT", @@ -8019,6 +8084,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/superjson": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.3.tgz", + "integrity": "sha512-0j+U70KUtP8+roVPbwfqkyQI7lBt7ETnuA7KXbTDX3mCKiD/4fXs2ldKSMdt0MCfpTwiMxo20yFU3vu6ewETpQ==", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/supports-color": { "version": "5.5.0", "dev": true, @@ -10303,34 +10379,52 @@ "dev": true, "requires": {} }, + "@tanstack/match-sorter-utils": { + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz", + "integrity": "sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==", + "requires": { + "remove-accents": "0.4.2" + } + }, "@tanstack/query-core": { - "version": "4.29.7", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.7.tgz", - "integrity": "sha512-GXG4b5hV2Loir+h2G+RXhJdoZhJLnrBWsuLB2r0qBRyhWuXq9w/dWxzvpP89H0UARlH6Mr9DiVj4SMtpkF/aUA==" + "version": "4.29.11", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.11.tgz", + "integrity": "sha512-8C+hF6SFAb/TlFZyS9FItgNwrw4PMa7YeX+KQYe2ZAiEz6uzg6yIr+QBzPkUwZ/L0bXvGd1sufTm3wotoz+GwQ==" }, "@tanstack/query-persist-client-core": { - "version": "4.29.7", - "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-4.29.7.tgz", - "integrity": "sha512-/QahvSq9/f8hetCsCd9MaOy6fAoPn0YDGDcl6TTobqdr9kHMgrM9laP9yKJFg2hm5/jIsrCMDO/iCnxBiUhrqw==", + "version": "4.29.11", + "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-4.29.11.tgz", + "integrity": "sha512-CSmMZchr+446r79NJ/pjD2yfjqNqFV7k8BnqOq4yTZvXsaQLEIn3tsaU45IsPgs4N7g9OBfPUPDdapSQvck2WQ==", "requires": { - "@tanstack/query-core": "4.29.7" + "@tanstack/query-core": "4.29.11" } }, "@tanstack/react-query": { - "version": "4.29.7", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.7.tgz", - "integrity": "sha512-ijBWEzAIo09fB1yd22slRZzprrZ5zMdWYzBnCg5qiXuFbH78uGN1qtGz8+Ed4MuhaPaYSD+hykn+QEKtQviEtg==", + "version": "4.29.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.12.tgz", + "integrity": "sha512-zhcN6+zF6cxprxhTHQajHGlvxgK8npnp9uLe9yaWhGc6sYcPWXzyO4raL4HomUzQOPzu3jLvkriJQ7BOrDM8vA==", "requires": { - "@tanstack/query-core": "4.29.7", + "@tanstack/query-core": "4.29.11", + "use-sync-external-store": "^1.2.0" + } + }, + "@tanstack/react-query-devtools": { + "version": "4.29.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.29.12.tgz", + "integrity": "sha512-ug4YGQhMhh6QI8/sWJhjXxuvdeehxf1cyxpTifGMH5qreQ5ECHT6vzqG/aKvADQDzqLBGrF0q4wTDnRRYvvtrA==", + "requires": { + "@tanstack/match-sorter-utils": "^8.7.0", + "superjson": "^1.10.0", "use-sync-external-store": "^1.2.0" } }, "@tanstack/react-query-persist-client": { - "version": "4.29.7", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-4.29.7.tgz", - "integrity": "sha512-KYUeESnthjjcfakpAei9Cz5gsIm1uDAVHrKcIAoARQwksk4j0KAo9ieExoIhL9v4mpTOlE9GsuZ/y06ANmaVaQ==", + "version": "4.29.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-4.29.12.tgz", + "integrity": "sha512-rh6zZJB+3j8lr+YsEkVadnqmUELmqNFZQzGGsHS5col/YOjYsMe9ppqaUjIMJ2aXnFXye50sbe4KxHhSGoaNVw==", "requires": { - "@tanstack/query-persist-client-core": "4.29.7" + "@tanstack/query-persist-client-core": "4.29.11" } }, "@tlon/sigil-js": { @@ -11055,6 +11149,14 @@ "safe-buffer": "~5.1.1" } }, + "copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "requires": { + "is-what": "^4.1.8" + } + }, "core-js": { "version": "3.21.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz", @@ -12692,6 +12794,11 @@ "version": "0.1.0", "dev": true }, + "is-what": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.12.tgz", + "integrity": "sha512-w7JwFt3gIzbzpp+I//2Ov2UeTGgKM10PxJRD6yf7yGruT3415CdT8TDR6F9/mBAakZKsIaysQVVvxryctQUbnw==" + }, "is-wsl": { "version": "2.2.0", "dev": true, @@ -13740,6 +13847,11 @@ "version": "3.2.0", "dev": true }, + "remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, "rename-keys": { "version": "1.2.0" }, @@ -14069,6 +14181,14 @@ "version": "3.1.1", "dev": true }, + "superjson": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.3.tgz", + "integrity": "sha512-0j+U70KUtP8+roVPbwfqkyQI7lBt7ETnuA7KXbTDX3mCKiD/4fXs2ldKSMdt0MCfpTwiMxo20yFU3vu6ewETpQ==", + "requires": { + "copy-anything": "^3.0.2" + } + }, "supports-color": { "version": "5.5.0", "dev": true, diff --git a/ui/package.json b/ui/package.json index 8d8d006..68e7e65 100644 --- a/ui/package.json +++ b/ui/package.json @@ -28,8 +28,9 @@ "@radix-ui/react-portal": "^0.0.15", "@radix-ui/react-radio-group": "^0.1.5", "@radix-ui/react-toggle": "^0.0.10", - "@tanstack/react-query": "^4.29.5", - "@tanstack/react-query-persist-client": "^4.29.5", + "@tanstack/react-query": "^4.29.12", + "@tanstack/react-query-devtools": "^4.29.12", + "@tanstack/react-query-persist-client": "^4.29.12", "@tlon/sigil-js": "^1.4.4", "@tloncorp/mock-http-api": "^1.2.0", "@types/lodash": "^4.14.172", diff --git a/ui/src/app.tsx b/ui/src/app.tsx index afc5797..298a5a5 100644 --- a/ui/src/app.tsx +++ b/ui/src/app.tsx @@ -1,5 +1,6 @@ import React, { useEffect } from 'react'; import Mousetrap from 'mousetrap'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { BrowserRouter, Switch, @@ -132,6 +133,7 @@ export function App() { onReset={() => window.location.reload()} > + diff --git a/ui/src/state/hark.ts b/ui/src/state/hark.ts index 6b35cbe..05add0a 100644 --- a/ui/src/state/hark.ts +++ b/ui/src/state/hark.ts @@ -95,3 +95,17 @@ export function useSawSeamMutation() { }, }); } + +export function useHasInviteToGroup(): Skein | undefined { + const skeins = useSkeins(); + if (!skeins.data) { + return undefined; + } + + return skeins.data.find( + (skein) => + skein.top.rope.desk === 'groups' && + skein.top.con.some((con) => con === ' sent you an invite to ') && + skein.unread + ); +} diff --git a/ui/src/state/settings.ts b/ui/src/state/settings.ts index eb6cca6..11bcd20 100644 --- a/ui/src/state/settings.ts +++ b/ui/src/state/settings.ts @@ -49,6 +49,12 @@ export interface SettingsState { }; } +interface GroupsSettingsState extends SettingsState { + groups: { + hasBeenUsed: boolean; + }; +} + export const useSettings = () => { const { data, ...rest } = useReactQuerySubscription< { desk: SettingsState }, @@ -72,6 +78,29 @@ export const useSettings = () => { }, [rest, data]); }; +export const useGroupsSettings = () => { + const { data, ...rest } = useReactQuerySubscription< + { desk: GroupsSettingsState }, + SettingsEvent + >({ + scry: `/desk/groups`, + scryApp: 'settings', + app: 'settings', + path: `/desk/groups`, + queryKey: ['settings', 'groups'], + }); + + return useMemo(() => { + if (!data) { + return { data: undefined, ...rest }; + } + + const { desk } = data; + + return { data: desk, ...rest }; + }, [rest, data]); +}; + export function useDisplay(): SettingsState['display'] { const { data, isLoading } = useSettings(); @@ -291,3 +320,16 @@ export function useBrowserNotifications(browserId: string): boolean { const browserSetting = getBrowserSetting(settings, browserId); return browserSetting?.browserNotifications ?? false; } + +export function useGroupsHasBeenUsed() { + const { data, isLoading } = useGroupsSettings(); + + return useMemo(() => { + if (isLoading || data === undefined) { + return false; + } + + const setting = data.groups.hasBeenUsed; + return setting; + }, [isLoading, data]); +} diff --git a/ui/src/tiles/Tile.tsx b/ui/src/tiles/Tile.tsx index e9d5848..f6fa68e 100644 --- a/ui/src/tiles/Tile.tsx +++ b/ui/src/tiles/Tile.tsx @@ -1,6 +1,7 @@ import classNames from 'classnames'; -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, useState } from 'react'; import { useDrag } from 'react-dnd'; +import * as Popover from '@radix-ui/react-popover'; import { chadIsRunning } from '@urbit/api'; import { TileMenu } from './TileMenu'; import { Spinner } from '../components/Spinner'; @@ -11,6 +12,8 @@ import { useTileColor } from './useTileColor'; import { usePike } from '../state/kiln'; import { Bullet } from '../components/icons/Bullet'; import { dragTypes } from './TileGrid'; +import { useGroupsHasBeenUsed } from '@/state/settings'; +import { useHasInviteToGroup } from '@/state/hark'; type TileProps = { charge: ChargeWithDesk; @@ -23,6 +26,15 @@ export const Tile: FunctionComponent = ({ desk, disabled = false, }) => { + const [showGroupsPopover, setShowGroupsPopover] = useState(false); + const hasOpenedGroups = useGroupsHasBeenUsed(); + const invite = useHasInviteToGroup(); + const inviteGroupName = + invite && + typeof invite.top.con[2] === 'object' && + 'emph' in invite.top.con[2] + ? invite.top.con[2].emph + : 'a group'; const addRecentApp = useRecentsStore((state) => state.addRecentApp); const { title, image, color, chad, href } = charge; const pike = usePike(desk); @@ -66,6 +78,45 @@ export const Tile: FunctionComponent = ({ onAuxClick={() => addRecentApp(desk)} >
+ {desk === 'groups' && !hasOpenedGroups && ( + setShowGroupsPopover(o)} + > +
setShowGroupsPopover(true)} + onMouseOut={() => setShowGroupsPopover(false)} + > + + <> +
+ + + + + e.preventDefault()} + onCloseAutoFocus={(e) => e.preventDefault()} + > +

+ {invite ? ( + <>You have an invitation to join {inviteGroupName}. + ) : ( + <> + Open Groups to create, join, and accept invitations to + communities. + + )} +

+
+
+
+ + )}
{pike?.zest === 'held' && !disabled && ( @@ -85,13 +136,15 @@ export const Tile: FunctionComponent = ({ )}
- + {desk === 'groups' && !hasOpenedGroups ? null : ( + + )} {title && (