Merge remote-tracking branch 'origin/master' into next/arvo

This commit is contained in:
Philip Monk 2021-11-07 22:22:47 -08:00
commit 18e4c620df
No known key found for this signature in database
GPG Key ID: B66E1F02604E44EC
63 changed files with 444 additions and 939 deletions

View File

@ -74,15 +74,15 @@ jobs:
# for the docker build. We don't want in on Mac, where it isn't but
# it breaks the nix install. The two `if` clauses should be mutually
# exclusive
- uses: cachix/install-nix-action@v12
- uses: cachix/install-nix-action@v13
with:
extra_nix_config: |
system-features = nixos-test benchmark big-parallel kvm
if: ${{ matrix.os == 'ubuntu-latest' }}
- uses: cachix/install-nix-action@v12
- uses: cachix/install-nix-action@v13
if: ${{ matrix.os != 'ubuntu-latest' }}
- uses: cachix/cachix-action@v8
- uses: cachix/cachix-action@v10
with:
name: ares
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
@ -107,8 +107,8 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: cachix/install-nix-action@v12
- uses: cachix/cachix-action@v8
- uses: cachix/install-nix-action@v13
- uses: cachix/cachix-action@v10
with:
name: ares
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}

View File

@ -1,20 +0,0 @@
name: group-timer
on:
push:
branches:
- 'ops/group-timer'
jobs:
glob:
runs-on: ubuntu-latest
name: "Create and deploy a glob to ~difmex-passed"
steps:
- uses: actions/checkout@v2
with:
lfs: true
- uses: ./.github/actions/glob
with:
ship: 'difmex-passed'
credentials: ${{ secrets.JANEWAY_SERVICE_KEY }}
ssh-sec-key: ${{ secrets.JANEWAY_SSH_SEC_KEY }}
ssh-pub-key: ${{ secrets.JANEWAY_SSH_PUB_KEY }}

View File

@ -16,11 +16,11 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: cachix/install-nix-action@v12
- uses: cachix/install-nix-action@v13
with:
extra_nix_config: |
system-features = nixos-test benchmark big-parallel kvm
- uses: cachix/cachix-action@v8
- uses: cachix/cachix-action@v10
with:
name: ares
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}

View File

@ -17,8 +17,8 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: cachix/install-nix-action@v12
- uses: cachix/cachix-action@v8
- uses: cachix/install-nix-action@v13
- uses: cachix/cachix-action@v10
with:
name: ${{ secrets.CACHIX_NAME }}
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
@ -35,7 +35,8 @@ jobs:
- name: Run upload to bootstrap.urbit.org
run: |
version="$(cat ./pkg/urbit/version)"
system="$(nix eval --raw '(builtins.currentSystem)')"
system="$(nix-instantiate --eval --expr 'builtins.currentSystem')"
system=${system:1:${#system}-2}
target="gs://bootstrap.urbit.org/ci/urbit-v${version}-${system}-${GITHUB_SHA:0:9}.tgz"
gsutil cp -n ./result "$target"

View File

@ -1,11 +0,0 @@
> Why do I have a folder named ".vercel" in my project?
The ".vercel" folder is created when you link a directory to a Vercel project.
> What does the "project.json" file contain?
The "project.json" file contains:
- The ID of the Vercel project that you linked ("projectId")
- The ID of the user or team your Vercel project is owned by ("orgId")
> Should I commit the ".vercel" folder?
No, you should not share the ".vercel" folder with anyone.
Upon creation, it will be automatically added to your ".gitignore" file.

View File

@ -1 +0,0 @@
{"orgId":"EDiU8DZExvM9N4unZGYQbG3d","projectId":"prj_fbAU5smemBgtr5t8lsk5ZoT9zNtI"}

View File

@ -66,7 +66,7 @@
=/ fire-arm=nock
~| [%failed-to-compile-test-arm name]
q:(~(mint ut typ) p:!>(*tang) [%limb name])
[name |.(;;(tang .*(cor fire-arm)))]
[name |.(;;(tang ~>(%bout.[1 name] .*(cor fire-arm))))]
:: +has-test-prefix: does the arm define a test we should run?
::
++ has-test-prefix

View File

@ -478,6 +478,8 @@
"""
;li:"glob!"
==
(safari and internet explorer do not support uploading directory
trees properly. please glob from other browsers.)
;+ ?: =(~ desks)
;p:"no desks eligible for glob upload"
;form(method "post", enctype "multipart/form-data")

View File

@ -403,11 +403,12 @@
(~(put by half-open) bin now.bowl)
=/ existing (get-lid archive/time bin)
=/ new (merge-notification existing note)
=? half-open (lth 30 (lent body.new))
(~(del by half-open) bin)
=. poke-core
(put-lid archive/time bin new)
=. poke-core (del-lid lid bin)
=. poke-core (give %archived now.bowl unseen+~ (~(got re archive) time bin))
=. poke-core (give %archived now.bowl seen+~ (~(got re archive) time bin))
=. poke-core (give %archived time lid (~(got re archive) time bin))
$(bins t.bins)
::
++ read-count

View File

@ -44,7 +44,7 @@
%kick :_(this (drop safe-watch:kiln:cc))
::
%fact
?. ?=(%kiln-vats-diff p.cage.sign) `this
?. ?=(%kiln-vats-diff-0 p.cage.sign) `this
=+ !<(=diff:hood q.cage.sign)
?+ -.diff `this
::

View File

@ -24,11 +24,7 @@
def ~(. (default-agent this %|) bol)
io ~(. agentio bol)
::
++ on-init
^- (quip card _this)
=^ cards state
(put-entry:do q.byk.bol %tutorial %seen b+|)
[cards this]
++ on-init on-init:def
::
++ on-save !>(state)
::

View File

@ -4,7 +4,7 @@
glob-http+['https://bootstrap.urbit.org/glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob' 0v5.fdf99.nph65.qecq3.ncpjn.q13mb]
::glob-ames+~zod^0v0
base+'grid'
version+[1 0 0]
version+[1 0 1]
website+'https://tlon.io'
license+'MIT'
==

View File

@ -30,7 +30,7 @@
[%read-count place]
::
+$ state
$: %8
$: %9
places=(map place:hark stats:hark)
seen=timebox:hark
unseen=timebox:hark
@ -87,4 +87,17 @@
=/ actual-archive=notification:hark
(~(got re archive.state) (add *time ~s1) bin)
(expect-eq !>(expected-archive) !>(actual-archive))
::
++ test-half-open-capped
=| run=@ud
|-
?: =(run 31)
=+ !<(=state on-save:agent)
(expect-eq !>(~) !>(half-open.state))
=^ movs agent
(~(on-poke agent (bowl run)) %hark-action !>((add-note run)))
=^ mavs agent
(~(on-poke agent (bowl run)) %hark-action !>(read-count))
$(run +(run))
::
--

View File

@ -22,16 +22,19 @@ export const RemoveApp = () => {
return (
<Dialog open onOpenChange={(open) => !open && history.push('/')}>
<DialogContent showClose={false} className="space-y-6" containerClass="w-full max-w-md">
<h1 className="h4">Remove &ldquo;{getAppName(docket)}&rdquo;?</h1>
<h1 className="h4">Uninstall &ldquo;{getAppName(docket)}&rdquo;?</h1>
<p className="text-base tracking-tight pr-6">
This will remove the software&apos;s tile from your home screen.
The app tile will be removed from Landscape, all processes will be stopped and their data archived, and the app will stop receiving updates.
</p>
<p className="text-base tracking-tight pr-6">
If the app is reinstalled, the archived data will be restored and you'll be able to pick up where you left off.
</p>
<div className="flex space-x-6">
<DialogClose as={Button} variant="secondary">
Cancel
</DialogClose>
<DialogClose as={Button} onClick={handleRemoveApp}>
Remove &ldquo;{getAppName(docket)}&rdquo;
Uninstall
</DialogClose>
</div>
</DialogContent>

View File

@ -27,15 +27,17 @@ export const SuspendApp = () => {
<DialogContent showClose={false} className="space-y-6" containerClass="w-full max-w-md">
<h1 className="h4">Suspend &ldquo;{getAppName(charge)}&rdquo;</h1>
<p className="text-base tracking-tight pr-6">
Suspending an app will turn off automatic updates. You cannot use an app when it is
suspended, but you can resume it at any time.
All processes will be stopped and data archived. The app will continue to receive updates from its publisher.
</p>
<p className="text-base tracking-tight pr-6">
When unsuspended, archived data will be loaded and all processes will resume running, so you can pick up where you left off.
</p>
<div className="flex space-x-6">
<DialogClose as={Button} variant="secondary">
Cancel
</DialogClose>
<DialogClose as={Button} onClick={handleSuspendApp}>
Suspend &ldquo;{getAppName(charge)}&rdquo;
Suspend
</DialogClose>
</div>
</DialogContent>

View File

@ -85,7 +85,7 @@ export const TileMenu = ({ desk, chad, menuColor, lightText, className }: TileMe
)}
{suspended && <Item onSelect={() => toggleDocket(desk)}>Resume App</Item>}
<Item as={Link} to={`/app/${desk}/remove`} onSelect={linkOnSelect}>
Remove App
Uninstall App
</Item>
</DropdownMenu.Group>
<DropdownMenu.Arrow className="w-4 h-[10px] fill-current" style={{ color: menuColor }} />

View File

@ -1,5 +1,5 @@
name: urbit-king
version: 1.6
version: 1.7
license: MIT
license-file: LICENSE
data-files:

View File

@ -111,11 +111,6 @@ module.exports = {
'process.env.LANDSCAPE_SHORTHASH': JSON.stringify(GIT_DESC),
'process.env.LANDSCAPE_STORAGE_VERSION': JSON.stringify(Date.now()),
'process.env.LANDSCAPE_LAST_WIPE': JSON.stringify('2021-10-20'),
'process.env.TUTORIAL_HOST': JSON.stringify('~difmex-passed'),
'process.env.TUTORIAL_GROUP': JSON.stringify('beginner-island'),
'process.env.TUTORIAL_CHAT': JSON.stringify('introduce-yourself-7010'),
'process.env.TUTORIAL_BOOK': JSON.stringify('guides-9684'),
'process.env.TUTORIAL_LINKS': JSON.stringify('community-articles-2143')
}),
// new CleanWebpackPlugin(),

View File

@ -75,11 +75,6 @@ module.exports = {
'process.env.LANDSCAPE_SHORTHASH': JSON.stringify(GIT_DESC),
'process.env.LANDSCAPE_STORAGE_VERSION': Date.now().toString(),
'process.env.LANDSCAPE_LAST_WIPE': '2021-10-20',
'process.env.TUTORIAL_HOST': JSON.stringify('~difmex-passed'),
'process.env.TUTORIAL_GROUP': JSON.stringify('beginner-island'),
'process.env.TUTORIAL_CHAT': JSON.stringify('introduce-yourself-7010'),
'process.env.TUTORIAL_BOOK': JSON.stringify('guides-9684'),
'process.env.TUTORIAL_LINKS': JSON.stringify('community-articles-2143')
}),
new HtmlWebpackPlugin({
title: 'Groups',

View File

@ -93,7 +93,6 @@ const otherIndex = function(config) {
messages: result('Messages', '/~landscape/messages', 'messages', null),
logout: result('Log Out', '/~/logout', 'logout', null)
};
other.push(result('Tutorial', '/?tutorial=true', 'tutorial', null));
for(const cat of config.categories) {
if(idx[cat]) {
other.push(idx[cat]);

View File

@ -1,173 +0,0 @@
import { Associations } from '@urbit/api';
import { AlignX, AlignY } from '~/logic/lib/relativePosition';
import { TutorialProgress } from '~/types';
import { Direction } from '~/views/components/Triangle';
export const MODAL_WIDTH = 256;
export const MODAL_HEIGHT = 256;
export const MODAL_WIDTH_PX = `${MODAL_WIDTH}px`;
export const MODAL_HEIGHT_PX = `${MODAL_HEIGHT}px`;
export const TUTORIAL_HOST = process.env.TUTORIAL_HOST!;
export const TUTORIAL_GROUP = process.env.TUTORIAL_GROUP!;
export const TUTORIAL_CHAT = process.env.TUTORIAL_CHAT!;
export const TUTORIAL_BOOK = process.env.TUTORIAL_BOOK!;
export const TUTORIAL_LINKS = process.env.TUTORIAL_LINKS!;
export const TUTORIAL_GROUP_RESOURCE = `/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}` ;
interface StepDetail {
title: string;
description: string;
url: string;
alignX: AlignX | AlignX[];
alignY: AlignY | AlignY[];
offsetX: number;
offsetY: number;
arrow?: Direction;
}
export function hasTutorialGroup(props: { associations: Associations }) {
return (
TUTORIAL_GROUP_RESOURCE in props.associations.groups
);
}
export const getTrianglePosition = (dir: Direction) => {
const midY = `${MODAL_HEIGHT / 2 - 8}px`;
const midX = `${MODAL_WIDTH / 2 - 8}px`;
switch(dir) {
case 'East':
return {
top: midY,
right: '-32px'
};
case 'West':
return {
top: midY,
left: '-32px'
};
case 'North':
return {
top: '-32px',
left: midX
};
case 'South':
return {
bottom: '-32px',
left: midX
};
}
};
export const progressDetails: Record<TutorialProgress, StepDetail> = {
hidden: {} as any,
exit: {} as any,
done: {
title: 'End',
description:
'This tutorial is finished. Would you like to leave Beginner Island?',
url: '/',
alignX: 'right',
alignY: 'top',
offsetX: MODAL_WIDTH + 8,
offsetY: 0
},
start: {
title: 'New Group added',
description:
'We just added you to the Beginner island group to show you around. This group is public, but other groups can be private',
url: '/',
alignX: 'right',
alignY: 'top',
arrow: 'West',
offsetX: MODAL_WIDTH + 24,
offsetY: 64
},
'group-desc': {
title: 'What\'s a group',
description:
'A group contains members and tends to be centered around a topic or multiple topics.',
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`,
alignX: 'left',
alignY: 'top',
arrow: 'East',
offsetX: MODAL_WIDTH + 24,
offsetY: 80
},
channels: {
title: 'Channels',
description:
'Inside a group you have three types of Channels: Chat, Collection, or Notebook. Mix and match these depending on your group context!',
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`,
alignY: 'top',
alignX: 'right',
arrow: 'West',
offsetX: MODAL_WIDTH + 24,
offsetY: -8
},
chat: {
title: 'Chat',
description:
'Chat channels are for messaging within your group. Direct Messages can be accessed from Messages in the top right',
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/chat/ship/${TUTORIAL_HOST}/${TUTORIAL_CHAT}`,
alignY: 'top',
arrow: 'North',
alignX: 'right',
offsetY: -56,
offsetX: -8
},
link: {
title: 'Collection',
description:
'A collection is where you can share and view links, images, and other media within your group. Every item in a Collection can have its own comment thread.',
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/link/ship/${TUTORIAL_HOST}/${TUTORIAL_LINKS}`,
alignY: 'top',
alignX: 'right',
arrow: 'North',
offsetX: -8,
offsetY: -56
},
publish: {
title: 'Notebook',
description:
'Notebooks are for creating long-form content within your group. Use markdown to create rich posts with headers, lists and images.',
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/publish/ship/${TUTORIAL_HOST}/${TUTORIAL_BOOK}`,
alignY: 'top',
alignX: 'right',
arrow: 'North',
offsetX: -8,
offsetY: -56
},
notifications: {
title: 'Notifications',
description: 'You will get updates from subscribed channels and mentions here. You can access Notifications through Leap.',
url: '/~notifications',
alignY: 'top',
alignX: 'left',
arrow: 'North',
offsetX: 0,
offsetY: -48
},
profile: {
title: 'Profile',
description:
'Your profile is customizable and can be shared with other ships. Enter as much or as little information as youd like.',
url: `/~profile/~${window.ship}`,
alignY: 'top',
alignX: 'right',
arrow: 'South',
offsetX: -300 + MODAL_WIDTH / 2,
offsetY: -4
},
leap: {
title: 'Leap',
description:
'Leap allows you to go to a specific channel, message, collection, profile or group simply by typing in a command or selecting a shortcut from the dropdown menu.',
url: `/~profile/~${window.ship}`,
alignY: 'top',
alignX: 'left',
arrow: 'North',
offsetX: 76,
offsetY: -48
}
};

View File

@ -15,11 +15,12 @@ export function useResize<T extends HTMLElement>(
callback(entry, observer);
}
}
let el = ref.current;
const resizeObs = new ResizeObserver(observer);
resizeObs.observe(ref.current, { box: 'border-box' });
resizeObs.observe(el, { box: 'border-box' });
return () => {
resizeObs.unobserve(ref.current);
resizeObs.unobserve(el);
};
}, [callback]);

View File

@ -4,14 +4,11 @@ import { patp2dec } from 'urbit-ob';
import f from 'lodash/fp';
import { Association, Contact, Patp } from '@urbit/api';
import { enableMapSet } from 'immer';
import useSettingsState from '../state/settings';
/* eslint-disable max-lines */
import anyAscii from 'any-ascii';
import { sigil as sigiljs, stringRenderer } from '@tlon/sigil-js';
import bigInt, { BigInteger } from 'big-integer';
import { foregroundFromBackground } from '~/logic/lib/sigil';
import { IconRef, Workspace } from '~/types';
import useContactState from '../state/contact';
enableMapSet();
@ -462,13 +459,6 @@ export function pluralize(text: string, isPlural = false, vowel = false) {
return isPlural ? `${text}s` : `${vowel ? 'an' : 'a'} ${text}`;
}
// Hide is an optional second parameter for when this function is used in class components
export function useShowNickname(contact: Contact | null, hide?: boolean): boolean {
const hideState = useSettingsState(state => state.calm.hideNicknames);
const hideNicknames = typeof hide !== 'undefined' ? hide : hideState;
return Boolean(contact && contact.nickname && !hideNicknames);
}
interface useHoveringInterface {
hovering: boolean;
bind: {
@ -513,21 +503,6 @@ export const svgDataURL = svg => 'data:image/svg+xml;base64,' + btoa(svg);
export const svgBlobURL = svg => URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' }));
export const favicon = () => {
let background = '#ffffff';
const contacts = useContactState.getState().contacts;
if (Object.prototype.hasOwnProperty.call(contacts, `~${window.ship}`)) {
background = `#${uxToHex(contacts[`~${window.ship}`].color)}`;
}
const foreground = foregroundFromBackground(background);
const svg = sigiljs({
patp: window.ship,
renderer: stringRenderer,
size: 16,
colors: [background, foreground]
});
return svg;
};
export function binaryIndexOf(arr: BigInteger[], target: BigInteger): number | undefined {
let leftBound = 0;

View File

@ -1,4 +1,4 @@
import { Contact, deSig, Patp, Rolodex } from '@urbit/api';
import { Contact, deSig, Patp, Rolodex, uxToHex } from '@urbit/api';
import { useCallback } from 'react';
import _ from 'lodash';
import { reduce, reduceNacks } from '../reducers/contact-update';
@ -7,6 +7,8 @@ import {
createSubscription,
reduceStateN
} from './base';
import { sigil as sigiljs, stringRenderer } from '@tlon/sigil-js';
import { foregroundFromBackground } from '~/logic/lib/sigil';
export interface ContactState {
contacts: Rolodex;
@ -51,4 +53,21 @@ export function useOurContact() {
return useContact(`~${window.ship}`);
}
export const favicon = () => {
let background = '#ffffff';
const contacts = useContactState.getState().contacts;
if (Object.prototype.hasOwnProperty.call(contacts, `~${window.ship}`)) {
background = `#${uxToHex(contacts[`~${window.ship}`].color)}`;
}
const foreground = foregroundFromBackground(background);
const svg = sigiljs({
patp: window.ship,
renderer: stringRenderer,
size: 16,
colors: [background, foreground]
});
return svg;
};
export default useContactState;

View File

@ -9,7 +9,8 @@ import {
harkBinToId,
decToUd,
unixToDa,
opened
opened,
markEachAsRead
} from '@urbit/api';
import { Poke } from '@urbit/http-api';
import { patp2dec } from 'urbit-ob';
@ -25,6 +26,7 @@ import {
reduceStateN
} from './base';
import { reduce, reduceGraph, reduceGroup } from '../reducers/hark-update';
import useMetadataState from './metadata';
export const HARK_FETCH_MORE_COUNT = 3;
@ -43,6 +45,8 @@ export interface HarkState {
unreads: Unreads;
archiveNote: (bin: HarkBin, lid: HarkLid) => Promise<void>;
readCount: (path: string) => Promise<void>;
readGraph: (graph: string) => Promise<void>;
readGroup: (group: string) => Promise<void>;
}
const useHarkState = createState<HarkState>(
@ -54,6 +58,38 @@ const useHarkState = createState<HarkState>(
poke: async (poke: Poke<any>) => {
await pokeOptimisticallyN(useHarkState, poke, [reduce]);
},
readGraph: async (graph: string) => {
const prefix = `/graph/${graph.slice(6)}`;
let counts = [] as string[];
let eaches = [] as [string, string][];
Object.entries(get().unreads).forEach(([path, unreads]) => {
if (path.startsWith(prefix)) {
if(unreads.count > 0) {
counts.push(path);
}
unreads.each.forEach(unread => {
eaches.push([path, unread]);
});
}
});
get().set(draft => {
counts.forEach(path => {
draft.unreads[path].count = 0;
});
eaches.forEach(([path, each]) => {
draft.unreads[path].each = [];
});
});
await Promise.all([
...counts.map(path => markCountAsRead({ desk: window.desk, path })),
...eaches.map(([path, each]) => markEachAsRead({ desk: window.desk, path }, each))
].map(pok => api.poke(pok)));
},
readGroup: async (group: string) => {
const graphs =
_.pickBy(useMetadataState.getState().associations.graph, a => a.group === group);
await Promise.all(Object.keys(graphs).map(get().readGraph));
},
readCount: async (path) => {
const poke = markCountAsRead({ desk: (window as any).desk, path });
await pokeOptimisticallyN(useHarkState, poke, [reduce]);

View File

@ -3,7 +3,7 @@ import f from 'lodash/fp';
import React from 'react';
import create, { State } from 'zustand';
import { persist } from 'zustand/middleware';
import { BackgroundConfig, LeapCategories, RemoteContentPolicy, TutorialProgress, tutorialProgress } from '~/types/local-update';
import { BackgroundConfig, LeapCategories, RemoteContentPolicy } from '~/types/local-update';
import airlock from '~/logic/api';
import { bootstrapApi } from '../api/bootstrap';
import { clearStorageMigration, createStorageKey, storageVersion, wait } from '~/logic/lib/util';
@ -15,15 +15,9 @@ export interface LocalState {
hideAvatars: boolean;
hideNicknames: boolean;
remoteContentPolicy: RemoteContentPolicy;
tutorialProgress: TutorialProgress;
hideGroups: boolean;
hideUtilities: boolean;
tutorialRef: HTMLElement | null,
hideTutorial: () => void;
nextTutStep: () => void;
prevTutStep: () => void;
hideLeapCats: LeapCategories[];
setTutorialRef: (el: HTMLElement | null) => void;
dark: boolean;
mobile: boolean;
breaks: {
@ -62,27 +56,6 @@ const useLocalState = create<LocalStateZus>(persist((set, get) => ({
hideLeapCats: [],
hideGroups: false,
hideUtilities: false,
tutorialProgress: 'hidden',
tutorialRef: null,
setTutorialRef: (el: HTMLElement | null) => set(produce((state) => {
state.tutorialRef = el;
})),
hideTutorial: () => set(produce((state) => {
state.tutorialProgress = 'hidden';
state.tutorialRef = null;
})),
nextTutStep: () => set(produce((state) => {
const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress);
if(currIdx < tutorialProgress.length) {
state.tutorialProgress = tutorialProgress[currIdx + 1];
}
})),
prevTutStep: () => set(produce((state) => {
const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress);
if(currIdx > 0) {
state.tutorialProgress = tutorialProgress[currIdx - 1];
}
})),
remoteContentPolicy: {
imageShown: true,
audioShown: true,
@ -132,8 +105,8 @@ const useLocalState = create<LocalStateZus>(persist((set, get) => ({
set: fn => set(produce(fn))
}), {
blacklist: [
'suspendedFocus', 'toggleOmnibox', 'omniboxShown', 'tutorialProgress',
'prevTutStep', 'nextTutStep', 'tutorialRef', 'setTutorialRef', 'subscription',
'suspendedFocus', 'toggleOmnibox', 'omniboxShown',
'subscription',
'errorCount', 'breaks'
],
name: createStorageKey('local'),

View File

@ -16,7 +16,7 @@ import {
import { useCallback } from 'react';
import { reduceUpdate } from '../reducers/settings-update';
import airlock from '~/logic/api';
import { getDeskSettings, Value } from '@urbit/api';
import { Contact, getDeskSettings, Value } from '@urbit/api';
import { putEntry } from '@urbit/api/settings';
export interface ShortcutMapping {
@ -49,10 +49,6 @@ export interface SettingsState {
leap: {
categories: LeapCategories[];
};
tutorial: {
seen: boolean;
joined?: number;
};
}
export const selectSettingsState = <K extends keyof (SettingsState & BaseState<SettingsState>)>(keys: K[]) =>
@ -88,10 +84,6 @@ const useSettingsState = createState<SettingsState>(
leap: {
categories: leapCategories
},
tutorial: {
seen: true,
joined: undefined
},
keyboard: {
cycleForward: 'ctrl+\'',
cycleBack: 'ctrl+;',
@ -139,4 +131,11 @@ export function useTheme() {
return useSettingsState(selTheme);
}
// Hide is an optional second parameter for when this function is used in class components
export function useShowNickname(contact: Contact | null, hide?: boolean): boolean {
const hideState = useSettingsState(state => state.calm.hideNicknames);
const hideNicknames = typeof hide !== 'undefined' ? hide : hideState;
return Boolean(contact && contact.nickname && !hideNicknames);
}
export default useSettingsState;

View File

@ -1,10 +1,7 @@
export const tutorialProgress = ['hidden', 'start', 'group-desc', 'channels', 'chat', 'link', 'publish', 'profile', 'leap', 'notifications', 'done', 'exit'] as const;
export const leapCategories = ['mychannel', 'messages', 'updates', 'profile', 'logout'];
export type LeapCategories = typeof leapCategories[number];
export type TutorialProgress = typeof tutorialProgress[number];
interface LocalUpdateSetDark {
setDark: boolean;
}

View File

@ -10,16 +10,15 @@ import { hot } from 'react-hot-loader/root';
import { BrowserRouter as Router, withRouter } from 'react-router-dom';
import styled, { ThemeProvider } from 'styled-components';
import gcpManager from '~/logic/lib/gcpManager';
import { favicon, svgDataURL } from '~/logic/lib/util';
import { svgDataURL } from '~/logic/lib/util';
import withState from '~/logic/lib/withState';
import useContactState from '~/logic/state/contact';
import useContactState, { favicon } from '~/logic/state/contact';
import useLocalState from '~/logic/state/local';
import useSettingsState from '~/logic/state/settings';
import useGraphState from '~/logic/state/graph';
import { ShortcutContextProvider } from '~/logic/lib/shortcutContext';
import ErrorBoundary from '~/views/components/ErrorBoundary';
import { TutorialModal } from '~/views/landscape/components/TutorialModal';
import './apps/chat/css/custom.css';
import Omnibox from './components/leap/Omnibox';
import StatusBar from './components/StatusBar';
@ -171,7 +170,6 @@ class App extends React.Component {
</Helmet>
<Root>
<Router basename="/apps/landscape">
<TutorialModal />
<ErrorBoundary>
<StatusBarWithRouter
props={this.props}

View File

@ -13,11 +13,11 @@ import { useIdlingState } from '~/logic/lib/idling';
import { Sigil } from '~/logic/lib/sigil';
import { useCopy } from '~/logic/lib/useCopy';
import {
cite, daToUnix, useHovering, useShowNickname, uxToHex
cite, daToUnix, useHovering, uxToHex
} from '~/logic/lib/util';
import { useContact } from '~/logic/state/contact';
import { useDark } from '~/logic/state/join';
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
import useSettingsState, { selectCalmState, useShowNickname } from '~/logic/state/settings';
import { Dropdown } from '~/views/components/Dropdown';
import ProfileOverlay from '~/views/components/ProfileOverlay';
import { GraphContent } from '~/views/landscape/components/Graph/GraphContent';

View File

@ -1,27 +1,11 @@
/* eslint-disable max-lines-per-function */
import { Box, Button, Col, Icon, Row, Text } from '@tlon/indigo-react';
import f from 'lodash/fp';
import React, { ReactElement, useEffect, useMemo, useState } from 'react';
import { Box, Icon, Row, Text } from '@tlon/indigo-react';
import React, { ReactElement } from 'react';
import { Helmet } from 'react-helmet';
import { Route } from 'react-router-dom';
import styled from 'styled-components';
import {
hasTutorialGroup,
TUTORIAL_BOOK,
TUTORIAL_CHAT,
TUTORIAL_GROUP,
TUTORIAL_HOST,
TUTORIAL_LINKS
} from '~/logic/lib/tutorialModal';
import { useModal } from '~/logic/lib/useModal';
import { useQuery } from '~/logic/lib/useQuery';
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
import useHarkState from '~/logic/state/hark';
import useLocalState from '~/logic/state/local';
import useMetadataState from '~/logic/state/metadata';
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
import { StarIcon } from '~/views/components/StarIcon';
import { StatelessAsyncButton } from '~/views/components/StatelessAsyncButton';
import { JoinGroup } from '~/views/landscape/components/JoinGroup';
import { NewGroup } from '~/views/landscape/components/NewGroup';
import Groups from './components/Groups';
@ -30,9 +14,6 @@ import Tiles from './components/tiles';
import Tile from './components/tiles/tile';
import { Invite } from './components/Invite';
import './css/custom.css';
import { join } from '@urbit/api/groups';
import { joinGraph } from '@urbit/api/graph';
import airlock from '~/logic/api';
const ScrollbarLessBox = styled(Box)`
scrollbar-width: none !important;
@ -42,111 +23,14 @@ const ScrollbarLessBox = styled(Box)`
}
`;
const tutSelector = f.pick(['tutorialProgress', 'nextTutStep', 'hideGroups']);
interface LaunchAppProps {
connection: string;
}
export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
const { connection } = props;
const [exitingTut, setExitingTut] = useState(false);
const seen = useSettingsState(s => s?.tutorial?.seen) ?? true;
const associations = useMetadataState(s => s.associations);
const hasLoaded = useMemo(() => Boolean(connection === 'connected'), [connection]);
const notificationsCount = useHarkState(state => state.notificationsCount);
const calmState = useSettingsState(selectCalmState);
const { hideUtilities } = calmState;
const { tutorialProgress, nextTutStep } = useLocalState(tutSelector);
let { hideGroups } = useLocalState(tutSelector);
!hideGroups ? { hideGroups } = calmState : null;
const waiter = useWaitForProps({ ...props, associations });
const { query } = useQuery();
const { modal, showModal } = useModal({
position: 'relative',
maxWidth: '350px',
modal: function modal(dismiss) {
const onDismiss = (e) => {
const { putEntry } = useSettingsState.getState();
e.stopPropagation();
putEntry('tutorial', 'seen', true);
dismiss();
};
const onContinue = async (e) => {
const { putEntry } = useSettingsState.getState();
e.stopPropagation();
if (!hasTutorialGroup({ associations })) {
await airlock.poke(join(TUTORIAL_HOST, TUTORIAL_GROUP));
await putEntry('tutorial', 'joined', Date.now());
await waiter(hasTutorialGroup);
await Promise.all(
[TUTORIAL_BOOK, TUTORIAL_CHAT, TUTORIAL_LINKS].map(graph => airlock.thread(joinGraph(TUTORIAL_HOST, graph))));
await waiter((p) => {
return `/ship/${TUTORIAL_HOST}/${TUTORIAL_CHAT}` in p.associations.graph &&
`/ship/${TUTORIAL_HOST}/${TUTORIAL_BOOK}` in p.associations.graph &&
`/ship/${TUTORIAL_HOST}/${TUTORIAL_LINKS}` in p.associations.graph;
});
}
nextTutStep();
dismiss();
};
return exitingTut ? (
<Col maxWidth="350px" p={3}>
<Icon icon="Info" fill="black"></Icon>
<Text my={3} lineHeight="tall">
You can always restart the tutorial by typing &ldquo;tutorial&rdquo; in Leap
</Text>
<Row gapX={2} justifyContent="flex-end">
<Button primary onClick={onDismiss}>Ok</Button>
</Row>
</Col>
) : (
<Col maxWidth="350px" p={3}>
<Box position="absolute" left="-16px" top="-16px">
<StarIcon width="32px" height="32px" color="blue" display="block" />
</Box>
<Text mb={3} lineHeight="tall" fontWeight="medium">Welcome</Text>
<Text mb={3} lineHeight="tall">
You have been invited to use Groups, an interface to chat
and interact with communities
<br />
Would you like a tour of Groups?
</Text>
<Row gapX={2} justifyContent="flex-end">
<Button
backgroundColor="washedGray"
onClick={() => setExitingTut(true)}
>Skip</Button>
<StatelessAsyncButton primary onClick={onContinue}>
Yes
</StatelessAsyncButton>
</Row>
</Col>
);
}
});
useEffect(() => {
if(query.get('tutorial')) {
if (hasTutorialGroup({ associations })) {
if (nextTutStep) {
nextTutStep();
}
} else {
showModal();
}
}
}, [query, showModal]);
useEffect(() => {
if(hasLoaded && !seen && tutorialProgress === 'hidden') {
showModal();
}
}, [seen, hasLoaded]);
const { hideUtilities, hideGroups } = calmState;
return (
<>
@ -157,7 +41,6 @@ export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
<Invite />
</Route>
<ScrollbarLessBox height='100%' overflowY='scroll' display="flex" flexDirection="column">
{modal}
<Box
mx={2}
display='grid'

View File

@ -1,30 +1,25 @@
import { Box, Col, Text } from '@tlon/indigo-react';
import { Association, Associations, Unreads } from '@urbit/api';
import f from 'lodash/fp';
import moment from 'moment';
import React, { useRef } from 'react';
import React from 'react';
import { getNotificationCount } from '~/logic/lib/hark';
import { TUTORIAL_GROUP, TUTORIAL_GROUP_RESOURCE, TUTORIAL_HOST } from '~/logic/lib/tutorialModal';
import { alphabeticalOrder } from '~/logic/lib/util';
import useGroupState from '~/logic/state/group';
import useHarkState, { selHarkGraph } from '~/logic/state/hark';
import useMetadataState from '~/logic/state/metadata';
import useSettingsState, { selectCalmState, SettingsState } from '~/logic/state/settings';
import { useTutorialModal } from '~/views/components/useTutorialModal';
import useSettingsState, {
selectCalmState
} from '~/logic/state/settings';
import Tile from '../components/tiles/tile';
const sortGroupsAlph = (a: Association, b: Association) =>
a.group === TUTORIAL_GROUP_RESOURCE
? -1
: b.group === TUTORIAL_GROUP_RESOURCE
? 1
: alphabeticalOrder(a.metadata.title, b.metadata.title);
alphabeticalOrder(a.metadata.title, b.metadata.title);
const getGraphUnreads = (associations: Associations) => {
const state = useHarkState.getState();
const selUnread = (graph: string) => {
const { count, each } = selHarkGraph(graph)(state);
const result = count + each.length;
const result = count + each.length;
return result;
};
return (path: string) =>
@ -36,7 +31,10 @@ const getGraphUnreads = (associations: Associations) => {
)(associations.graph);
};
const getGraphNotifications = (associations: Associations, unreads: Unreads) => (path: string) =>
const getGraphNotifications = (
associations: Associations,
unreads: Unreads
) => (path: string) =>
f.flow(
f.pickBy((a: Association) => a.group === path),
f.map('resource'),
@ -52,8 +50,11 @@ export default function Groups(props: Parameters<typeof Box>[0]) {
const groups = Object.values(associations?.groups || {})
.filter(e => e?.group in groupState)
.sort(sortGroupsAlph);
const graphUnreads = getGraphUnreads(associations || {} as Associations);
const graphNotifications = getGraphNotifications(associations || {} as Associations, unreads);
const graphUnreads = getGraphUnreads(associations || ({} as Associations));
const graphNotifications = getGraphNotifications(
associations || ({} as Associations),
unreads
);
return (
<>
@ -83,37 +84,26 @@ interface GroupProps {
unreads: number;
first: boolean;
}
const selectJoined = (s: SettingsState) => s.tutorial.joined;
function Group(props: GroupProps) {
const { path, title, unreads, updates, first = false } = props;
const anchorRef = useRef<HTMLDivElement>(null);
const isTutorialGroup = path === `/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`;
useTutorialModal(
'start',
isTutorialGroup,
anchorRef
);
const { hideUnreads } = useSettingsState(selectCalmState);
const joined = useSettingsState(selectJoined);
const days = Math.max(0, Math.floor(moment.duration(moment(joined)
.add(14, 'days')
.diff(moment()))
.as('days'))) || 0;
return (
<Tile ref={anchorRef} position="relative" bg={isTutorialGroup ? 'lightBlue' : undefined} to={`/~landscape${path}`} gridColumnStart={first ? 1 : null}>
<Tile
position="relative"
to={`/~landscape${path}`}
gridColumnStart={first ? 1 : null}
>
<Col height="100%" justifyContent="space-between">
<Text>{title}</Text>
{!hideUnreads && (<Col>
{isTutorialGroup && joined &&
(<Text>{days} day{days !== 1 && 's'} remaining</Text>)
}
{updates > 0 &&
(<Text mt={1} color="blue">{updates} update{updates !== 1 && 's'} </Text>)
}
{unreads > 0 &&
(<Text color="lightGray">{unreads}</Text>)
}
</Col>
{!hideUnreads && (
<Col>
{updates > 0 && (
<Text mt={1} color="blue">
{updates} update{updates !== 1 && 's'}{' '}
</Text>
)}
{unreads > 0 && <Text color="lightGray">{unreads}</Text>}
</Col>
)}
</Col>
</Tile>

View File

@ -1,11 +1,10 @@
import { Action, Box, Col, Icon, Row, Text } from '@tlon/indigo-react';
import React, { ReactElement, ReactNode, useEffect, useRef } from 'react';
import React, { ReactElement, ReactNode, useEffect } from 'react';
import Helmet from 'react-helmet';
import { Link, Route, Switch, useHistory, useLocation } from 'react-router-dom';
import useHarkState from '~/logic/state/hark';
import { Body } from '~/views/components/Body';
import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
import { useTutorialModal } from '~/views/components/useTutorialModal';
import { Archive } from './Archive';
import { NewBox } from './NewBox';
@ -41,8 +40,6 @@ export function NavLink({
export default function NotificationsScreen(props: any): ReactElement {
const relativePath = (p: string) => baseUrl + p;
const anchorRef = useRef<HTMLElement | null>(null);
useTutorialModal('notifications', true, anchorRef);
const notificationsCount = useHarkState(state => state.notificationsCount);
const onReadAll = async () => {};
@ -89,7 +86,6 @@ export default function NotificationsScreen(props: any): ReactElement {
fontWeight="bold"
fontSize={2}
lineHeight={1}
ref={anchorRef}
>
Notifications
</Text>

View File

@ -1,6 +1,6 @@
import { BaseImage, Box, Center, Row, Text } from '@tlon/indigo-react';
import { retrieve } from '@urbit/api';
import React, { ReactElement, useEffect, useRef } from 'react';
import React, { ReactElement, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { Sigil } from '~/logic/lib/sigil';
import { uxToHex } from '~/logic/lib/util';
@ -8,7 +8,6 @@ import useContactState from '~/logic/state/contact';
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
import RichText from '~/views/components/RichText';
import { SetStatusBarModal } from '~/views/components/SetStatusBarModal';
import { useTutorialModal } from '~/views/components/useTutorialModal';
import { EditProfile } from './EditProfile';
import { ViewProfile } from './ViewProfile';
import airlock from '~/logic/api';
@ -32,10 +31,6 @@ export function ProfileImages(props: any): ReactElement {
const { contact, hideCover, ship } = props;
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : '#000000';
const anchorRef = useRef<HTMLDivElement>(null);
useTutorialModal('profile', ship === `~${window.ship}`, anchorRef);
const cover =
contact?.cover && !hideCover ? (
<BaseImage
@ -69,7 +64,7 @@ export function ProfileImages(props: any): ReactElement {
return (
<>
<Row ref={anchorRef} width='100%' height='400px' position='relative'>
<Row width='100%' height='400px' position='relative'>
{cover}
<Center position='absolute' width='100%' height='100%'>
{props.children}

View File

@ -61,6 +61,9 @@ export function Note(props: NoteProps & RouteComponentProps) {
const noteId = bigInt(index[1]);
useEffect(() => {
airlock.poke(markEachAsRead(toHarkPlace(association.resource), `/${index[1]}`));
// Unread may be malformed, dismiss anyway
// TODO: remove when %read-graph is implemented
airlock.poke(markEachAsRead(toHarkPlace(association.resource), `/1`));
}, [association, props.note]);
const adminLinks: JSX.Element[] = [];

View File

@ -2,11 +2,12 @@ import { Box, Button, Col, Row, Text } from '@tlon/indigo-react';
import { Association, Graph, readGraph } from '@urbit/api';
import React, { ReactElement, useCallback } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { useShowNickname } from '~/logic/lib/util';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
import { useShowNickname } from '~/logic/state/settings';
import airlock from '~/logic/api';
import { NotebookPosts } from './NotebookPosts';
import useHarkState from '~/logic/state/hark';
interface NotebookProps {
ship: string;
@ -35,7 +36,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps): ReactEleme
const showNickname = useShowNickname(contact);
const readBook = useCallback(() => {
airlock.poke(readGraph(association.resource));
useHarkState.getState().readGraph(association.resource);
}, [association.resource]);
if (!group) {

View File

@ -3,10 +3,10 @@ import moment from 'moment';
import React, { ReactElement, ReactNode } from 'react';
import { Sigil } from '~/logic/lib/sigil';
import { useCopy } from '~/logic/lib/useCopy';
import { cite, useShowNickname, uxToHex } from '~/logic/lib/util';
import { cite, uxToHex } from '~/logic/lib/util';
import { useContact } from '~/logic/state/contact';
import { useDark } from '~/logic/state/join';
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
import useSettingsState, { selectCalmState, useShowNickname } from '~/logic/state/settings';
import { PropFunc } from '~/types';
import ProfileOverlay from './ProfileOverlay';
import Timestamp from './Timestamp';

View File

@ -2,8 +2,9 @@ import { Text } from '@tlon/indigo-react';
import { Contact, Content, Group } from '@urbit/api';
import React from 'react';
import { referenceToPermalink } from '~/logic/lib/permalinks';
import { cite, deSig, useShowNickname } from '~/logic/lib/util';
import { cite, deSig } from '~/logic/lib/util';
import { useContact } from '~/logic/state/contact';
import { useShowNickname } from '~/logic/state/settings';
import { PropFunc } from '~/types';
import ProfileOverlay from '~/views/components/ProfileOverlay';
import RichText from '~/views/components/RichText';

View File

@ -19,9 +19,8 @@ import { getRelativePosition } from '~/logic/lib/relativePosition';
import { Sigil } from '~/logic/lib/sigil';
import { useCopy } from '~/logic/lib/useCopy';
import { useOutsideClick } from '~/logic/lib/useOutsideClick';
import { useShowNickname } from '~/logic/lib/util';
import { useContact } from '~/logic/state/contact';
import useSettingsState, { SettingsState } from '~/logic/state/settings';
import useSettingsState, { SettingsState, useShowNickname } from '~/logic/state/settings';
import { Portal } from './Portal';
import { ProfileStatus } from './ProfileStatus';
import RichText from './RichText';

View File

@ -7,7 +7,7 @@ import {
Row,
Text
} from '@tlon/indigo-react';
import React, { useRef } from 'react';
import React from 'react';
import { Link } from 'react-router-dom';
import { Sigil } from '~/logic/lib/sigil';
import { uxToHex } from '~/logic/lib/util';
@ -18,7 +18,6 @@ import { Dropdown } from './Dropdown';
import { ProfileStatus } from './ProfileStatus';
import ReconnectButton from './ReconnectButton';
import { StatusBarItem } from './StatusBarItem';
import { useTutorialModal } from './useTutorialModal';
import { StatusBarJoins } from './StatusBarJoins';
import useHarkState from '~/logic/state/hark';
@ -48,13 +47,6 @@ const StatusBar = (props) => {
<Sigil ship={ship} size={16} color={color} icon />
);
const anchorRef = useRef(null);
const leapHighlight = useTutorialModal('leap', true, anchorRef);
const floatLeap =
leapHighlight && window.matchMedia('(max-width: 550px)').matches;
return (
<Box
display='grid'
@ -77,9 +69,9 @@ const StatusBar = (props) => {
>
<Icon icon='Dashboard' color='black' />
</Button>
<StatusBarItem position="relative" float={floatLeap} mr={2} onClick={() => toggleOmnibox()}>
<StatusBarItem position="relative" mr={2} onClick={() => toggleOmnibox()}>
<Icon icon='LeapArrow' />
<Text ref={anchorRef} ml={2} color='black'>
<Text ml={2} color='black'>
Leap
</Text>
<Text display={['none', 'inline']} ml={2} color='gray'>

View File

@ -119,7 +119,7 @@ export function Omnibox(props: OmniboxProps): ReactElement {
if (category === 'other') {
return [
'other',
index.get('other').filter(({ app }) => app !== 'tutorial')
index.get('other')
];
}
return [category, []];
@ -159,7 +159,6 @@ export function Omnibox(props: OmniboxProps): ReactElement {
defaultApps.includes(app.toLowerCase()) ||
app === 'profile' ||
app === 'messages' ||
app === 'tutorial' ||
app === 'Links' ||
app === 'Terminal' ||
app === 'home' ||

View File

@ -169,17 +169,6 @@ export class OmniboxResult extends Component<OmniboxResultProps, OmniboxResultSt
color={iconFill}
/>
);
} else if (icon === 'tutorial') {
graphic = (
<Icon
display='inline-block'
verticalAlign='middle'
icon='Tutorial'
mr={2}
size='18px'
color={iconFill}
/>
);
} else {
graphic = (
<Icon

View File

@ -1,23 +0,0 @@
import { MutableRefObject, useEffect } from 'react';
import useLocalState, { selectLocalState } from '~/logic/state/local';
import { TutorialProgress } from '~/types';
const localSelector = selectLocalState(['tutorialProgress', 'setTutorialRef']);
export function useTutorialModal(
onProgress: TutorialProgress,
show: boolean,
anchorRef: MutableRefObject<HTMLElement | null>
) {
const { tutorialProgress, setTutorialRef } = useLocalState(localSelector);
useEffect(() => {
if (show && (onProgress === tutorialProgress) && anchorRef?.current) {
setTutorialRef(anchorRef.current);
}
return () => {};
}, [tutorialProgress, show, anchorRef]);
return show && onProgress === tutorialProgress;
}

View File

@ -1,9 +1,7 @@
import { Col, Row, Text, Icon } from '@tlon/indigo-react';
import { Metadata } from '@urbit/api';
import React, { ReactElement, ReactNode, useRef } from 'react';
import { TUTORIAL_GROUP, TUTORIAL_HOST } from '~/logic/lib/tutorialModal';
import React, { ReactElement, ReactNode } from 'react';
import { PropFunc, IconRef } from '~/types';
import { useTutorialModal } from '~/views/components/useTutorialModal';
import { MetadataIcon } from './MetadataIcon';
import { useCopy } from '~/logic/lib/useCopy';
interface GroupSummaryProps {
@ -17,17 +15,24 @@ interface GroupSummaryProps {
locked?: boolean;
}
export function GroupSummary(props: GroupSummaryProps & PropFunc<typeof Col>): ReactElement {
const { channelCount, memberCount, metadata, resource, children, ...rest } = props;
const anchorRef = useRef<HTMLElement | null>(null);
useTutorialModal(
'group-desc',
resource === `/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`,
anchorRef
export function GroupSummary(
props: GroupSummaryProps & PropFunc<typeof Col>
): ReactElement {
const {
channelCount,
memberCount,
metadata,
resource,
children,
...rest
} = props;
const { doCopy, copyDisplay } = useCopy(
`web+urbitgraph://group${resource?.slice(5)}`,
'Copy',
'Checkmark'
);
const { doCopy, copyDisplay } = useCopy(`web+urbitgraph://group${resource?.slice(5)}`, "Copy", "Checkmark");
return (
<Col {...rest} ref={anchorRef} gapY={4} maxWidth={['100%', '288px']}>
<Col {...rest} gapY={4} maxWidth={['100%', '288px']}>
<Row gapX={2} width="100%">
<MetadataIcon
width="40px"
@ -37,21 +42,22 @@ export function GroupSummary(props: GroupSummaryProps & PropFunc<typeof Col>): R
/>
<Col justifyContent="space-between" flexGrow={1} overflow="hidden">
<Row justifyContent="space-between">
<Text
fontSize={1}
textOverflow="ellipsis"
whiteSpace="nowrap"
overflow="hidden"
>{metadata.title}
</Text>
{props?.AllowCopy &&
<Icon
color="gray"
icon={props?.locked ? "Locked" : copyDisplay as IconRef}
onClick={!props?.locked ? doCopy : null}
cursor={props?.locked ? "default" : "pointer"}
/>
}
<Text
fontSize={1}
textOverflow="ellipsis"
whiteSpace="nowrap"
overflow="hidden"
>
{metadata.title}
</Text>
{props?.AllowCopy && (
<Icon
color="gray"
icon={props?.locked ? 'Locked' : (copyDisplay as IconRef)}
onClick={!props?.locked ? doCopy : null}
cursor={props?.locked ? 'default' : 'pointer'}
/>
)}
</Row>
<Row gapX={4} justifyContent="space-between">
<Text fontSize={1} gray>
@ -64,17 +70,17 @@ export function GroupSummary(props: GroupSummaryProps & PropFunc<typeof Col>): R
</Col>
</Row>
<Row width="100%">
{metadata.description &&
<Text
{metadata.description && (
<Text
gray
width="100%"
fontSize={1}
textOverflow="ellipsis"
overflow="hidden"
>
>
{metadata.description}
</Text>
}
)}
</Row>
{children}
</Col>

View File

@ -43,7 +43,7 @@ export function GroupsPane(props: GroupsPaneProps) {
useShortcut('readGroup', useCallback(() => {
if(groupPath) {
airlock.poke(readGroup(groupPath));
useHarkState.getState().readGroup(groupPath);
}
}, [groupPath]));

View File

@ -12,7 +12,6 @@ import React, { ReactElement, useCallback, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import urbitOb from 'urbit-ob';
import * as Yup from 'yup';
import { TUTORIAL_GROUP_RESOURCE } from '~/logic/lib/tutorialModal';
import { useQuery } from '~/logic/lib/useQuery';
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
import { getModuleIcon } from '~/logic/lib/util';
@ -23,7 +22,6 @@ import { FormError } from '~/views/components/FormError';
import { StatelessAsyncButton } from '~/views/components/StatelessAsyncButton';
import { GroupSummary } from './GroupSummary';
import airlock from '~/logic/api';
import useSettingsState from '~/logic/state/settings';
const formSchema = Yup.object({
group: Yup.string()
@ -77,10 +75,6 @@ export function JoinGroup(props: JoinGroupProps): ReactElement {
const onConfirm = useCallback(async (group: string) => {
const [,,ship,name] = group.split('/');
const { putEntry } = useSettingsState.getState();
if(group === TUTORIAL_GROUP_RESOURCE) {
await putEntry('tutorial', 'joined', Date.now());
}
if (group in groups) {
return history.push(`/~landscape${group}`);
}

View File

@ -1,14 +1,13 @@
import {
Col
} from '@tlon/indigo-react';
import React, { ReactElement, useRef } from 'react';
import React, { ReactElement } from 'react';
import styled from 'styled-components';
import { roleForShip } from '~/logic/lib/group';
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
import { getGroupFromWorkspace } from '~/logic/lib/workspace';
import useGroupState from '~/logic/state/group';
import { Workspace } from '~/types';
import { useTutorialModal } from '~/views/components/useTutorialModal';
import { GroupSwitcher } from '../GroupSwitcher';
import { SidebarList } from './SidebarList';
import { SidebarListHeader } from './SidebarListHeader';
@ -48,12 +47,8 @@ export function Sidebar(props: SidebarProps): ReactElement | null {
const role = groups?.[groupPath] ? roleForShip(groups[groupPath], window.ship) : undefined;
const isAdmin = (role === 'admin') || (workspace?.type === 'home');
const anchorRef = useRef<HTMLDivElement>(null);
useTutorialModal('channels', true, anchorRef);
return (
<ScrollbarLessCol
ref={anchorRef}
display={display}
width="100%"
gridRow="1/2"

View File

@ -1,12 +1,10 @@
import _ from 'lodash';
import React, { useRef, ReactNode } from 'react';
import React, { ReactNode } from 'react';
import urbitOb from 'urbit-ob';
import { Icon, Row, Box, Text, BaseImage } from '@tlon/indigo-react';
import { Association, cite, deSig } from '@urbit/api';
import { HoverBoxLink } from '~/views/components/HoverBox';
import { Sigil } from '~/logic/lib/sigil';
import { useTutorialModal } from '~/views/components/useTutorialModal';
import { TUTORIAL_HOST, TUTORIAL_GROUP } from '~/logic/lib/tutorialModal';
import { Workspace } from '~/types/workspace';
import useContactState, { useContact } from '~/logic/state/contact';
import { getItemTitle, getModuleIcon, uxToHex } from '~/logic/lib/util';
@ -24,13 +22,15 @@ function useAssociationStatus(resource: string) {
const { count, each } = stats;
const hasNotifications = false;
const hasUnread = count > 0 || each.length > 0;
return hasNotifications
? 'notification'
: hasUnread
? 'unread'
: isSubscribed
? undefined
: 'unsubscribed';
if(!isSubscribed) {
return 'unsubscribed';
} else if (hasNotifications) {
return 'notification';
} else if (hasUnread) {
return 'unread';
} else {
return undefined;
}
}
function SidebarItemBase(props: {
@ -186,12 +186,6 @@ export const SidebarAssociationItem = React.memo((props: {
const group = useGroupState(state => state.groups[groupPath]);
const { hideNicknames } = useSettingsState(s => s.calm);
const contacts = useContactState(s => s.contacts);
const anchorRef = useRef<HTMLAnchorElement>(null);
useTutorialModal(
mod as any,
groupPath === `/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`,
anchorRef
);
const isUnmanaged = group?.hidden || false;
const DM = isUnmanaged && props.workspace?.type === 'messages';
const itemStatus = useAssociationStatus(rid);

View File

@ -1,264 +0,0 @@
import { Box, Button, Col, Icon, Row, Text } from '@tlon/indigo-react';
import { leaveGroup } from '@urbit/api';
import _ from 'lodash';
import React, { useCallback, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { getRelativePosition } from '~/logic/lib/relativePosition';
import {
getTrianglePosition, MODAL_WIDTH_PX, progressDetails,
TUTORIAL_GROUP, TUTORIAL_HOST
} from '~/logic/lib/tutorialModal';
import useLocalState, { selectLocalState } from '~/logic/state/local';
import { tutorialProgress as progress } from '~/types';
import { ModalOverlay } from '~/views/components/ModalOverlay';
import { Portal } from '~/views/components/Portal';
import { StatelessAsyncButton } from '~/views/components/StatelessAsyncButton';
import { Triangle } from '~/views/components/Triangle';
import airlock from '~/logic/api';
import useSettingsState from '~/logic/state/settings';
const localSelector = selectLocalState([
'tutorialProgress',
'nextTutStep',
'prevTutStep',
'tutorialRef',
'hideTutorial',
'set'
]);
export function TutorialModal() {
const {
tutorialProgress,
tutorialRef,
nextTutStep,
prevTutStep,
hideTutorial
} = useLocalState(localSelector);
const {
title,
description,
arrow = 'North',
alignX,
alignY,
offsetX,
offsetY
} = progressDetails[tutorialProgress];
const [coords, setCoords] = useState({});
const [paused, setPaused] = useState(false);
const history = useHistory();
const next = useCallback( () => {
const idx = progress.findIndex(p => p === tutorialProgress);
const { url } = progressDetails[progress[idx + 1]];
nextTutStep();
history.push(url);
},
[nextTutStep, history, tutorialProgress, setCoords]
);
const prev = useCallback(() => {
const idx = progress.findIndex(p => p === tutorialProgress);
prevTutStep();
history.push(progressDetails[progress[idx - 1]].url);
}, [prevTutStep, history, tutorialProgress]);
const updatePos = useCallback(() => {
const newCoords = getRelativePosition(
tutorialRef,
alignX,
alignY,
offsetX,
offsetY
);
const withMobile: any = _.mapValues(newCoords, (value: string[], key: string) => {
if(key === 'bottom' || key === 'left') {
return ['0px', ...value];
}
return ['unset', ...value];
});
if(!('bottom' in withMobile)) {
withMobile.bottom = ['0px', 'unset'];
}
if(!('left' in withMobile)) {
withMobile.left = ['0px', 'unset'];
}
if (newCoords) {
setCoords(withMobile);
} else {
setCoords({});
}
}, [tutorialRef]);
const dismiss = useCallback(async () => {
setPaused(false);
hideTutorial();
const { putEntry } = useSettingsState.getState();
await putEntry('tutorial', 'seen', true);
}, [hideTutorial]);
const bailExit = useCallback(() => {
setPaused(false);
}, []);
const tryExit = useCallback(() => {
setPaused(true);
}, []);
const doLeaveGroup = useCallback(async () => {
await airlock.thread(leaveGroup(TUTORIAL_HOST, TUTORIAL_GROUP));
await dismiss();
}, [dismiss]);
const progressIdx = progress.findIndex(p => p === tutorialProgress);
useEffect(() => {
if (
tutorialProgress !== 'hidden' &&
tutorialProgress !== 'done' &&
tutorialRef
) {
const interval = setInterval(updatePos, 100);
return () => {
setCoords({});
clearInterval(interval);
};
}
return () => {};
}, [tutorialRef, tutorialProgress, updatePos]);
const triPos = getTrianglePosition(arrow);
if (tutorialProgress === 'done') {
return (
<Portal>
<ModalOverlay dismiss={dismiss} borderRadius={2} maxWidth="270px" backgroundColor="white">
<Col p={3} bg="lightBlue">
<Col mb={3}>
<Text lineHeight="tall" fontWeight="bold">
Tutorial Finished
</Text>
<Text fontSize={0} gray>
{progressIdx} of {progress.length - 2}
</Text>
</Col>
<Text lineHeight="tall">
This tutorial is finished. Would you like to leave Beginner Island?
</Text>
<Row mt={3} gapX={2} justifyContent="flex-end">
<Button backgroundColor="washedGray" onClick={dismiss}>
Later
</Button>
<StatelessAsyncButton primary destructive onClick={doLeaveGroup}>
Leave Group
</StatelessAsyncButton>
</Row>
</Col>
</ModalOverlay>
</Portal>
);
}
if (tutorialProgress === 'hidden') {
return null;
}
if(paused) {
return (
<ModalOverlay dismiss={bailExit} borderRadius={2} maxWidth="270px" backgroundColor="white">
<Col p={3}>
<Col mb={3}>
<Text lineHeight="tall" fontWeight="bold">
End Tutorial Now?
</Text>
</Col>
<Text lineHeight="tall">
You can always restart the tutorial by typing &quot;tutorial&quot; in Leap.
</Text>
<Row mt={3} gapX={2} justifyContent="flex-end">
<Button backgroundColor="washedGray" onClick={bailExit}>
Cancel
</Button>
<StatelessAsyncButton primary destructive onClick={dismiss}>
End Tutorial
</StatelessAsyncButton>
</Row>
</Col>
</ModalOverlay>
);
}
if(Object.keys(coords).length === 0) {
return null;
}
return (
<Portal>
<Box
position="fixed"
{...coords}
bg="white"
zIndex={50}
display="flex"
flexDirection="column"
width={['100%', MODAL_WIDTH_PX]}
borderRadius={2}
>
<Col
position="relative"
justifyContent="space-between"
height="100%"
width="100%"
borderRadius={2}
p={3}
bg="lightBlue"
>
<Triangle
{...triPos}
position="absolute"
size={16}
color="lightBlue"
direction={arrow}
height="0px"
width="0px"
display={['none', 'block']}
/>
<Box
right="16px"
top="16px"
position="absolute"
cursor="pointer"
onClick={tryExit}
>
<Icon icon="X" />
</Box>
<Col mb={3}>
<Text lineHeight="tall" fontWeight="bold">
{title}
</Text>
<Text fontSize={0} gray>
{progressIdx} of {progress.length - 2}
</Text>
</Col>
<Text lineHeight="tall">{description}</Text>
<Row gapX={2} mt={3} justifyContent="flex-end">
{ progressIdx > 1 && (
<Button bg="washedGray" onClick={prev}>
Back
</Button>
)}
<Button primary onClick={next}>
Next
</Button>
</Row>
</Col>
</Box>
</Portal>
);
}

View File

@ -486,7 +486,7 @@
update-core
?- mode.kind
%count (hark %unread-count place %.y 1)
%each (hark %unread-each place /(rsh 4 (scot %ui (rear index.post))))
%each (hark %unread-each place /(rsh 4 (scot %ui (rear self-idx))))
%none update-core
==
==
@ -497,7 +497,7 @@
update-core
?- mode.kind
%count (hark %unread-count place %.n 1)
%each (hark %read-each place /(rsh 4 (scot %ui (rear index.post))))
%each (hark %read-each place /(rsh 4 (scot %ui (rear self-idx))))
%none update-core
==
==

View File

@ -1,9 +1,9 @@
:~ title+'Groups'
info+'A suite of applications to communicate on Urbit'
color+0xee.5432
glob-http+['https://bootstrap.urbit.org/glob-0v4.10b75.pd4h0.n481d.ln7c6.cf174.glob' 0v4.10b75.pd4h0.n481d.ln7c6.cf174]
glob-http+['https://bootstrap.urbit.org/glob-0v2.fn2uu.2iu5q.ddmsu.01br7.8uaft.glob' 0v2.fn2uu.2iu5q.ddmsu.01br7.8uaft]
base+'landscape'
version+[1 3 5]
version+[1 0 2]
website+'https://tlon.io'
license+'MIT'
==

View File

@ -6,6 +6,7 @@
**/
# define c3__a c3_s1('a')
# define c3__a50 c3_s3('a','5','0')
# define c3__abel c3_s4('a','b','e','l')
# define c3__abo c3_s3('a','b','o')
# define c3__actd c3_s4('a','c','t','d')
@ -112,6 +113,7 @@
# define c3__boot c3_s4('b','o','o','t')
# define c3__born c3_s4('b','o','r','n')
# define c3__both c3_s4('b','o','t','h')
# define c3__bout c3_s4('b','o','u','t')
# define c3__box c3_s3('b','o','x')
# define c3__br c3_s2('b','r')
# define c3__bran c3_s4('b','r','a','n')

View File

@ -72,9 +72,9 @@
c3_l jax_l; // index in global dashboard
} u3j_core;
/* u3e_dash, u3_Dash, u3D: jet dashboard singleton
/* u3j_dash, u3_Dash, u3D: jet dashboard singleton
*/
typedef struct _u3e_dash {
typedef struct _u3j_dash {
u3j_core* dev_u; // null-terminated static list
c3_l len_l; // dynamic array length
c3_l all_l; // allocated length

View File

@ -93,6 +93,12 @@
void
u3t_trace_close();
/* u3t_trace_time(): returns current time since system epoc,
* whatever it is per system, in microseconds.
*/
c3_d
u3t_trace_time();
/* u3t_nock_trace_push(): pushes a frame onto the trace stack;
* return yes if active push.
*/

View File

@ -2114,8 +2114,75 @@ static c3_c* _k140_ha[] = {
0
};
// TODO: probably need different ha hashes
static u3j_core _a50_two__by_d[] =
{ { "apt", 7, _140_two__by_apt_a, 0, _140_two__by_apt_ha },
{ "del", 7, _140_two__by_del_a, 0, _140_two__by_del_ha },
{ "get", 7, _140_two__by_get_a, 0, _140_two__by_get_ha },
{ "has", 7, _140_two__by_has_a, 0, _140_two__by_has_ha },
{ "put", 7, _140_two__by_put_a, 0, _140_two__by_put_ha },
{}
};
static u3j_core _a50_two__in_d[] =
{ { "apt", 7, _140_two__in_apt_a, 0, _140_two__in_apt_ha },
{ "del", 7, _140_two__in_del_a, 0, _140_two__in_del_ha },
{ "put", 7, _140_two__in_put_a, 0, _140_two__in_put_ha },
{}
};
u3j_core _a50_d[] =
{ { "add", 7, _140_one_add_a, 0, _140_one_add_ha },
{ "dec", 7, _140_one_dec_a, 0, _140_one_dec_ha },
{ "div", 7, _140_one_div_a, 0, _140_one_div_ha },
{ "dvr", 7, _140_one_dvr_a, 0, _140_one_dvr_ha },
{ "gte", 7, _140_one_gte_a, 0, _140_one_gte_ha },
{ "gth", 7, _140_one_gth_a, 0, _140_one_gth_ha },
{ "lte", 7, _140_one_lte_a, 0, _140_one_lte_ha },
{ "lth", 7, _140_one_lth_a, 0, _140_one_lth_ha },
{ "mod", 7, _140_one_mod_a, 0, _140_one_mod_ha },
{ "mul", 7, _140_one_mul_a, 0, _140_one_mul_ha },
{ "sub", 7, _140_one_sub_a, 0, _140_one_sub_ha },
{ "bex", 7, _140_two_bex_a, 0, _140_two_bex_ha },
{ "cat", 7, _140_two_cat_a, 0, _140_two_cat_ha },
{ "can", 7, _140_two_can_a, 0, _140_two_can_ha },
{ "con", 7, _140_two_con_a, 0, _140_two_con_ha },
{ "cut", 7, _140_two_cut_a, 0, _140_two_cut_ha },
{ "dis", 7, _140_two_dis_a, 0, _140_two_dis_ha },
{ "dor", 7, _140_two_dor_a, 0, _140_two_dor_ha },
{ "end", 7, _140_two_end_a, 0, _140_two_end_ha },
{ "gor", 7, _140_two_gor_a, 0, _140_two_gor_ha },
{ "lsh", 7, _140_two_lsh_a, 0, _140_two_lsh_ha },
{ "met", 7, _140_two_met_a, 0, _140_two_met_ha },
{ "mix", 7, _140_two_mix_a, 0, _140_two_mix_ha },
{ "mor", 7, _140_two_mor_a, 0, _140_two_mor_ha },
{ "mug", 7, _140_two_mug_a, 0, _140_two_mug_ha },
{ "muk", 59, _140_two_muk_a, 0, _140_two_muk_ha }, // TODO: why 59?
{ "rep", 7, _140_two_rep_a, 0, _140_two_rep_ha },
{ "rip", 7, _140_two_rip_a, 0, _140_two_rip_ha },
{ "rsh", 7, _140_two_rsh_a, 0, _140_two_rsh_ha },
{ "swp", 7, _140_two_swp_a, 0, _140_two_swp_ha },
{ "flop", 7, _140_two_flop_a, 0, _140_two_flop_ha },
{ "lent", 7, _140_two_lent_a, 0, _140_two_lent_ha },
{ "levy", 7, _140_two_levy_a, 0, _140_two_levy_ha },
{ "reap", 7, _140_two_reap_a, 0, _140_two_reap_ha },
{ "slag", 7, _140_two_slag_a, 0, _140_two_slag_ha },
{ "snag", 7, _140_two_snag_a, 0, _140_two_snag_ha },
{ "turn", 7, _140_two_turn_a, 0, _140_two_turn_ha },
{ "welp", 7, _140_two_welp_a, 0, _140_two_welp_ha },
{ "by", 7, 0, _a50_two__by_d, _140_two__by_ha },
{ "in", 7, 0, _a50_two__in_d, _140_two__in_ha },
{}
};
static u3j_core _d[] = {
{ "k140", 0, 0, _k140_d, _k140_ha, 0, (u3j_core*) 140, 0 },
{ "a50", 0, 0, _a50_d, _k140_ha, 0, (u3j_core*) c3__a50, 0 },
{}
};

View File

@ -311,7 +311,6 @@ _ce_patch_verify(u3_ce_patch* pat_u)
fprintf(stderr, "loom: patch version mismatch: have %u, need %u\r\n",
pat_u->con_u->ver_y,
u3e_version);
c3_assert(0);
return c3n;
}
@ -322,12 +321,10 @@ _ce_patch_verify(u3_ce_patch* pat_u)
if ( -1 == lseek(pat_u->mem_i, (i_w << (u3a_page + 2)), SEEK_SET) ) {
fprintf(stderr, "loom: patch seek: %s\r\n", strerror(errno));
c3_assert(0);
return c3n;
}
if ( -1 == read(pat_u->mem_i, mem_w, (1 << (u3a_page + 2))) ) {
fprintf(stderr, "loom: patch read: %s\r\n", strerror(errno));
c3_assert(0);
return c3n;
}
{
@ -336,7 +333,6 @@ _ce_patch_verify(u3_ce_patch* pat_u)
if ( mug_w != nug_w ) {
fprintf(stderr, "loom: patch mug mismatch %d/%d; (%x, %x)\r\n",
pag_w, i_w, mug_w, nug_w);
c3_assert(0);
return c3n;
}
#if 0
@ -605,8 +601,17 @@ _ce_patch_compose(void)
static void
_ce_patch_sync(u3_ce_patch* pat_u)
{
c3_sync(pat_u->ctl_i);
c3_sync(pat_u->mem_i);
if ( -1 == c3_sync(pat_u->ctl_i) ) {
fprintf(stderr, "loom: control file sync failed: %s\r\n",
strerror(errno));
c3_assert(!"loom: control sync");
}
if ( -1 == c3_sync(pat_u->mem_i) ) {
fprintf(stderr, "loom: patch file sync failed: %s\r\n",
strerror(errno));
c3_assert(!"loom: patch sync");
}
}
/* _ce_image_sync(): make sure image is synced to disk.
@ -614,7 +619,12 @@ _ce_patch_sync(u3_ce_patch* pat_u)
static void
_ce_image_sync(u3e_image* img_u)
{
c3_sync(img_u->fid_i);
if ( -1 == c3_sync(img_u->fid_i) ) {
fprintf(stderr, "loom: image (%s) sync failed: %s\r\n",
img_u->nam_c,
strerror(errno));
c3_assert(!"loom: image sync");
}
}
/* _ce_image_resize(): resize image, truncating if it shrunk.
@ -794,7 +804,11 @@ u3e_save(void)
// u3a_print_memory(stderr, "sync: save", 4096 * pat_u->con_u->pgs_w);
_ce_patch_sync(pat_u);
_ce_patch_verify(pat_u);
if ( c3n == _ce_patch_verify(pat_u) ) {
c3_assert(!"loom: save failed");
}
_ce_patch_apply(pat_u);
#ifdef U3_SNAPSHOT_VALIDATION

View File

@ -1004,8 +1004,8 @@ _n_emit(u3_noun *ops, u3_noun op)
static c3_w _n_comp(u3_noun*, u3_noun, c3_o, c3_o);
/* _n_bint(): hint-processing helper for _n_comp.
* hif: hint-formula (first part of 10). RETAIN.
* nef: next-formula (second part of 10). RETAIN.
* hif: hint-formula (first part of 11). RETAIN.
* nef: next-formula (second part of 11). RETAIN.
*/
static c3_w
_n_bint(u3_noun* ops, u3_noun hif, u3_noun nef, c3_o los_o, c3_o tel_o)
@ -1021,18 +1021,22 @@ _n_bint(u3_noun* ops, u3_noun hif, u3_noun nef, c3_o los_o, c3_o tel_o)
return _n_comp(ops, nef, los_o, tel_o);
}
// no currently recognized static hints
//
case u3_none: {
case c3__bout: {
u3_noun fen = u3_nul;
c3_w nef_w = _n_comp(&fen, nef, los_o, tel_o);
// add appropriate hind opcode
++nef_w; _n_emit(&fen, ( c3y == los_o ) ? HILL : HILK);
// skip over the cleanup opcode
++nef_w; _n_emit(&fen, u3nc(SBIP, 1));
// call hilt_fore
// HILB overflows to HILS
//
++tot_w; _n_emit(ops, u3nc(HILB, u3nc(u3k(hif), u3k(nef))));
++tot_w; _n_emit(ops, u3nc(SBIN, nef_w + 1));
// if fore return c3n, skip fen
++tot_w; _n_emit(ops, u3nc(SBIN, nef_w));
tot_w += nef_w; _n_apen(ops, fen);
++tot_w; _n_emit(ops, ( c3y == los_o ) ? HILL : HILK);
// post-skip cleanup opcode
++tot_w; _n_emit(ops, ( c3y == los_o ) ? TOSS : SWAP);
} break;
}
}
@ -1052,19 +1056,24 @@ _n_bint(u3_noun* ops, u3_noun hif, u3_noun nef, c3_o los_o, c3_o tel_o)
tot_w += _n_comp(ops, nef, los_o, tel_o);
} break;
// no currently recognized dynamic hints
//
case u3_none: {
case c3__bout: {
u3_noun fen = u3_nul;
c3_w nef_w = _n_comp(&fen, nef, los_o, tel_o);
// add appropriate hind opcode
++nef_w; _n_emit(&fen, ( c3y == los_o ) ? HINL : HINK);
// skip over the cleanup opcode
++nef_w; _n_emit(&fen, u3nc(SBIP, 1));
// push clue
tot_w += _n_comp(ops, hod, c3n, c3n);
// call hint_fore
// HINB overflows to HINS
//
++tot_w; _n_emit(ops, u3nc(HINB, u3nc(u3k(zep), u3k(nef))));
++tot_w; _n_emit(ops, u3nc(SBIN, nef_w + 1));
// if fore return c3n, skip fen
++tot_w; _n_emit(ops, u3nc(SBIN, nef_w));
tot_w += nef_w; _n_apen(ops, fen);
++tot_w; _n_emit(ops, ( c3y == los_o ) ? HINL : HINK);
// post-skip cleanup opcode
++tot_w; _n_emit(ops, ( c3y == los_o ) ? TOSS : SWAP);
} break;
}
} break;
@ -1677,18 +1686,25 @@ u3n_find(u3_noun key, u3_noun fol)
}
/* _n_hilt_fore(): literal (atomic) dynamic hint, before formula evaluation.
** lit: hint atom. TRANSFER
** hin: [hint-atom, formula]. TRANSFER
** bus: subject. RETAIN
** out: token for _n_hilt_hind();
** conventionally, [lit] or [lit data]. ~ if unused.
** out: token for _n_hilt_hind(); convention:
** [hint-atom] or [hint-atom data], ~ if unused.
**
** any hints herein must be whitelisted in _n_burn().
*/
static c3_o
_n_hilt_fore(u3_atom lit, u3_noun bus, u3_noun* out) // transfer, retain, n/a
_n_hilt_fore(u3_noun hin, u3_noun bus, u3_noun* out)
{
u3z(lit);
*out = u3_nul;
if ( c3__bout == u3h(hin) ) {
u3_atom now = u3i_chub(u3t_trace_time());
*out = u3i_cell(u3h(hin), now);
}
else {
*out = u3_nul;
}
u3z(hin);
return c3y;
}
@ -1697,9 +1713,20 @@ _n_hilt_fore(u3_atom lit, u3_noun bus, u3_noun* out) // transfer, retain, n/a
** pro: product of formula evaluation. RETAIN
*/
static void
_n_hilt_hind(u3_noun tok, u3_noun pro) // transfer, retain
_n_hilt_hind(u3_noun tok, u3_noun pro)
{
c3_assert( u3_nul == tok );
u3_noun p_tok, q_tok;
if ( (c3y == u3r_cell(tok, &p_tok, &q_tok)) && (c3__bout == p_tok) ) {
u3_atom delta = u3ka_sub(u3i_chub(u3t_trace_time()), u3k(q_tok));
c3_c str_c[64];
snprintf(str_c, 63, "took %" PRIu64 "\xc2\xb5s", u3r_chub(0, delta) );
u3t_slog(u3nc(0, u3i_string(str_c)));
u3z(delta);
}
else {
c3_assert( u3_nul == tok );
}
u3z(tok);
}
@ -1707,16 +1734,24 @@ _n_hilt_hind(u3_noun tok, u3_noun pro) // transfer, retain
** hin: [hint-atom, formula]. TRANSFER
** bus: subject. RETAIN
** clu: product of the hint-formula. TRANSFER
** also, token for _n_hint_hind();
** conventionally, [hint-atom] or [hint-atom data]. ~ if unused.
** also, token for _n_hilt_hind(); convention:
** [hint-atom] or [hint-atom data], ~ if unused.
**
** any hints herein must be whitelisted in _n_burn().
*/
static c3_o
_n_hint_fore(u3_cell hin, u3_noun bus, u3_noun* clu)
{
u3z(hin); u3z(*clu);
*clu = u3_nul;
if ( c3__bout == u3h(hin) ) {
u3_atom now = u3i_chub(u3t_trace_time());
*clu = u3i_trel(u3h(hin), *clu, now);
}
else {
u3z(*clu);
*clu = u3_nul;
}
u3z(hin);
return c3y;
}
@ -1727,7 +1762,43 @@ _n_hint_fore(u3_cell hin, u3_noun bus, u3_noun* clu)
static void
_n_hint_hind(u3_noun tok, u3_noun pro)
{
c3_assert( u3_nul == tok );
u3_noun p_tok, q_tok, r_tok;
if ( (c3y == u3r_trel(tok, &p_tok, &q_tok, &r_tok))
&& (c3__bout == p_tok) )
{
// get the microseconds elapsed
u3_atom delta = u3ka_sub(u3i_chub(u3t_trace_time()), u3k(r_tok));
// unpack q_tok to get the priority integer and the tank
// p_q_tok is the priority, q_q_tok is the tank we will work with
u3_noun p_q_tok, q_q_tok;
c3_assert(c3y == u3r_cell(q_tok, &p_q_tok, &q_q_tok));
// format the timing report
c3_c str_c[64];
snprintf(str_c, 63, "took %" PRIu64 "\xc2\xb5s", u3r_chub(0, delta) );
// join the timing report with the original tank from q_q_tok like so:
// "q_q_tok: report"
// prepend the priority to form a cell of the same shape q_tok
// send this to ut3_slog so that it can be logged out
u3t_slog(
u3nc(
u3k(p_q_tok),
u3nt(
c3__rose,
u3nt(u3nt(':', ' ', u3_nul), u3_nul, u3_nul),
u3nt(u3k(q_q_tok), u3i_string(str_c), u3_nul)
)
)
);
u3z(delta);
}
else {
c3_assert( u3_nul == tok );
}
u3z(tok);
}

View File

@ -937,7 +937,6 @@ struct ur_walk_fore_s {
ur_root_t *r;
uint32_t prev;
uint32_t size;
uint32_t fill;
ur_nref *top;
};
@ -950,7 +949,6 @@ ur_walk_fore_init_with(ur_root_t *r,
w->top = _oom("walk_fore", malloc(s_size * sizeof(*w->top)));
w->prev = s_prev;
w->size = s_size;
w->fill = 0;
w->r = r;
return w;
@ -969,45 +967,42 @@ ur_walk_fore_with(ur_walk_fore_t *w,
void (*atom)(ur_root_t*, ur_nref, void*),
ur_bool_t (*cell)(ur_root_t*, ur_nref, void*))
{
ur_root_t *r = w->r;
ur_nref *don = w->top;
ur_root_t *r = w->r;
uint32_t fill = 1;
w->top += ++w->fill;
*w->top = ref;
while ( w->top != don ) {
do {
// visit atom, pop stack
//
if ( !ur_deep(ref) ) {
atom(r, ref, v);
w->top--; w->fill--;
fill--;
}
// visit cell, pop stack if false
//
else if ( !cell(r, ref, v) ) {
w->top--; w->fill--;
fill--;
}
// push the tail, continue into the head
//
else {
*w->top = ur_tail(r, ref);
w->top[fill++] = ur_tail(r, ref);
// reallocate "stack" if full
//
if ( w->size == w->fill ) {
if ( w->size == fill ) {
uint32_t next = w->prev + w->size;
don = _oom("walk_fore", realloc(don, next * sizeof(*don)));
w->top = don + w->fill;
w->top = _oom("walk_fore", realloc(w->top, next * sizeof(*w->top)));
w->prev = w->size;
w->size = next;
}
w->top++; w->fill++;
*w->top = ur_head(r, ref);
w->top[fill] = ur_head(r, ref);
}
ref = *w->top;
}
ref = w->top[fill];
} while ( fill );
}
void

View File

@ -134,7 +134,7 @@ void
ur_jam_done(ur_jam_t *j)
{
ur_dict_free((ur_dict_t*)&j->dict);
free(j->w);
ur_walk_fore_done(j->w);
free(j);
}

View File

@ -390,6 +390,10 @@ u3_dawn_vent(u3_noun ship, u3_noun feed)
u3l_log("boot: verifying keys\r\n");
// TODO: remove when L2 is supported
u3l_log("boot: if you're trying to start an L2 ship,"
" upgrade your binary\r\n");
// (each seed (lest error=@tas))
//
sed = u3dq("veri:dawn", u3k(ship), u3k(feed), u3k(pot), u3k(liv));

View File

@ -687,7 +687,7 @@ _pier_wyrd_fail(u3_pier* pir_u, u3_ovum* egg_u, u3_noun lud)
// XX organizing version constants
//
#define VERE_NAME "vere"
#define VERE_ZUSE 420
#define VERE_ZUSE 419
/* _pier_wyrd_aver(): check for %wend effect and version downgrade. RETAIN
*/
@ -1114,7 +1114,7 @@ _pier_on_lord_play_bail(void* ptr_v, u3_info fon_u,
// dispose successful
//
while ( tac_u->eve_d < eve_d ) {
while ( tac_u->eve_d <= eve_d ) {
nex_u = tac_u->nex_u;
las_l = tac_u->mug_l;
u3_fact_free(tac_u);

View File

@ -1 +1 @@
1.6
1.7

View File

@ -751,16 +751,12 @@ _serf_play_list(u3_serf* sef_u, u3_noun eve)
eve = u3t(eve);
}
// event succeeded, save and continue
// event failed, stop and send trace
//
else {
u3_noun dud = u3k(u3t(gon));
// reset sent event counter
//
sef_u->sen_d = sef_u->dun_d;
u3z(gon);
sef_u->sen_d = sef_u->dun_d;
// XX reclaim on meme ?
//
@ -770,7 +766,7 @@ _serf_play_list(u3_serf* sef_u, u3_noun eve)
u3z(vev);
return u3nc(c3__bail, u3nt(u3i_chubs(1, &sef_u->dun_d),
sef_u->mug_l,
dud));
gon));
}
}