diff --git a/.github/workflows/ops-group-timer.yml b/.github/workflows/ops-group-timer.yml deleted file mode 100644 index f28a615abb..0000000000 --- a/.github/workflows/ops-group-timer.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: group-timer -on: - push: - branches: - - 'ops/group-timer' -jobs: - glob: - runs-on: ubuntu-latest - name: "Create and deploy a glob to ~difmex-passed" - steps: - - uses: actions/checkout@v2 - with: - lfs: true - - uses: ./.github/actions/glob - with: - ship: 'difmex-passed' - credentials: ${{ secrets.JANEWAY_SERVICE_KEY }} - ssh-sec-key: ${{ secrets.JANEWAY_SSH_SEC_KEY }} - ssh-pub-key: ${{ secrets.JANEWAY_SSH_PUB_KEY }} - diff --git a/pkg/garden/app/settings-store.hoon b/pkg/garden/app/settings-store.hoon index b6c8808c4a..33f9acf6bf 100644 --- a/pkg/garden/app/settings-store.hoon +++ b/pkg/garden/app/settings-store.hoon @@ -24,11 +24,7 @@ def ~(. (default-agent this %|) bol) io ~(. agentio bol) :: - ++ on-init - ^- (quip card _this) - =^ cards state - (put-entry:do q.byk.bol %tutorial %seen b+|) - [cards this] + ++ on-init on-init:def :: ++ on-save !>(state) :: diff --git a/pkg/interface/config/webpack.dev.js b/pkg/interface/config/webpack.dev.js index 7727d925c2..699cc4cd3b 100644 --- a/pkg/interface/config/webpack.dev.js +++ b/pkg/interface/config/webpack.dev.js @@ -111,11 +111,6 @@ module.exports = { 'process.env.LANDSCAPE_SHORTHASH': JSON.stringify(GIT_DESC), 'process.env.LANDSCAPE_STORAGE_VERSION': JSON.stringify(Date.now()), 'process.env.LANDSCAPE_LAST_WIPE': JSON.stringify('2021-10-20'), - 'process.env.TUTORIAL_HOST': JSON.stringify('~difmex-passed'), - 'process.env.TUTORIAL_GROUP': JSON.stringify('beginner-island'), - 'process.env.TUTORIAL_CHAT': JSON.stringify('introduce-yourself-7010'), - 'process.env.TUTORIAL_BOOK': JSON.stringify('guides-9684'), - 'process.env.TUTORIAL_LINKS': JSON.stringify('community-articles-2143') }), // new CleanWebpackPlugin(), diff --git a/pkg/interface/config/webpack.prod.js b/pkg/interface/config/webpack.prod.js index 88b077367f..42f99c463d 100644 --- a/pkg/interface/config/webpack.prod.js +++ b/pkg/interface/config/webpack.prod.js @@ -75,11 +75,6 @@ module.exports = { 'process.env.LANDSCAPE_SHORTHASH': JSON.stringify(GIT_DESC), 'process.env.LANDSCAPE_STORAGE_VERSION': Date.now().toString(), 'process.env.LANDSCAPE_LAST_WIPE': '2021-10-20', - 'process.env.TUTORIAL_HOST': JSON.stringify('~difmex-passed'), - 'process.env.TUTORIAL_GROUP': JSON.stringify('beginner-island'), - 'process.env.TUTORIAL_CHAT': JSON.stringify('introduce-yourself-7010'), - 'process.env.TUTORIAL_BOOK': JSON.stringify('guides-9684'), - 'process.env.TUTORIAL_LINKS': JSON.stringify('community-articles-2143') }), new HtmlWebpackPlugin({ title: 'Groups', diff --git a/pkg/interface/src/logic/lib/omnibox.ts b/pkg/interface/src/logic/lib/omnibox.ts index c7ad008123..9febb443f9 100644 --- a/pkg/interface/src/logic/lib/omnibox.ts +++ b/pkg/interface/src/logic/lib/omnibox.ts @@ -93,7 +93,6 @@ const otherIndex = function(config) { messages: result('Messages', '/~landscape/messages', 'messages', null), logout: result('Log Out', '/~/logout', 'logout', null) }; - other.push(result('Tutorial', '/?tutorial=true', 'tutorial', null)); for(const cat of config.categories) { if(idx[cat]) { other.push(idx[cat]); diff --git a/pkg/interface/src/logic/lib/tutorialModal.ts b/pkg/interface/src/logic/lib/tutorialModal.ts deleted file mode 100644 index 81908f4ee5..0000000000 --- a/pkg/interface/src/logic/lib/tutorialModal.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { Associations } from '@urbit/api'; -import { AlignX, AlignY } from '~/logic/lib/relativePosition'; -import { TutorialProgress } from '~/types'; -import { Direction } from '~/views/components/Triangle'; - -export const MODAL_WIDTH = 256; -export const MODAL_HEIGHT = 256; -export const MODAL_WIDTH_PX = `${MODAL_WIDTH}px`; -export const MODAL_HEIGHT_PX = `${MODAL_HEIGHT}px`; - -export const TUTORIAL_HOST = process.env.TUTORIAL_HOST!; -export const TUTORIAL_GROUP = process.env.TUTORIAL_GROUP!; -export const TUTORIAL_CHAT = process.env.TUTORIAL_CHAT!; -export const TUTORIAL_BOOK = process.env.TUTORIAL_BOOK!; -export const TUTORIAL_LINKS = process.env.TUTORIAL_LINKS!; -export const TUTORIAL_GROUP_RESOURCE = `/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}` ; - -interface StepDetail { - title: string; - description: string; - url: string; - alignX: AlignX | AlignX[]; - alignY: AlignY | AlignY[]; - offsetX: number; - offsetY: number; - arrow?: Direction; -} - -export function hasTutorialGroup(props: { associations: Associations }) { - return ( - TUTORIAL_GROUP_RESOURCE in props.associations.groups - ); -} - -export const getTrianglePosition = (dir: Direction) => { - const midY = `${MODAL_HEIGHT / 2 - 8}px`; - const midX = `${MODAL_WIDTH / 2 - 8}px`; - switch(dir) { - case 'East': - return { - top: midY, - right: '-32px' - }; - case 'West': - return { - top: midY, - left: '-32px' - }; - case 'North': - return { - top: '-32px', - left: midX - }; - case 'South': - return { - bottom: '-32px', - left: midX - }; - } -}; - -export const progressDetails: Record = { - hidden: {} as any, - exit: {} as any, - done: { - title: 'End', - description: - 'This tutorial is finished. Would you like to leave Beginner Island?', - url: '/', - alignX: 'right', - alignY: 'top', - offsetX: MODAL_WIDTH + 8, - offsetY: 0 - }, - start: { - title: 'New Group added', - description: - 'We just added you to the Beginner island group to show you around. This group is public, but other groups can be private', - url: '/', - alignX: 'right', - alignY: 'top', - arrow: 'West', - offsetX: MODAL_WIDTH + 24, - offsetY: 64 - }, - 'group-desc': { - title: 'What\'s a group', - description: - 'A group contains members and tends to be centered around a topic or multiple topics.', - url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`, - alignX: 'left', - alignY: 'top', - arrow: 'East', - offsetX: MODAL_WIDTH + 24, - offsetY: 80 - }, - channels: { - title: 'Channels', - description: - 'Inside a group you have three types of Channels: Chat, Collection, or Notebook. Mix and match these depending on your group context!', - url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`, - alignY: 'top', - alignX: 'right', - arrow: 'West', - offsetX: MODAL_WIDTH + 24, - offsetY: -8 - }, - chat: { - title: 'Chat', - description: - 'Chat channels are for messaging within your group. Direct Messages can be accessed from Messages in the top right', - url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/chat/ship/${TUTORIAL_HOST}/${TUTORIAL_CHAT}`, - alignY: 'top', - arrow: 'North', - alignX: 'right', - offsetY: -56, - offsetX: -8 - }, - link: { - title: 'Collection', - description: - 'A collection is where you can share and view links, images, and other media within your group. Every item in a Collection can have it’s own comment thread.', - url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/link/ship/${TUTORIAL_HOST}/${TUTORIAL_LINKS}`, - alignY: 'top', - alignX: 'right', - arrow: 'North', - offsetX: -8, - offsetY: -56 - }, - publish: { - title: 'Notebook', - description: - 'Notebooks are for creating long-form content within your group. Use markdown to create rich posts with headers, lists and images.', - url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/publish/ship/${TUTORIAL_HOST}/${TUTORIAL_BOOK}`, - alignY: 'top', - alignX: 'right', - arrow: 'North', - offsetX: -8, - offsetY: -56 - }, - notifications: { - title: 'Notifications', - description: 'You will get updates from subscribed channels and mentions here. You can access Notifications through Leap.', - url: '/~notifications', - alignY: 'top', - alignX: 'left', - arrow: 'North', - offsetX: 0, - offsetY: -48 - }, - profile: { - title: 'Profile', - description: - 'Your profile is customizable and can be shared with other ships. Enter as much or as little information as you’d like.', - url: `/~profile/~${window.ship}`, - alignY: 'top', - alignX: 'right', - arrow: 'South', - offsetX: -300 + MODAL_WIDTH / 2, - offsetY: -4 - }, - leap: { - title: 'Leap', - description: - 'Leap allows you to go to a specific channel, message, collection, profile or group simply by typing in a command or selecting a shortcut from the dropdown menu.', - url: `/~profile/~${window.ship}`, - alignY: 'top', - alignX: 'left', - arrow: 'North', - offsetX: 76, - offsetY: -48 - } -}; diff --git a/pkg/interface/src/logic/lib/useResize.ts b/pkg/interface/src/logic/lib/useResize.ts index 4f159883cf..4daf40b1de 100644 --- a/pkg/interface/src/logic/lib/useResize.ts +++ b/pkg/interface/src/logic/lib/useResize.ts @@ -15,11 +15,12 @@ export function useResize( callback(entry, observer); } } + let el = ref.current; const resizeObs = new ResizeObserver(observer); - resizeObs.observe(ref.current, { box: 'border-box' }); + resizeObs.observe(el, { box: 'border-box' }); return () => { - resizeObs.unobserve(ref.current); + resizeObs.unobserve(el); }; }, [callback]); diff --git a/pkg/interface/src/logic/lib/util.tsx b/pkg/interface/src/logic/lib/util.tsx index 2b03815875..70dec91922 100644 --- a/pkg/interface/src/logic/lib/util.tsx +++ b/pkg/interface/src/logic/lib/util.tsx @@ -4,14 +4,11 @@ import { patp2dec } from 'urbit-ob'; import f from 'lodash/fp'; import { Association, Contact, Patp } from '@urbit/api'; import { enableMapSet } from 'immer'; -import useSettingsState from '../state/settings'; /* eslint-disable max-lines */ import anyAscii from 'any-ascii'; import { sigil as sigiljs, stringRenderer } from '@tlon/sigil-js'; import bigInt, { BigInteger } from 'big-integer'; -import { foregroundFromBackground } from '~/logic/lib/sigil'; import { IconRef, Workspace } from '~/types'; -import useContactState from '../state/contact'; enableMapSet(); @@ -462,13 +459,6 @@ export function pluralize(text: string, isPlural = false, vowel = false) { return isPlural ? `${text}s` : `${vowel ? 'an' : 'a'} ${text}`; } -// Hide is an optional second parameter for when this function is used in class components -export function useShowNickname(contact: Contact | null, hide?: boolean): boolean { - const hideState = useSettingsState(state => state.calm.hideNicknames); - const hideNicknames = typeof hide !== 'undefined' ? hide : hideState; - return Boolean(contact && contact.nickname && !hideNicknames); -} - interface useHoveringInterface { hovering: boolean; bind: { @@ -513,21 +503,6 @@ export const svgDataURL = svg => 'data:image/svg+xml;base64,' + btoa(svg); export const svgBlobURL = svg => URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' })); -export const favicon = () => { - let background = '#ffffff'; - const contacts = useContactState.getState().contacts; - if (Object.prototype.hasOwnProperty.call(contacts, `~${window.ship}`)) { - background = `#${uxToHex(contacts[`~${window.ship}`].color)}`; - } - const foreground = foregroundFromBackground(background); - const svg = sigiljs({ - patp: window.ship, - renderer: stringRenderer, - size: 16, - colors: [background, foreground] - }); - return svg; -}; export function binaryIndexOf(arr: BigInteger[], target: BigInteger): number | undefined { let leftBound = 0; diff --git a/pkg/interface/src/logic/state/contact.ts b/pkg/interface/src/logic/state/contact.ts index 4646edab58..bf234ef32c 100644 --- a/pkg/interface/src/logic/state/contact.ts +++ b/pkg/interface/src/logic/state/contact.ts @@ -1,4 +1,4 @@ -import { Contact, deSig, Patp, Rolodex } from '@urbit/api'; +import { Contact, deSig, Patp, Rolodex, uxToHex } from '@urbit/api'; import { useCallback } from 'react'; import _ from 'lodash'; import { reduce, reduceNacks } from '../reducers/contact-update'; @@ -7,6 +7,8 @@ import { createSubscription, reduceStateN } from './base'; +import { sigil as sigiljs, stringRenderer } from '@tlon/sigil-js'; +import { foregroundFromBackground } from '~/logic/lib/sigil'; export interface ContactState { contacts: Rolodex; @@ -51,4 +53,21 @@ export function useOurContact() { return useContact(`~${window.ship}`); } +export const favicon = () => { + let background = '#ffffff'; + const contacts = useContactState.getState().contacts; + if (Object.prototype.hasOwnProperty.call(contacts, `~${window.ship}`)) { + background = `#${uxToHex(contacts[`~${window.ship}`].color)}`; + } + const foreground = foregroundFromBackground(background); + const svg = sigiljs({ + patp: window.ship, + renderer: stringRenderer, + size: 16, + colors: [background, foreground] + }); + return svg; +}; + + export default useContactState; diff --git a/pkg/interface/src/logic/state/hark.ts b/pkg/interface/src/logic/state/hark.ts index d3e276a9e8..ad0f99c679 100644 --- a/pkg/interface/src/logic/state/hark.ts +++ b/pkg/interface/src/logic/state/hark.ts @@ -9,7 +9,8 @@ import { harkBinToId, decToUd, unixToDa, - opened + opened, + markEachAsRead } from '@urbit/api'; import { Poke } from '@urbit/http-api'; import { patp2dec } from 'urbit-ob'; @@ -25,6 +26,7 @@ import { reduceStateN } from './base'; import { reduce, reduceGraph, reduceGroup } from '../reducers/hark-update'; +import useMetadataState from './metadata'; export const HARK_FETCH_MORE_COUNT = 3; @@ -43,6 +45,8 @@ export interface HarkState { unreads: Unreads; archiveNote: (bin: HarkBin, lid: HarkLid) => Promise; readCount: (path: string) => Promise; + readGraph: (graph: string) => Promise; + readGroup: (group: string) => Promise; } const useHarkState = createState( @@ -54,6 +58,38 @@ const useHarkState = createState( poke: async (poke: Poke) => { await pokeOptimisticallyN(useHarkState, poke, [reduce]); }, + readGraph: async (graph: string) => { + const prefix = `/graph/${graph.slice(6)}`; + let counts = [] as string[]; + let eaches = [] as [string, string][]; + Object.entries(get().unreads).forEach(([path, unreads]) => { + if (path.startsWith(prefix)) { + if(unreads.count > 0) { + counts.push(path); + } + unreads.each.forEach(unread => { + eaches.push([path, unread]); + }); + } + }); + get().set(draft => { + counts.forEach(path => { + draft.unreads[path].count = 0; + }); + eaches.forEach(([path, each]) => { + draft.unreads[path].each = []; + }); + }); + await Promise.all([ + ...counts.map(path => markCountAsRead({ desk: window.desk, path })), + ...eaches.map(([path, each]) => markEachAsRead({ desk: window.desk, path }, each)) + ].map(pok => api.poke(pok))); + }, + readGroup: async (group: string) => { + const graphs = + _.pickBy(useMetadataState.getState().associations.graph, a => a.group === group); + await Promise.all(Object.keys(graphs).map(get().readGraph)); + }, readCount: async (path) => { const poke = markCountAsRead({ desk: (window as any).desk, path }); await pokeOptimisticallyN(useHarkState, poke, [reduce]); diff --git a/pkg/interface/src/logic/state/local.tsx b/pkg/interface/src/logic/state/local.tsx index 3bd6016fa0..b1947bbb2e 100644 --- a/pkg/interface/src/logic/state/local.tsx +++ b/pkg/interface/src/logic/state/local.tsx @@ -3,7 +3,7 @@ import f from 'lodash/fp'; import React from 'react'; import create, { State } from 'zustand'; import { persist } from 'zustand/middleware'; -import { BackgroundConfig, LeapCategories, RemoteContentPolicy, TutorialProgress, tutorialProgress } from '~/types/local-update'; +import { BackgroundConfig, LeapCategories, RemoteContentPolicy } from '~/types/local-update'; import airlock from '~/logic/api'; import { bootstrapApi } from '../api/bootstrap'; import { clearStorageMigration, createStorageKey, storageVersion, wait } from '~/logic/lib/util'; @@ -15,15 +15,9 @@ export interface LocalState { hideAvatars: boolean; hideNicknames: boolean; remoteContentPolicy: RemoteContentPolicy; - tutorialProgress: TutorialProgress; hideGroups: boolean; hideUtilities: boolean; - tutorialRef: HTMLElement | null, - hideTutorial: () => void; - nextTutStep: () => void; - prevTutStep: () => void; hideLeapCats: LeapCategories[]; - setTutorialRef: (el: HTMLElement | null) => void; dark: boolean; mobile: boolean; breaks: { @@ -62,27 +56,6 @@ const useLocalState = create(persist((set, get) => ({ hideLeapCats: [], hideGroups: false, hideUtilities: false, - tutorialProgress: 'hidden', - tutorialRef: null, - setTutorialRef: (el: HTMLElement | null) => set(produce((state) => { - state.tutorialRef = el; - })), - hideTutorial: () => set(produce((state) => { - state.tutorialProgress = 'hidden'; - state.tutorialRef = null; - })), - nextTutStep: () => set(produce((state) => { - const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress); - if(currIdx < tutorialProgress.length) { - state.tutorialProgress = tutorialProgress[currIdx + 1]; - } - })), - prevTutStep: () => set(produce((state) => { - const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress); - if(currIdx > 0) { - state.tutorialProgress = tutorialProgress[currIdx - 1]; - } - })), remoteContentPolicy: { imageShown: true, audioShown: true, @@ -132,8 +105,8 @@ const useLocalState = create(persist((set, get) => ({ set: fn => set(produce(fn)) }), { blacklist: [ - 'suspendedFocus', 'toggleOmnibox', 'omniboxShown', 'tutorialProgress', - 'prevTutStep', 'nextTutStep', 'tutorialRef', 'setTutorialRef', 'subscription', + 'suspendedFocus', 'toggleOmnibox', 'omniboxShown', + 'subscription', 'errorCount', 'breaks' ], name: createStorageKey('local'), diff --git a/pkg/interface/src/logic/state/settings.ts b/pkg/interface/src/logic/state/settings.ts index 9762a8bfdc..82f5710a2e 100644 --- a/pkg/interface/src/logic/state/settings.ts +++ b/pkg/interface/src/logic/state/settings.ts @@ -16,7 +16,7 @@ import { import { useCallback } from 'react'; import { reduceUpdate } from '../reducers/settings-update'; import airlock from '~/logic/api'; -import { getDeskSettings, Value } from '@urbit/api'; +import { Contact, getDeskSettings, Value } from '@urbit/api'; import { putEntry } from '@urbit/api/settings'; export interface ShortcutMapping { @@ -49,10 +49,6 @@ export interface SettingsState { leap: { categories: LeapCategories[]; }; - tutorial: { - seen: boolean; - joined?: number; - }; } export const selectSettingsState = )>(keys: K[]) => @@ -88,10 +84,6 @@ const useSettingsState = createState( leap: { categories: leapCategories }, - tutorial: { - seen: true, - joined: undefined - }, keyboard: { cycleForward: 'ctrl+\'', cycleBack: 'ctrl+;', @@ -139,4 +131,11 @@ export function useTheme() { return useSettingsState(selTheme); } +// Hide is an optional second parameter for when this function is used in class components +export function useShowNickname(contact: Contact | null, hide?: boolean): boolean { + const hideState = useSettingsState(state => state.calm.hideNicknames); + const hideNicknames = typeof hide !== 'undefined' ? hide : hideState; + return Boolean(contact && contact.nickname && !hideNicknames); +} + export default useSettingsState; diff --git a/pkg/interface/src/types/local-update.ts b/pkg/interface/src/types/local-update.ts index 31e99560d6..9d9dd8220f 100644 --- a/pkg/interface/src/types/local-update.ts +++ b/pkg/interface/src/types/local-update.ts @@ -1,10 +1,7 @@ -export const tutorialProgress = ['hidden', 'start', 'group-desc', 'channels', 'chat', 'link', 'publish', 'profile', 'leap', 'notifications', 'done', 'exit'] as const; - export const leapCategories = ['mychannel', 'messages', 'updates', 'profile', 'logout']; export type LeapCategories = typeof leapCategories[number]; -export type TutorialProgress = typeof tutorialProgress[number]; interface LocalUpdateSetDark { setDark: boolean; } diff --git a/pkg/interface/src/views/App.js b/pkg/interface/src/views/App.js index 1490e73977..41fc6c3468 100644 --- a/pkg/interface/src/views/App.js +++ b/pkg/interface/src/views/App.js @@ -10,16 +10,15 @@ import { hot } from 'react-hot-loader/root'; import { BrowserRouter as Router, withRouter } from 'react-router-dom'; import styled, { ThemeProvider } from 'styled-components'; import gcpManager from '~/logic/lib/gcpManager'; -import { favicon, svgDataURL } from '~/logic/lib/util'; +import { svgDataURL } from '~/logic/lib/util'; import withState from '~/logic/lib/withState'; -import useContactState from '~/logic/state/contact'; +import useContactState, { favicon } from '~/logic/state/contact'; import useLocalState from '~/logic/state/local'; import useSettingsState from '~/logic/state/settings'; import useGraphState from '~/logic/state/graph'; import { ShortcutContextProvider } from '~/logic/lib/shortcutContext'; import ErrorBoundary from '~/views/components/ErrorBoundary'; -import { TutorialModal } from '~/views/landscape/components/TutorialModal'; import './apps/chat/css/custom.css'; import Omnibox from './components/leap/Omnibox'; import StatusBar from './components/StatusBar'; @@ -171,7 +170,6 @@ class App extends React.Component { - { - const { connection } = props; - const [exitingTut, setExitingTut] = useState(false); - const seen = useSettingsState(s => s?.tutorial?.seen) ?? true; - const associations = useMetadataState(s => s.associations); - const hasLoaded = useMemo(() => Boolean(connection === 'connected'), [connection]); const notificationsCount = useHarkState(state => state.notificationsCount); const calmState = useSettingsState(selectCalmState); - const { hideUtilities } = calmState; - const { tutorialProgress, nextTutStep } = useLocalState(tutSelector); - let { hideGroups } = useLocalState(tutSelector); - !hideGroups ? { hideGroups } = calmState : null; - - const waiter = useWaitForProps({ ...props, associations }); - - const { query } = useQuery(); - - const { modal, showModal } = useModal({ - position: 'relative', - maxWidth: '350px', - modal: function modal(dismiss) { - const onDismiss = (e) => { - const { putEntry } = useSettingsState.getState(); - e.stopPropagation(); - putEntry('tutorial', 'seen', true); - dismiss(); - }; - const onContinue = async (e) => { - const { putEntry } = useSettingsState.getState(); - e.stopPropagation(); - if (!hasTutorialGroup({ associations })) { - await airlock.poke(join(TUTORIAL_HOST, TUTORIAL_GROUP)); - await putEntry('tutorial', 'joined', Date.now()); - await waiter(hasTutorialGroup); - await Promise.all( - [TUTORIAL_BOOK, TUTORIAL_CHAT, TUTORIAL_LINKS].map(graph => airlock.thread(joinGraph(TUTORIAL_HOST, graph)))); - - await waiter((p) => { - return `/ship/${TUTORIAL_HOST}/${TUTORIAL_CHAT}` in p.associations.graph && - `/ship/${TUTORIAL_HOST}/${TUTORIAL_BOOK}` in p.associations.graph && - `/ship/${TUTORIAL_HOST}/${TUTORIAL_LINKS}` in p.associations.graph; - }); - } - nextTutStep(); - dismiss(); - }; - return exitingTut ? ( - - - - You can always restart the tutorial by typing “tutorial” in Leap - - - - - - ) : ( - - - - - Welcome - - You have been invited to use Groups, an interface to chat - and interact with communities -
- Would you like a tour of Groups? -
- - - - Yes - - - - ); - } - }); - - useEffect(() => { - if(query.get('tutorial')) { - if (hasTutorialGroup({ associations })) { - if (nextTutStep) { - nextTutStep(); - } - } else { - showModal(); - } - } - }, [query, showModal]); - - useEffect(() => { - if(hasLoaded && !seen && tutorialProgress === 'hidden') { - showModal(); - } - }, [seen, hasLoaded]); + const { hideUtilities, hideGroups } = calmState; return ( <> @@ -157,7 +41,6 @@ export const LaunchApp = (props: LaunchAppProps): ReactElement | null => { - {modal} - a.group === TUTORIAL_GROUP_RESOURCE - ? -1 - : b.group === TUTORIAL_GROUP_RESOURCE - ? 1 - : alphabeticalOrder(a.metadata.title, b.metadata.title); + alphabeticalOrder(a.metadata.title, b.metadata.title); const getGraphUnreads = (associations: Associations) => { const state = useHarkState.getState(); const selUnread = (graph: string) => { const { count, each } = selHarkGraph(graph)(state); - const result = count + each.length; + const result = count + each.length; return result; }; return (path: string) => @@ -36,7 +31,10 @@ const getGraphUnreads = (associations: Associations) => { )(associations.graph); }; -const getGraphNotifications = (associations: Associations, unreads: Unreads) => (path: string) => +const getGraphNotifications = ( + associations: Associations, + unreads: Unreads +) => (path: string) => f.flow( f.pickBy((a: Association) => a.group === path), f.map('resource'), @@ -52,8 +50,11 @@ export default function Groups(props: Parameters[0]) { const groups = Object.values(associations?.groups || {}) .filter(e => e?.group in groupState) .sort(sortGroupsAlph); - const graphUnreads = getGraphUnreads(associations || {} as Associations); - const graphNotifications = getGraphNotifications(associations || {} as Associations, unreads); + const graphUnreads = getGraphUnreads(associations || ({} as Associations)); + const graphNotifications = getGraphNotifications( + associations || ({} as Associations), + unreads + ); return ( <> @@ -83,37 +84,26 @@ interface GroupProps { unreads: number; first: boolean; } -const selectJoined = (s: SettingsState) => s.tutorial.joined; function Group(props: GroupProps) { const { path, title, unreads, updates, first = false } = props; - const anchorRef = useRef(null); - const isTutorialGroup = path === `/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`; - useTutorialModal( - 'start', - isTutorialGroup, - anchorRef - ); const { hideUnreads } = useSettingsState(selectCalmState); - const joined = useSettingsState(selectJoined); - const days = Math.max(0, Math.floor(moment.duration(moment(joined) - .add(14, 'days') - .diff(moment())) - .as('days'))) || 0; return ( - + {title} - {!hideUnreads && ( - {isTutorialGroup && joined && - ({days} day{days !== 1 && 's'} remaining) - } - {updates > 0 && - ({updates} update{updates !== 1 && 's'} ) - } - {unreads > 0 && - ({unreads}) - } - + {!hideUnreads && ( + + {updates > 0 && ( + + {updates} update{updates !== 1 && 's'}{' '} + + )} + {unreads > 0 && {unreads}} + )} diff --git a/pkg/interface/src/views/apps/notifications/notifications.tsx b/pkg/interface/src/views/apps/notifications/notifications.tsx index cc75e996a1..9b35836fb6 100644 --- a/pkg/interface/src/views/apps/notifications/notifications.tsx +++ b/pkg/interface/src/views/apps/notifications/notifications.tsx @@ -1,11 +1,10 @@ import { Action, Box, Col, Icon, Row, Text } from '@tlon/indigo-react'; -import React, { ReactElement, ReactNode, useEffect, useRef } from 'react'; +import React, { ReactElement, ReactNode, useEffect } from 'react'; import Helmet from 'react-helmet'; import { Link, Route, Switch, useHistory, useLocation } from 'react-router-dom'; import useHarkState from '~/logic/state/hark'; import { Body } from '~/views/components/Body'; import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction'; -import { useTutorialModal } from '~/views/components/useTutorialModal'; import { Archive } from './Archive'; import { NewBox } from './NewBox'; @@ -41,8 +40,6 @@ export function NavLink({ export default function NotificationsScreen(props: any): ReactElement { const relativePath = (p: string) => baseUrl + p; - const anchorRef = useRef(null); - useTutorialModal('notifications', true, anchorRef); const notificationsCount = useHarkState(state => state.notificationsCount); const onReadAll = async () => {}; @@ -89,7 +86,6 @@ export default function NotificationsScreen(props: any): ReactElement { fontWeight="bold" fontSize={2} lineHeight={1} - ref={anchorRef} > Notifications diff --git a/pkg/interface/src/views/apps/profile/components/Profile.tsx b/pkg/interface/src/views/apps/profile/components/Profile.tsx index a4fad437f2..ae2ce5c44f 100644 --- a/pkg/interface/src/views/apps/profile/components/Profile.tsx +++ b/pkg/interface/src/views/apps/profile/components/Profile.tsx @@ -1,6 +1,6 @@ import { BaseImage, Box, Center, Row, Text } from '@tlon/indigo-react'; import { retrieve } from '@urbit/api'; -import React, { ReactElement, useEffect, useRef } from 'react'; +import React, { ReactElement, useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { Sigil } from '~/logic/lib/sigil'; import { uxToHex } from '~/logic/lib/util'; @@ -8,7 +8,6 @@ import useContactState from '~/logic/state/contact'; import useSettingsState, { selectCalmState } from '~/logic/state/settings'; import RichText from '~/views/components/RichText'; import { SetStatusBarModal } from '~/views/components/SetStatusBarModal'; -import { useTutorialModal } from '~/views/components/useTutorialModal'; import { EditProfile } from './EditProfile'; import { ViewProfile } from './ViewProfile'; import airlock from '~/logic/api'; @@ -32,10 +31,6 @@ export function ProfileImages(props: any): ReactElement { const { contact, hideCover, ship } = props; const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : '#000000'; - const anchorRef = useRef(null); - - useTutorialModal('profile', ship === `~${window.ship}`, anchorRef); - const cover = contact?.cover && !hideCover ? ( - + {cover}
{props.children} diff --git a/pkg/interface/src/views/apps/publish/components/Note.tsx b/pkg/interface/src/views/apps/publish/components/Note.tsx index f6d21066b7..af074dde5a 100644 --- a/pkg/interface/src/views/apps/publish/components/Note.tsx +++ b/pkg/interface/src/views/apps/publish/components/Note.tsx @@ -61,6 +61,9 @@ export function Note(props: NoteProps & RouteComponentProps) { const noteId = bigInt(index[1]); useEffect(() => { airlock.poke(markEachAsRead(toHarkPlace(association.resource), `/${index[1]}`)); + // Unread may be malformed, dismiss anyway + // TODO: remove when %read-graph is implemented + airlock.poke(markEachAsRead(toHarkPlace(association.resource), `/1`)); }, [association, props.note]); const adminLinks: JSX.Element[] = []; diff --git a/pkg/interface/src/views/apps/publish/components/Notebook.tsx b/pkg/interface/src/views/apps/publish/components/Notebook.tsx index 66e04178de..8a4cbb458d 100644 --- a/pkg/interface/src/views/apps/publish/components/Notebook.tsx +++ b/pkg/interface/src/views/apps/publish/components/Notebook.tsx @@ -2,11 +2,12 @@ import { Box, Button, Col, Row, Text } from '@tlon/indigo-react'; import { Association, Graph, readGraph } from '@urbit/api'; import React, { ReactElement, useCallback } from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { useShowNickname } from '~/logic/lib/util'; import useContactState from '~/logic/state/contact'; import useGroupState from '~/logic/state/group'; +import { useShowNickname } from '~/logic/state/settings'; import airlock from '~/logic/api'; import { NotebookPosts } from './NotebookPosts'; +import useHarkState from '~/logic/state/hark'; interface NotebookProps { ship: string; @@ -35,7 +36,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps): ReactEleme const showNickname = useShowNickname(contact); const readBook = useCallback(() => { - airlock.poke(readGraph(association.resource)); + useHarkState.getState().readGraph(association.resource); }, [association.resource]); if (!group) { diff --git a/pkg/interface/src/views/components/Author.tsx b/pkg/interface/src/views/components/Author.tsx index b9007c3ca7..c0f7231315 100644 --- a/pkg/interface/src/views/components/Author.tsx +++ b/pkg/interface/src/views/components/Author.tsx @@ -3,10 +3,10 @@ import moment from 'moment'; import React, { ReactElement, ReactNode } from 'react'; import { Sigil } from '~/logic/lib/sigil'; import { useCopy } from '~/logic/lib/useCopy'; -import { cite, useShowNickname, uxToHex } from '~/logic/lib/util'; +import { cite, uxToHex } from '~/logic/lib/util'; import { useContact } from '~/logic/state/contact'; import { useDark } from '~/logic/state/join'; -import useSettingsState, { selectCalmState } from '~/logic/state/settings'; +import useSettingsState, { selectCalmState, useShowNickname } from '~/logic/state/settings'; import { PropFunc } from '~/types'; import ProfileOverlay from './ProfileOverlay'; import Timestamp from './Timestamp'; diff --git a/pkg/interface/src/views/components/MentionText.tsx b/pkg/interface/src/views/components/MentionText.tsx index ed8d2b2499..ea431485ac 100644 --- a/pkg/interface/src/views/components/MentionText.tsx +++ b/pkg/interface/src/views/components/MentionText.tsx @@ -2,8 +2,9 @@ import { Text } from '@tlon/indigo-react'; import { Contact, Content, Group } from '@urbit/api'; import React from 'react'; import { referenceToPermalink } from '~/logic/lib/permalinks'; -import { cite, deSig, useShowNickname } from '~/logic/lib/util'; +import { cite, deSig } from '~/logic/lib/util'; import { useContact } from '~/logic/state/contact'; +import { useShowNickname } from '~/logic/state/settings'; import { PropFunc } from '~/types'; import ProfileOverlay from '~/views/components/ProfileOverlay'; import RichText from '~/views/components/RichText'; diff --git a/pkg/interface/src/views/components/ProfileOverlay.tsx b/pkg/interface/src/views/components/ProfileOverlay.tsx index 803b124f9f..7e858ae322 100644 --- a/pkg/interface/src/views/components/ProfileOverlay.tsx +++ b/pkg/interface/src/views/components/ProfileOverlay.tsx @@ -19,9 +19,8 @@ import { getRelativePosition } from '~/logic/lib/relativePosition'; import { Sigil } from '~/logic/lib/sigil'; import { useCopy } from '~/logic/lib/useCopy'; import { useOutsideClick } from '~/logic/lib/useOutsideClick'; -import { useShowNickname } from '~/logic/lib/util'; import { useContact } from '~/logic/state/contact'; -import useSettingsState, { SettingsState } from '~/logic/state/settings'; +import useSettingsState, { SettingsState, useShowNickname } from '~/logic/state/settings'; import { Portal } from './Portal'; import { ProfileStatus } from './ProfileStatus'; import RichText from './RichText'; diff --git a/pkg/interface/src/views/components/StatusBar.tsx b/pkg/interface/src/views/components/StatusBar.tsx index 35a600676e..fc014f4d2d 100644 --- a/pkg/interface/src/views/components/StatusBar.tsx +++ b/pkg/interface/src/views/components/StatusBar.tsx @@ -7,7 +7,7 @@ import { Row, Text } from '@tlon/indigo-react'; -import React, { useRef } from 'react'; +import React from 'react'; import { Link } from 'react-router-dom'; import { Sigil } from '~/logic/lib/sigil'; import { uxToHex } from '~/logic/lib/util'; @@ -18,7 +18,6 @@ import { Dropdown } from './Dropdown'; import { ProfileStatus } from './ProfileStatus'; import ReconnectButton from './ReconnectButton'; import { StatusBarItem } from './StatusBarItem'; -import { useTutorialModal } from './useTutorialModal'; import { StatusBarJoins } from './StatusBarJoins'; import useHarkState from '~/logic/state/hark'; @@ -48,13 +47,6 @@ const StatusBar = (props) => { ); - const anchorRef = useRef(null); - - const leapHighlight = useTutorialModal('leap', true, anchorRef); - - const floatLeap = - leapHighlight && window.matchMedia('(max-width: 550px)').matches; - return ( { > - toggleOmnibox()}> + toggleOmnibox()}> - + Leap diff --git a/pkg/interface/src/views/components/leap/Omnibox.tsx b/pkg/interface/src/views/components/leap/Omnibox.tsx index 84a38bc713..1b8db87786 100644 --- a/pkg/interface/src/views/components/leap/Omnibox.tsx +++ b/pkg/interface/src/views/components/leap/Omnibox.tsx @@ -119,7 +119,7 @@ export function Omnibox(props: OmniboxProps): ReactElement { if (category === 'other') { return [ 'other', - index.get('other').filter(({ app }) => app !== 'tutorial') + index.get('other') ]; } return [category, []]; @@ -159,7 +159,6 @@ export function Omnibox(props: OmniboxProps): ReactElement { defaultApps.includes(app.toLowerCase()) || app === 'profile' || app === 'messages' || - app === 'tutorial' || app === 'Links' || app === 'Terminal' || app === 'home' || diff --git a/pkg/interface/src/views/components/leap/OmniboxResult.tsx b/pkg/interface/src/views/components/leap/OmniboxResult.tsx index fedb0f7ea2..e85c3ae69e 100644 --- a/pkg/interface/src/views/components/leap/OmniboxResult.tsx +++ b/pkg/interface/src/views/components/leap/OmniboxResult.tsx @@ -169,17 +169,6 @@ export class OmniboxResult extends Component ); - } else if (icon === 'tutorial') { - graphic = ( - - ); } else { graphic = ( -) { - const { tutorialProgress, setTutorialRef } = useLocalState(localSelector); - - useEffect(() => { - if (show && (onProgress === tutorialProgress) && anchorRef?.current) { - setTutorialRef(anchorRef.current); - } - - return () => {}; - }, [tutorialProgress, show, anchorRef]); - - return show && onProgress === tutorialProgress; -} diff --git a/pkg/interface/src/views/landscape/components/GroupSummary.tsx b/pkg/interface/src/views/landscape/components/GroupSummary.tsx index d7c57c3e83..731ceb1ec8 100644 --- a/pkg/interface/src/views/landscape/components/GroupSummary.tsx +++ b/pkg/interface/src/views/landscape/components/GroupSummary.tsx @@ -1,9 +1,7 @@ import { Col, Row, Text, Icon } from '@tlon/indigo-react'; import { Metadata } from '@urbit/api'; -import React, { ReactElement, ReactNode, useRef } from 'react'; -import { TUTORIAL_GROUP, TUTORIAL_HOST } from '~/logic/lib/tutorialModal'; +import React, { ReactElement, ReactNode } from 'react'; import { PropFunc, IconRef } from '~/types'; -import { useTutorialModal } from '~/views/components/useTutorialModal'; import { MetadataIcon } from './MetadataIcon'; import { useCopy } from '~/logic/lib/useCopy'; interface GroupSummaryProps { @@ -17,17 +15,24 @@ interface GroupSummaryProps { locked?: boolean; } -export function GroupSummary(props: GroupSummaryProps & PropFunc): ReactElement { - const { channelCount, memberCount, metadata, resource, children, ...rest } = props; - const anchorRef = useRef(null); - useTutorialModal( - 'group-desc', - resource === `/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`, - anchorRef +export function GroupSummary( + props: GroupSummaryProps & PropFunc +): ReactElement { + const { + channelCount, + memberCount, + metadata, + resource, + children, + ...rest + } = props; + const { doCopy, copyDisplay } = useCopy( + `web+urbitgraph://group${resource?.slice(5)}`, + 'Copy', + 'Checkmark' ); - const { doCopy, copyDisplay } = useCopy(`web+urbitgraph://group${resource?.slice(5)}`, "Copy", "Checkmark"); return ( - + ): R /> - {metadata.title} - - {props?.AllowCopy && - - } + + {metadata.title} + + {props?.AllowCopy && ( + + )} @@ -64,17 +70,17 @@ export function GroupSummary(props: GroupSummaryProps & PropFunc): R - {metadata.description && - + > {metadata.description} - } + )} {children} diff --git a/pkg/interface/src/views/landscape/components/GroupsPane.tsx b/pkg/interface/src/views/landscape/components/GroupsPane.tsx index 741a4e50a2..3465aa65c2 100644 --- a/pkg/interface/src/views/landscape/components/GroupsPane.tsx +++ b/pkg/interface/src/views/landscape/components/GroupsPane.tsx @@ -43,7 +43,7 @@ export function GroupsPane(props: GroupsPaneProps) { useShortcut('readGroup', useCallback(() => { if(groupPath) { - airlock.poke(readGroup(groupPath)); + useHarkState.getState().readGroup(groupPath); } }, [groupPath])); diff --git a/pkg/interface/src/views/landscape/components/JoinGroup.tsx b/pkg/interface/src/views/landscape/components/JoinGroup.tsx index db309c75c5..bbdaddc6a7 100644 --- a/pkg/interface/src/views/landscape/components/JoinGroup.tsx +++ b/pkg/interface/src/views/landscape/components/JoinGroup.tsx @@ -12,7 +12,6 @@ import React, { ReactElement, useCallback, useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import urbitOb from 'urbit-ob'; import * as Yup from 'yup'; -import { TUTORIAL_GROUP_RESOURCE } from '~/logic/lib/tutorialModal'; import { useQuery } from '~/logic/lib/useQuery'; import { useWaitForProps } from '~/logic/lib/useWaitForProps'; import { getModuleIcon } from '~/logic/lib/util'; @@ -23,7 +22,6 @@ import { FormError } from '~/views/components/FormError'; import { StatelessAsyncButton } from '~/views/components/StatelessAsyncButton'; import { GroupSummary } from './GroupSummary'; import airlock from '~/logic/api'; -import useSettingsState from '~/logic/state/settings'; const formSchema = Yup.object({ group: Yup.string() @@ -77,10 +75,6 @@ export function JoinGroup(props: JoinGroupProps): ReactElement { const onConfirm = useCallback(async (group: string) => { const [,,ship,name] = group.split('/'); - const { putEntry } = useSettingsState.getState(); - if(group === TUTORIAL_GROUP_RESOURCE) { - await putEntry('tutorial', 'joined', Date.now()); - } if (group in groups) { return history.push(`/~landscape${group}`); } diff --git a/pkg/interface/src/views/landscape/components/Sidebar/Sidebar.tsx b/pkg/interface/src/views/landscape/components/Sidebar/Sidebar.tsx index 54ae250c5b..ee9fbc2691 100644 --- a/pkg/interface/src/views/landscape/components/Sidebar/Sidebar.tsx +++ b/pkg/interface/src/views/landscape/components/Sidebar/Sidebar.tsx @@ -1,14 +1,13 @@ import { Col } from '@tlon/indigo-react'; -import React, { ReactElement, useRef } from 'react'; +import React, { ReactElement } from 'react'; import styled from 'styled-components'; import { roleForShip } from '~/logic/lib/group'; import { useLocalStorageState } from '~/logic/lib/useLocalStorageState'; import { getGroupFromWorkspace } from '~/logic/lib/workspace'; import useGroupState from '~/logic/state/group'; import { Workspace } from '~/types'; -import { useTutorialModal } from '~/views/components/useTutorialModal'; import { GroupSwitcher } from '../GroupSwitcher'; import { SidebarList } from './SidebarList'; import { SidebarListHeader } from './SidebarListHeader'; @@ -48,12 +47,8 @@ export function Sidebar(props: SidebarProps): ReactElement | null { const role = groups?.[groupPath] ? roleForShip(groups[groupPath], window.ship) : undefined; const isAdmin = (role === 'admin') || (workspace?.type === 'home'); - const anchorRef = useRef(null); - useTutorialModal('channels', true, anchorRef); - return ( 0 || each.length > 0; - return hasNotifications - ? 'notification' - : hasUnread - ? 'unread' - : isSubscribed - ? undefined - : 'unsubscribed'; + if(!isSubscribed) { + return 'unsubscribed'; + } else if (hasNotifications) { + return 'notification'; + } else if (hasUnread) { + return 'unread'; + } else { + return undefined; + } } function SidebarItemBase(props: { @@ -186,12 +186,6 @@ export const SidebarAssociationItem = React.memo((props: { const group = useGroupState(state => state.groups[groupPath]); const { hideNicknames } = useSettingsState(s => s.calm); const contacts = useContactState(s => s.contacts); - const anchorRef = useRef(null); - useTutorialModal( - mod as any, - groupPath === `/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`, - anchorRef - ); const isUnmanaged = group?.hidden || false; const DM = isUnmanaged && props.workspace?.type === 'messages'; const itemStatus = useAssociationStatus(rid); diff --git a/pkg/interface/src/views/landscape/components/TutorialModal.tsx b/pkg/interface/src/views/landscape/components/TutorialModal.tsx deleted file mode 100644 index c8f3802f31..0000000000 --- a/pkg/interface/src/views/landscape/components/TutorialModal.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import { Box, Button, Col, Icon, Row, Text } from '@tlon/indigo-react'; -import { leaveGroup } from '@urbit/api'; -import _ from 'lodash'; -import React, { useCallback, useEffect, useState } from 'react'; -import { useHistory } from 'react-router-dom'; -import { getRelativePosition } from '~/logic/lib/relativePosition'; -import { - getTrianglePosition, MODAL_WIDTH_PX, progressDetails, - - TUTORIAL_GROUP, TUTORIAL_HOST -} from '~/logic/lib/tutorialModal'; -import useLocalState, { selectLocalState } from '~/logic/state/local'; -import { tutorialProgress as progress } from '~/types'; -import { ModalOverlay } from '~/views/components/ModalOverlay'; -import { Portal } from '~/views/components/Portal'; -import { StatelessAsyncButton } from '~/views/components/StatelessAsyncButton'; -import { Triangle } from '~/views/components/Triangle'; -import airlock from '~/logic/api'; -import useSettingsState from '~/logic/state/settings'; - -const localSelector = selectLocalState([ - 'tutorialProgress', - 'nextTutStep', - 'prevTutStep', - 'tutorialRef', - 'hideTutorial', - 'set' -]); - -export function TutorialModal() { - const { - tutorialProgress, - tutorialRef, - nextTutStep, - prevTutStep, - hideTutorial - } = useLocalState(localSelector); - const { - title, - description, - arrow = 'North', - alignX, - alignY, - offsetX, - offsetY - } = progressDetails[tutorialProgress]; - - const [coords, setCoords] = useState({}); - const [paused, setPaused] = useState(false); - - const history = useHistory(); - - const next = useCallback( () => { - const idx = progress.findIndex(p => p === tutorialProgress); - const { url } = progressDetails[progress[idx + 1]]; - nextTutStep(); - history.push(url); - }, - [nextTutStep, history, tutorialProgress, setCoords] - ); - const prev = useCallback(() => { - const idx = progress.findIndex(p => p === tutorialProgress); - prevTutStep(); - history.push(progressDetails[progress[idx - 1]].url); - }, [prevTutStep, history, tutorialProgress]); - - const updatePos = useCallback(() => { - const newCoords = getRelativePosition( - tutorialRef, - alignX, - alignY, - offsetX, - offsetY - ); - const withMobile: any = _.mapValues(newCoords, (value: string[], key: string) => { - if(key === 'bottom' || key === 'left') { - return ['0px', ...value]; - } - return ['unset', ...value]; - }); - if(!('bottom' in withMobile)) { - withMobile.bottom = ['0px', 'unset']; - } - if(!('left' in withMobile)) { - withMobile.left = ['0px', 'unset']; - } - - if (newCoords) { - setCoords(withMobile); - } else { - setCoords({}); - } - }, [tutorialRef]); - - const dismiss = useCallback(async () => { - setPaused(false); - hideTutorial(); - const { putEntry } = useSettingsState.getState(); - await putEntry('tutorial', 'seen', true); - }, [hideTutorial]); - - const bailExit = useCallback(() => { - setPaused(false); - }, []); - - const tryExit = useCallback(() => { - setPaused(true); - }, []); - - const doLeaveGroup = useCallback(async () => { - await airlock.thread(leaveGroup(TUTORIAL_HOST, TUTORIAL_GROUP)); - await dismiss(); - }, [dismiss]); - - const progressIdx = progress.findIndex(p => p === tutorialProgress); - - useEffect(() => { - if ( - tutorialProgress !== 'hidden' && - tutorialProgress !== 'done' && - tutorialRef - ) { - const interval = setInterval(updatePos, 100); - return () => { - setCoords({}); - clearInterval(interval); - }; - } - return () => {}; - }, [tutorialRef, tutorialProgress, updatePos]); - - const triPos = getTrianglePosition(arrow); - - if (tutorialProgress === 'done') { - return ( - - - - - - Tutorial Finished - - - {progressIdx} of {progress.length - 2} - - - - This tutorial is finished. Would you like to leave Beginner Island? - - - - - Leave Group - - - - - - ); - } - - if (tutorialProgress === 'hidden') { - return null; - } - - if(paused) { - return ( - - - - - End Tutorial Now? - - - - You can always restart the tutorial by typing "tutorial" in Leap. - - - - - End Tutorial - - - - - - ); - } - - if(Object.keys(coords).length === 0) { - return null; - } - - return ( - - - - - - - - - - - {title} - - - {progressIdx} of {progress.length - 2} - - - - {description} - - { progressIdx > 1 && ( - - )} - - - - - - ); -} diff --git a/pkg/landscape/app/hark-graph-hook.hoon b/pkg/landscape/app/hark-graph-hook.hoon index 5e7a628ddf..210f88dba5 100644 --- a/pkg/landscape/app/hark-graph-hook.hoon +++ b/pkg/landscape/app/hark-graph-hook.hoon @@ -486,7 +486,7 @@ update-core ?- mode.kind %count (hark %unread-count place %.y 1) - %each (hark %unread-each place /(rsh 4 (scot %ui (rear index.post)))) + %each (hark %unread-each place /(rsh 4 (scot %ui (rear self-idx)))) %none update-core == == @@ -497,7 +497,7 @@ update-core ?- mode.kind %count (hark %unread-count place %.n 1) - %each (hark %read-each place /(rsh 4 (scot %ui (rear index.post)))) + %each (hark %read-each place /(rsh 4 (scot %ui (rear self-idx)))) %none update-core == == diff --git a/pkg/landscape/desk.docket-0 b/pkg/landscape/desk.docket-0 index 15e3d9879b..d1b1065129 100644 --- a/pkg/landscape/desk.docket-0 +++ b/pkg/landscape/desk.docket-0 @@ -1,7 +1,7 @@ :~ title+'Groups' info+'A suite of applications to communicate on Urbit' color+0xee.5432 - glob-http+['https://bootstrap.urbit.org/glob-0v4.10b75.pd4h0.n481d.ln7c6.cf174.glob' 0v4.10b75.pd4h0.n481d.ln7c6.cf174] + glob-http+['https://bootstrap.urbit.org/glob-0v3.c3vkk.bvn2l.fmc76.l3ro9.0i5j0.glob' 0v3.c3vkk.bvn2l.fmc76.l3ro9.0i5j0] base+'landscape' version+[1 3 5] website+'https://tlon.io'