From 1c38deb502bba8336a282ed99ecfa6ff6692141a Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Wed, 17 Feb 2021 15:47:34 +1000 Subject: [PATCH 01/48] settings: match designs for already implemented features --- pkg/interface/src/logic/state/local.tsx | 7 +- .../views/apps/notifications/preferences.tsx | 2 - .../settings/components/lib/BackButton.tsx | 11 ++ .../components/lib/BackgroundPicker.tsx | 32 +++-- .../settings/components/lib/BucketList.tsx | 37 ++++-- .../apps/settings/components/lib/CalmPref.tsx | 115 +++++++++++++++++ .../settings/components/lib/DisplayForm.tsx | 38 +++--- .../components/lib/NotificationPref.tsx | 88 +++++++++++++ .../apps/settings/components/lib/S3Form.tsx | 56 ++++---- .../apps/settings/components/lib/Security.tsx | 71 ++++++---- .../apps/settings/components/settings.tsx | 121 +++++++++++++----- .../src/views/apps/settings/settings.tsx | 108 +++++++++++----- .../src/views/components/ColorInput.tsx | 6 +- 13 files changed, 534 insertions(+), 158 deletions(-) create mode 100644 pkg/interface/src/views/apps/settings/components/lib/BackButton.tsx create mode 100644 pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx create mode 100644 pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx diff --git a/pkg/interface/src/logic/state/local.tsx b/pkg/interface/src/logic/state/local.tsx index 64e68a0ca..169582477 100644 --- a/pkg/interface/src/logic/state/local.tsx +++ b/pkg/interface/src/logic/state/local.tsx @@ -6,7 +6,7 @@ import produce from 'immer'; import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgress } from "~/types/local-update"; -export interface LocalState extends State { +export interface LocalState { hideAvatars: boolean; hideNicknames: boolean; remoteContentPolicy: RemoteContentPolicy; @@ -23,10 +23,13 @@ export interface LocalState extends State { toggleOmnibox: () => void; set: (fn: (state: LocalState) => void) => void }; + +type LocalStateZus = LocalState & State; + export const selectLocalState = (keys: K[]) => f.pick(keys); -const useLocalState = create(persist((set, get) => ({ +const useLocalState = create(persist((set, get) => ({ dark: false, background: undefined, hideAvatars: false, diff --git a/pkg/interface/src/views/apps/notifications/preferences.tsx b/pkg/interface/src/views/apps/notifications/preferences.tsx index 8d3f900b4..99b889329 100644 --- a/pkg/interface/src/views/apps/notifications/preferences.tsx +++ b/pkg/interface/src/views/apps/notifications/preferences.tsx @@ -31,12 +31,10 @@ export default function NotificationPreferences( mentions: graphConfig.mentions, watchOnSelf: graphConfig.watchOnSelf, dnd, - watching: graphConfig.watching, }; const onSubmit = useCallback( async (values: FormSchema, actions: FormikHelpers) => { - console.log(values); try { let promises: Promise[] = []; if (values.mentions !== graphConfig.mentions) { diff --git a/pkg/interface/src/views/apps/settings/components/lib/BackButton.tsx b/pkg/interface/src/views/apps/settings/components/lib/BackButton.tsx new file mode 100644 index 000000000..0ba81209c --- /dev/null +++ b/pkg/interface/src/views/apps/settings/components/lib/BackButton.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { Text } from '@tlon/indigo-react'; + +export function BackButton(props: {}) { + return ( + + {"<- Back to System Preferences"} + + ); +} diff --git a/pkg/interface/src/views/apps/settings/components/lib/BackgroundPicker.tsx b/pkg/interface/src/views/apps/settings/components/lib/BackgroundPicker.tsx index 53aed1b3e..1e50e95f8 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/BackgroundPicker.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/BackgroundPicker.tsx @@ -1,6 +1,7 @@ import React from "react"; import { Box, + Text, Row, Label, Col, @@ -28,31 +29,38 @@ export function BackgroundPicker({ }) { const rowSpace = { my: 0, alignItems: 'center' }; - const radioProps = { my: 4, mr: 4, name: 'bgType' }; + const colProps = { my: 3, mr: 4, gapY: 1 }; return ( - + - - {bgType === "url" && ( + + + Set an image background - )} + - - {bgType === "color" && ( - - )} + + + Set a hex-based background + + - + ); } diff --git a/pkg/interface/src/views/apps/settings/components/lib/BucketList.tsx b/pkg/interface/src/views/apps/settings/components/lib/BucketList.tsx index 5ffdc270a..12a955f40 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/BucketList.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/BucketList.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from "react"; +import React, { useCallback, useState } from "react"; import { ManagedTextInputField as Input, @@ -11,8 +11,9 @@ import { MenuButton, MenuList, MenuItem, + Row, } from "@tlon/indigo-react"; -import { Formik } from "formik"; +import { Formik, FormikHelpers } from "formik"; import GlobalApi from "~/logic/api/global"; @@ -27,9 +28,12 @@ export function BucketList({ }) { const _buckets = Array.from(buckets); + const [adding, setAdding] = useState(false); + const onSubmit = useCallback( - (values: { newBucket: string }) => { + (values: { newBucket: string }, actions: FormikHelpers) => { api.s3.addBucket(values.newBucket); + actions.resetForm({ values: { newBucket: "" } }); }, [api] ); @@ -68,7 +72,7 @@ export function BucketList({ alignItems="center" borderRadius={1} border={1} - borderColor="washedGray" + borderColor="lightGray" fontSize={1} pl={2} mb={2} @@ -92,10 +96,27 @@ export function BucketList({ )} ))} - - + {adding && ( + + )} + + + + ); diff --git a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx new file mode 100644 index 000000000..c36d70f6d --- /dev/null +++ b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx @@ -0,0 +1,115 @@ +import React, {useCallback} from "react"; +import { + Box, + ManagedToggleSwitchField as Toggle, + Button, + Col, + Text, +} from "@tlon/indigo-react"; +import { Formik, Form, FormikHelpers } from "formik"; +import * as Yup from "yup"; +import { BackButton } from "./BackButton"; +import useLocalState, { selectLocalState } from "~/logic/state/local"; + +interface FormSchema { + hideAvatars: boolean; + hideNicknames: boolean; + imageShown: boolean; + audioShown: boolean; + oembedShown: boolean; +} + +const localSelector = selectLocalState([ + "hideAvatars", + "hideNicknames", + "remoteContentPolicy", + "set", +]); + +export function CalmPrefs(props: {}) { + const { + hideAvatars, + hideNicknames, + remoteContentPolicy, + set: setLocalState, + } = useLocalState(localSelector); + const { + imageShown, + videoShown, + oembedShown, + audioShown, + } = remoteContentPolicy; + + const initialValues: FormSchema = { + hideAvatars, + hideNicknames, + imageShown, + videoShown, + oembedShown, + audioShown, + }; + + const onSubmit = useCallback(async (values: FormSchema, actions: FormikHelpers) => { + setLocalState(state => { + const { hideAvatars, hideNicknames, ...remote } = values; + Object.assign(state.remoteContentPolicy, remote); + state.hideNicknames = hideNicknames; + state.hideAvatars = hideAvatars; + }); + + }, [setLocalState]); + + return ( + +
+ + + + + CalmEngine + + + Modulate various elemednts across Landscape to maximize calmness + + + User-set identity + + + Remote Content + + + + + + + + +
+ ); +} diff --git a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx index 1fcc3a120..e3288a689 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx @@ -3,7 +3,9 @@ import React from 'react'; import { Box, ManagedCheckboxField as Checkbox, - Button + Button, + Col, + Text } from '@tlon/indigo-react'; import { Formik, Form } from 'formik'; import * as Yup from 'yup'; @@ -12,6 +14,7 @@ import GlobalApi from '~/logic/api/global'; import { uxToHex } from '~/logic/lib/util'; import { S3State, BackgroundConfig } from '~/types'; import { BackgroundPicker, BgType } from './BackgroundPicker'; +import { BackButton } from "./BackButton"; import useLocalState, { LocalState } from '~/logic/state/local'; const formSchema = Yup.object().shape({ @@ -81,35 +84,26 @@ export default function DisplayForm(props: DisplayFormProps) { > {props => (
- - - Display Preferences - + + + + + Display Preferences + + + Customize visual interfaces across your Landscape + + - - - - +
)} diff --git a/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx b/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx new file mode 100644 index 000000000..cdbfecae1 --- /dev/null +++ b/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx @@ -0,0 +1,88 @@ +import React, { useCallback } from "react"; +import { + Col, + Text, + ManagedToggleSwitchField as Toggle, +} from "@tlon/indigo-react"; +import { Form, FormikHelpers } from "formik"; +import { FormikOnBlur } from "~/views/components/FormikOnBlur"; +import { BackButton } from "./BackButton"; +import GlobalApi from "~/logic/api/global"; +import {NotificationGraphConfig} from "~/types"; + +interface FormSchema { + mentions: boolean; + dnd: boolean; + watchOnSelf: boolean; +} + +export function NotificationPreferences(props: { + api: GlobalApi; + graphConfig: NotificationGraphConfig; + dnd: boolean; +}) { + const { graphConfig, api, dnd } = props; + const initialValues = { + mentions: graphConfig.mentions, + dnd: dnd, + watchOnSelf: graphConfig.watchOnSelf, + }; + + const onSubmit = useCallback(async (values: FormSchema, actions: FormikHelpers) => { + try { + let promises: Promise[] = []; + if (values.mentions !== graphConfig.mentions) { + promises.push(api.hark.setMentions(values.mentions)); + } + if (values.watchOnSelf !== graphConfig.watchOnSelf) { + promises.push(api.hark.setWatchOnSelf(values.watchOnSelf)); + } + if (values.dnd !== dnd && !_.isUndefined(values.dnd)) { + promises.push(api.hark.setDoNotDisturb(values.dnd)) + } + + await Promise.all(promises); + actions.setStatus({ success: null }); + actions.resetForm({ values: initialValues }); + } catch (e) { + console.error(e); + actions.setStatus({ error: e.message }); + } + }, [api]); + + return ( + + + + + Notification Preferences + + + Set notification visibility and default behaviours for groups and + messaging + + + +
+ + + + + + +
+ + ); +} diff --git a/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx b/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx index 963e2feb1..dcbb2180c 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx @@ -49,7 +49,7 @@ export default function S3Form(props: S3FormProps) { ); return ( <> - + -
- - S3 Credentials - - - - - + + + + + S3 Storage Setup + + + Store credentials for your S3 object storage buckets on your + Urbit ship, and upload media freely to various modules. Learn + more + + + + + + +
- - - S3 Buckets - + + + + S3 Buckets + + + Your 'active' bucket will be the one used when Landscape uploads a + file + + - - Security - - - Log out of this session - - - You will be logged out of your Urbit on this browser. + + + + + Security Preferences + + + Manage sessions, login credentials and Landscape access + + + + + Log out of this session + + + {allSessions + ? "You will be logged out of all browsers that have currently logged into your Urbit." + : "You will be logged out of your Urbit on this browser."} + + setAllSessions((s) => !s)} + > + Log out of all sessions +
-
-
- - Log out of all sessions - - - You will be logged out of all browsers that have currently logged into your Urbit. -
- - -
-
- + + ); } diff --git a/pkg/interface/src/views/apps/settings/components/settings.tsx b/pkg/interface/src/views/apps/settings/components/settings.tsx index c199f8291..218ea0b40 100644 --- a/pkg/interface/src/views/apps/settings/components/settings.tsx +++ b/pkg/interface/src/views/apps/settings/components/settings.tsx @@ -1,37 +1,96 @@ -import React from 'react'; +import React from "react"; -import { Box } from '@tlon/indigo-react'; +import { Row, Icon, Box, Col, Text } from "@tlon/indigo-react"; -import GlobalApi from '~/logic/api/global'; -import { StoreState } from '~/logic/store/type'; -import DisplayForm from './lib/DisplayForm'; -import S3Form from './lib/S3Form'; -import SecuritySettings from './lib/Security'; -import RemoteContentForm from './lib/RemoteContent'; +import GlobalApi from "~/logic/api/global"; +import { StoreState } from "~/logic/store/type"; +import DisplayForm from "./lib/DisplayForm"; +import S3Form from "./lib/S3Form"; +import SecuritySettings from "./lib/Security"; +import RemoteContentForm from "./lib/RemoteContent"; +import { NotificationPreferences } from "./lib/NotificationPref"; +import { CalmPrefs } from "./lib/CalmPref"; +import { Link } from "react-router-dom"; -type ProfileProps = StoreState & { api: GlobalApi; ship: string }; - -export default function Settings({ - api, - s3 -}: ProfileProps) { +export function SettingsItem(props: { + title: string; + description: string; + to: string; +}) { + const { to, title, description } = props; return ( - - - - - - + + + + + {title} + {description} + + + + ); +} + +export default function Settings(props: {}) { + return ( + + + System Preferences + Configure and customize Landscape + + + + + + + + + + + + ); } diff --git a/pkg/interface/src/views/apps/settings/settings.tsx b/pkg/interface/src/views/apps/settings/settings.tsx index bfe331ecd..8c0d78e31 100644 --- a/pkg/interface/src/views/apps/settings/settings.tsx +++ b/pkg/interface/src/views/apps/settings/settings.tsx @@ -1,48 +1,94 @@ -import React from "react"; +import React, { ReactNode } from "react"; import { Route, Link, Switch } from "react-router-dom"; -import Helmet from 'react-helmet'; +import Helmet from "react-helmet"; import { Box, Text, Row, Col, Icon, BaseImage } from "@tlon/indigo-react"; import Settings from "./components/settings"; +import { NotificationPreferences } from "./components/lib/NotificationPref"; +import DisplayForm from './components/lib/DisplayForm'; +import S3Form from "./components/lib/S3Form"; import useLocalState from "~/logic/state/local"; +import {CalmPrefs} from "./components/lib/CalmPref"; +import SecuritySettings from "./components/lib/Security"; + +export const Skeleton = (props: { children: ReactNode }) => ( + + + {props.children} + + +); export default function SettingsScreen(props: any) { const { ship, dark } = props; - const hideAvatars = useLocalState(state => state.hideAvatars); + const hideAvatars = useLocalState((state) => state.hideAvatars); return ( <> Landscape - Settings - { - return ( - - - - - - ); - }} - /> + + + { + return ( + + ); + }} + /> + { + return ( + + ); + }} + /> + { + return ( + + ); + }} + /> + { + return ( + + ); + }} + /> + { + return ( + + ); + }} + /> + { + return ; + }} + /> + + ); } diff --git a/pkg/interface/src/views/components/ColorInput.tsx b/pkg/interface/src/views/components/ColorInput.tsx index 0ee279db1..127d6e97e 100644 --- a/pkg/interface/src/views/components/ColorInput.tsx +++ b/pkg/interface/src/views/components/ColorInput.tsx @@ -13,12 +13,13 @@ import { uxToHex, hexToUx } from "~/logic/lib/util"; type ColorInputProps = Parameters[0] & { id: string; - label: string; + label?: string; + placeholder?: string; disabled?: boolean; }; export function ColorInput(props: ColorInputProps) { - const { id, label, caption, disabled, ...rest } = props; + const { id, placeholder, label, caption, disabled, ...rest } = props; const [{ value, onBlur }, meta, { setValue }] = useField(id); const hex = value.replace('#', '').replace("0x","").replace(".", ""); @@ -54,6 +55,7 @@ export function ColorInput(props: ColorInputProps) { value={hex} disabled={disabled || false} borderRight={0} + placeholder={placeholder} /> Date: Wed, 17 Feb 2021 16:48:05 +1000 Subject: [PATCH 02/48] leap: add category blacklist --- pkg/interface/src/logic/lib/omnibox.js | 28 +++++--- pkg/interface/src/logic/state/local.tsx | 6 +- pkg/interface/src/types/local-update.ts | 4 ++ pkg/interface/src/views/apps/launch/app.js | 8 ++- .../settings/components/lib/DisplayForm.tsx | 69 ++++++++++++------- .../settings/components/lib/LeapSettings.tsx | 64 +++++++++++++++++ .../src/views/apps/settings/settings.tsx | 9 +++ .../src/views/components/leap/Omnibox.tsx | 26 +++++-- 8 files changed, 170 insertions(+), 44 deletions(-) create mode 100644 pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx diff --git a/pkg/interface/src/logic/lib/omnibox.js b/pkg/interface/src/logic/lib/omnibox.js index eabb8017e..b775127fe 100644 --- a/pkg/interface/src/logic/lib/omnibox.js +++ b/pkg/interface/src/logic/lib/omnibox.js @@ -1,7 +1,7 @@ import { cite } from '~/logic/lib/util'; import { isChannelAdmin } from '~/logic/lib/group'; - const indexes = new Map([ +const makeIndexes = () => new Map([ ['ships', []], ['commands', []], ['subscriptions', []], @@ -70,18 +70,30 @@ const appIndex = function (apps) { return applications; }; -const otherIndex = function() { +const otherIndex = function(hide) { const other = []; - other.push(result('My Channels', '/~landscape/home', 'home', null)); - other.push(result('Notifications', '/~notifications', 'inbox', null)); - other.push(result('Profile and Settings', `/~profile/~${window.ship}`, 'profile', null)); + console.log(hide); + const notBanned = (cat) => hide.findIndex(c => c === cat) === -1; + if(notBanned('mychannel')) { + other.push(result('My Channels', '/~landscape/home', 'home', null)); + } + if(notBanned('updates')) { + other.push(result('Notifications', '/~notifications', 'inbox', null)); + } + if(notBanned('profile')) { + other.push(result('Profile and Settings', `/~profile/~${window.ship}`, 'profile', null)); + } + other.push(result('Messages', '/~landscape/messages', 'messages', null)); - other.push(result('Log Out', '/~/logout', 'logout', null)); + if(notBanned('logout')) { + other.push(result('Log Out', '/~/logout', 'logout', null)); + } return other; }; -export default function index(contacts, associations, apps, currentGroup, groups) { +export default function index(contacts, associations, apps, currentGroup, groups, hide) { + const indexes = makeIndexes(); indexes.set('ships', shipIndex(contacts)); // all metadata from all apps is indexed // into subscriptions and landscape @@ -141,7 +153,7 @@ export default function index(contacts, associations, apps, currentGroup, groups indexes.set('subscriptions', subscriptions); indexes.set('groups', landscape); indexes.set('apps', appIndex(apps)); - indexes.set('other', otherIndex()); + indexes.set('other', otherIndex(hide)); return indexes; }; diff --git a/pkg/interface/src/logic/state/local.tsx b/pkg/interface/src/logic/state/local.tsx index 169582477..079fbe6d1 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 create, { State } from 'zustand'; import { persist } from 'zustand/middleware'; import produce from 'immer'; -import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgress } from "~/types/local-update"; +import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgress, LeapCategories } from "~/types/local-update"; export interface LocalState { @@ -11,10 +11,12 @@ export interface LocalState { hideNicknames: boolean; remoteContentPolicy: RemoteContentPolicy; tutorialProgress: TutorialProgress; + hideGroups: boolean; tutorialRef: HTMLElement | null, hideTutorial: () => void; nextTutStep: () => void; prevTutStep: () => void; + hideLeapCats: LeapCategories[]; setTutorialRef: (el: HTMLElement | null) => void; dark: boolean; background: BackgroundConfig; @@ -34,6 +36,8 @@ const useLocalState = create(persist((set, get) => ({ background: undefined, hideAvatars: false, hideNicknames: false, + hideLeapCats: [], + hideGroups: false, tutorialProgress: 'hidden', tutorialRef: null, setTutorialRef: (el: HTMLElement | null) => set(produce(state => { diff --git a/pkg/interface/src/types/local-update.ts b/pkg/interface/src/types/local-update.ts index 5849931fe..731bd8244 100644 --- a/pkg/interface/src/types/local-update.ts +++ b/pkg/interface/src/types/local-update.ts @@ -1,5 +1,9 @@ export const tutorialProgress = ['hidden', 'start', 'group-desc', 'channels', 'chat', 'link', 'publish', 'profile', 'leap', 'notifications', 'done', 'exit'] as const; +export const leapCategories = ["commands", "mychannel", "messages", "updates", "profile", "logout"] as const; + +export type LeapCategories = typeof leapCategories[number]; + export type TutorialProgress = typeof tutorialProgress[number]; interface LocalUpdateSetDark { setDark: boolean; diff --git a/pkg/interface/src/views/apps/launch/app.js b/pkg/interface/src/views/apps/launch/app.js index 1e0c937e1..887cb5e34 100644 --- a/pkg/interface/src/views/apps/launch/app.js +++ b/pkg/interface/src/views/apps/launch/app.js @@ -38,7 +38,7 @@ const ScrollbarLessBox = styled(Box)` } `; -const tutSelector = f.pick(['tutorialProgress', 'nextTutStep']); +const tutSelector = f.pick(['tutorialProgress', 'nextTutStep', 'hideGroups']); export default function LaunchApp(props) { const history = useHistory(); @@ -82,7 +82,7 @@ export default function LaunchApp(props) { } }, [query]); - const { tutorialProgress, nextTutStep } = useLocalState(tutSelector); + const { tutorialProgress, nextTutStep, hideGroups } = useLocalState(tutSelector); const waiter = useWaitForProps(props); @@ -198,7 +198,9 @@ export default function LaunchApp(props) { > - + {!hideGroups && + () + } {hashBox} diff --git a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx index e3288a689..0c6924a64 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx @@ -1,30 +1,32 @@ -import React from 'react'; +import React from "react"; import { Box, ManagedCheckboxField as Checkbox, Button, Col, - Text -} from '@tlon/indigo-react'; -import { Formik, Form } from 'formik'; -import * as Yup from 'yup'; + Text, + ManagedToggleSwitchField as Toggle, +} from "@tlon/indigo-react"; +import { Formik, Form } from "formik"; +import * as Yup from "yup"; -import GlobalApi from '~/logic/api/global'; -import { uxToHex } from '~/logic/lib/util'; -import { S3State, BackgroundConfig } from '~/types'; -import { BackgroundPicker, BgType } from './BackgroundPicker'; +import GlobalApi from "~/logic/api/global"; +import { uxToHex } from "~/logic/lib/util"; +import { S3State, BackgroundConfig } from "~/types"; +import { BackgroundPicker, BgType } from "./BackgroundPicker"; import { BackButton } from "./BackButton"; -import useLocalState, { LocalState } from '~/logic/state/local'; +import useLocalState, { LocalState } from "~/logic/state/local"; const formSchema = Yup.object().shape({ bgType: Yup.string() - .oneOf(['none', 'color', 'url'], 'invalid') - .required('Required'), + .oneOf(["none", "color", "url"], "invalid") + .required("Required"), bgUrl: Yup.string().url(), bgColor: Yup.string(), avatars: Yup.boolean(), - nicknames: Yup.boolean() + nicknames: Yup.boolean(), + hideGroups: Yup.boolean(), }); interface FormSchema { @@ -33,6 +35,7 @@ interface FormSchema { bgUrl: string | undefined; avatars: boolean; nicknames: boolean; + hideGroups: boolean; } interface DisplayFormProps { @@ -43,16 +46,22 @@ interface DisplayFormProps { export default function DisplayForm(props: DisplayFormProps) { const { api, s3 } = props; - const { hideAvatars, hideNicknames, background, set: setLocalState } = useLocalState(); + const { + hideAvatars, + hideNicknames, + background, + hideGroups, + set: setLocalState, + } = useLocalState(); let bgColor, bgUrl; - if (background?.type === 'url') { + if (background?.type === "url") { bgUrl = background.url; } - if (background?.type === 'color') { + if (background?.type === "color") { bgColor = background.color; } - const bgType = background?.type || 'none'; + const bgType = background?.type || "none"; return ( { const bgConfig: BackgroundConfig = - values.bgType === 'color' - ? { type: 'color', color: `#${uxToHex(values.bgColor || '0x0')}` } - : values.bgType === 'url' - ? { type: 'url', url: values.bgUrl || '' } + values.bgType === "color" + ? { type: "color", color: `#${uxToHex(values.bgColor || "0x0")}` } + : values.bgType === "url" + ? { type: "url", url: values.bgUrl || "" } : undefined; setLocalState((state: LocalState) => { state.background = bgConfig; state.hideAvatars = values.avatars; state.hideNicknames = values.nicknames; + state.hideGroups = values.hideGroups; + }); actions.setSubmitting(false); }} > - {props => ( + {(props) => (
- + Display Preferences - + Customize visual interfaces across your Landscape @@ -100,6 +112,11 @@ export default function DisplayForm(props: DisplayFormProps) { api={api} s3={s3} /> + diff --git a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx new file mode 100644 index 000000000..aaa87dc42 --- /dev/null +++ b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx @@ -0,0 +1,64 @@ +import React, { useCallback } from "react"; +import _ from 'lodash'; +import { + Col, + Text, + ManagedToggleSwitchField as Toggle, + ManagedCheckboxField, +} from "@tlon/indigo-react"; +import { Form, FormikHelpers } from "formik"; +import { FormikOnBlur } from "~/views/components/FormikOnBlur"; +import { BackButton } from "./BackButton"; +import GlobalApi from "~/logic/api/global"; +import { NotificationGraphConfig, LeapCategories, leapCategories } from "~/types"; +import useLocalState, {selectLocalState} from "~/logic/state/local"; + +type FormSchema = { + [c in LeapCategories]: boolean; +} + +const localSelector = selectLocalState(["hideLeapCats", "set"]); + +export function LeapSettings(props: {}) { + + const { hideLeapCats, set: setLocalState } = useLocalState(localSelector); + + const initialValues: FormSchema = leapCategories.reduce((acc, val, key) => { + return {...acc, [val]: hideLeapCats.findIndex(c => c === val) !== -1 }; + }, {} as FormSchema); + + const onSubmit = useCallback((values: FormSchema) => { + setLocalState(state => { + state.hideLeapCats = _.keys(_.pickBy(values, v => v)) as any; + }); + + }, [setLocalState]); + + return ( + + + + + Leap + + + Customize Leap ordering, omit modules or results + + + + + + + Customize default Leap sections + + + + + + + + + + + ); +} diff --git a/pkg/interface/src/views/apps/settings/settings.tsx b/pkg/interface/src/views/apps/settings/settings.tsx index 8c0d78e31..dde98baec 100644 --- a/pkg/interface/src/views/apps/settings/settings.tsx +++ b/pkg/interface/src/views/apps/settings/settings.tsx @@ -11,6 +11,7 @@ import S3Form from "./components/lib/S3Form"; import useLocalState from "~/logic/state/local"; import {CalmPrefs} from "./components/lib/CalmPref"; import SecuritySettings from "./components/lib/Security"; +import {LeapSettings} from "./components/lib/LeapSettings"; export const Skeleton = (props: { children: ReactNode }) => ( @@ -38,6 +39,14 @@ export default function SettingsScreen(props: any) { + { + return ( + + ); + }} + /> { diff --git a/pkg/interface/src/views/components/leap/Omnibox.tsx b/pkg/interface/src/views/components/leap/Omnibox.tsx index 24385f613..a5a2b46f4 100644 --- a/pkg/interface/src/views/components/leap/Omnibox.tsx +++ b/pkg/interface/src/views/components/leap/Omnibox.tsx @@ -6,7 +6,7 @@ import makeIndex from '~/logic/lib/omnibox'; import Mousetrap from 'mousetrap'; import OmniboxInput from './OmniboxInput'; import OmniboxResult from './OmniboxResult'; -import { withLocalState } from '~/logic/state/local'; +import useLocalState, { withLocalState, selectLocalState } from '~/logic/state/local'; import { deSig } from '~/logic/lib/util'; import defaultApps from '~/logic/lib/default-apps'; @@ -28,10 +28,12 @@ interface OmniboxProps { } const SEARCHED_CATEGORIES = ['ships', 'other', 'commands', 'groups', 'subscriptions', 'apps']; +const localSelector = selectLocalState(["hideLeapCats"]); export function Omnibox(props: OmniboxProps) { const location = useLocation(); const history = useHistory(); + const { hideLeapCats } = useLocalState(localSelector); const omniboxRef = useRef(null) const inputRef = useRef(null); @@ -45,18 +47,30 @@ export function Omnibox(props: OmniboxProps) { : props.contacts; }, [props.contacts, query]); - const index = useMemo(() => { - const selectedGroup = location.pathname.startsWith('/~landscape/ship/') + const selectedGroup = useMemo( + () => location.pathname.startsWith('/~landscape/ship/') ? '/' + location.pathname.split('/').slice(2,5).join('/') - : null; + : null, + [location.pathname] + ); + + const index = useMemo(() => { return makeIndex( contacts, props.associations, props.tiles, selectedGroup, - props.groups + props.groups, + hideLeapCats, ); - }, [location.pathname, contacts, props.associations, props.groups, props.tiles]); + }, [ + selectedGroup, + hideLeapCats, + contacts, + props.associations, + props.groups, + props.tiles + ]); const onOutsideClick = useCallback(() => { props.show && props.toggle() From 41604e4cd4dc096b1f4f1fbcf4984a5116ada95e Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Mon, 22 Feb 2021 15:13:08 +1000 Subject: [PATCH 03/48] settings: hook reducer into zustand --- pkg/interface/src/logic/api/settings.ts | 6 +- .../src/logic/reducers/settings-update.ts | 79 +++++++++++-------- pkg/interface/src/logic/state/settings.tsx | 56 +++++++++++++ pkg/interface/src/logic/store/store.ts | 2 +- pkg/interface/src/views/App.js | 15 ++-- .../settings/components/lib/DisplayForm.tsx | 74 ++++++++--------- 6 files changed, 144 insertions(+), 88 deletions(-) create mode 100644 pkg/interface/src/logic/state/settings.tsx diff --git a/pkg/interface/src/logic/api/settings.ts b/pkg/interface/src/logic/api/settings.ts index cd0cd736f..d612df35f 100644 --- a/pkg/interface/src/logic/api/settings.ts +++ b/pkg/interface/src/logic/api/settings.ts @@ -51,8 +51,10 @@ export default class SettingsApi extends BaseApi { } async getAll() { - const data = await this.scry("settings-store", "/all"); - this.store.handleEvent({data: {"settings-data": data.all}}); + const { all } = await this.scry("settings-store", "/all"); + this.store.handleEvent({data: + {"settings-data": { all } } + }); } async getBucket(bucket: Key) { diff --git a/pkg/interface/src/logic/reducers/settings-update.ts b/pkg/interface/src/logic/reducers/settings-update.ts index 9716cbb85..fac6515e4 100644 --- a/pkg/interface/src/logic/reducers/settings-update.ts +++ b/pkg/interface/src/logic/reducers/settings-update.ts @@ -1,77 +1,86 @@ import _ from 'lodash'; -import { StoreState } from '../../store/type'; -import { - SettingsUpdate, -} from '~/types/settings'; +import { SettingsUpdate } from '~/types/settings'; +import useSettingsState, { SettingsStateZus } from "~/logic/state/settings"; +import produce from 'immer'; +import { unstable_batchedUpdates } from 'react-dom'; -type SettingsState = Pick; +export default class SettingsStateZusettingsReducer{ + reduce(json: any) { -export default class SettingsReducer{ - reduce(json: Cage, state: S) { - let data = json["settings-event"]; - if (data) { - this.putBucket(data, state); - this.delBucket(data, state); - this.putEntry(data, state); - this.delEntry(data, state); - } - data = json["settings-data"]; - if (data) { - this.getAll(data, state); - this.getBucket(data, state); - this.getEntry(data, state); - } + const old = useSettingsState.getState(); + const newState = produce(old, state => { + let data = json["settings-event"]; + if (data) { + console.log(data); + this.putBucket(data, state); + this.delBucket(data, state); + this.putEntry(data, state); + this.delEntry(data, state); + } + data = json["settings-data"]; + if (data) { + console.log(data); + this.getAll(data, state); + this.getBucket(data, state); + this.getEntry(data, state); + } + }); + console.log(newState); + useSettingsState.setState(newState); } - putBucket(json: SettingsUpdate, state: S) { + putBucket(json: SettingsUpdate, state: SettingsStateZus) { const data = _.get(json, 'put-bucket', false); if (data) { - state.settings[data["bucket-key"]] = data.bucket; + state[data["bucket-key"]] = data.bucket; } } - delBucket(json: SettingsUpdate, state: S) { + delBucket(json: SettingsUpdate, state: SettingsStateZus) { const data = _.get(json, 'del-bucket', false); if (data) { delete state.settings[data["bucket-key"]]; } } - putEntry(json: SettingsUpdate, state: S) { + putEntry(json: SettingsUpdate, state: SettingsStateZus) { const data = _.get(json, 'put-entry', false); if (data) { - if (!state.settings[data["bucket-key"]]) { - state.settings[data["bucket-key"]] = {}; + if (!state[data["bucket-key"]]) { + state[data["bucket-key"]] = {}; } - state.settings[data["bucket-key"]][data["entry-key"]] = data.value; + state[data["bucket-key"]][data["entry-key"]] = data.value; } } - delEntry(json: SettingsUpdate, state: S) { + delEntry(json: SettingsUpdate, state: SettingsStateZus) { const data = _.get(json, 'del-entry', false); if (data) { - delete state.settings[data["bucket-key"]][data["entry-key"]]; + delete state[data["bucket-key"]][data["entry-key"]]; } } - getAll(json: any, state: S) { - state.settings = json; + getAll(json: any, state: SettingsStateZus) { + const data = _.get(json, 'all'); + if(data) { + Object.assign(state, json); + } } - getBucket(json: any, state: S) { + getBucket(json: any, state: SettingsStateZus) { const key = _.get(json, 'bucket-key', false); const bucket = _.get(json, 'bucket', false); if (key && bucket) { - state.settings[key] = bucket; + state[key] = bucket; } } - getEntry(json: any, state: S) { + getEntry(json: any, state: SettingsStateZus) { const bucketKey = _.get(json, 'bucket-key', false); const entryKey = _.get(json, 'entry-key', false); const entry = _.get(json, 'entry', false); if (bucketKey && entryKey && entry) { - state.settings[bucketKey][entryKey] = entry; + state[bucketKey][entryKey] = entry; } } } diff --git a/pkg/interface/src/logic/state/settings.tsx b/pkg/interface/src/logic/state/settings.tsx new file mode 100644 index 000000000..bf2bfb6dc --- /dev/null +++ b/pkg/interface/src/logic/state/settings.tsx @@ -0,0 +1,56 @@ +import React, { ReactNode } from "react"; +import f from 'lodash/fp'; +import create, { State } from 'zustand'; +import { persist } from 'zustand/middleware'; +import produce from 'immer'; +import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgress, LeapCategories } from "~/types/local-update"; + + +export interface SettingsState { + display: { + backgroundType: 'none' | 'url' | 'color'; + background?: string; + dark: boolean; + }; + calm: { + hideNicknames: boolean; + hideAvatars: boolean; + remoteContentPolicy: RemoteContentPolicy; + } + set: (fn: (state: SettingsState) => void) => void +}; + +export type SettingsStateZus = SettingsState & State; + +export const selectSettingsState = + (keys: K[]) => f.pick(keys); + +const useSettingsState = create((set) => ({ + display: { + backgroundType: 'none', + background: undefined, + dark: false, + }, + calm: { + hideNicknames: false, + hideAvatars: false, + remoteContentPolicy: { + imageShown: true, + oembedShown: true, + audioShown: true, + videoShown: true + } + }, + set: (fn: (state: SettingsState) => void) => set(produce(fn)) +})); + +function withSettingsState(Component: any, stateMemberKeys?: S[]) { + return React.forwardRef((props: Omit, ref) => { + const localState = stateMemberKeys + ? useSettingsState(selectSettingsState(stateMemberKeys)) + : useSettingsState(); + return + }); +} + +export { useSettingsState as default, withSettingsState }; diff --git a/pkg/interface/src/logic/store/store.ts b/pkg/interface/src/logic/store/store.ts index b2ab60c6c..ca512fcb3 100644 --- a/pkg/interface/src/logic/store/store.ts +++ b/pkg/interface/src/logic/store/store.ts @@ -115,7 +115,7 @@ export default class GlobalStore extends BaseStore { GraphReducer(data, this.state); HarkReducer(data, this.state); ContactReducer(data, this.state); - this.settingsReducer.reduce(data, this.state); + this.settingsReducer.reduce(data); GroupViewReducer(data, this.state); } } diff --git a/pkg/interface/src/views/App.js b/pkg/interface/src/views/App.js index 3e1650dc3..0ba7a7079 100644 --- a/pkg/interface/src/views/App.js +++ b/pkg/interface/src/views/App.js @@ -28,19 +28,20 @@ import GlobalApi from '~/logic/api/global'; import { uxToHex } from '~/logic/lib/util'; import { foregroundFromBackground } from '~/logic/lib/sigil'; import { withLocalState } from '~/logic/state/local'; +import { withSettingsState } from '~/logic/state/settings'; -const Root = styled.div` +const Root = withSettingsState(styled.div` font-family: ${p => p.theme.fonts.sans}; height: 100%; width: 100%; padding: 0; margin: 0; - ${p => p.background?.type === 'url' ? ` - background-image: url('${p.background?.url}'); + ${p => p.display.backgroundType === 'url' ? ` + background-image: url('${p.display.background}'); background-size: cover; - ` : p.background?.type === 'color' ? ` - background-color: ${p.background.color}; + ` : p.display.backgroundType === 'color' ? ` + background-color: ${p.display.background}; ` : `background-color: ${p.theme.colors.white};` } display: flex; @@ -64,7 +65,7 @@ const Root = styled.div` border-radius: 1rem; border: 0px solid transparent; } -`; +`, ['display']); const StatusBarWithRouter = withRouter(StatusBar); @@ -148,7 +149,7 @@ class App extends React.Component { ? : null} - + diff --git a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx index 0c6924a64..be5c4f3fb 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx @@ -16,26 +16,20 @@ import { uxToHex } from "~/logic/lib/util"; import { S3State, BackgroundConfig } from "~/types"; import { BackgroundPicker, BgType } from "./BackgroundPicker"; import { BackButton } from "./BackButton"; -import useLocalState, { LocalState } from "~/logic/state/local"; +import useSettingsState, { SettingsState, selectSettingsState } from "~/logic/state/settings"; const formSchema = Yup.object().shape({ bgType: Yup.string() .oneOf(["none", "color", "url"], "invalid") .required("Required"), - bgUrl: Yup.string().url(), - bgColor: Yup.string(), - avatars: Yup.boolean(), - nicknames: Yup.boolean(), - hideGroups: Yup.boolean(), + background: Yup.string(), + }); interface FormSchema { bgType: BgType; bgColor: string | undefined; bgUrl: string | undefined; - avatars: boolean; - nicknames: boolean; - hideGroups: boolean; } interface DisplayFormProps { @@ -43,55 +37,54 @@ interface DisplayFormProps { s3: S3State; } +const settingsSel = selectSettingsState(["display"]); + export default function DisplayForm(props: DisplayFormProps) { const { api, s3 } = props; const { - hideAvatars, - hideNicknames, - background, - hideGroups, - set: setLocalState, - } = useLocalState(); + display: { + background, + backgroundType, + } + } = useSettingsState(settingsSel); + + console.log(backgroundType); let bgColor, bgUrl; - if (background?.type === "url") { - bgUrl = background.url; + if (backgroundType === "url") { + bgUrl = background; } - if (background?.type === "color") { - bgColor = background.color; + if (backgroundType === "color") { + bgColor = background; } - const bgType = background?.type || "none"; + const bgType = backgroundType || "none"; return ( { - const bgConfig: BackgroundConfig = - values.bgType === "color" - ? { type: "color", color: `#${uxToHex(values.bgColor || "0x0")}` } + onSubmit={async (values, actions) => { + let promises = [] as Promise[]; + promises.push(api.settings.putEntry('display', 'backgroundType', values.bgType)); + + promises.push( + api.settings.putEntry('display', 'background', + values.bgType === "color" + ? `#${uxToHex(values.bgColor || "0x0")}` : values.bgType === "url" - ? { type: "url", url: values.bgUrl || "" } - : undefined; + ? values.bgUrl || "" + : false + )); - setLocalState((state: LocalState) => { - state.background = bgConfig; - state.hideAvatars = values.avatars; - state.hideNicknames = values.nicknames; - state.hideGroups = values.hideGroups; + await Promise.all(promises); - }); - actions.setSubmitting(false); }} > {(props) => ( @@ -112,11 +105,6 @@ export default function DisplayForm(props: DisplayFormProps) { api={api} s3={s3} /> - From b6fb575ebc6eca4836b9dd39fb824949d19e3c2d Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Mon, 22 Feb 2021 15:25:59 +1000 Subject: [PATCH 04/48] settings: CalmEngine on settings-store --- pkg/interface/src/logic/lib/util.ts | 4 +- .../src/logic/reducers/settings-update.ts | 3 +- pkg/interface/src/logic/state/settings.tsx | 20 ++++--- .../views/apps/profile/components/Profile.tsx | 5 +- .../apps/profile/components/ViewProfile.tsx | 8 +-- .../src/views/apps/profile/profile.tsx | 6 +- .../apps/settings/components/lib/CalmPref.tsx | 58 ++++++++++--------- .../settings/components/lib/LeapSettings.tsx | 17 +----- .../apps/settings/components/settings.tsx | 23 ++++---- .../src/views/apps/settings/settings.tsx | 2 +- .../src/views/components/StatusBar.js | 11 ++-- .../landscape/components/Participants.tsx | 6 +- 12 files changed, 77 insertions(+), 86 deletions(-) diff --git a/pkg/interface/src/logic/lib/util.ts b/pkg/interface/src/logic/lib/util.ts index 670c83d43..bf198bcb9 100644 --- a/pkg/interface/src/logic/lib/util.ts +++ b/pkg/interface/src/logic/lib/util.ts @@ -3,7 +3,7 @@ import _ from "lodash"; import f, { memoize } from "lodash/fp"; import bigInt, { BigInteger } from "big-integer"; import { Contact } from '~/types'; -import useLocalState from '../state/local'; +import useSettingsState from '../state/settings'; export const MOBILE_BROWSER_REGEX = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i; @@ -377,7 +377,7 @@ export function pluralize(text: string, isPlural = false, vowel = false) { // 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 hideNicknames = typeof hide !== 'undefined' ? hide : useLocalState(state => state.hideNicknames); + const hideNicknames = typeof hide !== 'undefined' ? hide : useSettingsState(state => state.calm.hideNicknames); return !!(contact && contact.nickname && !hideNicknames); } diff --git a/pkg/interface/src/logic/reducers/settings-update.ts b/pkg/interface/src/logic/reducers/settings-update.ts index fac6515e4..2ad475e72 100644 --- a/pkg/interface/src/logic/reducers/settings-update.ts +++ b/pkg/interface/src/logic/reducers/settings-update.ts @@ -2,7 +2,6 @@ import _ from 'lodash'; import { SettingsUpdate } from '~/types/settings'; import useSettingsState, { SettingsStateZus } from "~/logic/state/settings"; import produce from 'immer'; -import { unstable_batchedUpdates } from 'react-dom'; export default class SettingsStateZusettingsReducer{ reduce(json: any) { @@ -63,7 +62,7 @@ export default class SettingsStateZusettingsReducer{ getAll(json: any, state: SettingsStateZus) { const data = _.get(json, 'all'); if(data) { - Object.assign(state, json); + _.merge(state, data); } } diff --git a/pkg/interface/src/logic/state/settings.tsx b/pkg/interface/src/logic/state/settings.tsx index bf2bfb6dc..ca1f47954 100644 --- a/pkg/interface/src/logic/state/settings.tsx +++ b/pkg/interface/src/logic/state/settings.tsx @@ -15,15 +15,17 @@ export interface SettingsState { calm: { hideNicknames: boolean; hideAvatars: boolean; - remoteContentPolicy: RemoteContentPolicy; - } + }; + remoteContentPolicy: RemoteContentPolicy; set: (fn: (state: SettingsState) => void) => void }; export type SettingsStateZus = SettingsState & State; export const selectSettingsState = - (keys: K[]) => f.pick(keys); +(keys: K[]) => f.pick(keys); + +export const selectCalmState = (s: SettingsState) => s.calm; const useSettingsState = create((set) => ({ display: { @@ -34,12 +36,12 @@ const useSettingsState = create((set) => ({ calm: { hideNicknames: false, hideAvatars: false, - remoteContentPolicy: { - imageShown: true, - oembedShown: true, - audioShown: true, - videoShown: true - } + }, + remoteContentPolicy: { + imageShown: true, + oembedShown: true, + audioShown: true, + videoShown: true }, set: (fn: (state: SettingsState) => void) => set(produce(fn)) })); diff --git a/pkg/interface/src/views/apps/profile/components/Profile.tsx b/pkg/interface/src/views/apps/profile/components/Profile.tsx index 0072b591b..8887d3360 100644 --- a/pkg/interface/src/views/apps/profile/components/Profile.tsx +++ b/pkg/interface/src/views/apps/profile/components/Profile.tsx @@ -18,12 +18,11 @@ import RichText from '~/views/components/RichText' import useLocalState from "~/logic/state/local"; import { useHistory } from "react-router-dom"; import {useTutorialModal} from "~/views/components/useTutorialModal"; +import useSettingsState, {selectCalmState} from "~/logic/state/settings"; export function Profile(props: any) { - const { hideAvatars } = useLocalState(({ hideAvatars }) => ({ - hideAvatars - })); + const { hideAvatars } = useSettingsState(selectCalmState); const history = useHistory(); if (!props.ship) { diff --git a/pkg/interface/src/views/apps/profile/components/ViewProfile.tsx b/pkg/interface/src/views/apps/profile/components/ViewProfile.tsx index cd639b135..440d35589 100644 --- a/pkg/interface/src/views/apps/profile/components/ViewProfile.tsx +++ b/pkg/interface/src/views/apps/profile/components/ViewProfile.tsx @@ -18,14 +18,14 @@ import {GroupSummary} from "~/views/landscape/components/GroupSummary"; import {MetadataUpdatePreview} from "~/types"; import {GroupLink} from "~/views/components/GroupLink"; import {lengthOrder} from "~/logic/lib/util"; -import useLocalState from "~/logic/state/local"; +import useSettingsState, {selectSettingsState} from "~/logic/state/settings"; +const settingsSel = selectSettingsState(["calm"]); + export function ViewProfile(props: any) { const history = useHistory(); - const { hideNicknames } = useLocalState(({ hideNicknames }) => ({ - hideNicknames - })); + const { calm: { hideNicknames } } = useSettingsState(settingsSel); const { api, contact, nacked, isPublic, ship, associations, groups } = props; return ( diff --git a/pkg/interface/src/views/apps/profile/profile.tsx b/pkg/interface/src/views/apps/profile/profile.tsx index 977a0975e..08e600ea5 100644 --- a/pkg/interface/src/views/apps/profile/profile.tsx +++ b/pkg/interface/src/views/apps/profile/profile.tsx @@ -7,11 +7,13 @@ import { Box, Text, Row, Col, Icon, BaseImage } from "@tlon/indigo-react"; import { uxToHex } from "~/logic/lib/util"; import { Profile } from "./components/Profile"; -import useLocalState from "~/logic/state/local"; +import useSettingsState, {SettingsState} from "~/logic/state/settings"; + +const settingsSel = (s: SettingsState) => s.calm.hideAvatars; export default function ProfileScreen(props: any) { const { dark } = props; - const hideAvatars = useLocalState(state => state.hideAvatars); + const hideAvatars = useSettingsState(settingsSel); return ( <> diff --git a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx index c36d70f6d..d3c72a8e9 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx @@ -9,7 +9,8 @@ import { import { Formik, Form, FormikHelpers } from "formik"; import * as Yup from "yup"; import { BackButton } from "./BackButton"; -import useLocalState, { selectLocalState } from "~/logic/state/local"; +import useSettingsState, {selectSettingsState} from "~/logic/state/settings"; +import GlobalApi from "~/logic/api/global"; interface FormSchema { hideAvatars: boolean; @@ -17,28 +18,28 @@ interface FormSchema { imageShown: boolean; audioShown: boolean; oembedShown: boolean; + videoShown: boolean; } -const localSelector = selectLocalState([ - "hideAvatars", - "hideNicknames", - "remoteContentPolicy", - "set", -]); +const settingsSel = selectSettingsState(["calm", "remoteContentPolicy"]); -export function CalmPrefs(props: {}) { +export function CalmPrefs(props: { + api: GlobalApi; +}) { + const { api } = props; const { - hideAvatars, - hideNicknames, - remoteContentPolicy, - set: setLocalState, - } = useLocalState(localSelector); - const { - imageShown, - videoShown, - oembedShown, - audioShown, - } = remoteContentPolicy; + calm: { + hideAvatars, + hideNicknames, + }, + remoteContentPolicy: { + imageShown, + videoShown, + oembedShown, + audioShown, + } + } = useSettingsState(settingsSel); + const initialValues: FormSchema = { hideAvatars, @@ -49,15 +50,16 @@ export function CalmPrefs(props: {}) { audioShown, }; - const onSubmit = useCallback(async (values: FormSchema, actions: FormikHelpers) => { - setLocalState(state => { - const { hideAvatars, hideNicknames, ...remote } = values; - Object.assign(state.remoteContentPolicy, remote); - state.hideNicknames = hideNicknames; - state.hideAvatars = hideAvatars; - }); - - }, [setLocalState]); + const onSubmit = useCallback(async (v: FormSchema, actions: FormikHelpers) => { + return Promise.all([ + api.settings.putEntry('calm', 'hideAvatars', v.hideAvatars), + api.settings.putEntry('calm', 'hideNicknames', v.hideNicknames), + api.settings.putEntry('remoteContentPolicy', 'imageShown', v.imageShown), + api.settings.putEntry('remoteContentPolicy', 'videoShown', v.videoShown), + api.settings.putEntry('remoteContentPolicy', 'audioShown', v.audioShown), + api.settings.putEntry('remoteContentPolicy', 'oembedShown', v.oembedShown), + ]); + }, [api]); return ( diff --git a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx index aaa87dc42..6c5020679 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx @@ -13,26 +13,13 @@ import GlobalApi from "~/logic/api/global"; import { NotificationGraphConfig, LeapCategories, leapCategories } from "~/types"; import useLocalState, {selectLocalState} from "~/logic/state/local"; -type FormSchema = { - [c in LeapCategories]: boolean; -} - -const localSelector = selectLocalState(["hideLeapCats", "set"]); export function LeapSettings(props: {}) { - const { hideLeapCats, set: setLocalState } = useLocalState(localSelector); + //TODO: arrays in settings-store? - const initialValues: FormSchema = leapCategories.reduce((acc, val, key) => { - return {...acc, [val]: hideLeapCats.findIndex(c => c === val) !== -1 }; - }, {} as FormSchema); + return null; - const onSubmit = useCallback((values: FormSchema) => { - setLocalState(state => { - state.hideLeapCats = _.keys(_.pickBy(values, v => v)) as any; - }); - - }, [setLocalState]); return ( diff --git a/pkg/interface/src/views/apps/settings/components/settings.tsx b/pkg/interface/src/views/apps/settings/components/settings.tsx index 218ea0b40..7d7be7b5b 100644 --- a/pkg/interface/src/views/apps/settings/components/settings.tsx +++ b/pkg/interface/src/views/apps/settings/components/settings.tsx @@ -55,11 +55,6 @@ export default function Settings(props: {}) { title="Notifications" description="Set notification visibility and default behaviours for groups and messaging" /> - + + {/* + - + />*/} ); diff --git a/pkg/interface/src/views/apps/settings/settings.tsx b/pkg/interface/src/views/apps/settings/settings.tsx index dde98baec..660bbe024 100644 --- a/pkg/interface/src/views/apps/settings/settings.tsx +++ b/pkg/interface/src/views/apps/settings/settings.tsx @@ -78,7 +78,7 @@ export default function SettingsScreen(props: any) { path="/~settings/calm" render={() => { return ( - + ); }} /> diff --git a/pkg/interface/src/views/components/StatusBar.js b/pkg/interface/src/views/components/StatusBar.js index 16a2e2666..c2cbc6307 100644 --- a/pkg/interface/src/views/components/StatusBar.js +++ b/pkg/interface/src/views/components/StatusBar.js @@ -21,17 +21,18 @@ import { uxToHex } from "~/logic/lib/util"; import { SetStatusBarModal } from './SetStatusBarModal'; import { useTutorialModal } from './useTutorialModal'; -import useLocalState from '~/logic/state/local'; +import useLocalState, { selectLocalState } from '~/logic/state/local'; +import useSettingsState, { selectCalmState } from '~/logic/state/settings'; +const localSel = selectLocalState(['toggleOmnibox']); + const StatusBar = (props) => { const { ourContact, api, ship } = props; const invites = [].concat(...Object.values(props.invites).map(obj => Object.values(obj))); const metaKey = (window.navigator.platform.includes('Mac')) ? '⌘' : 'Ctrl+'; - const { toggleOmnibox, hideAvatars } = - useLocalState(({ toggleOmnibox, hideAvatars }) => - ({ toggleOmnibox, hideAvatars }) - ); + const { toggleOmnibox } = useLocalState(localSel); + const { hideAvatars } = useSettingsState(selectCalmState); const color = !!ourContact ? `#${uxToHex(props.ourContact.color)}` : '#000'; const xPadding = (!hideAvatars && ourContact?.avatar) ? '0' : '2'; diff --git a/pkg/interface/src/views/landscape/components/Participants.tsx b/pkg/interface/src/views/landscape/components/Participants.tsx index 6ae9275fb..6e311bea3 100644 --- a/pkg/interface/src/views/landscape/components/Participants.tsx +++ b/pkg/interface/src/views/landscape/components/Participants.tsx @@ -31,7 +31,7 @@ import { Dropdown } from '~/views/components/Dropdown'; import GlobalApi from '~/logic/api/global'; import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction'; import styled from 'styled-components'; -import useLocalState from '~/logic/state/local'; +import useSettingsState, { selectCalmState } from '~/logic/state/settings'; const TruncText = styled(Text)` white-space: nowrap; @@ -258,9 +258,7 @@ function Participant(props: { }) { const { contact, association, group, api } = props; const { title } = association.metadata; - const { hideAvatars, hideNicknames } = useLocalState( - ({ hideAvatars, hideNicknames }) => ({ hideAvatars, hideNicknames }) - ); + const { hideAvatars, hideNicknames } = useSettingsState(selectCalmState); const color = uxToHex(contact.color); const isInvite = 'invite' in group.policy; From bcb4791d7243a5ade81e66777a918ebd92f4ccea Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Mon, 22 Feb 2021 15:46:22 +1000 Subject: [PATCH 05/48] settings: bring S3 to design --- .../src/views/apps/settings/components/lib/S3Form.tsx | 4 +++- .../src/views/apps/settings/components/settings.tsx | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx b/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx index dcbb2180c..ea37b3cef 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx @@ -14,6 +14,7 @@ import { Formik } from "formik"; import GlobalApi from "../../../../api/global"; import { BucketList } from "./BucketList"; import { S3State } from "../../../../types"; +import {BackButton} from "./BackButton"; interface FormSchema { s3bucket: string; @@ -50,6 +51,7 @@ export default function S3Form(props: S3FormProps) { return ( <> +
- + S3 Storage Setup diff --git a/pkg/interface/src/views/apps/settings/components/settings.tsx b/pkg/interface/src/views/apps/settings/components/settings.tsx index 7d7be7b5b..741b9f8c9 100644 --- a/pkg/interface/src/views/apps/settings/components/settings.tsx +++ b/pkg/interface/src/views/apps/settings/components/settings.tsx @@ -66,9 +66,9 @@ export default function Settings(props: {}) { description="Modulate vearious elements across Landscape to maximize calmness" /> Date: Mon, 22 Feb 2021 16:58:52 +1000 Subject: [PATCH 06/48] settings: add leap config --- pkg/interface/src/logic/lib/omnibox.js | 32 ++++---- pkg/interface/src/logic/state/settings.tsx | 8 +- pkg/interface/src/types/local-update.ts | 2 +- .../settings/components/lib/LeapSettings.tsx | 76 +++++++++++++++---- .../apps/settings/components/settings.tsx | 4 +- .../src/views/apps/settings/settings.tsx | 4 +- .../src/views/components/ShuffleFields.tsx | 56 ++++++++++++++ .../src/views/components/leap/Omnibox.tsx | 10 ++- 8 files changed, 153 insertions(+), 39 deletions(-) create mode 100644 pkg/interface/src/views/components/ShuffleFields.tsx diff --git a/pkg/interface/src/logic/lib/omnibox.js b/pkg/interface/src/logic/lib/omnibox.js index b775127fe..7ffbffc06 100644 --- a/pkg/interface/src/logic/lib/omnibox.js +++ b/pkg/interface/src/logic/lib/omnibox.js @@ -70,25 +70,25 @@ const appIndex = function (apps) { return applications; }; -const otherIndex = function(hide) { +const otherIndex = function(config) { const other = []; - console.log(hide); - const notBanned = (cat) => hide.findIndex(c => c === cat) === -1; - if(notBanned('mychannel')) { - other.push(result('My Channels', '/~landscape/home', 'home', null)); - } - if(notBanned('updates')) { - other.push(result('Notifications', '/~notifications', 'inbox', null)); - } - if(notBanned('profile')) { - other.push(result('Profile and Settings', `/~profile/~${window.ship}`, 'profile', null)); - } - - other.push(result('Messages', '/~landscape/messages', 'messages', null)); - if(notBanned('logout')) { - other.push(result('Log Out', '/~/logout', 'logout', null)); + const idx = { + mychannel: result('My Channels', '/~landscape/home', 'home', null), + updates: result('Notifications', '/~notifications', 'inbox', null), + profile: result('Profile and Settings', `/~profile/~${window.ship}`, 'profile', null), + messages: result('Messages', '/~landscape/messages', 'messages', null), + logout: result('Log Out', '/~/logout', 'logout', null) + }; + + console.log(config); + + for(let cat of JSON.parse(config.categories)) { + if(idx[cat]) { + other.push(idx[cat]); + } } + return other; }; diff --git a/pkg/interface/src/logic/state/settings.tsx b/pkg/interface/src/logic/state/settings.tsx index ca1f47954..97354b605 100644 --- a/pkg/interface/src/logic/state/settings.tsx +++ b/pkg/interface/src/logic/state/settings.tsx @@ -3,7 +3,7 @@ import f from 'lodash/fp'; import create, { State } from 'zustand'; import { persist } from 'zustand/middleware'; import produce from 'immer'; -import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgress, LeapCategories } from "~/types/local-update"; +import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgress, LeapCategories, leapCategories } from "~/types/local-update"; export interface SettingsState { @@ -17,6 +17,9 @@ export interface SettingsState { hideAvatars: boolean; }; remoteContentPolicy: RemoteContentPolicy; + leap: { + categories: string; + } set: (fn: (state: SettingsState) => void) => void }; @@ -43,6 +46,9 @@ const useSettingsState = create((set) => ({ audioShown: true, videoShown: true }, + leap: { + categories: JSON.stringify(leapCategories), + }, set: (fn: (state: SettingsState) => void) => set(produce(fn)) })); diff --git a/pkg/interface/src/types/local-update.ts b/pkg/interface/src/types/local-update.ts index 731bd8244..9813729f3 100644 --- a/pkg/interface/src/types/local-update.ts +++ b/pkg/interface/src/types/local-update.ts @@ -1,6 +1,6 @@ export const tutorialProgress = ['hidden', 'start', 'group-desc', 'channels', 'chat', 'link', 'publish', 'profile', 'leap', 'notifications', 'done', 'exit'] as const; -export const leapCategories = ["commands", "mychannel", "messages", "updates", "profile", "logout"] as const; +export const leapCategories = ["mychannel", "messages", "updates", "profile", "logout"] as const; export type LeapCategories = typeof leapCategories[number]; diff --git a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx index 6c5020679..ef832207f 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx @@ -1,26 +1,78 @@ import React, { useCallback } from "react"; -import _ from 'lodash'; +import _ from "lodash"; import { Col, Text, ManagedToggleSwitchField as Toggle, ManagedCheckboxField, + BaseInput, } from "@tlon/indigo-react"; -import { Form, FormikHelpers } from "formik"; +import { Form, FormikHelpers, useField, useFormikContext } from "formik"; import { FormikOnBlur } from "~/views/components/FormikOnBlur"; import { BackButton } from "./BackButton"; import GlobalApi from "~/logic/api/global"; -import { NotificationGraphConfig, LeapCategories, leapCategories } from "~/types"; -import useLocalState, {selectLocalState} from "~/logic/state/local"; +import { + NotificationGraphConfig, + LeapCategories, + leapCategories, +} from "~/types"; +import useSettingsState, { selectSettingsState } from "~/logic/state/settings"; +import { ShuffleFields } from "~/views/components/ShuffleFields"; +const labels: Record = { + mychannel: "My Channel", + updates: "Notifications", + profile: "Profile and Settings", + messages: "Messages", + logout: "Log Out", +}; -export function LeapSettings(props: {}) { +interface FormSchema { + categories: { display: boolean; category: LeapCategories }[]; +} - //TODO: arrays in settings-store? +function CategoryCheckbox(props: { index: number }) { + const { index } = props; + const { values } = useFormikContext(); + const cats = values.categories; + const catNameId = `categories[${index}].category`; + const [field] = useField(catNameId); - return null; + const { category } = cats[index]; + const label = labels[category]; + + return ( + + ); +} + +const settingsSel = selectSettingsState(["leap", "set"]); + +export function LeapSettings(props: { api: GlobalApi; }) { + const { api } = props; + const { leap, set: setSettingsState } = useSettingsState(settingsSel); + const categories = JSON.parse(leap.categories) as LeapCategories[]; + const missing = _.difference(leapCategories, categories); + console.log(categories); + + const initialValues = { + categories: [ + ...categories.map((cat) => ({ + category: cat, + display: true, + })), + ...missing.map((cat) => ({ category: cat, display: false })), + ], + }; + + const onSubmit = async (values: FormSchema) => { + const result = values.categories.reduce( + (acc, { display, category }) => (display ? [...acc, category] : acc), + [] as LeapCategories[] + ); + await api.settings.putEntry('leap', 'categories', JSON.stringify(result)); + }; - return ( @@ -38,11 +90,9 @@ export function LeapSettings(props: {}) { Customize default Leap sections - - - - - + + {(index, helpers) => } + diff --git a/pkg/interface/src/views/apps/settings/components/settings.tsx b/pkg/interface/src/views/apps/settings/components/settings.tsx index 741b9f8c9..9deeec6eb 100644 --- a/pkg/interface/src/views/apps/settings/components/settings.tsx +++ b/pkg/interface/src/views/apps/settings/components/settings.tsx @@ -85,12 +85,12 @@ export default function Settings(props: {}) { to="hosting" title="Hosting Services" description="Hosting-specific service configuration" - /> + />*/} */} + /> ); diff --git a/pkg/interface/src/views/apps/settings/settings.tsx b/pkg/interface/src/views/apps/settings/settings.tsx index 660bbe024..9b1bb2690 100644 --- a/pkg/interface/src/views/apps/settings/settings.tsx +++ b/pkg/interface/src/views/apps/settings/settings.tsx @@ -43,7 +43,7 @@ export default function SettingsScreen(props: any) { path={["/~settings/leap"]} render={() => { return ( - + ); }} /> @@ -67,7 +67,7 @@ export default function SettingsScreen(props: any) { }} /> { return ( diff --git a/pkg/interface/src/views/components/ShuffleFields.tsx b/pkg/interface/src/views/components/ShuffleFields.tsx new file mode 100644 index 000000000..b37156dc4 --- /dev/null +++ b/pkg/interface/src/views/components/ShuffleFields.tsx @@ -0,0 +1,56 @@ +import React, { ReactNode, useMemo, useCallback } from "react"; + +import { + FieldArray, + FieldArrayRenderProps, + Field, + useFormikContext, +} from "formik"; +import { Icon, Col, Row } from "@tlon/indigo-react"; + +interface ShuffleFieldsProps { + name: N; + children: (index: number, props: FieldArrayRenderProps) => ReactNode; +} + +type Value = { + [k in I]: T[]; +}; + +export function ShuffleFields>( + props: ShuffleFieldsProps +) { + const { name, children } = props; + const { values } = useFormikContext(); + const fields: T[] = useMemo(() => values[name], [values, name]); + + return ( + { + const goUp = (i: number) => () => { + if(i > 0) { + arrayHelpers.swap(i - 1, i); + } + }; + const goDown = (i: number) => () => { + if(i < fields.length - 1) { + arrayHelpers.swap(i + 1, i); + + } + }; + return ( + + {fields.map((field, i) => ( + + {children(i, arrayHelpers)} + + + + ))} + + ); + }} + /> + ); +} diff --git a/pkg/interface/src/views/components/leap/Omnibox.tsx b/pkg/interface/src/views/components/leap/Omnibox.tsx index a5a2b46f4..865001f0a 100644 --- a/pkg/interface/src/views/components/leap/Omnibox.tsx +++ b/pkg/interface/src/views/components/leap/Omnibox.tsx @@ -13,6 +13,7 @@ import defaultApps from '~/logic/lib/default-apps'; import {Associations, Contacts, Groups, Tile, Invites} from '~/types'; import {useOutsideClick} from '~/logic/lib/useOutsideClick'; import {Portal} from '../Portal'; +import useSettingsState, {SettingsState} from '~/logic/state/settings'; interface OmniboxProps { associations: Associations; @@ -28,12 +29,13 @@ interface OmniboxProps { } const SEARCHED_CATEGORIES = ['ships', 'other', 'commands', 'groups', 'subscriptions', 'apps']; -const localSelector = selectLocalState(["hideLeapCats"]); +const settingsSel = (s: SettingsState) => s.leap; export function Omnibox(props: OmniboxProps) { const location = useLocation(); const history = useHistory(); - const { hideLeapCats } = useLocalState(localSelector); + //const { hideLeapCats } = useLocalState(localSelector); + const leapConfig = useSettingsState(settingsSel); const omniboxRef = useRef(null) const inputRef = useRef(null); @@ -61,11 +63,11 @@ export function Omnibox(props: OmniboxProps) { props.tiles, selectedGroup, props.groups, - hideLeapCats, + leapConfig, ); }, [ selectedGroup, - hideLeapCats, + leapConfig, contacts, props.associations, props.groups, From 13f0ea755b309c2f892eb48405744bccd299f3ee Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 23 Feb 2021 13:32:41 +1000 Subject: [PATCH 07/48] settings: sidebar layout --- pkg/interface/src/logic/lib/omnibox.js | 3 - .../apps/settings/components/lib/CalmPref.tsx | 9 +- .../settings/components/lib/DisplayForm.tsx | 9 +- .../settings/components/lib/LeapSettings.tsx | 1 - .../components/lib/NotificationPref.tsx | 1 - .../apps/settings/components/lib/S3Form.tsx | 1 - .../apps/settings/components/lib/Security.tsx | 2 - .../src/views/apps/settings/settings.tsx | 165 +++++++++++------- .../src/views/components/ShuffleFields.tsx | 14 +- 9 files changed, 117 insertions(+), 88 deletions(-) diff --git a/pkg/interface/src/logic/lib/omnibox.js b/pkg/interface/src/logic/lib/omnibox.js index 7ffbffc06..c53a6af6e 100644 --- a/pkg/interface/src/logic/lib/omnibox.js +++ b/pkg/interface/src/logic/lib/omnibox.js @@ -80,15 +80,12 @@ const otherIndex = function(config) { logout: result('Log Out', '/~/logout', 'logout', null) }; - console.log(config); - for(let cat of JSON.parse(config.categories)) { if(idx[cat]) { other.push(idx[cat]); } } - return other; }; diff --git a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx index d3c72a8e9..5bf8a3e89 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx @@ -11,6 +11,7 @@ import * as Yup from "yup"; import { BackButton } from "./BackButton"; import useSettingsState, {selectSettingsState} from "~/logic/state/settings"; import GlobalApi from "~/logic/api/global"; +import {AsyncButton} from "~/views/components/AsyncButton"; interface FormSchema { hideAvatars: boolean; @@ -51,7 +52,7 @@ export function CalmPrefs(props: { }; const onSubmit = useCallback(async (v: FormSchema, actions: FormikHelpers) => { - return Promise.all([ + await Promise.all([ api.settings.putEntry('calm', 'hideAvatars', v.hideAvatars), api.settings.putEntry('calm', 'hideNicknames', v.hideNicknames), api.settings.putEntry('remoteContentPolicy', 'imageShown', v.imageShown), @@ -59,13 +60,13 @@ export function CalmPrefs(props: { api.settings.putEntry('remoteContentPolicy', 'audioShown', v.audioShown), api.settings.putEntry('remoteContentPolicy', 'oembedShown', v.oembedShown), ]); + actions.setStatus({ success: null }); }, [api]); return (
- CalmEngine @@ -107,9 +108,9 @@ export function CalmPrefs(props: { caption="Embedded content may contain scripts that can track you" /> - +
diff --git a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx index be5c4f3fb..381a55e46 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx @@ -17,6 +17,7 @@ import { S3State, BackgroundConfig } from "~/types"; import { BackgroundPicker, BgType } from "./BackgroundPicker"; import { BackButton } from "./BackButton"; import useSettingsState, { SettingsState, selectSettingsState } from "~/logic/state/settings"; +import {AsyncButton} from "~/views/components/AsyncButton"; const formSchema = Yup.object().shape({ bgType: Yup.string() @@ -49,7 +50,6 @@ export default function DisplayForm(props: DisplayFormProps) { } } = useSettingsState(settingsSel); - console.log(backgroundType); let bgColor, bgUrl; if (backgroundType === "url") { @@ -85,12 +85,13 @@ export default function DisplayForm(props: DisplayFormProps) { await Promise.all(promises); + actions.setStatus({ success: null }); + }} > {(props) => (
- Display Preferences @@ -105,9 +106,9 @@ export default function DisplayForm(props: DisplayFormProps) { api={api} s3={s3} /> - + )} diff --git a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx index ef832207f..ad535062e 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx @@ -75,7 +75,6 @@ export function LeapSettings(props: { api: GlobalApi; }) { return ( - Leap diff --git a/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx b/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx index cdbfecae1..e68a55f3d 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx @@ -52,7 +52,6 @@ export function NotificationPreferences(props: { return ( - Notification Preferences diff --git a/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx b/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx index ea37b3cef..f05876557 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx @@ -51,7 +51,6 @@ export default function S3Form(props: S3FormProps) { return ( <> - - Security Preferences diff --git a/pkg/interface/src/views/apps/settings/settings.tsx b/pkg/interface/src/views/apps/settings/settings.tsx index 9b1bb2690..a9b749450 100644 --- a/pkg/interface/src/views/apps/settings/settings.tsx +++ b/pkg/interface/src/views/apps/settings/settings.tsx @@ -1,17 +1,20 @@ import React, { ReactNode } from "react"; -import { Route, Link, Switch } from "react-router-dom"; +import { Route, Link, Switch, useLocation } from "react-router-dom"; import Helmet from "react-helmet"; import { Box, Text, Row, Col, Icon, BaseImage } from "@tlon/indigo-react"; import Settings from "./components/settings"; import { NotificationPreferences } from "./components/lib/NotificationPref"; -import DisplayForm from './components/lib/DisplayForm'; +import DisplayForm from "./components/lib/DisplayForm"; import S3Form from "./components/lib/S3Form"; import useLocalState from "~/logic/state/local"; -import {CalmPrefs} from "./components/lib/CalmPref"; +import { CalmPrefs } from "./components/lib/CalmPref"; import SecuritySettings from "./components/lib/Security"; -import {LeapSettings} from "./components/lib/LeapSettings"; +import { LeapSettings } from "./components/lib/LeapSettings"; +import { useHashLink } from "~/logic/lib/useHashLink"; +import { SidebarItem as BaseSidebarItem } from "~/views/landscape/components/SidebarItem"; +import { PropFunc } from "~/types"; export const Skeleton = (props: { children: ReactNode }) => ( @@ -22,81 +25,113 @@ export const Skeleton = (props: { children: ReactNode }) => ( bg="white" border={1} borderColor="washedGray" - overflowY="auto" > {props.children} ); +type ProvSideProps = "to" | "selected"; +type BaseProps = PropFunc; +function SidebarItem(props: { hash: string } & Omit) { + const { hash, icon, text, ...rest } = props; + + const to = `/~settings#${hash}`; + + const location = useLocation(); + const selected = location.hash.slice(1) === hash; + + return ( + + ); +} + +function SettingsItem(props: { hash: string; children: ReactNode }) { + const { hash, children } = props; + + return ( + + {children} + + ); +} + export default function SettingsScreen(props: any) { const { ship, dark } = props; - const hideAvatars = useLocalState((state) => state.hideAvatars); + + useHashLink(); + return ( <> Landscape - Settings - - { - return ( - - ); - }} - /> - { - return ( - - ); - }} - /> - { - return ( - - ); - }} - /> - { - return ( - - ); - }} - /> - { - return ( - - ); - }} - /> - { - return ( - - ); - }} - /> - { - return ; - }} - /> - + + + + System Preferences + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); diff --git a/pkg/interface/src/views/components/ShuffleFields.tsx b/pkg/interface/src/views/components/ShuffleFields.tsx index b37156dc4..2f991a17b 100644 --- a/pkg/interface/src/views/components/ShuffleFields.tsx +++ b/pkg/interface/src/views/components/ShuffleFields.tsx @@ -6,7 +6,7 @@ import { Field, useFormikContext, } from "formik"; -import { Icon, Col, Row } from "@tlon/indigo-react"; +import { Icon, Col, Row, Box } from "@tlon/indigo-react"; interface ShuffleFieldsProps { name: N; @@ -40,15 +40,15 @@ export function ShuffleFields>( } }; return ( - + {fields.map((field, i) => ( - + + + {children(i, arrayHelpers)} - - - + ))} - + ); }} /> From c0849f2c0bedc1135ea1c3479583787936730651 Mon Sep 17 00:00:00 2001 From: Isaac Visintainer Date: Wed, 24 Feb 2021 17:28:26 -0800 Subject: [PATCH 08/48] settings: add array type --- pkg/arvo/app/settings-store.hoon | 13 +++++++------ pkg/arvo/lib/settings.hoon | 6 ++++-- pkg/arvo/sur/settings.hoon | 10 ++++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/pkg/arvo/app/settings-store.hoon b/pkg/arvo/app/settings-store.hoon index 7410533ed..2fdc12e47 100644 --- a/pkg/arvo/app/settings-store.hoon +++ b/pkg/arvo/app/settings-store.hoon @@ -4,13 +4,12 @@ +$ card card:agent:gall +$ versioned-state $% state-0 + state-1 == -+$ state-0 - $: %0 - =settings - == ++$ state-0 [%0 settings=settings-0] ++$ state-1 [%1 =settings] -- -=| state-0 +=| state-1 =* state - :: %- agent:dbug @@ -32,8 +31,10 @@ |= =old=vase ^- (quip card _this) =/ old !<(versioned-state old-vase) + |- ?- -.old - %0 [~ this(state old)] + %0 $(old [%1 +.old]) + %1 [~ this(state old)] == :: ++ on-poke diff --git a/pkg/arvo/lib/settings.hoon b/pkg/arvo/lib/settings.hoon index 8730de6e8..edf13e8d4 100644 --- a/pkg/arvo/lib/settings.hoon +++ b/pkg/arvo/lib/settings.hoon @@ -50,7 +50,7 @@ %- pairs :~ bucket-key+s+b entry-key+s+k - value+(val v) + value+(value v) == :: ++ del-entry @@ -68,6 +68,7 @@ %s val %b val %n (numb p.val) + %a [%a (turn p.val value)] == :: ++ bucket @@ -105,7 +106,7 @@ %- ot :~ bucket-key+so entry-key+so - value+val + value+value == :: ++ del-entry @@ -121,6 +122,7 @@ %s jon %b jon %n [%n (rash p.jon dem)] + %a [%a (turn p.jon value)] == :: ++ bucket diff --git a/pkg/arvo/sur/settings.hoon b/pkg/arvo/sur/settings.hoon index c2cf521ce..67a071f7d 100644 --- a/pkg/arvo/sur/settings.hoon +++ b/pkg/arvo/sur/settings.hoon @@ -1,11 +1,21 @@ |% ++$ settings-0 (map key bucket-0) ++$ bucket-0 (map key val-0) ++$ val-0 + $% [%s p=@t] + [%b p=?] + [%n p=@] + == +:: +$ settings (map key bucket) +$ bucket (map key val) +$ key term +$ val + $~ [%n 0] $% [%s p=@t] [%b p=?] [%n p=@] + [%a p=(list val)] == +$ event $% [%put-bucket =key =bucket] From d7bbf3b54b5dfb7642224d5da3f2daf9ee26a2e5 Mon Sep 17 00:00:00 2001 From: Tyler Brown Cifu Shuster Date: Wed, 24 Feb 2021 19:54:00 -0800 Subject: [PATCH 09/48] api: brought to parity with livenet --- pkg/npm/api/contacts/index.ts | 52 +-- pkg/npm/api/contacts/lib.ts | 94 +++++ pkg/npm/api/contacts/{index.d.ts => types.ts} | 22 +- pkg/npm/api/graph/index.ts | 372 +----------------- pkg/npm/api/graph/lib.ts | 271 +++++++++++++ pkg/npm/api/graph/{index.d.ts => types.ts} | 6 +- pkg/npm/api/groups/index.d.ts | 2 - pkg/npm/api/groups/index.ts | 119 +----- pkg/npm/api/groups/lib.ts | 215 ++++++++++ pkg/npm/api/groups/types.ts | 2 + pkg/npm/api/groups/{update.d.ts => update.ts} | 36 +- pkg/npm/api/groups/{view.d.ts => view.ts} | 0 pkg/npm/api/hark/index.ts | 2 + pkg/npm/api/hark/lib.ts | 250 ++++++++++++ pkg/npm/api/hark/{index.d.ts => types.ts} | 6 +- pkg/npm/api/index.d.ts | 7 - pkg/npm/api/{index.js => index.ts} | 9 +- pkg/npm/api/invite/index.ts | 30 +- pkg/npm/api/invite/lib.ts | 28 ++ pkg/npm/api/invite/{index.d.ts => types.ts} | 16 +- pkg/npm/api/lib/index.ts | 2 + pkg/npm/api/lib/{util.ts => lib.ts} | 67 +++- pkg/npm/api/lib/{index.d.ts => types.ts} | 7 +- pkg/npm/api/metadata/index.ts | 45 +-- pkg/npm/api/metadata/lib.ts | 77 ++++ pkg/npm/api/metadata/{index.d.ts => types.ts} | 27 +- pkg/npm/api/package.json | 9 +- pkg/npm/api/s3/index.ts | 0 pkg/npm/api/s3/lib.ts | 47 +++ pkg/npm/api/s3/types.ts | 60 +++ pkg/npm/api/settings/index.ts | 2 + pkg/npm/api/settings/lib.ts | 50 +++ pkg/npm/api/settings/{index.d.ts => types.ts} | 14 +- pkg/npm/api/tsconfig.json | 36 +- 34 files changed, 1254 insertions(+), 728 deletions(-) create mode 100644 pkg/npm/api/contacts/lib.ts rename pkg/npm/api/contacts/{index.d.ts => types.ts} (69%) create mode 100644 pkg/npm/api/graph/lib.ts rename pkg/npm/api/graph/{index.d.ts => types.ts} (91%) delete mode 100644 pkg/npm/api/groups/index.d.ts create mode 100644 pkg/npm/api/groups/lib.ts create mode 100644 pkg/npm/api/groups/types.ts rename pkg/npm/api/groups/{update.d.ts => update.ts} (78%) rename pkg/npm/api/groups/{view.d.ts => view.ts} (100%) create mode 100644 pkg/npm/api/hark/lib.ts rename pkg/npm/api/hark/{index.d.ts => types.ts} (92%) delete mode 100644 pkg/npm/api/index.d.ts rename pkg/npm/api/{index.js => index.ts} (61%) create mode 100644 pkg/npm/api/invite/lib.ts rename pkg/npm/api/invite/{index.d.ts => types.ts} (72%) create mode 100644 pkg/npm/api/lib/index.ts rename pkg/npm/api/lib/{util.ts => lib.ts} (79%) rename pkg/npm/api/lib/{index.d.ts => types.ts} (95%) create mode 100644 pkg/npm/api/metadata/lib.ts rename pkg/npm/api/metadata/{index.d.ts => types.ts} (75%) create mode 100644 pkg/npm/api/s3/index.ts create mode 100644 pkg/npm/api/s3/lib.ts create mode 100644 pkg/npm/api/s3/types.ts create mode 100644 pkg/npm/api/settings/lib.ts rename pkg/npm/api/settings/{index.d.ts => types.ts} (76%) diff --git a/pkg/npm/api/contacts/index.ts b/pkg/npm/api/contacts/index.ts index ca5ba9d01..341e81711 100644 --- a/pkg/npm/api/contacts/index.ts +++ b/pkg/npm/api/contacts/index.ts @@ -1,50 +1,2 @@ -import { Enc, Path, Patp, Poke } from ".."; -import { - Contact, - ContactUpdateAdd, - ContactUpdateEdit, - ContactUpdateRemove, - ContactEditField, - ContactShare, - ContactUpdate, -} from "./index.d"; - -export const storeAction = (data: T): Poke => ({ - app: "contact-store", - mark: "contact-action", - json: data, -}); - -export const add = (ship: Patp, contact: Contact): Poke => { - contact["last-updated"] = Date.now(); - - return storeAction({ - add: { ship, contact }, - }); -}; - -export const remove = (ship: Patp): Poke => - storeAction({ - remove: { ship }, - }); - -export const share = (recipient: Patp): Poke => ({ - app: "contact-push-hook", - mark: "contact-action", - json: { share: recipient }, -}); - -export const edit = ( - path: Path, - ship: Patp, - editField: ContactEditField -): Poke => - storeAction({ - edit: { - path, - ship, - "edit-field": editField, - timestamp: Date.now(), - }, - }); - +export * from './types'; +export * from './lib'; \ No newline at end of file diff --git a/pkg/npm/api/contacts/lib.ts b/pkg/npm/api/contacts/lib.ts new file mode 100644 index 000000000..c7bc1d7d7 --- /dev/null +++ b/pkg/npm/api/contacts/lib.ts @@ -0,0 +1,94 @@ + +import { Path, Patp, Poke, resourceAsPath } from "../lib"; +import { + Contact, + ContactUpdateAdd, + ContactUpdateEdit, + ContactUpdateRemove, + ContactEditField, + ContactShare, + ContactUpdate, + ContactUpdateAllowShips, + ContactUpdateAllowGroup, + ContactUpdateSetPublic, +} from "./types"; + +const storeAction = (data: T): Poke => ({ + app: "contact-store", + mark: "contact-action", + json: data, +}); + +export { storeAction as contactStoreAction }; + +export const addContact = (ship: Patp, contact: Contact): Poke => { + contact["last-updated"] = Date.now(); + + return storeAction({ + add: { ship, contact }, + }); +}; + +export const removeContact = (ship: Patp): Poke => + storeAction({ + remove: { ship }, + }); + +export const share = (recipient: Patp): Poke => ({ + app: "contact-push-hook", + mark: "contact-action", + json: { share: recipient }, +}); + +export const editContact = ( + ship: Patp, + editField: ContactEditField +): Poke => + storeAction({ + edit: { + ship, + "edit-field": editField, + timestamp: Date.now(), + }, + }); + +export const allowShips = ( + ships: Patp[] +): Poke => storeAction({ + allow: { + ships + } +}); + +export const allowGroup = ( + ship: string, + name: string +): Poke => storeAction({ + allow: { + group: resourceAsPath({ ship, name }) + } +}); + +export const setPublic = ( + setPublic: any +): Poke => { + return storeAction({ + 'set-public': setPublic + }); +} + +export const retrieve = ( + ship: string +) => { + const resource = { ship, name: '' }; + return { + app: 'contact-pull-hook', + mark: 'pull-hook-action', + json: { + add: { + resource, + ship + } + } + }; +} diff --git a/pkg/npm/api/contacts/index.d.ts b/pkg/npm/api/contacts/types.ts similarity index 69% rename from pkg/npm/api/contacts/index.d.ts rename to pkg/npm/api/contacts/types.ts index bbb037c89..dffacec76 100644 --- a/pkg/npm/api/contacts/index.d.ts +++ b/pkg/npm/api/contacts/types.ts @@ -1,47 +1,49 @@ import { Path, Patp } from ".."; -import {Resource} from "../groups/update.d"; +import { Resource } from "../groups"; export type ContactUpdate = | ContactUpdateAdd | ContactUpdateRemove | ContactUpdateEdit | ContactUpdateInitial + | ContactUpdateAllowGroup + | ContactUpdateAllowShips + | ContactUpdateSetPublic; -interface ContactUpdateAdd { + export interface ContactUpdateAdd { add: { ship: Patp; contact: Contact; }; } -interface ContactUpdateRemove { +export interface ContactUpdateRemove { remove: { ship: Patp; }; } -interface ContactUpdateEdit { +export interface ContactUpdateEdit { edit: { - path: Path; ship: Patp; "edit-field": ContactEditField; timestamp: number; }; } -interface ContactUpdateAllowShips { +export interface ContactUpdateAllowShips { allow: { ships: Patp[]; } } -interface ContactUpdateAllowGroup { +export interface ContactUpdateAllowGroup { allow: { group: Path; } } -interface ContactUpdateSetPublic { +export interface ContactUpdateSetPublic { 'set-public': boolean; } @@ -49,7 +51,7 @@ export interface ContactShare { share: Patp; } -interface ContactUpdateInitial { +export interface ContactUpdateInitial { initial: Rolodex; } @@ -57,6 +59,8 @@ export type Rolodex = { [p in Patp]: Contact; }; +export type Contacts = Rolodex; + export interface Contact { nickname: string; bio: string; diff --git a/pkg/npm/api/graph/index.ts b/pkg/npm/api/graph/index.ts index 82d4cc986..4fed660f4 100644 --- a/pkg/npm/api/graph/index.ts +++ b/pkg/npm/api/graph/index.ts @@ -1,370 +1,2 @@ -import _ from 'lodash'; -import { PatpNoSig, Patp, Poke, Thread, Path, Enc } from '..'; -import { Content, GraphNode, Post, GraphNodePoke, GraphChildrenPoke } from './index.d'; -import { deSig, unixToDa } from '../lib/util'; -import { makeResource, resourceFromPath } from '../groups/index'; -import { GroupPolicy } from '../groups/update.d'; - -export const createBlankNodeWithChildPost = ( - ship: PatpNoSig, - parentIndex: string = '', - childIndex: string = '', - contents: Content[] -): GraphNodePoke => { - const date = unixToDa(Date.now()).toString(); - const nodeIndex = parentIndex + '/' + date; - - const childGraph: GraphChildrenPoke = {}; - childGraph[childIndex] = { - post: { - author: `~${ship}`, - index: nodeIndex + '/' + childIndex, - 'time-sent': Date.now(), - contents, - hash: null, - signatures: [] - }, - children: null - }; - - return { - post: { - author: `~${ship}`, - index: nodeIndex, - 'time-sent': Date.now(), - contents: [], - hash: null, - signatures: [] - }, - children: childGraph - }; -}; - -function markPending(nodes: any) { - _.forEach(nodes, node => { - node.post.author = deSig(node.post.author); - node.post.pending = true; - markPending(node.children || {}); - }); -} - -export const createPost = ( - ship: PatpNoSig, - contents: Content[], - parentIndex: string = '', - childIndex:string = 'DATE_PLACEHOLDER' -): Post => { - if (childIndex === 'DATE_PLACEHOLDER') { - childIndex = unixToDa(Date.now()).toString(); - } - return { - author: `~${ship}`, - index: parentIndex + '/' + childIndex, - 'time-sent': Date.now(), - contents, - hash: null, - signatures: [] - }; -}; - -function moduleToMark(mod: string): string | undefined { - if(mod === 'link') { - return 'graph-validator-link'; - } - if(mod === 'publish') { - return 'graph-validator-publish'; - } - if(mod === 'chat') { - return 'graph-validator-chat'; - } - return undefined; -} - -const storeAction = (data: T): Poke => ({ - app: 'graph-store', - mark: 'graph-update', - json: data -}); - -export { storeAction as graphStoreAction }; - -const viewAction = (threadName: string, action: T): Thread => ({ - inputMark: 'graph-view-action', - outputMark: 'json', - threadName, - body: action -}); - -export { viewAction as graphViewAction }; - -const hookAction = (data: T): Poke => ({ - app: 'graph-push-hook', - mark: 'graph-update', - json: data -}); - -export { hookAction as graphHookAction }; - - -export const createManagedGraph = ( - ship: PatpNoSig, - name: string, - title: string, - description: string, - group: Path, - mod: string -): Thread => { - const associated = { group: resourceFromPath(group) }; - const resource = makeResource(`~${ship}`, name); - - return viewAction('graph-create', { - create: { - resource, - title, - description, - associated, - module: mod, - mark: moduleToMark(mod) - } - }); -} - -export const createUnmanagedGraph = ( - ship: PatpNoSig, - name: string, - title: string, - description: string, - policy: Enc, - mod: string -): Thread => { - const resource = makeResource(`~${ship}`, name); - - return viewAction('graph-create', { - create: { - resource, - title, - description, - associated: { policy }, - module: mod, - mark: moduleToMark(mod) - } - }); -} - -export const joinGraph = ( - ship: Patp, - name: string -): Thread => { - const resource = makeResource(ship, name); - return viewAction('graph-join', { - join: { - resource, - ship, - } - }); -} - -export const deleteGraph = ( - ship: PatpNoSig, - name: string -): Thread => { - const resource = makeResource(`~${ship}`, name); - return viewAction('graph-delete', { - "delete": { - resource - } - }); -} - -export const leaveGraph = ( - ship: Patp, - name: string -): Thread => { - const resource = makeResource(ship, name); - return viewAction('graph-leave', { - "leave": { - resource - } - }); -} - -export const groupifyGraph = ( - ship: Patp, - name: string, - toPath?: string -): Thread => { - const resource = makeResource(ship, name); - const to = toPath && resourceFromPath(toPath); - - return viewAction('graph-groupify', { - groupify: { - resource, - to - } - }); -} - -export const evalCord = ( - cord: string -): Thread => { - return ({ - inputMark: 'graph-view-action', - outputMark: 'tang', - threadName: 'graph-eval', - body: { - eval: cord - } - }); -} - -export const addGraph = ( - ship: Patp, - name: string, - graph: any, - mark: any -): Poke => { - return storeAction({ - 'add-graph': { - resource: { ship, name }, - graph, - mark - } - }); -} - -export const addPost = ( - ship: Patp, - name: string, - post: Post -) => { - let nodes = {}; - nodes[post.index] = { - post, - children: null - }; - return addNodes(ship, name, nodes); -} - -export const addNode = ( - ship: Patp, - name: string, - node: GraphNode -) => { - let nodes = {}; - nodes[node.post.index] = node; - - return addNodes(ship, name, nodes); -} - -export const addNodes = ( - ship: Patp, - name: string, - nodes: Object -): Poke => { - const action = { - 'add-nodes': { - resource: { ship, name }, - nodes - } - }; - - markPending(action['add-nodes'].nodes); - action['add-nodes'].resource.ship = action['add-nodes'].resource.ship.slice(1); - // this.store.handleEvent({ data: { 'graph-update': action } });// TODO address this.store - return hookAction(action); -} - -export const removeNodes = ( - ship: Patp, - name: string, - indices: string[] -): Poke => { - return hookAction({ - 'remove-nodes': { - resource: { ship, name }, - indices - } - }); -} - -// TODO these abominations -// getKeys() { -// return this.scry('graph-store', '/keys') -// .then((keys) => { -// this.store.handleEvent({ -// data: keys -// }); -// }); -// } - -// getTags() { -// return this.scry('graph-store', '/tags') -// .then((tags) => { -// this.store.handleEvent({ -// data: tags -// }); -// }); -// } - -// getTagQueries() { -// return this.scry('graph-store', '/tag-queries') -// .then((tagQueries) => { -// this.store.handleEvent({ -// data: tagQueries -// }); -// }); -// } - -// getGraph(ship: string, resource: string) { -// return this.scry('graph-store', `/graph/${ship}/${resource}`) -// .then((graph) => { -// this.store.handleEvent({ -// data: graph -// }); -// }); -// } - -// async getNewest(ship: string, resource: string, count: number, index = '') { -// const data = await this.scry('graph-store', `/newest/${ship}/${resource}/${count}${index}`); -// this.store.handleEvent({ data }); -// } - -// async getOlderSiblings(ship: string, resource: string, count: number, index = '') { -// const idx = index.split('/').map(decToUd).join('/'); -// const data = await this.scry('graph-store', -// `/node-siblings/older/${ship}/${resource}/${count}${idx}` -// ); -// this.store.handleEvent({ data }); -// } - -// async getYoungerSiblings(ship: string, resource: string, count: number, index = '') { -// const idx = index.split('/').map(decToUd).join('/'); -// const data = await this.scry('graph-store', -// `/node-siblings/younger/${ship}/${resource}/${count}${idx}` -// ); -// this.store.handleEvent({ data }); -// } - - -// getGraphSubset(ship: string, resource: string, start: string, end: string) { -// return this.scry( -// 'graph-store', -// `/graph-subset/${ship}/${resource}/${end}/${start}` -// ).then((subset) => { -// this.store.handleEvent({ -// data: subset -// }); -// }); -// } - -// getNode(ship: string, resource: string, index: string) { -// const idx = index.split('/').map(numToUd).join('/'); -// return this.scry( -// 'graph-store', -// `/node/${ship}/${resource}${idx}` -// ).then((node) => { -// this.store.handleEvent({ -// data: node -// }); -// }); -// } +export * from './lib'; +export * from './types'; \ No newline at end of file diff --git a/pkg/npm/api/graph/lib.ts b/pkg/npm/api/graph/lib.ts new file mode 100644 index 000000000..d88ba3cba --- /dev/null +++ b/pkg/npm/api/graph/lib.ts @@ -0,0 +1,271 @@ +import _ from 'lodash'; +import { GroupPolicy, makeResource, resourceFromPath } from '../groups'; + +import { deSig, unixToDa } from '../lib'; +import { Enc, Path, Patp, PatpNoSig, Poke, Thread } from '../lib/types'; +import { Content, GraphChildrenPoke, GraphNode, GraphNodePoke, Post } from './types'; + +export const createBlankNodeWithChildPost = ( + ship: PatpNoSig, + parentIndex: string = '', + childIndex: string = '', + contents: Content[] +): any => { // TODO should be GraphNode + const date = unixToDa(Date.now()).toString(); + const nodeIndex = parentIndex + '/' + date; + + const childGraph: GraphChildrenPoke = {}; + childGraph[childIndex] = { + post: { + author: `~${ship}`, + index: nodeIndex + '/' + childIndex, + 'time-sent': Date.now(), + contents, + hash: null, + signatures: [] + }, + children: null + }; + + return { + post: { + author: `~${ship}`, + index: nodeIndex, + 'time-sent': Date.now(), + contents: [], + hash: null, + signatures: [] + }, + children: childGraph + }; +}; + +export const markPending = (nodes: any): void => { + _.forEach(nodes, node => { + node.post.author = deSig(node.post.author); + node.post.pending = true; + markPending(node.children || {}); + }); +}; + +export const createPost = ( + ship: PatpNoSig, + contents: Content[], + parentIndex: string = '', + childIndex:string = 'DATE_PLACEHOLDER' +): Post => { + if (childIndex === 'DATE_PLACEHOLDER') { + childIndex = unixToDa(Date.now()).toString(); + } + return { + author: `~${ship}`, + index: parentIndex + '/' + childIndex, + 'time-sent': Date.now(), + contents, + hash: null, + signatures: [] + }; +}; + +function moduleToMark(mod: string): string | undefined { + if(mod === 'link') { + return 'graph-validator-link'; + } + if(mod === 'publish') { + return 'graph-validator-publish'; + } + if(mod === 'chat') { + return 'graph-validator-chat'; + } + return undefined; +} + +const storeAction = (data: T): Poke => ({ + app: 'graph-store', + mark: 'graph-update', + json: data +}); + +export { storeAction as graphStoreAction }; + +const viewAction = (threadName: string, action: T): Thread => ({ + inputMark: 'graph-view-action', + outputMark: 'json', + threadName, + body: action +}); + +export { viewAction as graphViewAction }; + +const hookAction = (data: T): Poke => ({ + app: 'graph-push-hook', + mark: 'graph-update', + json: data +}); + +export { hookAction as graphHookAction }; + + +export const createManagedGraph = ( + ship: PatpNoSig, + name: string, + title: string, + description: string, + group: Path, + mod: string +): Thread => { + const associated = { group: resourceFromPath(group) }; + const resource = makeResource(`~${ship}`, name); + + return viewAction('graph-create', { + create: { + resource, + title, + description, + associated, + module: mod, + mark: moduleToMark(mod) + } + }); +} + +export const createUnmanagedGraph = ( + ship: PatpNoSig, + name: string, + title: string, + description: string, + policy: Enc, + mod: string +): Thread => viewAction('graph-create', { + create: { + resource: makeResource(`~${ship}`, name), + title, + description, + associated: { policy }, + module: mod, + mark: moduleToMark(mod) + } +}); + +export const joinGraph = ( + ship: Patp, + name: string +): Thread => viewAction('graph-join', { + join: { + resource: makeResource(ship, name), + ship, + } +}); + +export const deleteGraph = ( + ship: PatpNoSig, + name: string +): Thread => viewAction('graph-delete', { + "delete": { + resource: makeResource(`~${ship}`, name) + } +}); + +export const leaveGraph = ( + ship: Patp, + name: string +): Thread => viewAction('graph-leave', { + "leave": { + resource: makeResource(ship, name) + } +}); + +export const groupifyGraph = ( + ship: Patp, + name: string, + toPath?: string +): Thread => { + const resource = makeResource(ship, name); + const to = toPath && resourceFromPath(toPath); + + return viewAction('graph-groupify', { + groupify: { + resource, + to + } + }); +} + +export const evalCord = ( + cord: string +): Thread => { + return ({ + inputMark: 'graph-view-action', + outputMark: 'tang', + threadName: 'graph-eval', + body: { + eval: cord + } + }); +} + +export const addGraph = ( + ship: Patp, + name: string, + graph: any, + mark: any +): Poke => { + return storeAction({ + 'add-graph': { + resource: { ship, name }, + graph, + mark + } + }); +} + +export const addNodes = ( + ship: Patp, + name: string, + nodes: Object +): Poke => { + const action = { + 'add-nodes': { + resource: { ship, name }, + nodes + } + }; + + return hookAction(action); +}; + +export const addPost = ( + ship: Patp, + name: string, + post: Post +) => { + let nodes: Record = {}; + nodes[post.index] = { + post, + children: null + }; + return addNodes(ship, name, nodes); +} + +export const addNode = ( + ship: Patp, + name: string, + node: GraphNode +): Poke => { + let nodes: Record = {}; + nodes[node.post.index] = node; + + return addNodes(ship, name, nodes); +} + + +export const removeNodes = ( + ship: Patp, + name: string, + indices: string[] +): Poke => hookAction({ + 'remove-nodes': { + resource: { ship, name }, + indices + } +}); diff --git a/pkg/npm/api/graph/index.d.ts b/pkg/npm/api/graph/types.ts similarity index 91% rename from pkg/npm/api/graph/index.d.ts rename to pkg/npm/api/graph/types.ts index b98872e81..8bf8edc86 100644 --- a/pkg/npm/api/graph/index.d.ts +++ b/pkg/npm/api/graph/types.ts @@ -9,8 +9,8 @@ export interface UrlContent { } export interface CodeContent { code: { - expresssion: string; - output: string | undefined; + expression: string; + output: string[] | undefined; } } @@ -47,7 +47,7 @@ export interface GraphChildrenPoke { } export interface GraphNode { - children: Graph; + children: Graph | null; post: Post; } diff --git a/pkg/npm/api/groups/index.d.ts b/pkg/npm/api/groups/index.d.ts deleted file mode 100644 index 76567715c..000000000 --- a/pkg/npm/api/groups/index.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './update.d'; -export * from './view.d'; \ No newline at end of file diff --git a/pkg/npm/api/groups/index.ts b/pkg/npm/api/groups/index.ts index 02122d3e5..341e81711 100644 --- a/pkg/npm/api/groups/index.ts +++ b/pkg/npm/api/groups/index.ts @@ -1,117 +1,2 @@ -import { Enc, Path, Patp, PatpNoSig, Poke } from ".."; -import { - Group, - GroupAction, - GroupPolicyDiff, - GroupUpdateAddMembers, - GroupUpdateAddTag, - GroupUpdateChangePolicy, - GroupUpdateRemoveGroup, - GroupUpdateRemoveMembers, - GroupUpdateRemoveTag, - Resource, - Tag -} from "./index.d"; -import { GroupPolicy } from "./update"; - -export const proxyAction = (data: T): Poke => ({ - app: 'group-push-hook', - mark: 'group-update', - json: data -}); - -export const storeAction = (data: T): Poke => ({ - app: 'group-store', - mark: 'group-update', - json: data -}); - -export const remove = ( - resource: Resource, - ships: PatpNoSig[] -): Poke => proxyAction({ - removeMembers: { - resource, - ships - } -}); - -export const addTag = ( - resource: Resource, - tag: Tag, - ships: Patp[] -): Poke => proxyAction({ - addTag: { - resource, - tag, - ships - } -}); - -export const removeTag = ( - tag: Tag, - resource: Resource, - ships: PatpNoSig[] -): Poke => proxyAction({ - removeTag: { - tag, - resource, - ships - } -}); - -export const add = ( - resource: Resource, - ships: PatpNoSig[] -): Poke => proxyAction({ - addMembers: { - resource, - ships - } -}); - -export const removeGroup = ( - resource: Resource -): Poke => storeAction({ - removeGroup: { - resource - } -}); - -export const changePolicy = ( - resource: Resource, - diff: GroupPolicyDiff -): Poke => proxyAction({ - changePolicy: { - resource, - diff - } -}); - -export const roleTags = ['janitor', 'moderator', 'admin']; -// TODO make this type better? - -export function roleForShip(group: Group, ship: PatpNoSig): string | undefined { - return roleTags.reduce((currRole, role) => { - const roleShips = group?.tags?.role?.[role]; - return roleShips && roleShips.has(ship) ? role : currRole; - }, undefined as string | undefined); -} - -export function resourceFromPath(path: Path): Resource { - const [, , ship, name] = path.split('/'); - return { ship, name } -} - -export function makeResource(ship: string, name:string) { - return { ship, name }; -} - -export const groupBunts = { - group: (): Group => ({ members: new Set(), tags: { role: {} }, hidden: false, policy: groupBunts.policy() }), - policy: (): GroupPolicy => ({ open: { banned: new Set(), banRanks: new Set() } }) -}; - -export const joinError = ['no-perms', 'strange'] as const; -export const joinResult = ['done', ...joinError] as const; -export const joinProgress = ['start', 'added', ...joinResult] as const; +export * from './types'; +export * from './lib'; \ No newline at end of file diff --git a/pkg/npm/api/groups/lib.ts b/pkg/npm/api/groups/lib.ts new file mode 100644 index 000000000..4cdd59f87 --- /dev/null +++ b/pkg/npm/api/groups/lib.ts @@ -0,0 +1,215 @@ +import _ from 'lodash'; + +import { Enc, Path, Patp, PatpNoSig, Poke, Thread } from '../lib/types'; +import { Group, GroupPolicy, GroupPolicyDiff, GroupUpdateAddMembers, GroupUpdateAddTag, GroupUpdateChangePolicy, GroupUpdateRemoveGroup, GroupUpdateRemoveMembers, GroupUpdateRemoveTag, Resource, RoleTags, Tag } from './types'; +import { GroupUpdate } from './update'; + +export const proxyAction = (data: T): Poke => ({ + app: 'group-push-hook', + mark: 'group-update', + json: data +}); + +const storeAction = (data: T): Poke => ({ + app: 'group-store', + mark: 'group-update', + json: data +}); + +export { storeAction as groupStoreAction }; + +const viewAction = (data: T): Poke => ({ + app: 'group-view', + mark: 'group-view-action', + json: data +}); + +export { viewAction as groupViewAction }; + +export const viewThread = (thread: string, action: T): Thread => ({ + inputMark: 'group-view-action', + outputMark: 'json', + threadName: thread, + body: action +}); + +export const removeMembers = ( + resource: Resource, + ships: PatpNoSig[] +): Poke => proxyAction({ + removeMembers: { + resource, + ships + } +}); + +export const addTag = ( + resource: Resource, + tag: Tag, + ships: Patp[] +): Poke => proxyAction({ + addTag: { + resource, + tag, + ships + } +}); + +export const removeTag = ( + tag: Tag, + resource: Resource, + ships: PatpNoSig[] +): Poke => proxyAction({ + removeTag: { + tag, + resource, + ships + } +}); + +export const addMembers = ( + resource: Resource, + ships: PatpNoSig[] +): Poke => proxyAction({ + addMembers: { + resource, + ships + } +}); + +export const removeGroup = ( + resource: Resource +): Poke => storeAction({ + removeGroup: { + resource + } +}); + +export const changePolicy = ( + resource: Resource, + diff: GroupPolicyDiff +): Poke => proxyAction({ + changePolicy: { + resource, + diff + } +}); + +export const join = ( + ship: string, + name: string +): Poke => viewAction({ + join: { + resource: makeResource(ship, name), + ship + } +}); + +export const createGroup = ( + name: string, + policy: Enc, + title: string, + description: string +): Thread => viewThread('group-create', { + create: { + name, + policy, + title, + description + } +}); + +export const deleteGroup = ( + ship: string, + name: string +): Thread => viewThread('group-delete', { + remove: makeResource(ship, name) +}); + +export const leaveGroup = ( + ship: string, + name: string +): Thread => viewThread('group-leave', { + leave: makeResource(ship, name) +}); + +export const invite = ( + ship: string, + name: string, + ships: Patp[], + description: string +): Thread => viewThread('group-invite', { + invite: { + resource: makeResource(ship, name), + ships, + description + } +}); + +export const roleTags = ['janitor', 'moderator', 'admin']; +// TODO make this type better? + +export const groupBunts = { + group: (): Group => ({ members: new Set(), tags: { role: {} }, hidden: false, policy: groupBunts.policy() }), + policy: (): GroupPolicy => ({ open: { banned: new Set(), banRanks: new Set() } }) +}; + +export const joinError = ['no-perms', 'strange'] as const; +export const joinResult = ['done', ...joinError] as const; +export const joinProgress = ['start', 'added', ...joinResult] as const; + +export const roleForShip = ( + group: Group, + ship: PatpNoSig +): RoleTags | undefined => { + return roleTags.reduce((currRole, role) => { + const roleShips = group?.tags?.role?.[role]; + return roleShips && roleShips.has(ship) ? role : currRole; + }, undefined as RoleTags | undefined); +} + +export const resourceFromPath = (path: Path): Resource => { + const [, , ship, name] = path.split('/'); + return { ship, name }; +} + +export const makeResource = (ship: string, name: string) => { + return { ship, name }; +} + +export const isWriter = (group: Group, resource: string, ship: string) => { + const writers: Set | undefined = _.get( + group, + ['tags', 'graph', resource, 'writers'], + undefined + ); + const admins = group?.tags?.role?.admin ?? new Set(); + if (_.isUndefined(writers)) { + return true; + } else { + return writers.has(ship) || admins.has(ship); + } +} + +export const isChannelAdmin = ( + group: Group, + resource: string, + ship: string +): boolean => { + const role = roleForShip(group, ship.slice(1)); + + return ( + isHost(resource, ship) || + role === 'admin' || + role === 'moderator' + ); +} + +export const isHost = ( + resource: string, + ship: string +): boolean => { + const [, , host] = resource.split('/'); + + return ship === host; +} diff --git a/pkg/npm/api/groups/types.ts b/pkg/npm/api/groups/types.ts new file mode 100644 index 000000000..6197c4f91 --- /dev/null +++ b/pkg/npm/api/groups/types.ts @@ -0,0 +1,2 @@ +export * from './update'; +export * from './view'; \ No newline at end of file diff --git a/pkg/npm/api/groups/update.d.ts b/pkg/npm/api/groups/update.ts similarity index 78% rename from pkg/npm/api/groups/update.d.ts rename to pkg/npm/api/groups/update.ts index 00d3ac340..f4747af67 100644 --- a/pkg/npm/api/groups/update.d.ts +++ b/pkg/npm/api/groups/update.ts @@ -38,33 +38,33 @@ export type OpenPolicyDiff = | AllowShipsDiff | BanShipsDiff; -interface AllowRanksDiff { +export interface AllowRanksDiff { allowRanks: ShipRank[]; } -interface BanRanksDiff { +export interface BanRanksDiff { banRanks: ShipRank[]; } -interface AllowShipsDiff { +export interface AllowShipsDiff { allowShips: PatpNoSig[]; } -interface BanShipsDiff { +export interface BanShipsDiff { banShips: PatpNoSig[]; } export type InvitePolicyDiff = AddInvitesDiff | RemoveInvitesDiff; -interface AddInvitesDiff { +export interface AddInvitesDiff { addInvites: PatpNoSig[]; } -interface RemoveInvitesDiff { +export interface RemoveInvitesDiff { removeInvites: PatpNoSig[]; } -interface ReplacePolicyDiff { +export interface ReplacePolicyDiff { replace: GroupPolicy; } @@ -75,7 +75,7 @@ export type GroupPolicyDiff = export type GroupPolicy = OpenPolicy | InvitePolicy; -interface TaggedShips { +export interface TaggedShips { [tag: string]: Set; } @@ -95,11 +95,11 @@ export type Groups = { [p in Path]: Group; }; -interface GroupUpdateInitial { +export interface GroupUpdateInitial { initial: Enc; } -interface GroupUpdateAddGroup { +export interface GroupUpdateAddGroup { addGroup: { resource: Resource; policy: Enc; @@ -107,21 +107,21 @@ interface GroupUpdateAddGroup { }; } -interface GroupUpdateAddMembers { +export interface GroupUpdateAddMembers { addMembers: { ships: PatpNoSig[]; resource: Resource; }; } -interface GroupUpdateRemoveMembers { +export interface GroupUpdateRemoveMembers { removeMembers: { ships: PatpNoSig[]; resource: Resource; }; } -interface GroupUpdateAddTag { +export interface GroupUpdateAddTag { addTag: { tag: Tag; resource: Resource; @@ -129,7 +129,7 @@ interface GroupUpdateAddTag { }; } -interface GroupUpdateRemoveTag { +export interface GroupUpdateRemoveTag { removeTag: { tag: Tag; resource: Resource; @@ -137,23 +137,23 @@ interface GroupUpdateRemoveTag { }; } -interface GroupUpdateChangePolicy { +export interface GroupUpdateChangePolicy { changePolicy: { resource: Resource; diff: GroupPolicyDiff }; } -interface GroupUpdateRemoveGroup { +export interface GroupUpdateRemoveGroup { removeGroup: { resource: Resource; }; } -interface GroupUpdateExpose { +export interface GroupUpdateExpose { expose: { resource: Resource; }; } -interface GroupUpdateInitialGroup { +export interface GroupUpdateInitialGroup { initialGroup: { resource: Resource; group: Enc; diff --git a/pkg/npm/api/groups/view.d.ts b/pkg/npm/api/groups/view.ts similarity index 100% rename from pkg/npm/api/groups/view.d.ts rename to pkg/npm/api/groups/view.ts diff --git a/pkg/npm/api/hark/index.ts b/pkg/npm/api/hark/index.ts index e69de29bb..341e81711 100644 --- a/pkg/npm/api/hark/index.ts +++ b/pkg/npm/api/hark/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export * from './lib'; \ No newline at end of file diff --git a/pkg/npm/api/hark/lib.ts b/pkg/npm/api/hark/lib.ts new file mode 100644 index 000000000..2c86e169e --- /dev/null +++ b/pkg/npm/api/hark/lib.ts @@ -0,0 +1,250 @@ +import f from 'lodash/fp'; +import bigInt, { BigInteger } from 'big-integer'; + +import { Poke } from '../lib/types'; +import { GraphNotifDescription, GraphNotificationContents, GraphNotifIndex, IndexedNotification, NotifIndex, Unreads } from './types'; +import { decToUd } from '../lib'; +import { Association } from '../metadata/types'; + +export const harkAction = (data: T): Poke => ({ + app: 'hark-store', + mark: 'hark-action', + json: data +}); + +const graphHookAction = (data: T): Poke => ({ + app: 'hark-graph-hook', + mark: 'hark-graph-hook-action', + json: data +}); + +export { graphHookAction as harkGraphHookAction }; + +const groupHookAction = (data: T): Poke => ({ + app: 'hark-group-hook', + mark: 'hark-group-hook-action', + json: data +}); + +export { groupHookAction as harkGroupHookAction }; + +export const actOnNotification = ( + frond: string, + intTime: BigInteger, + index: NotifIndex +): Poke => harkAction({ + [frond]: { + time: decToUd(intTime.toString()), + index + } +}); + +export const getParentIndex = ( + idx: GraphNotifIndex, + contents: GraphNotificationContents +): string | undefined => { + const origIndex = contents[0].index.slice(1).split('/'); + const ret = (i: string[]) => `/${i.join('/')}`; + switch (idx.description) { + case 'link': + return '/'; + case 'comment': + return ret(origIndex.slice(0, 1)); + case 'note': + return '/'; + case 'mention': + return undefined; + default: + return undefined; + } +} + +export const setMentions = ( + mentions: boolean +): Poke => graphHookAction({ + 'set-mentions': mentions +}); + +export const setWatchOnSelf = ( + watchSelf: boolean +): Poke => graphHookAction({ + 'set-watch-on-self': watchSelf +}); + +export const setDoNotDisturb = ( + dnd: boolean +): Poke => harkAction({ + 'set-dnd': dnd +}); + +export const archive = ( + time: BigInteger, + index: NotifIndex +): Poke => actOnNotification('archive', time, index); + +export const read = ( + time: BigInteger, + index: NotifIndex +): Poke => actOnNotification('read-note', time, index); + +export const readIndex = ( + index: NotifIndex +): Poke => harkAction({ + 'read-index': index +}); + +export const unread = ( + time: BigInteger, + index: NotifIndex +): Poke => actOnNotification('unread-note', time, index); + +export const markCountAsRead = ( + association: Association, + parent: string, + description: GraphNotifDescription +): Poke => harkAction({ + 'read-count': { + graph: { + graph: association.resource, + group: association.group, + module: association.metadata.module, + description: description, + index: parent + } + } +}); + +export const markEachAsRead = ( + association: Association, + parent: string, + child: string, + description: GraphNotifDescription, + module: string +): Poke => harkAction({ + 'read-each': { + index: { + graph: { + graph: association.resource, + group: association.group, + description: description, + module: module, + index: parent + } + }, + target: child + } +}); + +export const dec = ( + index: NotifIndex, + ref: string +): Poke => harkAction({ + dec: { + index, + ref + } +}); + +export const seen = () => harkAction({ seen: null }); + +export const readAll = () => harkAction({ 'read-all': null }); + +export const ignoreGroup = ( + group: string +): Poke => groupHookAction({ + ignore: group +}); + +export const ignoreGraph = ( + graph: string, + index: string +): Poke => graphHookAction({ + ignore: { + graph, + index + } +}); + +export const listenGroup = ( + group: string +): Poke => groupHookAction({ + listen: group +}); + +export const listenGraph = ( + graph: string, + index: string +): Poke => graphHookAction({ + listen: { + graph, + index + } +}); + +export const mute = ( + notif: IndexedNotification +): Poke | {} => { + if('graph' in notif.index && 'graph' in notif.notification.contents) { + const { index } = notif; + const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph); + if(!parentIndex) { + return {}; + } + return ignoreGraph(index.graph.graph, parentIndex); + } + if('group' in notif.index) { + const { group } = notif.index.group; + return ignoreGroup(group); + } + return {}; +} + +export const unmute = ( + notif: IndexedNotification +): Poke | {} => { + if('graph' in notif.index && 'graph' in notif.notification.contents) { + const { index } = notif; + const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph); + if(!parentIndex) { + return {}; + } + return listenGraph(index.graph.graph, parentIndex); + } + if('group' in notif.index) { + return listenGroup(notif.index.group.group); + } + return {}; +} + +export const getLastSeen = ( + unreads: Unreads, + path: string, + index: string +): BigInteger | undefined => { + const lastSeenIdx = unreads.graph?.[path]?.[index]?.unreads; + if (!(typeof lastSeenIdx === 'string')) { + return bigInt.zero; + } + return f.flow(f.split('/'), f.last, x => (x ? bigInt(x) : undefined))( + lastSeenIdx + ); +} + +export const getUnreadCount = ( + unreads: Unreads, + path: string, + index: string +): number => { + const graphUnreads = unreads.graph?.[path]?.[index]?.unreads ?? 0; + return typeof graphUnreads === 'number' ? graphUnreads : graphUnreads.size; +} + +export const getNotificationCount = ( + unreads: Unreads, + path: string +): number => { + const unread = unreads.graph?.[path] || {}; + return Object.keys(unread) + .map(index => unread[index]?.notifications || 0) + .reduce(f.add, 0); +} diff --git a/pkg/npm/api/hark/index.d.ts b/pkg/npm/api/hark/types.ts similarity index 92% rename from pkg/npm/api/hark/index.d.ts rename to pkg/npm/api/hark/types.ts index eaa92db40..202ccbdca 100644 --- a/pkg/npm/api/hark/index.d.ts +++ b/pkg/npm/api/hark/types.ts @@ -1,8 +1,8 @@ -import { Post } from "../graph/index.d"; -import { GroupUpdate } from "../groups/index.d"; +import { Post } from "../graph/types"; +import { GroupUpdate } from "../groups/types"; import BigIntOrderedMap from "../lib/BigIntOrderedMap"; -export type GraphNotifDescription = "link" | "comment" | "note" | "mention"; +export type GraphNotifDescription = "link" | "comment" | "note" | "mention" | "message"; export interface UnreadStats { unreads: Set | number; diff --git a/pkg/npm/api/index.d.ts b/pkg/npm/api/index.d.ts deleted file mode 100644 index eecb669f3..000000000 --- a/pkg/npm/api/index.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './contacts/index.d' -export * from './graph/index.d'; -export * from './groups/index.d'; -export * from './hark/index.d'; -export * from './invite/index.d'; -export * from './lib/index.d'; -export * from './metadata/index.d'; \ No newline at end of file diff --git a/pkg/npm/api/index.js b/pkg/npm/api/index.ts similarity index 61% rename from pkg/npm/api/index.js rename to pkg/npm/api/index.ts index 0c6904283..76836aa9a 100644 --- a/pkg/npm/api/index.js +++ b/pkg/npm/api/index.ts @@ -1,9 +1,3 @@ -import BigIntOrderedMap from './lib/BigIntOrderedMap'; - -export { - BigIntOrderedMap -}; - export * from './contacts'; export * from './graph'; export * from './groups'; @@ -11,4 +5,5 @@ export * from './hark'; export * from './invite'; export * from './metadata'; export * from './settings'; -export * from './index.d'; \ No newline at end of file +export * from './lib'; +export * from './lib/BigIntOrderedMap'; \ No newline at end of file diff --git a/pkg/npm/api/invite/index.ts b/pkg/npm/api/invite/index.ts index 51e6ab5fb..341e81711 100644 --- a/pkg/npm/api/invite/index.ts +++ b/pkg/npm/api/invite/index.ts @@ -1,28 +1,2 @@ -import { InviteUpdate, InviteUpdateAccept, InviteUpdateDecline } from "./index.d"; -import { Poke, Serial } from ".."; - -export const action = (data: T): Poke => ({ - app: 'invite-store', - mark: 'invite-action', - json: data -}); - -export const accept = ( - app: string, - uid: Serial -): Poke => action({ - accept: { - term: app, - uid - } -}); - -export const decline = ( - app: string, - uid: Serial -): Poke => action({ - decline: { - term: app, - uid - } -}); +export * from './types'; +export * from './lib'; \ No newline at end of file diff --git a/pkg/npm/api/invite/lib.ts b/pkg/npm/api/invite/lib.ts new file mode 100644 index 000000000..7a0852781 --- /dev/null +++ b/pkg/npm/api/invite/lib.ts @@ -0,0 +1,28 @@ +import { Poke, Serial } from ".."; +import { InviteUpdate, InviteUpdateAccept, InviteUpdateDecline } from "./types"; + +export const inviteAction = (data: T): Poke => ({ + app: 'invite-store', + mark: 'invite-action', + json: data +}); + +export const accept = ( + app: string, + uid: Serial +): Poke => inviteAction({ + accept: { + term: app, + uid + } +}); + +export const decline = ( + app: string, + uid: Serial +): Poke => inviteAction({ + decline: { + term: app, + uid + } +}); diff --git a/pkg/npm/api/invite/index.d.ts b/pkg/npm/api/invite/types.ts similarity index 72% rename from pkg/npm/api/invite/index.d.ts rename to pkg/npm/api/invite/types.ts index 05b43ce8a..de73435c5 100644 --- a/pkg/npm/api/invite/index.d.ts +++ b/pkg/npm/api/invite/types.ts @@ -1,5 +1,5 @@ import { Serial, PatpNoSig, Path } from '..'; -import { Resource } from "../groups/update.d"; +import { Resource } from "../groups"; export type InviteUpdate = InviteUpdateInitial @@ -10,30 +10,30 @@ export type InviteUpdate = | InviteUpdateAccepted | InviteUpdateDecline; -interface InviteUpdateAccept { +export interface InviteUpdateAccept { accept: { term: string; uid: Serial; } } -interface InviteUpdateInitial { +export interface InviteUpdateInitial { initial: Invites; } -interface InviteUpdateCreate { +export interface InviteUpdateCreate { create: { term: string; }; } -interface InviteUpdateDelete { +export interface InviteUpdateDelete { delete: { term: string; }; } -interface InviteUpdateInvite { +export interface InviteUpdateInvite { invite: { term: string; uid: Serial; @@ -41,14 +41,14 @@ interface InviteUpdateInvite { }; } -interface InviteUpdateAccepted { +export interface InviteUpdateAccepted { accepted: { term: string; uid: Serial; }; } -interface InviteUpdateDecline { +export interface InviteUpdateDecline { decline: { term: string; uid: Serial; diff --git a/pkg/npm/api/lib/index.ts b/pkg/npm/api/lib/index.ts new file mode 100644 index 000000000..4fed660f4 --- /dev/null +++ b/pkg/npm/api/lib/index.ts @@ -0,0 +1,2 @@ +export * from './lib'; +export * from './types'; \ No newline at end of file diff --git a/pkg/npm/api/lib/util.ts b/pkg/npm/api/lib/lib.ts similarity index 79% rename from pkg/npm/api/lib/util.ts rename to pkg/npm/api/lib/lib.ts index c646c3a8a..405b9b5cc 100644 --- a/pkg/npm/api/lib/util.ts +++ b/pkg/npm/api/lib/lib.ts @@ -1,23 +1,14 @@ import _ from "lodash"; import f from "lodash/fp"; import bigInt, { BigInteger } from "big-integer"; -import { Resource } from "../groups/index.d"; + +import { Resource } from "../groups/types"; +import { Post, GraphNode } from "../graph/types"; const DA_UNIX_EPOCH = bigInt("170141184475152167957503069145530368000"); // `@ud` ~1970.1.1 const DA_SECOND = bigInt("18446744073709551616"); // `@ud` ~s1 -/** - * Returns true if an app uses a graph backend - * - * @param {string} app The name of the app - * - * @return {boolean} Whether or not it uses a graph backend - */ -export function appIsGraph(app: string): boolean { - return app === 'publish' || app == 'link'; -} - /** * Given a bigint representing an urbit date, returns a unix timestamp. * @@ -136,28 +127,49 @@ export function deSig(ship: string): string | null { } // trim patps to match dojo, chat-cli -export function cite(ship: string): string { +export function cite(ship: string) { let patp = ship, - shortened = ""; - if (patp === null || patp === "") { - return ""; + shortened = ''; + if (patp === null || patp === '') { + return null; } - if (patp.startsWith("~")) { + if (patp.startsWith('~')) { patp = patp.substr(1); } // comet if (patp.length === 56) { - shortened = "~" + patp.slice(0, 6) + "_" + patp.slice(50, 56); + shortened = '~' + patp.slice(0, 6) + '_' + patp.slice(50, 56); return shortened; } // moon if (patp.length === 27) { - shortened = "~" + patp.slice(14, 20) + "^" + patp.slice(21, 27); + shortened = '~' + patp.slice(14, 20) + '^' + patp.slice(21, 27); return shortened; } return `~${patp}`; } + +export function uxToHex(ux: string) { + if (ux.length > 2 && ux.substr(0, 2) === '0x') { + const value = ux.substr(2).replace('.', '').padStart(6, '0'); + return value; + } + + const value = ux.replace('.', '').padStart(6, '0'); + return value; +} + +export const hexToUx = (hex: string): string => { + const ux = f.flow( + f.chunk(4), + f.map(x => _.dropWhile(x, (y: unknown) => y === 0).join('')), + f.join('.') + )(hex.split('')); + return `0x${ux}`; +}; + + // encode the string into @ta-safe format, using logic from +wood. // for example, 'some Chars!' becomes '~.some.~43.hars~21.' // @@ -209,3 +221,20 @@ export function numToUd(num: number): string { f.join('.') )(num.toString()) } + +export const buntPost = (): Post => ({ + author: '', + contents: [], + hash: null, + index: '', + signatures: [], + 'time-sent': 0 +}); + +export function makeNodeMap(posts: Post[]): Record { + const nodes: Record = {}; + posts.forEach((p: Post) => { + nodes[String(p.index)] = { children: null, post: p }; + }); + return nodes; +} diff --git a/pkg/npm/api/lib/index.d.ts b/pkg/npm/api/lib/types.ts similarity index 95% rename from pkg/npm/api/lib/index.d.ts rename to pkg/npm/api/lib/types.ts index f8f94ce66..20bbcb9cf 100644 --- a/pkg/npm/api/lib/index.d.ts +++ b/pkg/npm/api/lib/types.ts @@ -20,7 +20,7 @@ export type Serial = string; export type Jug = Map>; // name of app -export type AppName = 'chat' | 'link' | 'contacts' | 'publish' | 'graph'; +export type AppName = 'chat' | 'link' | 'contacts' | 'publish' | 'graph' | 'groups'; export type ShipRank = 'czar' | 'king' | 'duke' | 'earl' | 'pawn'; @@ -54,6 +54,11 @@ export interface Poke { json: Action; } +export interface Scry { + app: string; + path: string; +} + export interface Thread { inputMark: string; outputMark: string; diff --git a/pkg/npm/api/metadata/index.ts b/pkg/npm/api/metadata/index.ts index e880f1c9d..341e81711 100644 --- a/pkg/npm/api/metadata/index.ts +++ b/pkg/npm/api/metadata/index.ts @@ -1,43 +1,2 @@ -import { AppName, Path, PatpNoSig, Poke } from ".."; -import { Association, Metadata, MetadataUpdateAdd, MetadataUpdateUpdate } from './index.d'; - -export const action = (data: T): Poke => ({ - app: 'metadata-hook', - mark: 'metadata-action', - json: data -}); - -export const add = ( - appName: AppName, - resource: string, - group: string, - metadata: Metadata, -): Poke => { - return action({ - add: { - group, - resource: { - resource, - 'app-name': appName - }, - metadata - } - }); -} - -export const update = ( - association: Association, - newMetadata: Partial -): Poke => { - const { resource, metadata, group } = association; - return action({ - add: { - group, - resource: { - resource, - 'app-name': association['app-name'], - }, - metadata: {...metadata, ...newMetadata } - } - }); -} +export * from './types'; +export * from './lib'; \ No newline at end of file diff --git a/pkg/npm/api/metadata/lib.ts b/pkg/npm/api/metadata/lib.ts new file mode 100644 index 000000000..448c64aaa --- /dev/null +++ b/pkg/npm/api/metadata/lib.ts @@ -0,0 +1,77 @@ +import { AppName, Path, Poke, uxToHex, PatpNoSig } from "../lib"; +import { Association, Metadata, MetadataUpdate, MetadataUpdateAdd, MetadataUpdateRemove } from './types'; + +export const metadataAction = (data: T): Poke => ({ + app: 'metadata-push-hook', + mark: 'metadata-update', + json: data +}); + +export const add = ( + ship: PatpNoSig, + appName: AppName, + resource: Path, + group: Path, + title: string, + description: string, + dateCreated: string, + color: string, + moduleName: string, +): Poke => metadataAction({ + add: { + group, + resource: { + resource, + 'app-name': appName + }, + metadata: { + title, + description, + color, + 'date-created': dateCreated, + creator: `~${ship}`, + 'module': moduleName, + picture: '', + preview: false, + vip: '' + } + } +}); + +export { add as metadataAdd }; + +export const remove = ( + appName: AppName, + resource: string, + group: string +): Poke => metadataAction({ + remove: { + group, + resource: { + resource, + 'app-name': appName + } + } +}); + +export { remove as metadataRemove }; + +export const update = ( + association: Association, + newMetadata: Partial +): Poke => { + const metadata = { ...association.metadata, ...newMetadata }; + metadata.color = uxToHex(metadata.color); + return metadataAction({ + add: { + group: association.group, + resource: { + resource: association.resource, + 'app-name': association['app-name'] + }, + metadata + } + }); +} + +export { update as metadataUpdate }; \ No newline at end of file diff --git a/pkg/npm/api/metadata/index.d.ts b/pkg/npm/api/metadata/types.ts similarity index 75% rename from pkg/npm/api/metadata/index.d.ts rename to pkg/npm/api/metadata/types.ts index c0a12075d..1687bca79 100644 --- a/pkg/npm/api/metadata/index.d.ts +++ b/pkg/npm/api/metadata/types.ts @@ -1,4 +1,4 @@ -import { AppName, Path, Patp } from '..'; +import { AppName, Path, Patp } from "../lib"; export type MetadataUpdate = MetadataUpdateInitial @@ -6,28 +6,34 @@ export type MetadataUpdate = | MetadataUpdateUpdate | MetadataUpdateRemove; -interface MetadataUpdateInitial { +export interface MetadataUpdateInitial { associations: ResourceAssociations; } -type ResourceAssociations = { +export type ResourceAssociations = { [p in Path]: Association; } -type MetadataUpdateAdd = { +export type MetadataUpdateAdd = { add: AssociationPoke; } -type MetadataUpdateUpdate = { +export type MetadataUpdateUpdate = { update: AssociationPoke; } -type MetadataUpdateRemove = { - remove: MdResource & { - group: Path; +export type MetadataUpdateRemove = { + remove: { + resource: MdResource; + group: string; } } +export interface MdResource { + resource: string; + 'app-name': AppName; +} + export interface MetadataUpdatePreview { group: string; channels: Associations; @@ -42,10 +48,7 @@ export type AppAssociations = { [p in Path]: Association; } -interface MdResource { - resource: Path; - 'app-name': AppName; -} + export type Association = MdResource & { group: Path; diff --git a/pkg/npm/api/package.json b/pkg/npm/api/package.json index 55e8e273d..256e74751 100644 --- a/pkg/npm/api/package.json +++ b/pkg/npm/api/package.json @@ -7,15 +7,18 @@ "url": "ssh://git@github.com/urbit/urbit.git", "directory": "pkg/npm/api" }, - "main": "index.js", - "types": "index.d.ts", + "main": "dist/index.js", + "types": "dist/index.d", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "build": "npm run clean && tsc -p tsconfig.json", + "clean": "rm -rf dist/*" }, "author": "", "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5", + "@types/lodash": "^4.14.168", "@urbit/eslint-config": "^1.0.0", "big-integer": "^1.6.48", "lodash": "^4.17.20" diff --git a/pkg/npm/api/s3/index.ts b/pkg/npm/api/s3/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/npm/api/s3/lib.ts b/pkg/npm/api/s3/lib.ts new file mode 100644 index 000000000..4a114fd98 --- /dev/null +++ b/pkg/npm/api/s3/lib.ts @@ -0,0 +1,47 @@ +import { Poke } from '../lib/types'; +import { S3Update, S3UpdateAccessKeyId, S3UpdateAddBucket, S3UpdateCurrentBucket, S3UpdateEndpoint, S3UpdateRemoveBucket, S3UpdateSecretAccessKey } from './types'; + +const s3Action = ( + data: any +): Poke => ({ + app: 's3-store', + mark: 's3-action', + json: data +}); + +export const setCurrentBucket = ( + bucket: string +): Poke => s3Action({ + 'set-current-bucket': bucket +}); + +export const addBucket = ( + bucket: string +): Poke => s3Action({ + 'add-bucket': bucket +}); + +export const removeBucket = ( + bucket: string +): Poke => s3Action({ + 'remove-bucket': bucket +}); + +export const setEndpoint = ( + endpoint: string +): Poke => s3Action({ + 'set-endpoint': endpoint +}); + +export const setAccessKeyId = ( + accessKeyId: string +): Poke => s3Action({ + 'set-access-key-id': accessKeyId +}); + +export const setSecretAccessKey = ( + secretAccessKey: string +): Poke => s3Action({ + 'set-secret-access-key': secretAccessKey +}); + diff --git a/pkg/npm/api/s3/types.ts b/pkg/npm/api/s3/types.ts new file mode 100644 index 000000000..d9fec2e37 --- /dev/null +++ b/pkg/npm/api/s3/types.ts @@ -0,0 +1,60 @@ +export interface S3Credentials { + endpoint: string; + accessKeyId: string; + secretAccessKey: string; +} + +export interface S3Configuration { + buckets: Set; + currentBucket: string; +} + +export interface S3State { + configuration: S3Configuration; + credentials: S3Credentials | null; +} + +export interface S3UpdateCredentials { + credentials: S3Credentials; +} + +export interface S3UpdateConfiguration { + configuration: { + buckets: string[]; + currentBucket: string; + } +} + +export interface S3UpdateCurrentBucket { + setCurrentBucket: string; +} + +export interface S3UpdateAddBucket { + addBucket: string; +} + +export interface S3UpdateRemoveBucket { + removeBucket: string; +} + +export interface S3UpdateEndpoint { + setEndpoint: string; +} + +export interface S3UpdateAccessKeyId { + setAccessKeyId: string; +} + +export interface S3UpdateSecretAccessKey { + setSecretAccessKey: string; +} + +export type S3Update = + S3UpdateCredentials +| S3UpdateConfiguration +| S3UpdateCurrentBucket +| S3UpdateAddBucket +| S3UpdateRemoveBucket +| S3UpdateEndpoint +| S3UpdateAccessKeyId +| S3UpdateSecretAccessKey; diff --git a/pkg/npm/api/settings/index.ts b/pkg/npm/api/settings/index.ts index e69de29bb..341e81711 100644 --- a/pkg/npm/api/settings/index.ts +++ b/pkg/npm/api/settings/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export * from './lib'; \ No newline at end of file diff --git a/pkg/npm/api/settings/lib.ts b/pkg/npm/api/settings/lib.ts new file mode 100644 index 000000000..fbd2c80b5 --- /dev/null +++ b/pkg/npm/api/settings/lib.ts @@ -0,0 +1,50 @@ +import { Poke } from "../lib"; +import { PutBucket, Key, Bucket, DelBucket, Value, PutEntry, DelEntry, SettingsUpdate } from './types'; + +export const action = (data: T): Poke => ({ + app: 'settings-store', + mark: 'settings-event', + json: data +}); + +export const putBucket = ( + key: Key, + bucket: Bucket +): Poke => action({ + 'put-bucket': { + 'bucket-key': key, + 'bucket': bucket + } +}); + +export const delBucket = ( + key: Key +): Poke => action({ + 'del-bucket': { + 'bucket-key': key + } +}); + +export const putEntry = ( + bucket: Key, + key: Key, + value: Value +): Poke => action({ + 'put-entry': { + 'bucket-key': bucket, + 'entry-key': key, + value: value + } +}); + +export const delEntry = ( + bucket: Key, + key: Key +): Poke => action({ + 'del-entry': { + 'bucket-key': bucket, + 'entry-key': key + } +}); + +export * from './types'; \ No newline at end of file diff --git a/pkg/npm/api/settings/index.d.ts b/pkg/npm/api/settings/types.ts similarity index 76% rename from pkg/npm/api/settings/index.d.ts rename to pkg/npm/api/settings/types.ts index f0f50df49..5eafe4da2 100644 --- a/pkg/npm/api/settings/index.d.ts +++ b/pkg/npm/api/settings/types.ts @@ -3,20 +3,20 @@ export type Value = string | boolean | number; export type Bucket = Map; export type Settings = Map; -interface PutBucket { +export interface PutBucket { "put-bucket": { "bucket-key": Key; "bucket": Bucket; }; } -interface DelBucket { +export interface DelBucket { "del-bucket": { "bucket-key": Key; }; } -interface PutEntry { +export interface PutEntry { "put-entry": { "bucket-key": Key; "entry-key": Key; @@ -24,22 +24,22 @@ interface PutEntry { }; } -interface DelEntry { +export interface DelEntry { "del-entry": { "bucket-key": Key; "entry-key": Key; }; } -interface AllData { +export interface AllData { "all": Settings; } -interface BucketData { +export interface BucketData { "bucket": Bucket; } -interface EntryData { +export interface EntryData { "entry": Value; } diff --git a/pkg/npm/api/tsconfig.json b/pkg/npm/api/tsconfig.json index 1d5a91a97..c63058bbd 100644 --- a/pkg/npm/api/tsconfig.json +++ b/pkg/npm/api/tsconfig.json @@ -1,25 +1,19 @@ { + "include": ["*.ts"], + "exclude": ["node_modules", "dist", "@types"], "compilerOptions": { - "allowSyntheticDefaultImports": true, - "noFallthroughCasesInSwitch": true, - "noUnusedParameters": false, - "noImplicitReturns": true, + "outDir": "./dist", + "module": "ESNext", + "noImplicitAny": true, + "target": "ESNext", + "pretty": true, "moduleResolution": "node", "esModuleInterop": true, - "noUnusedLocals": false, - "noImplicitAny": false, - "noEmit": true, - "target": "es2015", - "module": "es2015", - "strict": true, - "jsx": "react", - "baseUrl": ".", - "paths": { - "~/*": ["src/*"] - } - }, - "include": [ - "**/*" - ], - "exclude": [ "node_modules" ] -} + "allowSyntheticDefaultImports": true, + "declaration": true, + "sourceMap": true, + "strict": false, + "noErrorTruncation": true, + "allowJs": true, + } +} \ No newline at end of file From ece8836a7ef4fbbbf417b489f8b3007f5253d1af Mon Sep 17 00:00:00 2001 From: Tyler Brown Cifu Shuster Date: Wed, 24 Feb 2021 19:58:32 -0800 Subject: [PATCH 10/48] http-api: changed eventsource tool --- pkg/npm/http-api/example/browser.js | 21 +- pkg/npm/http-api/example/node.js | 21 +- pkg/npm/http-api/index.js | 2 - pkg/npm/http-api/package.json | 25 +- pkg/npm/http-api/src/Urbit.ts | 372 +++++++++++++++++++++ pkg/npm/http-api/src/app/base.ts | 41 --- pkg/npm/http-api/src/index.ts | 460 +------------------------- pkg/npm/http-api/src/types.ts | 66 ++++ pkg/npm/http-api/src/types/index.d.ts | 47 --- pkg/npm/http-api/tsconfig-cjs.json | 7 - pkg/npm/http-api/tsconfig.json | 13 +- pkg/npm/http-api/webpack.prod.js | 109 ------ 12 files changed, 493 insertions(+), 691 deletions(-) delete mode 100644 pkg/npm/http-api/index.js create mode 100644 pkg/npm/http-api/src/Urbit.ts delete mode 100644 pkg/npm/http-api/src/app/base.ts create mode 100644 pkg/npm/http-api/src/types.ts delete mode 100644 pkg/npm/http-api/src/types/index.d.ts delete mode 100644 pkg/npm/http-api/tsconfig-cjs.json delete mode 100644 pkg/npm/http-api/webpack.prod.js diff --git a/pkg/npm/http-api/example/browser.js b/pkg/npm/http-api/example/browser.js index 9ec2eea57..9af8e1189 100644 --- a/pkg/npm/http-api/example/browser.js +++ b/pkg/npm/http-api/example/browser.js @@ -1,17 +1,32 @@ /* * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). - * This devtool is not neither made for production nor for readable output files. + * This devtool is neither made for production nor for readable output files. * It uses "eval()" calls to create a separate source file in the browser devtools. * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) * or disable the default devtool with "devtool: false". * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). */ /******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ "./src/example/browser.js": /*!********************************!*\ !*** ./src/example/browser.js ***! \********************************/ -/*! unknown exports (runtime-defined) */ -/*! runtime requirements: */ +/***/ (() => { + eval("// import Urbit from '../../dist/browser';\n// window.Urbit = Urbit;\n\n//# sourceURL=webpack://@urbit/http-api/./src/example/browser.js?"); + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module can't be inlined because the eval devtool is used. +/******/ var __webpack_exports__ = {}; +/******/ __webpack_modules__["./src/example/browser.js"](); +/******/ /******/ })() ; \ No newline at end of file diff --git a/pkg/npm/http-api/example/node.js b/pkg/npm/http-api/example/node.js index 4dc1b5b03..3d572dd70 100644 --- a/pkg/npm/http-api/example/node.js +++ b/pkg/npm/http-api/example/node.js @@ -1,17 +1,32 @@ /* * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). - * This devtool is not neither made for production nor for readable output files. + * This devtool is neither made for production nor for readable output files. * It uses "eval()" calls to create a separate source file in the browser devtools. * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) * or disable the default devtool with "devtool: false". * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). */ /******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ "./src/example/node.js": /*!*****************************!*\ !*** ./src/example/node.js ***! \*****************************/ -/*! unknown exports (runtime-defined) */ -/*! runtime requirements: */ +/***/ (() => { + eval("// import Urbit from '../../dist/index';\n// async function blastOff() {\n// const airlock = await Urbit.authenticate({\n// ship: 'zod',\n// url: 'localhost:8080',\n// code: 'lidlut-tabwed-pillex-ridrup',\n// verbose: true\n// });\n// airlock.subscribe('chat-view', '/primary');\n// }\n// blastOff();\n\n//# sourceURL=webpack://@urbit/http-api/./src/example/node.js?"); + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module can't be inlined because the eval devtool is used. +/******/ var __webpack_exports__ = {}; +/******/ __webpack_modules__["./src/example/node.js"](); +/******/ /******/ })() ; \ No newline at end of file diff --git a/pkg/npm/http-api/index.js b/pkg/npm/http-api/index.js deleted file mode 100644 index 351a32475..000000000 --- a/pkg/npm/http-api/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import Urbit from './dist'; -export { Urbit as default, Urbit }; \ No newline at end of file diff --git a/pkg/npm/http-api/package.json b/pkg/npm/http-api/package.json index 25d701a28..ca756dc1b 100644 --- a/pkg/npm/http-api/package.json +++ b/pkg/npm/http-api/package.json @@ -8,20 +8,15 @@ "url": "ssh://git@github.com/urbit/urbit.git", "directory": "pkg/npm/http-api" }, - "main": "dist/cjs/index.js", - "module": "dist/esm/index.js", - "browser": "dist/esm/index.js", - "types": "dist/esm/index.d.ts", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist", "src" ], - "engines": { - "node": ">=13" - }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "npm run clean && webpack --config webpack.prod.js && tsc -p tsconfig.json && tsc -p tsconfig-cjs.json", + "build": "npm run clean && tsc -p tsconfig.json", "clean": "rm -rf dist/*" }, "peerDependencies": {}, @@ -38,31 +33,29 @@ "@babel/plugin-proposal-object-rest-spread": "^7.12.1", "@babel/plugin-proposal-optional-chaining": "^7.12.1", "@babel/preset-typescript": "^7.12.1", + "@types/browser-or-node": "^1.2.0", "@types/eventsource": "^1.1.5", "@types/react": "^16.9.56", "@typescript-eslint/eslint-plugin": "^4.7.0", "@typescript-eslint/parser": "^4.7.0", - "@types/browser-or-node": "^1.2.0", "babel-loader": "^8.2.1", "clean-webpack-plugin": "^3.0.0", "tslib": "^2.0.3", "typescript": "^3.9.7", + "util": "^0.12.3", "webpack": "^5.4.0", "webpack-cli": "^3.3.12", "webpack-dev-server": "^3.11.0" }, "dependencies": { "@babel/runtime": "^7.12.5", + "@microsoft/fetch-event-source": "^2.0.0", + "@urbit/api": "file:../api", "browser-or-node": "^1.3.0", "browserify-zlib": "^0.2.0", - "buffer": "^5.7.1", - "encoding": "^0.1.13", - "eventsource": "^1.0.7", + "buffer": "^6.0.3", "node-fetch": "^2.6.1", "stream-browserify": "^3.0.0", - "stream-http": "^3.1.1", - "util": "^0.12.3", - "xmlhttprequest": "^1.8.0", - "xmlhttprequest-ssl": "^1.6.0" + "stream-http": "^3.1.1" } } diff --git a/pkg/npm/http-api/src/Urbit.ts b/pkg/npm/http-api/src/Urbit.ts new file mode 100644 index 000000000..4f0136168 --- /dev/null +++ b/pkg/npm/http-api/src/Urbit.ts @@ -0,0 +1,372 @@ +import { isBrowser, isNode } from 'browser-or-node'; +import { Action, Scry, Thread } from '@urbit/api'; +import { fetchEventSource, EventSourceMessage } from '@microsoft/fetch-event-source'; + +import { AuthenticationInterface, SubscriptionInterface, CustomEventHandler, PokeInterface, SubscriptionRequestInterface, headers, UrbitInterface, SSEOptions, PokeHandlers } from './types'; +import { uncamelize, hexString } from './utils'; + +/** + * A class for interacting with an urbit ship, given its URL and code + */ +export class Urbit implements UrbitInterface { + /** + * UID will be used for the channel: The current unix time plus a random hex string + */ + uid: string = `${Math.floor(Date.now() / 1000)}-${hexString(6)}`; + + /** + * Last Event ID is an auto-updated index of which events have been sent over this channel + */ + lastEventId: number = 0; + + lastAcknowledgedEventId: number = 0; + + /** + * SSE Client is null for now; we don't want to start polling until it the channel exists + */ + sseClientInitialized: boolean = false; + + /** + * Cookie gets set when we log in. + */ + cookie?: string | undefined; + + /** + * A registry of requestId to successFunc/failureFunc + * + * These functions are registered during a +poke and are executed + * in the onServerEvent()/onServerError() callbacks. Only one of + * the functions will be called, and the outstanding poke will be + * removed after calling the success or failure function. + */ + + outstandingPokes: Map = new Map(); + + /** + * A registry of requestId to subscription functions. + * + * These functions are registered during a +subscribe and are + * executed in the onServerEvent()/onServerError() callbacks. The + * event function will be called whenever a new piece of data on this + * subscription is available, which may be 0, 1, or many times. The + * disconnect function may be called exactly once. + */ + + outstandingSubscriptions: Map = new Map(); + + /** + * Ship can be set, in which case we can do some magic stuff like send chats + */ + ship?: string | null; + + /** + * If verbose, logs output eagerly. + */ + verbose?: boolean; + + /** This is basic interpolation to get the channel URL of an instantiated Urbit connection. */ + get channelUrl(): string { + return `${this.url}/~/channel/${this.uid}`; + } + + get fetchOptions(): any { + const headers: headers = { + 'Content-Type': 'application/json', + }; + if (!isBrowser) { + headers.Cookie = this.cookie; + } + return { + credentials: 'include', + headers + }; + } + + /** + * Constructs a new Urbit connection. + * + * @param url The URL (with protocol and port) of the ship to be accessed + * @param code The access code for the ship at that address + */ + constructor( + public url: string, + public code: string + ) { + return this; + } + + /** + * All-in-one hook-me-up. + * + * Given a ship, url, and code, this returns an airlock connection + * that is ready to go. It `|hi`s itself to create the channel, + * then opens the channel via EventSource. + * + * @param AuthenticationInterface + */ + static async authenticate({ ship, url, code, verbose = false }: AuthenticationInterface) { + const airlock = new Urbit(`http://${url}`, code); + airlock.verbose = verbose; + airlock.ship = ship; + await airlock.connect(); + await airlock.poke({ app: 'hood', mark: 'helm-hi', json: 'opening airlock' }); + await airlock.eventSource(); + return airlock; + } + + /** + * Connects to the Urbit ship. Nothing can be done until this is called. + * That's why we roll it into this.authenticate + */ + async connect(): Promise { + if (this.verbose) { + console.log(`password=${this.code} `, isBrowser ? "Connecting in browser context at " + `${this.url}/~/login` : "Connecting from node context"); + } + return fetch(`${this.url}/~/login`, { + method: 'post', + body: `password=${this.code}`, + credentials: 'include', + }).then(response => { + if (this.verbose) { + console.log('Received authentication response', response); + } + const cookie = response.headers.get('set-cookie'); + if (!this.ship) { + this.ship = new RegExp(/urbauth-~([\w-]+)/).exec(cookie)[1]; + } + if (!isBrowser) { + this.cookie = cookie; + } + }); + } + + + /** + * Initializes the SSE pipe for the appropriate channel. + */ + eventSource(): void{ + if (!this.sseClientInitialized) { + const sseOptions: SSEOptions = { + headers: {} + }; + if (isBrowser) { + sseOptions.withCredentials = true; + } else if (isNode) { + sseOptions.headers.Cookie = this.cookie; + } + fetchEventSource(this.channelUrl, { + // withCredentials: true, + onmessage: (event: EventSourceMessage) => { + if (this.verbose) { + console.log('Received SSE: ', event); + } + this.ack(Number(event.id)); + if (event.data && JSON.parse(event.data)) { + const data: any = JSON.parse(event.data); + if (data.response === 'poke' && this.outstandingPokes.has(data.id)) { + const funcs = this.outstandingPokes.get(data.id); + if (data.hasOwnProperty('ok')) { + funcs.onSuccess(); + } else if (data.hasOwnProperty('err')) { + funcs.onError(data.err); + } else { + console.error('Invalid poke response', data); + } + this.outstandingPokes.delete(data.id); + } else if (data.response === 'subscribe' || + (data.response === 'poke' && this.outstandingSubscriptions.has(data.id))) { + const funcs = this.outstandingSubscriptions.get(data.id); + if (data.hasOwnProperty('err')) { + funcs.err(data.err); + this.outstandingSubscriptions.delete(data.id); + } + } else if (data.response === 'diff' && this.outstandingSubscriptions.has(data.id)) { + const funcs = this.outstandingSubscriptions.get(data.id); + funcs.event(data.json); + } else if (data.response === 'quit' && this.outstandingSubscriptions.has(data.id)) { + const funcs = this.outstandingSubscriptions.get(data.id); + funcs.quit(data); + this.outstandingSubscriptions.delete(data.id); + } else { + console.log('Unrecognized response', data); + } + } + }, + onerror: (error) => { + console.error('pipe error', error); + } + }); + this.sseClientInitialized = true; + } + return; + } + + /** + * Autoincrements the next event ID for the appropriate channel. + */ + getEventId(): number { + this.lastEventId = Number(this.lastEventId) + 1; + return this.lastEventId; + } + + /** + * Acknowledges an event. + * + * @param eventId The event to acknowledge. + */ + ack(eventId: number): Promise { + return this.sendMessage('ack', { 'event-id': eventId }); + } + + /** + * This is a wrapper method that can be used to send any action with data. + * + * Every message sent has some common parameters, like method, headers, and data + * structure, so this method exists to prevent duplication. + * + * @param action The action to send + * @param data The data to send with the action + * + * @returns void | number If successful, returns the number of the message that was sent + */ + async sendMessage(action: Action, data?: object): Promise { + + const id = this.getEventId(); + if (this.verbose) { + console.log(`Sending message ${id}:`, action, data,); + } + let response: Response | undefined; + try { + response = await fetch(this.channelUrl, { + ...this.fetchOptions, + method: 'put', + body: JSON.stringify([{ + id, + action, + ...data, + }]), + }); + } catch (error) { + console.error('message error', error); + response = undefined; + } + if (this.verbose) { + console.log(`Received from message ${id}: `, response); + } + return id; + } + + /** + * Pokes a ship with data. + * + * @param app The app to poke + * @param mark The mark of the data being sent + * @param json The data to send + */ + poke(params: PokeInterface): Promise { + const { app, mark, json, onSuccess, onError } = { onSuccess: () => {}, onError: () => {}, ...params }; + return new Promise((resolve, reject) => { + this + .sendMessage('poke', { ship: this.ship, app, mark, json }) + .then(pokeId => { + if (!pokeId) { + return reject('Poke failed'); + } + if (!this.sseClientInitialized) resolve(pokeId); // A poke may occur before a listener has been opened + this.outstandingPokes.set(pokeId, { + onSuccess: () => { + onSuccess(); + resolve(pokeId); + }, + onError: (event) => { + onError(event); + reject(event.err); + } + }); + }).catch(error => { + console.error(error); + }); + }); + } + + /** + * Subscribes to a path on an app on a ship. + * + * @param app The app to subsribe to + * @param path The path to which to subscribe + * @param handlers Handlers to deal with various events of the subscription + */ + async subscribe(params: SubscriptionRequestInterface): Promise { + const { app, path, err, event, quit } = { err: () => {}, event: () => {}, quit: () => {}, ...params }; + + const subscriptionId = await this.sendMessage('subscribe', { ship: this.ship, app, path }); + + if (!subscriptionId) return; + + this.outstandingSubscriptions.set(subscriptionId, { + err, event, quit + }); + + return subscriptionId; + } + + /** + * Unsubscribes to a given subscription. + * + * @param subscription + */ + unsubscribe(subscription: string): Promise { + return this.sendMessage('unsubscribe', { subscription }); + } + + /** + * Deletes the connection to a channel. + */ + delete(): Promise { + return this.sendMessage('delete'); + } + + /** + * + * @param app The app into which to scry + * @param path The path at which to scry + */ + async scry(params: Scry): Promise { + const { app, path } = params; + const response = await fetch(`/~/scry/${app}${path}.json`, this.fetchOptions); + return await response.json(); + } + + /** + * + * @param inputMark The mark of the data being sent + * @param outputMark The mark of the data being returned + * @param threadName The thread to run + * @param body The data to send to the thread + */ + async thread(params: Thread): Promise { + const { inputMark, outputMark, threadName, body } = params; + const res = await fetch(`/spider/${inputMark}/${threadName}/${outputMark}.json`, { + ...this.fetchOptions, + method: 'POST', + body: JSON.stringify(body) + }); + + return res.json(); + } + + /** + * Utility function to connect to a ship that has its *.arvo.network domain configured. + * + * @param name Name of the ship e.g. zod + * @param code Code to log in + */ + static async onArvoNetwork(ship: string, code: string): Promise { + const url = `https://${ship}.arvo.network`; + return await Urbit.authenticate({ ship, url, code }); + } +} + + + +export default Urbit; diff --git a/pkg/npm/http-api/src/app/base.ts b/pkg/npm/http-api/src/app/base.ts deleted file mode 100644 index 715886b51..000000000 --- a/pkg/npm/http-api/src/app/base.ts +++ /dev/null @@ -1,41 +0,0 @@ -import Urbit from '..'; - -export interface UrbitAppInterface { - airlock: Urbit; - app: string; -} - -export default class UrbitApp implements UrbitAppInterface { - airlock: Urbit; - - get app(): string { - throw new Error('Access app property on base UrbitApp'); - } - - constructor(airlock: Urbit) { - this.airlock = airlock; - } - - /** - * Getter that barfs if no ship has been passed - */ - get ship(): string { - if (!this.airlock.ship) { - throw new Error('No ship specified'); - } - return this.airlock.ship; - } - - /** - * Helper to allow any app to handle subscriptions. - * - * @param path Path on app to subscribe to - */ - subscribe(path: string) { - const ship = this.ship; - const app = this.app; - // @ts-ignore - return this.airlock.subscribe(app, path); - } - // TODO handle methods that don't exist -} diff --git a/pkg/npm/http-api/src/index.ts b/pkg/npm/http-api/src/index.ts index b08f46e53..8e242ef6b 100644 --- a/pkg/npm/http-api/src/index.ts +++ b/pkg/npm/http-api/src/index.ts @@ -1,457 +1,3 @@ -import { isBrowser, isNode } from 'browser-or-node'; -import { Action, Thread } from '../../api'; - -import { AuthenticationInterface, SubscriptionInterface, CustomEventHandler, PokeInterface, SubscriptionRequestInterface, headers, UrbitInterface, SSEOptions, PokeHandlers } from './types'; -import UrbitApp from './app/base'; -import { uncamelize, hexString } from './utils'; - -/** - * A class for interacting with an urbit ship, given its URL and code - */ -export class Urbit implements UrbitInterface { - /** - * UID will be used for the channel: The current unix time plus a random hex string - */ - uid: string = `${Math.floor(Date.now() / 1000)}-${hexString(6)}`; - - /** - * Last Event ID is an auto-updated index of which events have been sent over this channel - */ - lastEventId: number = 0; - - lastAcknowledgedEventId: number = 0; - - /** - * SSE Client is null for now; we don't want to start polling until it the channel exists - */ - sseClient: EventSource | null = null; - - /** - * Cookie gets set when we log in. - */ - cookie?: string | undefined; - - /** - * A registry of requestId to successFunc/failureFunc - * - * These functions are registered during a +poke and are executed - * in the onServerEvent()/onServerError() callbacks. Only one of - * the functions will be called, and the outstanding poke will be - * removed after calling the success or failure function. - */ - - outstandingPokes: Map = new Map(); - - /** - * A registry of requestId to subscription functions. - * - * These functions are registered during a +subscribe and are - * executed in the onServerEvent()/onServerError() callbacks. The - * event function will be called whenever a new piece of data on this - * subscription is available, which may be 0, 1, or many times. The - * disconnect function may be called exactly once. - */ - - outstandingSubscriptions: Map = new Map(); - - /** - * Ship can be set, in which case we can do some magic stuff like send chats - */ - ship?: string | null; - - /** - * If verbose, logs output eagerly. - */ - verbose?: boolean; - - /** - * All registered apps, keyed by name - */ - static apps: Map = new Map(); - - /** This is basic interpolation to get the channel URL of an instantiated Urbit connection. */ - get channelUrl(): string { - return `${this.url}/~/channel/${this.uid}`; - } - - get fetchOptions(): any { - const headers: headers = { - 'Content-Type': 'application/json', - }; - if (!isBrowser) { - headers.Cookie = this.cookie; - } - return { - credentials: 'include', - headers - }; - } - - /** - * Constructs a new Urbit connection. - * - * @param url The URL (with protocol and port) of the ship to be accessed - * @param code The access code for the ship at that address - */ - constructor( - public url: string, - public code: string - ) { - return this; - // We return a proxy so we can set dynamic properties like `Urbit.onChatHook` - // @ts-ignore - return new Proxy(this, { - get(target: Urbit, property: string) { - // First check if this is a regular property - if (property in target) { - return (target as any)[property]; - } - - // Then check if it's a registered app - const app = Urbit.apps.get(uncamelize(property)); - if (app) { - return new app(target); - } - - // Then check to see if we're trying to register an EventSource watcher - if (property.startsWith('on')) { - const on = uncamelize(property.replace('on', '')).toLowerCase(); - return ((action: CustomEventHandler) => { - target.eventSource().addEventListener('message', (event: MessageEvent) => { - if (target.verbose) { - console.log(`Received SSE from ${on}: `, event); - } - if (event.data && JSON.parse(event.data)) { - const data: any = JSON.parse(event.data); - if (data.json.hasOwnProperty(on)) { - action(data.json[on], data.json.response); - } - } - }); - }); - } - - return undefined; - } - }) - } - - /** - * All-in-one hook-me-up. - * - * Given a ship, url, and code, this returns an airlock connection - * that is ready to go. It `|hi`s itself to create the channel, - * then opens the channel via EventSource. - * - * @param AuthenticationInterface - */ - static async authenticate({ ship, url, code, verbose = false }: AuthenticationInterface) { - const airlock = new Urbit(`http://${url}`, code); - airlock.verbose = verbose; - airlock.ship = ship; - await airlock.connect(); - await airlock.poke({ app: 'hood', mark: 'helm-hi', json: 'opening airlock' }); - await airlock.eventSource(); - return airlock; - } - - /** - * Connects to the Urbit ship. Nothing can be done until this is called. - * That's why we roll it into this.authenticate - */ - async connect(): Promise { - if (this.verbose) { - console.log(`password=${this.code} `, isBrowser ? "Connecting in browser context at " + `${this.url}/~/login` : "Connecting from node context"); - } - return fetch(`${this.url}/~/login`, { - method: 'post', - body: `password=${this.code}`, - credentials: 'include', - }).then(response => { - if (this.verbose) { - console.log('Received authentication response', response); - } - const cookie = response.headers.get('set-cookie'); - if (!this.ship) { - this.ship = new RegExp(/urbauth-~([\w-]+)/).exec(cookie)[1]; - } - if (!isBrowser) { - this.cookie = cookie; - } - }).catch(error => { - console.log(XMLHttpRequest); - console.log('errored') - console.log(error); - }); - } - - - /** - * Returns (and initializes, if necessary) the SSE pipe for the appropriate channel. - */ - eventSource(): EventSource { - if (!this.sseClient || this.sseClient.readyState === this.sseClient.CLOSED) { - const sseOptions: SSEOptions = { - headers: {} - }; - if (isBrowser) { - sseOptions.withCredentials = true; - } else if (isNode) { - sseOptions.headers.Cookie = this.cookie; - } - this.sseClient = new EventSource(this.channelUrl, { - withCredentials: true - }); - this.sseClient!.addEventListener('message', (event: MessageEvent) => { - if (this.verbose) { - console.log('Received SSE: ', event); - } - this.ack(Number(event.lastEventId)); - if (event.data && JSON.parse(event.data)) { - const data: any = JSON.parse(event.data); - if (data.response === 'poke' && this.outstandingPokes.has(data.id)) { - const funcs = this.outstandingPokes.get(data.id); - if (data.hasOwnProperty('ok')) { - funcs.onSuccess(); - } else if (data.hasOwnProperty('err')) { - funcs.onError(data.err); - } else { - console.error('Invalid poke response', data); - } - this.outstandingPokes.delete(data.id); - } else if (data.response === 'subscribe' || - (data.response === 'poke' && this.outstandingSubscriptions.has(data.id))) { - const funcs = this.outstandingSubscriptions.get(data.id); - if (data.hasOwnProperty('err')) { - funcs.err(data.err); - this.outstandingSubscriptions.delete(data.id); - } - } else if (data.response === 'diff' && this.outstandingSubscriptions.has(data.id)) { - const funcs = this.outstandingSubscriptions.get(data.id); - funcs.event(data.json); - } else if (data.response === 'quit' && this.outstandingSubscriptions.has(data.id)) { - const funcs = this.outstandingSubscriptions.get(data.id); - funcs.quit(data); - this.outstandingSubscriptions.delete(data.id); - } else { - console.log('Unrecognized response', data); - } - // An incoming message, for example: - // { - // id: 10, - // json: { - // 'chat-update' : { // This is where we hook our "on" handlers like "onChatUpdate" - // message: { - // envelope: { - // author: 'zod', - // letter: { - // text: 'hi' - // }, - // number: 10, - // uid: 'saludhafhsdf', - // when: 124459 - // }, - // path: '/~zod/mailbox' - // } - // } - // } - // } - } - }); - this.sseClient!.addEventListener('error', function(event: Event) { - console.error('pipe error', event); - }); - - } - return this.sseClient; - } - - addEventListener(callback: (data: any) => void) { - return this.eventSource().addEventListener('message', (event: MessageEvent) => { - if (event.data && JSON.parse(event.data)) { - callback(JSON.parse(event.data)); - } - }); - } - - /** - * Autoincrements the next event ID for the appropriate channel. - */ - getEventId(): number { - this.lastEventId = Number(this.lastEventId) + 1; - return this.lastEventId; - } - - /** - * Acknowledges an event. - * - * @param eventId The event to acknowledge. - */ - ack(eventId: number): Promise { - return this.sendMessage('ack', { 'event-id': eventId }); - } - - /** - * This is a wrapper method that can be used to send any action with data. - * - * Every message sent has some common parameters, like method, headers, and data - * structure, so this method exists to prevent duplication. - * - * @param action The action to send - * @param data The data to send with the action - * - * @returns void | number If successful, returns the number of the message that was sent - */ - async sendMessage(action: Action, data?: object): Promise { - - const id = this.getEventId(); - if (this.verbose) { - console.log(`Sending message ${id}:`, action, data,); - } - let response: Response | undefined; - try { - response = await fetch(this.channelUrl, { - ...this.fetchOptions, - method: 'put', - body: JSON.stringify([{ - id, - action, - ...data, - }]), - }); - } catch (error) { - console.error('message error', error); - response = undefined; - } - if (this.verbose) { - console.log(`Received from message ${id}: `, response); - } - return id; - } - - /** - * Pokes a ship with data. - * - * @param app The app to poke - * @param mark The mark of the data being sent - * @param json The data to send - */ - poke(params: PokeInterface): Promise { - const { app, mark, json, onSuccess, onError } = {onSuccess: () => {}, onError: () => {}, ...params}; - return new Promise((resolve, reject) => { - this - .sendMessage('poke', { ship: this.ship, app, mark, json }) - .then(pokeId => { - if (!pokeId) { - return reject('Poke failed'); - } - if (!this.sseClient) resolve(pokeId); // A poke may occur before a listener has been opened - this.outstandingPokes.set(pokeId, { - onSuccess: () => { - onSuccess(); - resolve(pokeId); - }, - onError: (event) => { - onError(event); - reject(event.err); - } - }); - }).catch(error => { - console.error(error); - }); - }); - } - - /** - * Subscribes to a path on an app on a ship. - * - * @param app The app to subsribe to - * @param path The path to which to subscribe - * @param handlers Handlers to deal with various events of the subscription - */ - async subscribe(params: SubscriptionRequestInterface): Promise { - const { app, path, err, event, quit } = { err: () => {}, event: () => {}, quit: () => {}, ...params }; - - const subscriptionId = await this.sendMessage('subscribe', { ship: this.ship, app, path }); - console.log('subscribed', subscriptionId); - - if (!subscriptionId) return; - - this.outstandingSubscriptions.set(subscriptionId, { - err, event, quit - }); - - return subscriptionId; - } - - /** - * Unsubscribes to a given subscription. - * - * @param subscription - */ - unsubscribe(subscription: string): Promise { - return this.sendMessage('unsubscribe', { subscription }); - } - - /** - * Deletes the connection to a channel. - */ - delete(): Promise { - return this.sendMessage('delete'); - } - - /** - * - * @param app The app into which to scry - * @param path The path at which to scry - */ - async scry(app: string, path: string): Promise { - const response = await fetch(`/~/scry/${app}${path}.json`, this.fetchOptions); - return await response.json(); - } - - /** - * - * @param inputMark The mark of the data being sent - * @param outputMark The mark of the data being returned - * @param threadName The thread to run - * @param body The data to send to the thread - */ - async spider(params: Thread): Promise { - const { inputMark, outputMark, threadName, body } = params; - const res = await fetch(`/spider/${inputMark}/${threadName}/${outputMark}.json`, { - ...this.fetchOptions, - method: 'POST', - body: JSON.stringify(body) - }); - - return res.json(); - } - - app(appName: string): UrbitApp { - const appClass = Urbit.apps.get(appName); - if (!appClass) { - throw new Error(`App ${appName} not found`); - } - return new appClass(this); - } - - /** - * Utility function to connect to a ship that has its *.arvo.network domain configured. - * - * @param name Name of the ship e.g. zod - * @param code Code to log in - */ - static async onArvoNetwork(ship: string, code: string): Promise { - const url = `https://${ship}.arvo.network`; - return await Urbit.authenticate({ ship, url, code }); - } - - static extend(appClass: any): void { - Urbit.apps.set(appClass.app, appClass); - } -} - - - -export default Urbit; +export * from './types'; +import Urbit from './Urbit'; +export { Urbit as default }; \ No newline at end of file diff --git a/pkg/npm/http-api/src/types.ts b/pkg/npm/http-api/src/types.ts new file mode 100644 index 000000000..04cc62da5 --- /dev/null +++ b/pkg/npm/http-api/src/types.ts @@ -0,0 +1,66 @@ +import { Action, Poke, Scry, Thread } from '@urbit/api'; + +export interface PokeHandlers { + onSuccess?: () => void; + onError?: (e: any) => void; +} + +export type PokeInterface = PokeHandlers & Poke; + +export interface AuthenticationInterface { + ship: string; + url: string; + code: string; + verbose?: boolean; +} + +export interface SubscriptionInterface { + err?(error: any): void; + event?(data: any): void; + quit?(data: any): void; +} + +export type SubscriptionRequestInterface = SubscriptionInterface & { + app: string; + path: string; +} + +export interface headers { + 'Content-Type': string; + Cookie?: string; +} + +export interface UrbitInterface { + uid: string; + lastEventId: number; + lastAcknowledgedEventId: number; + sseClientInitialized: boolean; + cookie?: string | undefined; + outstandingPokes: Map; + outstandingSubscriptions: Map; + verbose?: boolean; + ship?: string | null; + connect(): void; + connect(): Promise; + eventSource(): void; + getEventId(): number; + ack(eventId: number): Promise; + sendMessage(action: Action, data?: object): Promise; + poke(params: PokeInterface): Promise; + subscribe(params: SubscriptionRequestInterface): Promise; + unsubscribe(subscription: string): Promise; + delete(): Promise; + scry(params: Scry): Promise; + thread(params: Thread): Promise; +} + +export interface CustomEventHandler { + (data: any, response: string): void; +} + +export interface SSEOptions { + headers?: { + Cookie?: string + }; + withCredentials?: boolean; +} diff --git a/pkg/npm/http-api/src/types/index.d.ts b/pkg/npm/http-api/src/types/index.d.ts deleted file mode 100644 index 57d095f76..000000000 --- a/pkg/npm/http-api/src/types/index.d.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Action, Mark, Poke } from '../../../api/index'; - -export interface PokeHandlers { - onSuccess?: () => void; - onError?: (e: any) => void; -} - -export type PokeInterface = PokeHandlers & Poke; - - -export interface AuthenticationInterface { - ship: string; - url: string; - code: string; - verbose?: boolean; -} - -export interface SubscriptionInterface { - err?(error: any): void; - event?(data: any): void; - quit?(data: any): void; -} - -export type SubscriptionRequestInterface = SubscriptionInterface & { - app: string; - path: string; -} - -export interface headers { - 'Content-Type': string; - Cookie?: string; -} - -export interface UrbitInterface { - connect(): void; -} - -export interface CustomEventHandler { - (data: any, response: string): void; -} - -export interface SSEOptions { - headers?: { - Cookie?: string - }; - withCredentials?: boolean; -} diff --git a/pkg/npm/http-api/tsconfig-cjs.json b/pkg/npm/http-api/tsconfig-cjs.json deleted file mode 100644 index ec2ee6283..000000000 --- a/pkg/npm/http-api/tsconfig-cjs.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "CommonJS", - "outDir": "./dist/cjs" - }, -} \ No newline at end of file diff --git a/pkg/npm/http-api/tsconfig.json b/pkg/npm/http-api/tsconfig.json index b5703e6ed..2245cf9dc 100644 --- a/pkg/npm/http-api/tsconfig.json +++ b/pkg/npm/http-api/tsconfig.json @@ -1,18 +1,19 @@ { - "include": ["src/**/*.ts"], + "include": ["src/*.ts"], "exclude": ["node_modules", "dist", "@types"], "compilerOptions": { - "outDir": "./dist/esm", - "module": "ES2020", + "outDir": "./dist", + "module": "ESNext", "noImplicitAny": true, - "target": "ES2020", + "target": "ESNext", "pretty": true, "moduleResolution": "node", "esModuleInterop": true, "allowSyntheticDefaultImports": true, "declaration": true, "sourceMap": true, - "strict": false - // "lib": ["ES2020"], + "strict": false, + "noErrorTruncation": true, + "allowJs": true, } } \ No newline at end of file diff --git a/pkg/npm/http-api/webpack.prod.js b/pkg/npm/http-api/webpack.prod.js deleted file mode 100644 index aab72b662..000000000 --- a/pkg/npm/http-api/webpack.prod.js +++ /dev/null @@ -1,109 +0,0 @@ -const path = require('path'); -const webpack = require('webpack'); - -const shared = { - mode: 'production', - entry: { - app: './src/index.ts' - }, - module: { - rules: [ - { - test: /\.(j|t)s$/, - use: { - loader: 'babel-loader', - options: { - presets: ['@babel/typescript'], - plugins: [ - '@babel/plugin-proposal-class-properties', - '@babel/plugin-proposal-object-rest-spread', - '@babel/plugin-proposal-optional-chaining', - ], - } - }, - exclude: /node_modules/ - } - ] - }, - resolve: { - extensions: ['.js', '.ts', '.ts'], - fallback: { - fs: false, - child_process: false, - util: require.resolve("util/"), - buffer: require.resolve('buffer/'), - assert: false, - http: require.resolve('stream-http'), - https: require.resolve('stream-http'), - stream: require.resolve('stream-browserify'), - zlib: require.resolve("browserify-zlib"), - } - }, - - optimization: { - minimize: false, - usedExports: true - } -}; - -const serverConfig = { - ...shared, - target: 'node', - output: { - filename: 'index.js', - path: path.resolve(__dirname, 'dist'), - library: 'Urbit', - libraryExport: 'default' - }, - plugins: [ - new webpack.ProvidePlugin({ - XMLHttpRequest: ['xmlhttprequest-ssl', 'XMLHttpRequest'], - EventSource: 'eventsource', - fetch: ['node-fetch', 'default'], - }), - ], -}; - -const browserConfig = { - ...shared, - target: 'web', - output: { - filename: 'browser.js', - path: path.resolve(__dirname, 'dist'), - library: 'Urbit', - libraryExport: 'default' - }, - plugins: [ - new webpack.ProvidePlugin({ - Buffer: 'buffer', - }), - ], -}; - - -const exampleBrowserConfig = { - ...shared, - mode: 'development', - entry: { - app: './src/example/browser.js' - }, - output: { - filename: 'browser.js', - path: path.resolve(__dirname, 'example'), - } -}; - -const exampleNodeConfig = { - ...shared, - mode: 'development', - target: 'node', - entry: { - app: './src/example/node.js' - }, - output: { - filename: 'node.js', - path: path.resolve(__dirname, 'example'), - } -}; - -module.exports = [ serverConfig, browserConfig, exampleBrowserConfig, exampleNodeConfig ]; \ No newline at end of file From 99d04500c54a457070659db43ed3c943f28769c4 Mon Sep 17 00:00:00 2001 From: Tyler Brown Cifu Shuster Date: Thu, 25 Feb 2021 07:13:32 -0800 Subject: [PATCH 11/48] npm/api: changed metadata app property --- pkg/npm/api/metadata/lib.ts | 6 +++--- pkg/npm/api/metadata/types.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/npm/api/metadata/lib.ts b/pkg/npm/api/metadata/lib.ts index 448c64aaa..6a0548e0a 100644 --- a/pkg/npm/api/metadata/lib.ts +++ b/pkg/npm/api/metadata/lib.ts @@ -22,7 +22,7 @@ export const add = ( group, resource: { resource, - 'app-name': appName + app: appName }, metadata: { title, @@ -49,7 +49,7 @@ export const remove = ( group, resource: { resource, - 'app-name': appName + app: appName } } }); @@ -67,7 +67,7 @@ export const update = ( group: association.group, resource: { resource: association.resource, - 'app-name': association['app-name'] + app: association.app }, metadata } diff --git a/pkg/npm/api/metadata/types.ts b/pkg/npm/api/metadata/types.ts index 1687bca79..4bcd07691 100644 --- a/pkg/npm/api/metadata/types.ts +++ b/pkg/npm/api/metadata/types.ts @@ -31,7 +31,7 @@ export type MetadataUpdateRemove = { export interface MdResource { resource: string; - 'app-name': AppName; + app: AppName; } export interface MetadataUpdatePreview { From be88d603299aefa8fac03e436d712cf2493aecdb Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Thu, 25 Feb 2021 13:11:03 -0500 Subject: [PATCH 12/48] ResourceSkeleton: dynamically truncate description --- .../landscape/components/ResourceSkeleton.tsx | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx b/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx index 43750dd5d..44461f8df 100644 --- a/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx +++ b/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx @@ -1,5 +1,5 @@ import React, { ReactElement, ReactNode } from 'react'; -import { Icon, Box, Col, Text } from '@tlon/indigo-react'; +import { Icon, Box, Col, Row, Text } from '@tlon/indigo-react'; import styled from 'styled-components'; import { Link } from 'react-router-dom'; import urbitOb from 'urbit-ob'; @@ -12,7 +12,7 @@ import GlobalApi from '~/logic/api/global'; import { isWriter } from '~/logic/lib/group'; import { getItemTitle } from '~/logic/lib/util'; -const TruncatedBox = styled(Box)` +const TruncatedText = styled(RichText)` white-space: pre; text-overflow: ellipsis; overflow: hidden; @@ -88,7 +88,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement { > {'<- Back'} - + {title} - - {(workspace === '/messages') ? recipient : association?.metadata?.description} - - + + {canWrite && ( From a666ce902b993e79a525a70ef3524248ee5062d0 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Thu, 25 Feb 2021 13:22:13 -0500 Subject: [PATCH 13/48] ResourceSkeleton: render group DM members --- .../src/views/landscape/components/ResourceSkeleton.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx b/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx index 43750dd5d..1dce3f8f6 100644 --- a/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx +++ b/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx @@ -46,11 +46,13 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement { ? getItemTitle(association) : association?.metadata?.title; - let recipient = false; + let recipient = ""; if (urbitOb.isValidPatp(title)) { recipient = title; title = (props.contacts?.[title]?.nickname) ? props.contacts[title].nickname : title; + } else { + recipient = Array.from(group.members).map(e => `~${e}`).join(", ") } const [, , ship, resource] = rid.split('/'); From c63ffbdb734880b3faba27c977423f61474d7e84 Mon Sep 17 00:00:00 2001 From: matildepark Date: Thu, 25 Feb 2021 13:24:05 -0500 Subject: [PATCH 14/48] Revert "landscape: allow most patp punctation in tokenize" --- pkg/interface/src/logic/lib/tokenizeMessage.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pkg/interface/src/logic/lib/tokenizeMessage.js b/pkg/interface/src/logic/lib/tokenizeMessage.js index dc1ffeffc..39154c6ff 100644 --- a/pkg/interface/src/logic/lib/tokenizeMessage.js +++ b/pkg/interface/src/logic/lib/tokenizeMessage.js @@ -52,16 +52,13 @@ const tokenizeMessage = (text) => { } messages.push({ url: str }); message = []; - } else if (urbitOb.isValidPatp(str.replace(/[^a-z\-\~]/g, '')) && !isInCodeBlock) { + } else if(urbitOb.isValidPatp(str) && !isInCodeBlock) { if (message.length > 0) { // If we're in the middle of a message, add it to the stack and reset messages.push({ text: message.join(' ') }); message = []; } - messages.push({ mention: str.replace(/[^a-z\-\~]/g, '') }); - if (str.replace(/[a-z\-\~]/g, '').length > 0) { - messages.push({ text: str.replace(/[a-z\-\~]/g, '') }); - } + messages.push({ mention: str }); message = []; } else { From e8bbc56c48b7ddb137460d56c156d9650c434fea Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Thu, 25 Feb 2021 13:40:53 -0500 Subject: [PATCH 15/48] leap: cite patps --- pkg/interface/src/views/components/leap/OmniboxResult.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/interface/src/views/components/leap/OmniboxResult.js b/pkg/interface/src/views/components/leap/OmniboxResult.js index 6e35c7450..7b00f5a17 100644 --- a/pkg/interface/src/views/components/leap/OmniboxResult.js +++ b/pkg/interface/src/views/components/leap/OmniboxResult.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import { Box, Row, Icon, Text } from '@tlon/indigo-react'; import defaultApps from '~/logic/lib/default-apps'; import Sigil from '~/logic/lib/sigil'; -import { uxToHex } from '~/logic/lib/util'; +import { uxToHex, cite } from '~/logic/lib/util'; export class OmniboxResult extends Component { constructor(props) { @@ -99,7 +99,7 @@ export class OmniboxResult extends Component { style={{ flexShrink: 0 }} mr='1' > - {text} + {text.startsWith("~") ? cite(text) : text} Date: Thu, 25 Feb 2021 13:41:17 -0500 Subject: [PATCH 16/48] leap: truncate statuses Fixes urbit/landscape#487 --- .../src/views/components/leap/OmniboxResult.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/interface/src/views/components/leap/OmniboxResult.js b/pkg/interface/src/views/components/leap/OmniboxResult.js index 7b00f5a17..0259b9eb3 100644 --- a/pkg/interface/src/views/components/leap/OmniboxResult.js +++ b/pkg/interface/src/views/components/leap/OmniboxResult.js @@ -87,25 +87,29 @@ export class OmniboxResult extends Component { } onClick={navigate} width="100%" + justifyContent="space-between" ref={this.result} > + {graphic} {text.startsWith("~") ? cite(text) : text} + {subtext} From 52e5d4a0118fbaae921ac82a74f73b12a43731d5 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Thu, 25 Feb 2021 14:30:50 -0500 Subject: [PATCH 17/48] links: enforce valid URI on render Fixes urbit/landscape#280 --- pkg/interface/src/views/apps/links/components/LinkItem.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/interface/src/views/apps/links/components/LinkItem.tsx b/pkg/interface/src/views/apps/links/components/LinkItem.tsx index 8934416b6..027b25926 100644 --- a/pkg/interface/src/views/apps/links/components/LinkItem.tsx +++ b/pkg/interface/src/views/apps/links/components/LinkItem.tsx @@ -67,6 +67,7 @@ export const LinkItem = (props: LinkItemProps): ReactElement => { const size = node.children ? node.children.size : 0; const contents = node.post.contents; const hostname = URLparser.exec(contents[1].url) ? URLparser.exec(contents[1].url)[4] : null; + const href = URLparser.exec(contents[1].url) ? contents[1].url : `http://${contents[1].url}` const baseUrl = props.baseUrl || `/~404/${resource}`; @@ -120,7 +121,7 @@ export const LinkItem = (props: LinkItemProps): ReactElement => { { remoteRef.current = r }} renderUrl={false} - url={contents[1].url} + url={href} text={contents[0].text} unfold={true} onLoad={onMeasure} @@ -145,7 +146,7 @@ export const LinkItem = (props: LinkItemProps): ReactElement => { }} /> - + {hostname} From 86bc8b9444de1c7e470a24e338d3dddc18a833cf Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Fri, 26 Feb 2021 09:58:48 +1000 Subject: [PATCH 18/48] hark-store: bunt cache before rebuilding Previously, rebuilding the cache in any scenario other than on-load would add missing notifications but not remove read or dismissed notifications Fixes urbit/landscape#479 --- pkg/arvo/app/hark-store.hoon | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/arvo/app/hark-store.hoon b/pkg/arvo/app/hark-store.hoon index daef591ff..eac838313 100644 --- a/pkg/arvo/app/hark-store.hoon +++ b/pkg/arvo/app/hark-store.hoon @@ -767,6 +767,8 @@ ++ inflate-cache |= state-4 ^+ +.state + =. +.state + *cache =/ nots=(list [p=@da =timebox:store]) (tap:orm notifications) |- =* outer $ From 8ba29acc1573c64bc3429c2b73ee730e75dd9a06 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Thu, 25 Feb 2021 19:17:45 -0500 Subject: [PATCH 19/48] landscape: redirect properly on channel archive Previously landed on a crash. --- .../components/ChannelPopoverRoutes/ChannelPermissions.tsx | 2 +- .../views/landscape/components/ChannelPopoverRoutes/index.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/interface/src/views/landscape/components/ChannelPopoverRoutes/ChannelPermissions.tsx b/pkg/interface/src/views/landscape/components/ChannelPopoverRoutes/ChannelPermissions.tsx index 5ccccd406..f84cf69c3 100644 --- a/pkg/interface/src/views/landscape/components/ChannelPopoverRoutes/ChannelPermissions.tsx +++ b/pkg/interface/src/views/landscape/components/ChannelPopoverRoutes/ChannelPermissions.tsx @@ -151,7 +151,7 @@ export function GraphPermissions(props: GraphPermissionsProps) { } }; - const schema = formSchema(Array.from(group.members)); + const schema = formSchema(Array.from(group?.members ?? [])); return ( { const [,,,name] = association.resource.split('/'); - await api.graph.deleteGraph(name); - history.push(props.baseUrl); + api.graph.deleteGraph(name); + return history.push(props.rootUrl); }; const canAdmin = isChannelAdmin(group, association.resource); From 75c90ea4a5d5944e6c0ffecbdea9b17e16d4be89 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Thu, 25 Feb 2021 19:18:29 -0500 Subject: [PATCH 20/48] landscape: add border prop to loading Prevents borders showing when loading channels. Also spaces the text beside the spinner. --- pkg/interface/src/views/components/Body.tsx | 6 +++--- pkg/interface/src/views/components/Loading.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/interface/src/views/components/Body.tsx b/pkg/interface/src/views/components/Body.tsx index 4e2e64203..2f28d7c8d 100644 --- a/pkg/interface/src/views/components/Body.tsx +++ b/pkg/interface/src/views/components/Body.tsx @@ -5,7 +5,7 @@ import { Box } from '@tlon/indigo-react'; export function Body( props: { children: ReactNode } & Parameters[0] ) { - const { children, ...boxProps } = props; + const { children, border, ...boxProps } = props; return ( - {props.children} + {children} ); diff --git a/pkg/interface/src/views/components/Loading.tsx b/pkg/interface/src/views/components/Loading.tsx index 78822673e..85bbcc8bb 100644 --- a/pkg/interface/src/views/components/Loading.tsx +++ b/pkg/interface/src/views/components/Loading.tsx @@ -8,10 +8,10 @@ interface LoadingProps { } export function Loading({ text }: LoadingProps) { return ( - +
- {Boolean(text) && {text}} + {Boolean(text) && {text}}
); From f8d8e250c9cb9832bf91b194149c649067118658 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Thu, 25 Feb 2021 19:18:48 -0500 Subject: [PATCH 21/48] GroupPane: description defaults to regular font size --- pkg/interface/src/views/landscape/components/GroupsPane.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/interface/src/views/landscape/components/GroupsPane.tsx b/pkg/interface/src/views/landscape/components/GroupsPane.tsx index 2ba691b85..bffa328ae 100644 --- a/pkg/interface/src/views/landscape/components/GroupsPane.tsx +++ b/pkg/interface/src/views/landscape/components/GroupsPane.tsx @@ -206,7 +206,7 @@ export function GroupsPane(props: GroupsPaneProps) { resource={groupAssociation.group} />; } else { - summary = ( + summary = ( Create or select a channel to get started ); } From 2ed775986427256f31a8967ebfe9e6c76fe90f2c Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Thu, 25 Feb 2021 19:19:10 -0500 Subject: [PATCH 22/48] ChannelPopoverRoutes: add rootUrl type --- .../views/landscape/components/ChannelPopoverRoutes/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/interface/src/views/landscape/components/ChannelPopoverRoutes/index.tsx b/pkg/interface/src/views/landscape/components/ChannelPopoverRoutes/index.tsx index 374255348..b0ab86a6f 100644 --- a/pkg/interface/src/views/landscape/components/ChannelPopoverRoutes/index.tsx +++ b/pkg/interface/src/views/landscape/components/ChannelPopoverRoutes/index.tsx @@ -22,6 +22,7 @@ import { isChannelAdmin, isHost } from '~/logic/lib/group'; interface ChannelPopoverRoutesProps { baseUrl: string; + rootUrl: string; association: Association; group: Group; groups: Groups; From fef99fcf21e37f95211057e8d6e32eb8442e7b50 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Thu, 25 Feb 2021 19:20:26 -0500 Subject: [PATCH 23/48] GroupSearch, ShipSearch: use cursor: pointer --- pkg/interface/src/views/components/GroupSearch.tsx | 1 + pkg/interface/src/views/components/ShipSearch.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/pkg/interface/src/views/components/GroupSearch.tsx b/pkg/interface/src/views/components/GroupSearch.tsx index 870b08902..fe887e17e 100644 --- a/pkg/interface/src/views/components/GroupSearch.tsx +++ b/pkg/interface/src/views/components/GroupSearch.tsx @@ -41,6 +41,7 @@ const Candidate = ({ title, selected, onClick }): ReactElement => ( ( bg="white" color="black" fontSize={0} + cursor="pointer" p={1} width="100%" > From 20c6ecae41ca7cf93dfa98a145ad2baa8217ffba Mon Sep 17 00:00:00 2001 From: Tyler Brown Cifu Shuster Date: Thu, 25 Feb 2021 17:16:27 -0800 Subject: [PATCH 24/48] ignore example js --- pkg/npm/http-api/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/npm/http-api/.gitignore b/pkg/npm/http-api/.gitignore index e69de29bb..e4cb4c1c8 100644 --- a/pkg/npm/http-api/.gitignore +++ b/pkg/npm/http-api/.gitignore @@ -0,0 +1 @@ +example/*.js \ No newline at end of file From 7a8b8ec71804eff4edce97ddd146ae663182cfca Mon Sep 17 00:00:00 2001 From: Tyler Brown Cifu Shuster Date: Thu, 25 Feb 2021 17:17:07 -0800 Subject: [PATCH 25/48] npm/http-api: ignore build examples --- pkg/npm/http-api/example/browser.js | 32 ----------------------------- pkg/npm/http-api/example/node.js | 32 ----------------------------- 2 files changed, 64 deletions(-) delete mode 100644 pkg/npm/http-api/example/browser.js delete mode 100644 pkg/npm/http-api/example/node.js diff --git a/pkg/npm/http-api/example/browser.js b/pkg/npm/http-api/example/browser.js deleted file mode 100644 index 9af8e1189..000000000 --- a/pkg/npm/http-api/example/browser.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). - * This devtool is neither made for production nor for readable output files. - * It uses "eval()" calls to create a separate source file in the browser devtools. - * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) - * or disable the default devtool with "devtool: false". - * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). - */ -/******/ (() => { // webpackBootstrap -/******/ var __webpack_modules__ = ({ - -/***/ "./src/example/browser.js": -/*!********************************!*\ - !*** ./src/example/browser.js ***! - \********************************/ -/***/ (() => { - -eval("// import Urbit from '../../dist/browser';\n// window.Urbit = Urbit;\n\n//# sourceURL=webpack://@urbit/http-api/./src/example/browser.js?"); - -/***/ }) - -/******/ }); -/************************************************************************/ -/******/ -/******/ // startup -/******/ // Load entry module and return exports -/******/ // This entry module can't be inlined because the eval devtool is used. -/******/ var __webpack_exports__ = {}; -/******/ __webpack_modules__["./src/example/browser.js"](); -/******/ -/******/ })() -; \ No newline at end of file diff --git a/pkg/npm/http-api/example/node.js b/pkg/npm/http-api/example/node.js deleted file mode 100644 index 3d572dd70..000000000 --- a/pkg/npm/http-api/example/node.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). - * This devtool is neither made for production nor for readable output files. - * It uses "eval()" calls to create a separate source file in the browser devtools. - * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) - * or disable the default devtool with "devtool: false". - * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). - */ -/******/ (() => { // webpackBootstrap -/******/ var __webpack_modules__ = ({ - -/***/ "./src/example/node.js": -/*!*****************************!*\ - !*** ./src/example/node.js ***! - \*****************************/ -/***/ (() => { - -eval("// import Urbit from '../../dist/index';\n// async function blastOff() {\n// const airlock = await Urbit.authenticate({\n// ship: 'zod',\n// url: 'localhost:8080',\n// code: 'lidlut-tabwed-pillex-ridrup',\n// verbose: true\n// });\n// airlock.subscribe('chat-view', '/primary');\n// }\n// blastOff();\n\n//# sourceURL=webpack://@urbit/http-api/./src/example/node.js?"); - -/***/ }) - -/******/ }); -/************************************************************************/ -/******/ -/******/ // startup -/******/ // Load entry module and return exports -/******/ // This entry module can't be inlined because the eval devtool is used. -/******/ var __webpack_exports__ = {}; -/******/ __webpack_modules__["./src/example/node.js"](); -/******/ -/******/ })() -; \ No newline at end of file From 23805b4dde4d0efa1a976f43c0fc94318198d29f Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Fri, 26 Feb 2021 11:58:08 +1000 Subject: [PATCH 26/48] meta: automatically glob and deploy ops/group-timer on push --- .github/workflows/ops-group-timer.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/ops-group-timer.yml diff --git a/.github/workflows/ops-group-timer.yml b/.github/workflows/ops-group-timer.yml new file mode 100644 index 000000000..f28a615ab --- /dev/null +++ b/.github/workflows/ops-group-timer.yml @@ -0,0 +1,20 @@ +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 }} + From 9ecc426820d4c374484d9538c4d9b7f12eab488e Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Fri, 26 Feb 2021 11:58:38 +1000 Subject: [PATCH 27/48] meta: automatically merge master into ops/group-timer --- .github/workflows/merge.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index ff520c265..627d9e802 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -15,3 +15,13 @@ jobs: target_branch: release/next-js github_token: ${{ secrets.JANEWAY_BOT_TOKEN }} + merge-to-group-timer: + runs-on: ubuntu-latest + name: "Merge master to ops/group-timer" + steps: + - uses: actions/checkout@v2 + - uses: devmasx/merge-branch@v1.3.1 + with: + type: now + target_branch: ops/group-timer + github_token: ${{ secrets.JANEWAY_BOT_TOKEN }} From 6fa0cd29e2ab08baaab5f5dfe335a80e49f21e4d Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Fri, 26 Feb 2021 13:03:46 +1000 Subject: [PATCH 28/48] settings: migrate --- .../src/logic/lib/migrateSettings.ts | 72 +++++++++++++++++++ .../src/views/landscape/components/Content.js | 12 +++- 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 pkg/interface/src/logic/lib/migrateSettings.ts diff --git a/pkg/interface/src/logic/lib/migrateSettings.ts b/pkg/interface/src/logic/lib/migrateSettings.ts new file mode 100644 index 000000000..eddd67a9e --- /dev/null +++ b/pkg/interface/src/logic/lib/migrateSettings.ts @@ -0,0 +1,72 @@ +import useLocalState, { LocalState } from "~/logic/state/local"; +import useSettingsState from "~/logic/state/settings"; +import GlobalApi from "../api/global"; +import { BackgroundConfig, RemoteContentPolicy } from "~/types"; + +const getBackgroundString = (bg: BackgroundConfig) => { + if (bg?.type === "url") { + return bg.url; + } else if (bg?.type === "color") { + return bg.color; + } else { + return ""; + } +}; + +export function useMigrateSettings(api: GlobalApi) { + const local = useLocalState(); + const { display, remoteContentPolicy, calm } = useSettingsState(); + + return async () => { + if (!localStorage.has("localReducer")) { + return; + } + + let promises: Promise[] = []; + + if (local.hideAvatars !== calm.hideAvatars) { + promises.push( + api.settings.putEntry("calm", "hideAvatars", local.hideAvatars) + ); + } + + if (local.hideNicknames !== calm.hideNicknames) { + promises.push( + api.settings.putEntry("calm", "hideNicknames", local.hideNicknames) + ); + } + + if ( + local?.background?.type && + display.background !== getBackgroundString(local.background) + ) { + promises.push( + api.settings.putEntry( + "display", + "background", + getBackgroundString(local.background) + ) + ); + promises.push( + api.settings.putEntry( + "display", + "backgroundType", + local.background?.type + ) + ); + } + + Object.keys(local.remoteContentPolicy).forEach((_key) => { + const key = _key as keyof RemoteContentPolicy; + const localVal = local.remoteContentPolicy[key]; + if (localVal !== remoteContentPolicy[key]) { + promises.push( + api.settings.putEntry("remoteContentPolicy", key, localVal) + ); + } + }); + + await Promise.all(promises); + localStorage.removeItem("localReducer"); + }; +} diff --git a/pkg/interface/src/views/landscape/components/Content.js b/pkg/interface/src/views/landscape/components/Content.js index a507e4f3b..a0f5cbe93 100644 --- a/pkg/interface/src/views/landscape/components/Content.js +++ b/pkg/interface/src/views/landscape/components/Content.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { Component, useEffect } from 'react'; import { Box } from '@tlon/indigo-react'; import { Route, Switch } from 'react-router-dom'; import styled from 'styled-components'; @@ -12,6 +12,8 @@ import ErrorComponent from '~/views/components/Error'; import Notifications from '~/views/apps/notifications/notifications'; import GraphApp from '../../apps/graph/app'; +import { useMigrateSettings } from '~/logic/lib/migrateSettings'; + export const Container = styled(Box)` flex-grow: 1; @@ -22,6 +24,14 @@ export const Container = styled(Box)` export const Content = (props) => { + + const doMigrate = useMigrateSettings(); + useEffect(() => { + setTimeout(() => { + doMigrate(); + }, 10000); + }, []); + return ( From ccea39f865c1e8b949e4e0878313428875e1ea46 Mon Sep 17 00:00:00 2001 From: Ted Blackman Date: Thu, 25 Feb 2021 19:07:00 -0500 Subject: [PATCH 29/48] ames: fix comet handshake --- pkg/arvo/sys/vane/ames.hoon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/arvo/sys/vane/ames.hoon b/pkg/arvo/sys/vane/ames.hoon index ad799da54..5ea780792 100644 --- a/pkg/arvo/sys/vane/ames.hoon +++ b/pkg/arvo/sys/vane/ames.hoon @@ -1217,7 +1217,7 @@ on-hear-forward :: ?: ?& ?=(%pawn (clan:title sndr.packet)) - !(~(has by peers.ames-state) sndr.packet) + !?=([~ %known *] (~(get by peers.ames-state) sndr.packet)) == on-hear-open on-hear-shut From 04ea4c78b291b15a950b26da1f057b7df7aeee4e Mon Sep 17 00:00:00 2001 From: Ted Blackman Date: Thu, 25 Feb 2021 19:29:29 -0500 Subject: [PATCH 30/48] ames: +on-hear-shut remove dead code --- pkg/arvo/sys/vane/ames.hoon | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pkg/arvo/sys/vane/ames.hoon b/pkg/arvo/sys/vane/ames.hoon index 5ea780792..8791c051a 100644 --- a/pkg/arvo/sys/vane/ames.hoon +++ b/pkg/arvo/sys/vane/ames.hoon @@ -1289,14 +1289,9 @@ |= [=lane =packet dud=(unit goof)] ^+ event-core =/ sndr-state (~(get by peers.ames-state) sndr.packet) - :: if we don't know them, maybe enqueue a jael %public-keys request - :: - :: Ignore encrypted packets from alien comets. - :: TODO: maybe crash? + :: if we don't know them, ask jael for their keys and enqueue :: ?. ?=([~ %known *] sndr-state) - ?: =(%pawn (clan:title sndr.packet)) - event-core (enqueue-alien-todo sndr.packet |=(alien-agenda +<)) :: decrypt packet contents using symmetric-key.channel :: From 6bcbbf8f1a4756c195a324efcf9515b6f288f700 Mon Sep 17 00:00:00 2001 From: janeway Date: Fri, 26 Feb 2021 17:07:59 +1000 Subject: [PATCH 31/48] glob: update to 0v5.ip41o.9jcdb.4jb1f.sd508.fdssj --- bin/solid.pill | 4 ++-- pkg/arvo/app/glob.hoon | 2 +- pkg/arvo/app/landscape/index.html | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/solid.pill b/bin/solid.pill index a5f9f5b67..eb8cd14d0 100644 --- a/bin/solid.pill +++ b/bin/solid.pill @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7bf07220286010c21998e38497be5f2c771219bbcb6dac3e2c7bb0af5dbf4fb8 -size 9668893 +oid sha256:9812a52d34be0d6d47ca60b23d3386e7db296ff61fac7c4b1f33a35806f8cb7c +size 9751012 diff --git a/pkg/arvo/app/glob.hoon b/pkg/arvo/app/glob.hoon index 3643c74ff..5aada8ac2 100644 --- a/pkg/arvo/app/glob.hoon +++ b/pkg/arvo/app/glob.hoon @@ -5,7 +5,7 @@ /- glob /+ default-agent, verb, dbug |% -++ hash 0v3.10f5l.mmsef.76usq.9a3gk.0rmog +++ hash 0v5.ip41o.9jcdb.4jb1f.sd508.fdssj +$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))] +$ all-states $% state-0 diff --git a/pkg/arvo/app/landscape/index.html b/pkg/arvo/app/landscape/index.html index 009915d1b..1f359a06b 100644 --- a/pkg/arvo/app/landscape/index.html +++ b/pkg/arvo/app/landscape/index.html @@ -24,6 +24,6 @@
- + From 0030d0d83432181e1b2bd6ef0205e1ff2fdec552 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Fri, 26 Feb 2021 12:22:46 -0500 Subject: [PATCH 32/48] ResourceSkeleton: set flex-shrink priority, mobile On mobile devices, long channel names can push the menu offscreen. However, we don't want the title to be shrunk by a long description. We want the description to shrink first. So we set flex-shrink priorities, and also don't let the title shrink on desktop. --- .../src/views/landscape/components/ResourceSkeleton.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx b/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx index 716cac715..3cda44190 100644 --- a/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx +++ b/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx @@ -90,7 +90,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement { > {'<- Back'}
- + {title} @@ -109,7 +109,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement { @@ -125,7 +125,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement { {(workspace === '/messages') ? recipient : association?.metadata?.description} - + {canWrite && ( + New Post From 288cf1d0ad04b5529e5c8cd6042b4524001eb59e Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Fri, 26 Feb 2021 12:27:00 -0500 Subject: [PATCH 33/48] NewChannel: hide "<- Back" mobile nav on dms --- .../src/views/landscape/components/NewChannel.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/interface/src/views/landscape/components/NewChannel.tsx b/pkg/interface/src/views/landscape/components/NewChannel.tsx index e58e606f8..cf82256d9 100644 --- a/pkg/interface/src/views/landscape/components/NewChannel.tsx +++ b/pkg/interface/src/views/landscape/components/NewChannel.tsx @@ -115,10 +115,14 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps): ReactE return ( - history.push(props.baseUrl)}> - {'<- Back'} + history.push(props.baseUrl)} + > + {'<- Back'} - + {workspace?.type === 'messages' ? 'Direct Message' : 'New Channel'} Date: Fri, 12 Feb 2021 12:26:43 +0700 Subject: [PATCH 34/48] landscape: ignore punctuation tokenizing URLs Tiny fix to the URL regex so that it doesn't store punctuation marks (e.g. ",") inside the urls, breaking the links. --- pkg/interface/src/logic/lib/tokenizeMessage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/interface/src/logic/lib/tokenizeMessage.js b/pkg/interface/src/logic/lib/tokenizeMessage.js index dc1ffeffc..f16b6275a 100644 --- a/pkg/interface/src/logic/lib/tokenizeMessage.js +++ b/pkg/interface/src/logic/lib/tokenizeMessage.js @@ -1,6 +1,6 @@ import urbitOb from 'urbit-ob'; -const URL_REGEX = new RegExp(String(/^((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+)/.source)); +const URL_REGEX = new RegExp(String(/^((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+\w)/.source)); const isUrl = (string) => { try { From 639372e58fd899a51e2951c4842350c1e9fb7413 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Fri, 26 Feb 2021 13:06:48 -0500 Subject: [PATCH 35/48] leap: add missing import --- pkg/interface/src/views/components/leap/Omnibox.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/interface/src/views/components/leap/Omnibox.tsx b/pkg/interface/src/views/components/leap/Omnibox.tsx index cf63163ed..683adce1f 100644 --- a/pkg/interface/src/views/components/leap/Omnibox.tsx +++ b/pkg/interface/src/views/components/leap/Omnibox.tsx @@ -10,6 +10,7 @@ import makeIndex from '~/logic/lib/omnibox'; import OmniboxInput from './OmniboxInput'; import OmniboxResult from './OmniboxResult'; import { deSig } from '~/logic/lib/util'; +import { withLocalState } from '~/logic/state/local'; import defaultApps from '~/logic/lib/default-apps'; import {useOutsideClick} from '~/logic/lib/useOutsideClick'; @@ -51,8 +52,8 @@ export function Omnibox(props: OmniboxProps) { }, [props.contacts, query]); const selectedGroup = useMemo( - () => location.pathname.startsWith('/~landscape/ship/') - ? '/' + location.pathname.split('/').slice(2,5).join('/') + () => location.pathname.startsWith('/~landscape/ship/') + ? '/' + location.pathname.split('/').slice(2,5).join('/') : null, [location.pathname] ); From 9841e92325bc35d14df48fc8c7015e77192d2911 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Fri, 26 Feb 2021 13:07:09 -0500 Subject: [PATCH 36/48] settings: switch to multipage layout --- .../apps/settings/components/lib/CalmPref.tsx | 2 +- .../settings/components/lib/DisplayForm.tsx | 8 +-- .../settings/components/lib/LeapSettings.tsx | 8 +-- .../components/lib/NotificationPref.tsx | 2 +- .../apps/settings/components/lib/S3Form.tsx | 4 +- .../apps/settings/components/lib/Security.tsx | 10 ++-- .../src/views/apps/settings/settings.tsx | 51 ++++++++++--------- 7 files changed, 44 insertions(+), 41 deletions(-) diff --git a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx index 5bf8a3e89..f9ea6225c 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx @@ -66,7 +66,7 @@ export function CalmPrefs(props: { return (
- + CalmEngine diff --git a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx index a93f179ba..ce856ecb6 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx @@ -48,7 +48,7 @@ export default function DisplayForm(props: DisplayFormProps) { let bgColor, bgUrl; if (backgroundType === "url") { - bgUrl = background; + bgUrl = background; } if (backgroundType === "color") { bgColor = background; @@ -70,11 +70,11 @@ export default function DisplayForm(props: DisplayFormProps) { promises.push(api.settings.putEntry('display', 'backgroundType', values.bgType)); promises.push( - api.settings.putEntry('display', 'background', + api.settings.putEntry('display', 'background', values.bgType === "color" ? `#${uxToHex(values.bgColor || "0x0")}` : values.bgType === "url" - ? values.bgUrl || "" + ? values.bgUrl || "" : false )); @@ -86,7 +86,7 @@ export default function DisplayForm(props: DisplayFormProps) { > {(props) => ( - + Display Preferences diff --git a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx index ad535062e..fd9a7f1c4 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx @@ -74,19 +74,19 @@ export function LeapSettings(props: { api: GlobalApi; }) { }; return ( - - + + Leap - + Customize Leap ordering, omit modules or results - + Customize default Leap sections diff --git a/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx b/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx index e68a55f3d..e1d4cfe5c 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx @@ -51,7 +51,7 @@ export function NotificationPreferences(props: { }, [api]); return ( - + Notification Preferences diff --git a/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx b/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx index 93c10679f..b62887c54 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx @@ -48,7 +48,7 @@ export default function S3Form(props: S3FormProps): ReactElement { ); return ( <> - + - + S3 Storage Setup diff --git a/pkg/interface/src/views/apps/settings/components/lib/Security.tsx b/pkg/interface/src/views/apps/settings/components/lib/Security.tsx index 5dc343b88..a20d68459 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/Security.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/Security.tsx @@ -16,8 +16,8 @@ interface SecuritySettingsProps { export default function SecuritySettings({ api }: SecuritySettingsProps) { const [allSessions, setAllSessions] = useState(false); return ( - - + + Security Preferences @@ -26,10 +26,10 @@ export default function SecuritySettings({ api }: SecuritySettingsProps) { - + Log out of this session - + {allSessions ? "You will be logged out of all browsers that have currently logged into your Urbit." : "You will be logged out of your Urbit on this browser."} @@ -39,7 +39,7 @@ export default function SecuritySettings({ api }: SecuritySettingsProps) { selected={allSessions} onChange={() => setAllSessions((s) => !s)} > - Log out of all sessions + Log out of all sessions {allSessions && } diff --git a/pkg/interface/src/views/apps/settings/settings.tsx b/pkg/interface/src/views/apps/settings/settings.tsx index 303fed0c5..84b606234 100644 --- a/pkg/interface/src/views/apps/settings/settings.tsx +++ b/pkg/interface/src/views/apps/settings/settings.tsx @@ -50,11 +50,11 @@ function SidebarItem(props: { hash: string } & Omit) { ); } -function SettingsItem(props: { hash: string; children: ReactNode }) { - const { hash, children } = props; +function SettingsItem(props: { children: ReactNode }) { + const { children } = props; return ( - + {children} ); @@ -62,7 +62,8 @@ function SettingsItem(props: { hash: string; children: ReactNode }) { export default function SettingsScreen(props: any) { - useHashLink(); + const location = useLocation(); + const hash = location.hash.slice(1) return ( <> @@ -106,26 +107,28 @@ export default function SettingsScreen(props: any) { - - - - - - - - - - - - - - - - - + + {hash === "notifications" && ( + + )} + {hash === "display" && ( + + )} + {hash === "s3" && ( + + )} + {hash === "leap" && ( + + )} + {hash === "calm" && ( + + )} + {hash === "security" && ( + + )} From e266273ff1583b47f3e2a931278e90ae2a55536d Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Fri, 26 Feb 2021 13:21:27 -0500 Subject: [PATCH 37/48] ChatMessage, Sidebar: use new hideAvatars import --- .../src/views/apps/chat/components/ChatMessage.tsx | 4 +++- .../views/landscape/components/Sidebar/SidebarItem.tsx | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx b/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx index 5cf286f5a..9b24f65b7 100644 --- a/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx +++ b/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx @@ -16,6 +16,7 @@ import { cite, writeText, useShowNickname, + useHideAvatar, useHovering } from '~/logic/lib/util'; import { @@ -32,6 +33,7 @@ import RemoteContent from '~/views/components/RemoteContent'; import { Mention } from '~/views/components/MentionText'; import styled from 'styled-components'; import useLocalState from '~/logic/state/local'; +import useSettingsState, {selectCalmState} from "~/logic/state/settings"; import Timestamp from '~/views/components/Timestamp'; export const DATESTAMP_FORMAT = '[~]YYYY.M.D'; @@ -239,7 +241,7 @@ export const MessageAuthor = ({ const contact = `~${msg.author}` in contacts ? contacts[`~${msg.author}`] : false; const showNickname = useShowNickname(contact); - const { hideAvatars } = useLocalState(({ hideAvatars }) => ({ hideAvatars })); + const { hideAvatars } = useSettingsState(selectCalmState); const shipName = showNickname ? contact.nickname : cite(msg.author); const copyNotice = 'Copied'; const color = contact diff --git a/pkg/interface/src/views/landscape/components/Sidebar/SidebarItem.tsx b/pkg/interface/src/views/landscape/components/Sidebar/SidebarItem.tsx index 1d52a9fca..5521b19ce 100644 --- a/pkg/interface/src/views/landscape/components/Sidebar/SidebarItem.tsx +++ b/pkg/interface/src/views/landscape/components/Sidebar/SidebarItem.tsx @@ -8,10 +8,11 @@ import { HoverBoxLink } from '~/views/components/HoverBox'; import { Sigil } from '~/logic/lib/sigil'; import { getModuleIcon, getItemTitle, uxToHex } from '~/logic/lib/util'; import { useTutorialModal } from '~/views/components/useTutorialModal'; -import useLocalState from '~/logic/state/local'; import { TUTORIAL_HOST, TUTORIAL_GROUP } from '~/logic/lib/tutorialModal'; import { SidebarAppConfigs, SidebarItemStatus } from './types'; import { Workspace } from '~/types/workspace'; +import useSettingsState, { selectCalmState } from '~/logic/state/settings'; + function SidebarItemIndicator(props: { status?: SidebarItemStatus }) { switch (props.status) { @@ -56,9 +57,8 @@ export function SidebarItem(props: { return null; } const DM = (isUnmanaged && props.workspace?.type === 'messages'); - const { hideAvatars, hideNicknames } = useLocalState(({ hideAvatars, hideNicknames }) => ({ - hideAvatars, hideNicknames - })); + const { hideAvatars, hideNicknames } = useSettingsState(selectCalmState); + const itemStatus = app.getStatus(path); const hasUnread = itemStatus === 'unread' || itemStatus === 'mention'; From f0e1a24b42e1da6783bda3384a435dfb8b0ab840 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Fri, 26 Feb 2021 13:25:50 -0500 Subject: [PATCH 38/48] SidebarItem: centre icons --- pkg/interface/src/views/landscape/components/SidebarItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/interface/src/views/landscape/components/SidebarItem.tsx b/pkg/interface/src/views/landscape/components/SidebarItem.tsx index 71aea2a02..667ed2fed 100644 --- a/pkg/interface/src/views/landscape/components/SidebarItem.tsx +++ b/pkg/interface/src/views/landscape/components/SidebarItem.tsx @@ -34,7 +34,7 @@ export const SidebarItem = ({ justifyContent="space-between" {...rest} > - + {text} From 9f64db52c19eb42a4ca51155cb6c7b14667ac973 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Fri, 26 Feb 2021 13:25:57 -0500 Subject: [PATCH 39/48] settings: replace calm icon --- pkg/interface/src/views/apps/settings/settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/interface/src/views/apps/settings/settings.tsx b/pkg/interface/src/views/apps/settings/settings.tsx index 84b606234..9b9fef668 100644 --- a/pkg/interface/src/views/apps/settings/settings.tsx +++ b/pkg/interface/src/views/apps/settings/settings.tsx @@ -98,7 +98,7 @@ export default function SettingsScreen(props: any) { - + Date: Fri, 26 Feb 2021 13:26:32 -0500 Subject: [PATCH 40/48] leap: "Profile and Settings" -> "profile" --- pkg/interface/src/logic/lib/omnibox.js | 2 +- .../src/views/apps/settings/components/lib/LeapSettings.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/interface/src/logic/lib/omnibox.js b/pkg/interface/src/logic/lib/omnibox.js index c53a6af6e..08e4fff42 100644 --- a/pkg/interface/src/logic/lib/omnibox.js +++ b/pkg/interface/src/logic/lib/omnibox.js @@ -75,7 +75,7 @@ const otherIndex = function(config) { const idx = { mychannel: result('My Channels', '/~landscape/home', 'home', null), updates: result('Notifications', '/~notifications', 'inbox', null), - profile: result('Profile and Settings', `/~profile/~${window.ship}`, 'profile', null), + profile: result('Profile', `/~profile/~${window.ship}`, 'profile', null), messages: result('Messages', '/~landscape/messages', 'messages', null), logout: result('Log Out', '/~/logout', 'logout', null) }; diff --git a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx index fd9a7f1c4..9e1e9e877 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx @@ -22,7 +22,7 @@ import { ShuffleFields } from "~/views/components/ShuffleFields"; const labels: Record = { mychannel: "My Channel", updates: "Notifications", - profile: "Profile and Settings", + profile: "Profile", messages: "Messages", logout: "Log Out", }; From 2db485705eab2584506753c546d06ee5257f8b8e Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Fri, 26 Feb 2021 13:41:12 -0500 Subject: [PATCH 41/48] npm/api: add array type to settings value --- pkg/npm/api/settings/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/npm/api/settings/index.d.ts b/pkg/npm/api/settings/index.d.ts index f0f50df49..eed8cda71 100644 --- a/pkg/npm/api/settings/index.d.ts +++ b/pkg/npm/api/settings/index.d.ts @@ -1,5 +1,5 @@ export type Key = string; -export type Value = string | boolean | number; +export type Value = string | string[] | boolean | number; export type Bucket = Map; export type Settings = Map; From b4d82a9e8aad6976e4804370fc941838c5b18783 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Fri, 26 Feb 2021 13:41:40 -0500 Subject: [PATCH 42/48] leap, settings: store leap settings as array --- pkg/interface/src/logic/lib/migrateSettings.ts | 2 +- pkg/interface/src/logic/lib/omnibox.js | 2 +- pkg/interface/src/logic/state/settings.tsx | 10 +++++----- .../apps/settings/components/lib/LeapSettings.tsx | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/interface/src/logic/lib/migrateSettings.ts b/pkg/interface/src/logic/lib/migrateSettings.ts index eddd67a9e..bcfe44bfc 100644 --- a/pkg/interface/src/logic/lib/migrateSettings.ts +++ b/pkg/interface/src/logic/lib/migrateSettings.ts @@ -18,7 +18,7 @@ export function useMigrateSettings(api: GlobalApi) { const { display, remoteContentPolicy, calm } = useSettingsState(); return async () => { - if (!localStorage.has("localReducer")) { + if (!localStorage?.has("localReducer")) { return; } diff --git a/pkg/interface/src/logic/lib/omnibox.js b/pkg/interface/src/logic/lib/omnibox.js index 08e4fff42..74488c4b0 100644 --- a/pkg/interface/src/logic/lib/omnibox.js +++ b/pkg/interface/src/logic/lib/omnibox.js @@ -80,7 +80,7 @@ const otherIndex = function(config) { logout: result('Log Out', '/~/logout', 'logout', null) }; - for(let cat of JSON.parse(config.categories)) { + for(let cat of config.categories) { if(idx[cat]) { other.push(idx[cat]); } diff --git a/pkg/interface/src/logic/state/settings.tsx b/pkg/interface/src/logic/state/settings.tsx index 97354b605..91343c921 100644 --- a/pkg/interface/src/logic/state/settings.tsx +++ b/pkg/interface/src/logic/state/settings.tsx @@ -18,14 +18,14 @@ export interface SettingsState { }; remoteContentPolicy: RemoteContentPolicy; leap: { - categories: string; + categories: LeapCategories[]; } set: (fn: (state: SettingsState) => void) => void }; export type SettingsStateZus = SettingsState & State; -export const selectSettingsState = +export const selectSettingsState = (keys: K[]) => f.pick(keys); export const selectCalmState = (s: SettingsState) => s.calm; @@ -47,14 +47,14 @@ const useSettingsState = create((set) => ({ videoShown: true }, leap: { - categories: JSON.stringify(leapCategories), + categories: leapCategories, }, set: (fn: (state: SettingsState) => void) => set(produce(fn)) -})); +})); function withSettingsState(Component: any, stateMemberKeys?: S[]) { return React.forwardRef((props: Omit, ref) => { - const localState = stateMemberKeys + const localState = stateMemberKeys ? useSettingsState(selectSettingsState(stateMemberKeys)) : useSettingsState(); return diff --git a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx index 9e1e9e877..7dac9b621 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx @@ -51,7 +51,7 @@ const settingsSel = selectSettingsState(["leap", "set"]); export function LeapSettings(props: { api: GlobalApi; }) { const { api } = props; const { leap, set: setSettingsState } = useSettingsState(settingsSel); - const categories = JSON.parse(leap.categories) as LeapCategories[]; + const categories = leap.categories as LeapCategories[]; const missing = _.difference(leapCategories, categories); console.log(categories); @@ -70,7 +70,7 @@ export function LeapSettings(props: { api: GlobalApi; }) { (acc, { display, category }) => (display ? [...acc, category] : acc), [] as LeapCategories[] ); - await api.settings.putEntry('leap', 'categories', JSON.stringify(result)); + await api.settings.putEntry('leap', 'categories', result); }; return ( From 356d517b4a55611290a33facc6e174e47cd64673 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Fri, 26 Feb 2021 14:12:24 -0500 Subject: [PATCH 43/48] CalmEngine: add "hideUnreads" and "hideGroups" --- pkg/interface/src/logic/state/settings.tsx | 4 ++++ pkg/interface/src/views/apps/launch/app.js | 6 +++++- .../views/apps/launch/components/Groups.tsx | 6 ++++-- .../apps/settings/components/lib/CalmPref.tsx | 19 +++++++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/pkg/interface/src/logic/state/settings.tsx b/pkg/interface/src/logic/state/settings.tsx index 91343c921..5a8dc5446 100644 --- a/pkg/interface/src/logic/state/settings.tsx +++ b/pkg/interface/src/logic/state/settings.tsx @@ -15,6 +15,8 @@ export interface SettingsState { calm: { hideNicknames: boolean; hideAvatars: boolean; + hideUnreads: boolean; + hideGroups: boolean; }; remoteContentPolicy: RemoteContentPolicy; leap: { @@ -39,6 +41,8 @@ const useSettingsState = create((set) => ({ calm: { hideNicknames: false, hideAvatars: false, + hideUnreads: false, + hideGroups: false }, remoteContentPolicy: { imageShown: true, diff --git a/pkg/interface/src/views/apps/launch/app.js b/pkg/interface/src/views/apps/launch/app.js index c84c7f87f..465e08234 100644 --- a/pkg/interface/src/views/apps/launch/app.js +++ b/pkg/interface/src/views/apps/launch/app.js @@ -29,6 +29,8 @@ import { TUTORIAL_CHAT, TUTORIAL_LINKS } from '~/logic/lib/tutorialModal'; +import useSettingsState, { selectCalmState } from '~/logic/state/settings'; + const ScrollbarLessBox = styled(Box)` scrollbar-width: none !important; @@ -81,7 +83,9 @@ export default function LaunchApp(props) { } }, [query]); - const { tutorialProgress, nextTutStep, hideGroups } = useLocalState(tutSelector); + const { tutorialProgress, nextTutStep } = useLocalState(tutSelector); + let { hideGroups } = useLocalState(tutSelector); + !hideGroups ? { hideGroups } = useSettingsState(selectCalmState) : null; const waiter = useWaitForProps(props); diff --git a/pkg/interface/src/views/apps/launch/components/Groups.tsx b/pkg/interface/src/views/apps/launch/components/Groups.tsx index f5e1d70e3..3a7a5485b 100644 --- a/pkg/interface/src/views/apps/launch/components/Groups.tsx +++ b/pkg/interface/src/views/apps/launch/components/Groups.tsx @@ -9,6 +9,7 @@ import { getUnreadCount, getNotificationCount } from '~/logic/lib/hark'; import Tile from '../components/tiles/tile'; import { useTutorialModal } from '~/views/components/useTutorialModal'; import { TUTORIAL_HOST, TUTORIAL_GROUP } from '~/logic/lib/tutorialModal'; +import useSettingsState, { selectCalmState } from '~/logic/state/settings'; interface GroupsProps { associations: Associations; @@ -80,11 +81,12 @@ function Group(props: GroupProps) { isTutorialGroup, anchorRef.current ); + const { hideUnreads } = useSettingsState(selectCalmState) return ( {title} - + {!hideUnreads && ( {updates > 0 && ({updates} update{updates !== 1 && 's'} ) } @@ -92,7 +94,7 @@ function Group(props: GroupProps) { ({unreads}) } - + )} ); diff --git a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx index f9ea6225c..bb14e9a7d 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx @@ -16,6 +16,8 @@ import {AsyncButton} from "~/views/components/AsyncButton"; interface FormSchema { hideAvatars: boolean; hideNicknames: boolean; + hideUnreads: boolean; + hideGroups: boolean; imageShown: boolean; audioShown: boolean; oembedShown: boolean; @@ -32,6 +34,8 @@ export function CalmPrefs(props: { calm: { hideAvatars, hideNicknames, + hideUnreads, + hideGroups }, remoteContentPolicy: { imageShown, @@ -45,6 +49,8 @@ export function CalmPrefs(props: { const initialValues: FormSchema = { hideAvatars, hideNicknames, + hideUnreads, + hideGroups, imageShown, videoShown, oembedShown, @@ -55,6 +61,8 @@ export function CalmPrefs(props: { await Promise.all([ api.settings.putEntry('calm', 'hideAvatars', v.hideAvatars), api.settings.putEntry('calm', 'hideNicknames', v.hideNicknames), + api.settings.putEntry('calm', 'hideUnreads', v.hideUnreads), + api.settings.putEntry('calm', 'hideGroups', v.hideGroups), api.settings.putEntry('remoteContentPolicy', 'imageShown', v.imageShown), api.settings.putEntry('remoteContentPolicy', 'videoShown', v.videoShown), api.settings.putEntry('remoteContentPolicy', 'audioShown', v.audioShown), @@ -75,6 +83,17 @@ export function CalmPrefs(props: { Modulate various elemednts across Landscape to maximize calmness + Home screen + + User-set identity Date: Fri, 26 Feb 2021 14:29:39 -0500 Subject: [PATCH 44/48] settings: fix typos --- .../apps/settings/components/lib/CalmPref.tsx | 2 +- .../apps/settings/components/lib/DisplayForm.tsx | 2 +- .../views/apps/settings/components/lib/S3Form.tsx | 14 +++++++++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx index bb14e9a7d..7d4352f78 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx @@ -80,7 +80,7 @@ export function CalmPrefs(props: { CalmEngine - Modulate various elemednts across Landscape to maximize calmness + Modulate various elements across Landscape to maximize calmness Home screen diff --git a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx index ce856ecb6..8f5390941 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx @@ -91,7 +91,7 @@ export default function DisplayForm(props: DisplayFormProps) { Display Preferences - + Customize visual interfaces across your Landscape diff --git a/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx b/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx index b62887c54..1e5d7bbf6 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx @@ -7,7 +7,8 @@ import { Box, Text, Button, - Col + Col, + Anchor } from '@tlon/indigo-react'; import GlobalApi from "~/logic/api/global"; @@ -69,8 +70,15 @@ export default function S3Form(props: S3FormProps): ReactElement { Store credentials for your S3 object storage buckets on your - Urbit ship, and upload media freely to various modules. Learn - more + Urbit ship, and upload media freely to various modules. + + Learn more + From 0e50981c5162c33f0d0147479a46beb0ee2ad6a4 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Fri, 26 Feb 2021 16:41:05 -0500 Subject: [PATCH 45/48] sh/build-interface: hotfix for api build --- sh/build-interface | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sh/build-interface b/sh/build-interface index cebf79101..e90e04a14 100755 --- a/sh/build-interface +++ b/sh/build-interface @@ -2,7 +2,10 @@ set -ex -cd pkg/interface +cd pkg/npm/api +npm install & + +cd ../../interface npm install npm run build:prod & From 409d07d1f4dbc850278f3d9f91f56b7d46d4120e Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Fri, 26 Feb 2021 16:45:48 -0500 Subject: [PATCH 46/48] pkg/interface: force api build on prod --- pkg/interface/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/interface/package.json b/pkg/interface/package.json index 82d865170..629380f87 100644 --- a/pkg/interface/package.json +++ b/pkg/interface/package.json @@ -95,7 +95,7 @@ "tsc": "tsc", "tsc:watch": "tsc --watch", "build:dev": "cross-env NODE_ENV=development webpack --config config/webpack.dev.js", - "build:prod": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js", + "build:prod": "cd ../npm/api && npm i && cd ../../interface && cross-env NODE_ENV=production webpack --config config/webpack.prod.js", "start": "webpack-dev-server --config config/webpack.dev.js", "test": "echo \"Error: no test specified\" && exit 1" }, From e3f195870f64f473f517a2483c76cd7d3cbc69a5 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Fri, 26 Feb 2021 17:32:54 -0500 Subject: [PATCH 47/48] settings: format for mobile Fixes urbit/landscape#496 --- .../src/views/apps/settings/components/lib/BackButton.tsx | 2 +- .../src/views/apps/settings/components/lib/CalmPref.tsx | 1 + .../src/views/apps/settings/components/lib/DisplayForm.tsx | 2 ++ .../src/views/apps/settings/components/lib/LeapSettings.tsx | 1 + .../views/apps/settings/components/lib/NotificationPref.tsx | 1 + .../src/views/apps/settings/components/lib/S3Form.tsx | 1 + .../src/views/apps/settings/components/lib/Security.tsx | 2 ++ pkg/interface/src/views/apps/settings/settings.tsx | 5 +++-- 8 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/interface/src/views/apps/settings/components/lib/BackButton.tsx b/pkg/interface/src/views/apps/settings/components/lib/BackButton.tsx index 0ba81209c..570618c64 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/BackButton.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/BackButton.tsx @@ -5,7 +5,7 @@ import { Text } from '@tlon/indigo-react'; export function BackButton(props: {}) { return ( - {"<- Back to System Preferences"} + {"<- Back to System Preferences"} ); } diff --git a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx index 7d4352f78..968f4643e 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx @@ -75,6 +75,7 @@ export function CalmPrefs(props: { + CalmEngine diff --git a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx index 8f5390941..5751ced7d 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx @@ -13,6 +13,7 @@ import { S3State, BackgroundConfig } from "~/types"; import { BackgroundPicker, BgType } from "./BackgroundPicker"; import useSettingsState, { SettingsState, selectSettingsState } from "~/logic/state/settings"; import {AsyncButton} from "~/views/components/AsyncButton"; +import { BackButton } from "./BackButton"; const formSchema = Yup.object().shape({ bgType: Yup.string() @@ -87,6 +88,7 @@ export default function DisplayForm(props: DisplayFormProps) { {(props) => ( + Display Preferences diff --git a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx index 7dac9b621..3621943d3 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx @@ -75,6 +75,7 @@ export function LeapSettings(props: { api: GlobalApi; }) { return ( + Leap diff --git a/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx b/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx index e1d4cfe5c..e61c43303 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx @@ -52,6 +52,7 @@ export function NotificationPreferences(props: { return ( + Notification Preferences diff --git a/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx b/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx index 1e5d7bbf6..4254beb01 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx @@ -64,6 +64,7 @@ export default function S3Form(props: S3FormProps): ReactElement { > + S3 Storage Setup diff --git a/pkg/interface/src/views/apps/settings/components/lib/Security.tsx b/pkg/interface/src/views/apps/settings/components/lib/Security.tsx index a20d68459..7c9bc6293 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/Security.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/Security.tsx @@ -8,6 +8,7 @@ import { } from "@tlon/indigo-react"; import GlobalApi from "~/logic/api/global"; +import { BackButton } from "./BackButton"; interface SecuritySettingsProps { api: GlobalApi; @@ -17,6 +18,7 @@ export default function SecuritySettings({ api }: SecuritySettingsProps) { const [allSessions, setAllSessions] = useState(false); return ( + Security Preferences diff --git a/pkg/interface/src/views/apps/settings/settings.tsx b/pkg/interface/src/views/apps/settings/settings.tsx index 9b9fef668..10bed0ff9 100644 --- a/pkg/interface/src/views/apps/settings/settings.tsx +++ b/pkg/interface/src/views/apps/settings/settings.tsx @@ -76,9 +76,10 @@ export default function SettingsScreen(props: any) { height="100%" borderRight="1" borderRightColor="washedGray" - display={["none", "flex"]} + display={hash === "" ? "flex" : ["none", "flex"]} minWidth="250px" - maxWidth="350px" + width="100%" + maxWidth={["100vw", "350px"]} > Date: Fri, 26 Feb 2021 17:35:05 -0500 Subject: [PATCH 48/48] hark-fe: remove former preferences nav Fixes urbit/landscape#494 --- .../apps/notifications/notifications.tsx | 20 ----- .../views/apps/notifications/preferences.tsx | 86 ------------------- 2 files changed, 106 deletions(-) delete mode 100644 pkg/interface/src/views/apps/notifications/preferences.tsx diff --git a/pkg/interface/src/views/apps/notifications/notifications.tsx b/pkg/interface/src/views/apps/notifications/notifications.tsx index 3f9bd7555..c41847afd 100644 --- a/pkg/interface/src/views/apps/notifications/notifications.tsx +++ b/pkg/interface/src/views/apps/notifications/notifications.tsx @@ -8,7 +8,6 @@ import { Box, Col, Text, Row } from '@tlon/indigo-react'; import { Body } from '~/views/components/Body'; import { PropFunc } from '~/types/util'; import Inbox from './inbox'; -import NotificationPreferences from './preferences'; import { Dropdown } from '~/views/components/Dropdown'; import { FormikOnBlur } from '~/views/components/FormikOnBlur'; import GroupSearch from '~/views/components/GroupSearch'; @@ -76,18 +75,6 @@ export default function NotificationsScreen(props: any): ReactElement { borderBottomColor="washedGray" > Updates - - - - Inbox - - - - - Preferences - - - @@ -137,13 +124,6 @@ export default function NotificationsScreen(props: any): ReactElement { - {view === 'preferences' && ( - - )} {!view && } diff --git a/pkg/interface/src/views/apps/notifications/preferences.tsx b/pkg/interface/src/views/apps/notifications/preferences.tsx deleted file mode 100644 index d67ee18e1..000000000 --- a/pkg/interface/src/views/apps/notifications/preferences.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { ReactElement, useCallback } from 'react'; -import { Form, FormikHelpers } from 'formik'; -import _ from 'lodash'; - -import { Col, ManagedCheckboxField as Checkbox } from '@tlon/indigo-react'; -import { NotificationGraphConfig } from '@urbit/api'; - -import { FormikOnBlur } from '~/views/components/FormikOnBlur'; -import GlobalApi from '~/logic/api/global'; - -interface FormSchema { - mentions: boolean; - dnd: boolean; - watchOnSelf: boolean; - watching: string[]; -} - -interface NotificationPreferencesProps { - graphConfig: NotificationGraphConfig; - dnd: boolean; - api: GlobalApi; -} - -export default function NotificationPreferences( - props: NotificationPreferencesProps -): ReactElement { - const { graphConfig, api, dnd } = props; - - const initialValues: FormSchema = { - mentions: graphConfig.mentions, - watchOnSelf: graphConfig.watchOnSelf, - dnd, - }; - - const onSubmit = useCallback( - async (values: FormSchema, actions: FormikHelpers) => { - try { - const promises: Promise[] = []; - if (values.mentions !== graphConfig.mentions) { - promises.push(api.hark.setMentions(values.mentions)); - } - if (values.watchOnSelf !== graphConfig.watchOnSelf) { - promises.push(api.hark.setWatchOnSelf(values.watchOnSelf)); - } - if (values.dnd !== dnd && !_.isUndefined(values.dnd)) { - promises.push(api.hark.setDoNotDisturb(values.dnd)); - } - - await Promise.all(promises); - actions.setStatus({ success: null }); - actions.resetForm({ values: initialValues }); - } catch (e) { - console.error(e); - actions.setStatus({ error: e.message }); - } - }, - [api, graphConfig] - ); - - return ( - - - - - - - - - - ); -}