mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-01 11:33:41 +03:00
Merge remote-tracking branch 'origin/master' into next/arvo
This commit is contained in:
commit
18e4c620df
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@ -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 }}
|
||||
|
20
.github/workflows/ops-group-timer.yml
vendored
20
.github/workflows/ops-group-timer.yml
vendored
@ -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 }}
|
||||
|
4
.github/workflows/release-docker.yml
vendored
4
.github/workflows/release-docker.yml
vendored
@ -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 }}
|
||||
|
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@ -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"
|
||||
|
@ -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.
|
@ -1 +0,0 @@
|
||||
{"orgId":"EDiU8DZExvM9N4unZGYQbG3d","projectId":"prj_fbAU5smemBgtr5t8lsk5ZoT9zNtI"}
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
::
|
||||
|
@ -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)
|
||||
::
|
||||
|
@ -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'
|
||||
==
|
||||
|
@ -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))
|
||||
::
|
||||
--
|
||||
|
@ -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 “{getAppName(docket)}”?</h1>
|
||||
<h1 className="h4">Uninstall “{getAppName(docket)}”?</h1>
|
||||
<p className="text-base tracking-tight pr-6">
|
||||
This will remove the software'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 “{getAppName(docket)}”
|
||||
Uninstall
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
@ -27,15 +27,17 @@ export const SuspendApp = () => {
|
||||
<DialogContent showClose={false} className="space-y-6" containerClass="w-full max-w-md">
|
||||
<h1 className="h4">Suspend “{getAppName(charge)}”</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 “{getAppName(charge)}”
|
||||
Suspend
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
@ -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 }} />
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: urbit-king
|
||||
version: 1.6
|
||||
version: 1.7
|
||||
license: MIT
|
||||
license-file: LICENSE
|
||||
data-files:
|
||||
|
@ -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(),
|
||||
|
@ -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',
|
||||
|
@ -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]);
|
||||
|
@ -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 it’s own comment thread.',
|
||||
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/link/ship/${TUTORIAL_HOST}/${TUTORIAL_LINKS}`,
|
||||
alignY: 'top',
|
||||
alignX: 'right',
|
||||
arrow: 'North',
|
||||
offsetX: -8,
|
||||
offsetY: -56
|
||||
},
|
||||
publish: {
|
||||
title: 'Notebook',
|
||||
description:
|
||||
'Notebooks are for creating long-form content within your group. Use markdown to create rich posts with headers, lists and images.',
|
||||
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/publish/ship/${TUTORIAL_HOST}/${TUTORIAL_BOOK}`,
|
||||
alignY: 'top',
|
||||
alignX: 'right',
|
||||
arrow: 'North',
|
||||
offsetX: -8,
|
||||
offsetY: -56
|
||||
},
|
||||
notifications: {
|
||||
title: 'Notifications',
|
||||
description: 'You will get updates from subscribed channels and mentions here. You can access Notifications through Leap.',
|
||||
url: '/~notifications',
|
||||
alignY: 'top',
|
||||
alignX: 'left',
|
||||
arrow: 'North',
|
||||
offsetX: 0,
|
||||
offsetY: -48
|
||||
},
|
||||
profile: {
|
||||
title: 'Profile',
|
||||
description:
|
||||
'Your profile is customizable and can be shared with other ships. Enter as much or as little information as you’d like.',
|
||||
url: `/~profile/~${window.ship}`,
|
||||
alignY: 'top',
|
||||
alignX: 'right',
|
||||
arrow: 'South',
|
||||
offsetX: -300 + MODAL_WIDTH / 2,
|
||||
offsetY: -4
|
||||
},
|
||||
leap: {
|
||||
title: 'Leap',
|
||||
description:
|
||||
'Leap allows you to go to a specific channel, message, collection, profile or group simply by typing in a command or selecting a shortcut from the dropdown menu.',
|
||||
url: `/~profile/~${window.ship}`,
|
||||
alignY: 'top',
|
||||
alignX: 'left',
|
||||
arrow: 'North',
|
||||
offsetX: 76,
|
||||
offsetY: -48
|
||||
}
|
||||
};
|
@ -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]);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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]);
|
||||
|
@ -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'),
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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';
|
||||
|
@ -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 “tutorial” 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'
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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[] = [];
|
||||
|
@ -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) {
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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'>
|
||||
|
@ -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' ||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
@ -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>
|
||||
|
@ -43,7 +43,7 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
|
||||
useShortcut('readGroup', useCallback(() => {
|
||||
if(groupPath) {
|
||||
airlock.poke(readGroup(groupPath));
|
||||
useHarkState.getState().readGroup(groupPath);
|
||||
}
|
||||
}, [groupPath]));
|
||||
|
||||
|
@ -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}`);
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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 "tutorial" 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>
|
||||
);
|
||||
}
|
@ -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
|
||||
==
|
||||
==
|
||||
|
@ -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'
|
||||
==
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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 },
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
@ -1 +1 @@
|
||||
1.6
|
||||
1.7
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user