From 349dc6083c92307251589370adc20b3d62cfd9fe Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 25 May 2021 18:10:44 +1000 Subject: [PATCH 001/237] interface: add Figma integration to storybook --- pkg/interface/.storybook/main.js | 5 +-- pkg/interface/.storybook/preview.js | 1 + pkg/interface/package-lock.json | 49 +++++++++++++++++++++++++++++ pkg/interface/package.json | 1 + 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/pkg/interface/.storybook/main.js b/pkg/interface/.storybook/main.js index 26dfeaf66b..e7d82e8951 100644 --- a/pkg/interface/.storybook/main.js +++ b/pkg/interface/.storybook/main.js @@ -5,6 +5,7 @@ module.exports = { ], "addons": [ "@storybook/addon-links", - "@storybook/addon-essentials" + "@storybook/addon-essentials", + "storybook-addon-designs" ] -} \ No newline at end of file +} diff --git a/pkg/interface/.storybook/preview.js b/pkg/interface/.storybook/preview.js index 24d1498751..fdc95f9728 100644 --- a/pkg/interface/.storybook/preview.js +++ b/pkg/interface/.storybook/preview.js @@ -34,6 +34,7 @@ export const globalTypes = { export const decorators = [ (Story, context) => { + window.ship = 'sampel-palnet'; const theme = context.globals.theme === 'light' ? light : dark; useMetadataState.setState({ diff --git a/pkg/interface/package-lock.json b/pkg/interface/package-lock.json index e4eb0ff583..dabc260add 100644 --- a/pkg/interface/package-lock.json +++ b/pkg/interface/package-lock.json @@ -1881,6 +1881,25 @@ } } }, + "@figspec/components": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@figspec/components/-/components-0.1.8.tgz", + "integrity": "sha512-jSk3aREVOvQRncC/2HyieeOPxkjQmKyWn6prfXC+ijOHwIeR6jzhv5C/ryk4ZW2msXYAtPtIBL+/iF2pQVJ/Sg==", + "dev": true, + "requires": { + "copy-to-clipboard": "^3.0.0", + "lit-element": "^2.4.0" + } + }, + "@figspec/react": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@figspec/react/-/react-0.1.6.tgz", + "integrity": "sha512-oi0JL8uIXgJ+PWRl4LDxJ7WWa80E3jdYmi6wsHAFDq1vT0rKuyhqimEJzCezIrHHz4fXKpNRO98TO7ccma6hjw==", + "dev": true, + "requires": { + "@figspec/components": "^0.1.1" + } + }, "@hapi/hoek": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", @@ -7267,6 +7286,7 @@ }, "onchange": { "version": "7.1.0", + "resolved": "https://registry.npmjs.org/onchange/-/onchange-7.1.0.tgz", "integrity": "sha512-ZJcqsPiWUAUpvmnJri5TPBooqJOPmC0ttN65juhN15Q8xA+Nbg3BaxBHXQ45EistKKlKElb0edmbPWnKSBkvMg==", "requires": { "@blakeembrey/deque": "^1.0.5", @@ -7469,6 +7489,7 @@ }, "@typescript-eslint/eslint-plugin": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.24.0.tgz", "integrity": "sha512-qbCgkPM7DWTsYQGjx9RTuQGswi+bEt0isqDBeo+CKV0953zqI0Tp7CZ7Fi9ipgFA6mcQqF4NOVNwS/f2r6xShw==", "requires": { "@typescript-eslint/experimental-utils": "4.24.0", @@ -7496,6 +7517,7 @@ }, "@typescript-eslint/parser": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.24.0.tgz", "integrity": "sha512-dj1ZIh/4QKeECLb2f/QjRwMmDArcwc2WorWPRlB8UNTZlY1KpTVsbX7e3ZZdphfRw29aTFUSNuGB8w9X5sS97w==", "requires": { "@typescript-eslint/scope-manager": "4.24.0", @@ -7579,6 +7601,7 @@ }, "babel-eslint": { "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", "requires": { "@babel/code-frame": "^7.0.0", @@ -7727,6 +7750,7 @@ }, "eslint-plugin-react": { "version": "7.23.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.23.2.tgz", "integrity": "sha512-AfjgFQB+nYszudkxRkTFu0UR1zEQig0ArVMPloKhxwlwkzaw/fBiH0QWcBBhZONlXqQC51+nfqFrkn4EzHcGBw==", "requires": { "array-includes": "^3.1.3", @@ -8295,6 +8319,7 @@ }, "typescript": { "version": "4.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==" }, "unbox-primitive": { @@ -15498,6 +15523,21 @@ } } }, + "lit-element": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.5.1.tgz", + "integrity": "sha512-ogu7PiJTA33bEK0xGu1dmaX5vhcRjBXCFexPja0e7P7jqLhTpNKYRPmE+GmiCaRVAbiQKGkUgkh/i6+bh++dPQ==", + "dev": true, + "requires": { + "lit-html": "^1.1.1" + } + }, + "lit-html": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.4.1.tgz", + "integrity": "sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA==", + "dev": true + }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", @@ -20663,6 +20703,15 @@ "integrity": "sha512-7t+/wpKLanLzSnQPX8WAcuLCCeuSHoWdQuh9SB3xD0kNOM38DNf+0Oa+wmvxmYueRzkmh6IcdKFtvTa+ecgPDw==", "dev": true }, + "storybook-addon-designs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/storybook-addon-designs/-/storybook-addon-designs-6.0.0.tgz", + "integrity": "sha512-nwwUusxOmUt82ajTjfBDQfOU2zEO3WrHxG9J7rZmSLnoC42OC8P/FJtAuhL5JregCQildVbjIfeFfz7pMGJOjQ==", + "dev": true, + "requires": { + "@figspec/react": "^0.1.6" + } + }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", diff --git a/pkg/interface/package.json b/pkg/interface/package.json index 020fc5f920..3127293521 100644 --- a/pkg/interface/package.json +++ b/pkg/interface/package.json @@ -99,6 +99,7 @@ "react-hot-loader": "^4.13.0", "sass": "^1.32.5", "sass-loader": "^8.0.2", + "storybook-addon-designs": "^6.0.0", "ts-mdast": "^1.0.0", "typescript": "^4.2.4", "webpack": "^4.46.0", From 0353fcf6f657be2548be45ed963f74a33e4a897e Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 25 May 2021 18:11:43 +1000 Subject: [PATCH 002/237] collections: add LinkInput --- .../src/stories/LinkInput.stories.tsx | 62 +++++++++++++++ .../views/apps/links/components/LinkInput.tsx | 77 +++++++++++++++++++ .../src/views/components/ShipImage.tsx | 49 ++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 pkg/interface/src/stories/LinkInput.stories.tsx create mode 100644 pkg/interface/src/views/apps/links/components/LinkInput.tsx create mode 100644 pkg/interface/src/views/components/ShipImage.tsx diff --git a/pkg/interface/src/stories/LinkInput.stories.tsx b/pkg/interface/src/stories/LinkInput.stories.tsx new file mode 100644 index 0000000000..b1ed720e00 --- /dev/null +++ b/pkg/interface/src/stories/LinkInput.stories.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { Story, Meta } from '@storybook/react'; +import { withDesign } from 'storybook-addon-designs'; + +import { Box } from '@tlon/indigo-react'; +import { + LinkInput, + LinkInputProps +} from '~/views/apps/links/components/LinkInput'; + +export default { + title: 'Input/Collections', + component: LinkInput, + decorators: [withDesign] +} as Meta; + +const Template: Story = args => ( + + + +); + +export const Empty = Template.bind({}); + +Empty.parameters = { + design: { + type: 'figma', + url: + 'https://www.figma.com/file/H1RAHV4KscSTnvrIiL0z8C/Indigo?node-id=5113%3A60' + } +}; + +export const Filled = Template.bind({}); + +Filled.parameters = { + design: { + type: 'figma', + url: + 'https://www.figma.com/file/H1RAHV4KscSTnvrIiL0z8C/Indigo?node-id=5113%3A82' + } +}; + +Filled.args = { + url: 'https://www.youtube.com/watch?v=a_EvjlM7We0', + title: 'Gigi Masin - Stella Maris' +}; + +export const Overflow = Template.bind({}); + +Overflow.args = { + url: + 'https://www.youtube.com/watch?v=a_EvjlM7We0https://www.youtube.com/watch?v=a_EvjlM7We0', + title: 'Gigi Masin - Stella Maris' +}; + +Overflow.parameters = { + design: { + type: 'figma', + url: + 'https://www.figma.com/file/H1RAHV4KscSTnvrIiL0z8C/Indigo?node-id=5113%3A97' + } +}; diff --git a/pkg/interface/src/views/apps/links/components/LinkInput.tsx b/pkg/interface/src/views/apps/links/components/LinkInput.tsx new file mode 100644 index 0000000000..fe4948f39f --- /dev/null +++ b/pkg/interface/src/views/apps/links/components/LinkInput.tsx @@ -0,0 +1,77 @@ +import { Row, Col, BaseInput, Action } from '@tlon/indigo-react'; +import React, { ChangeEvent, useCallback, useState } from 'react'; +import styled from 'styled-components'; +import { PropFunc } from '~/types'; +import { ShipImage } from '~/views/components/ShipImage'; + +export interface LinkInputProps { + url?: string; + title?: string; +} + +const GradientRow = styled(Row)` + background: linear-gradient( + to right, + ${p => p.theme.colors.none}, + ${p => p.theme.colors.white} 50% + ); +`; + +const Input = (props: PropFunc) => ( + +); + +export function LinkInput(props: LinkInputProps) { + const [url, setUrl] = useState(props.url || ''); + const [title, setTitle] = useState(props.title || ''); + + const onUrlChange = useCallback((e: ChangeEvent) => { + setUrl(e.target.value); + }, []); + + const onTitleChange = useCallback((e: ChangeEvent) => { + setTitle(e.target.value); + }, []); + + return ( + + + + + + + + + + Post + + + + + ); +} diff --git a/pkg/interface/src/views/components/ShipImage.tsx b/pkg/interface/src/views/components/ShipImage.tsx new file mode 100644 index 0000000000..1a88415107 --- /dev/null +++ b/pkg/interface/src/views/components/ShipImage.tsx @@ -0,0 +1,49 @@ +import { uxToHex } from '@urbit/api'; +import React from 'react'; +import { Box, BaseImage } from '@tlon/indigo-react'; +import { useContact } from '~/logic/state/contact'; +import useSettingsState from '~/logic/state/settings'; +import { Sigil } from '~/logic/lib/sigil'; + +export function ShipImage(props: { + ship: string; + size?: number; + sigilClass?: string; +}) { + const { ship, size = 24, sigilClass = '' } = props; + const contact = useContact(ship); + const { hideAvatars } = useSettingsState(s => s.calm); + + const color = contact?.color ? uxToHex(contact.color) : '000000'; + + return contact?.avatar && !hideAvatars ? ( + + ) : ( + + + + ); +} From 5f8249fab02653f342d7371b5864b6ee3b56dac8 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Wed, 26 May 2021 13:18:48 +1000 Subject: [PATCH 003/237] collections: add LinkBlockItem --- .../src/stories/LinkBlockItem.stories.tsx | 76 ++++++++++++++++++ .../src/stories/LinkInput.stories.tsx | 2 +- .../apps/links/components/LinkBlockItem.tsx | 70 +++++++++++++++++ .../src/views/components/AudioPlayer.tsx | 77 +++++++++++++++++++ .../src/views/components/RemoteContent.tsx | 6 +- 5 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 pkg/interface/src/stories/LinkBlockItem.stories.tsx create mode 100644 pkg/interface/src/views/apps/links/components/LinkBlockItem.tsx create mode 100644 pkg/interface/src/views/components/AudioPlayer.tsx diff --git a/pkg/interface/src/stories/LinkBlockItem.stories.tsx b/pkg/interface/src/stories/LinkBlockItem.stories.tsx new file mode 100644 index 0000000000..b89ec4736b --- /dev/null +++ b/pkg/interface/src/stories/LinkBlockItem.stories.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { Meta } from '@storybook/react'; +import { withDesign } from 'storybook-addon-designs'; + +import { Col } from '@tlon/indigo-react'; +import { LinkBlockItem } from '~/views/apps/links/components/LinkBlockItem'; + +export default { + title: 'Collections/BlockItem', + component: LinkBlockItem, + decorators: [withDesign] +} as Meta; + +export const Image = () => ( + + + + +); + +Image.parameters = { + design: { + type: 'figma', + url: + 'https://www.figma.com/file/ovD1mlsYDa0agyYTdvCmGr/Landscape?node-id=8228%3A11' + } +}; + +export const Fallback = () => ( + + + + +); + +Fallback.parameters = { + design: { + type: 'figma', + url: + 'https://www.figma.com/file/ovD1mlsYDa0agyYTdvCmGr/Landscape?node-id=8228%3A57' + } +}; + +export const Audio = () => ( + + + +); + +Audio.parameters = { + design: { + type: 'figma', + url: + 'https://www.figma.com/file/ovD1mlsYDa0agyYTdvCmGr/Landscape?node-id=8229%3A0' + } +}; + +export const Youtube = () => ( + + + +); + +Youtube.parameters = { + design: { + type: 'figma', + url: + 'https://www.figma.com/file/ovD1mlsYDa0agyYTdvCmGr/Landscape?node-id=8229%3A0' + } +}; diff --git a/pkg/interface/src/stories/LinkInput.stories.tsx b/pkg/interface/src/stories/LinkInput.stories.tsx index b1ed720e00..093e6f260d 100644 --- a/pkg/interface/src/stories/LinkInput.stories.tsx +++ b/pkg/interface/src/stories/LinkInput.stories.tsx @@ -9,7 +9,7 @@ import { } from '~/views/apps/links/components/LinkInput'; export default { - title: 'Input/Collections', + title: 'Collections/Input', component: LinkInput, decorators: [withDesign] } as Meta; diff --git a/pkg/interface/src/views/apps/links/components/LinkBlockItem.tsx b/pkg/interface/src/views/apps/links/components/LinkBlockItem.tsx new file mode 100644 index 0000000000..d6b6c07774 --- /dev/null +++ b/pkg/interface/src/views/apps/links/components/LinkBlockItem.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { + BaseImage, + Icon, + Center, + Row, + Text +} from '@tlon/indigo-react'; +import { AUDIO_REGEX, IMAGE_REGEX } from '~/views/components/RemoteContent'; +import { AudioPlayer } from '~/views/components/AudioPlayer'; + +export interface LinkBlockItemProps { + url: string; + title?: string; +} + +function getYoutubeId(str: string): string | null { + const youtube = str.match(/youtube\.com.*(\?v=|\/embed\/)(.{11})/); + if(!youtube) { + return null; + } + return youtube.pop(); +} + +export function LinkBlockItem(props: LinkBlockItemProps) { + const { url, title } = props; + + const isImage = IMAGE_REGEX.test(url); + const isAudio = AUDIO_REGEX.test(url); + const youtube = getYoutubeId(url); + return ( +
+ {isImage ? ( + + ) : isAudio ? ( + + ) : youtube ? ( + + ) : ( + + + + {url} + + + )} +
+ ); +} diff --git a/pkg/interface/src/views/components/AudioPlayer.tsx b/pkg/interface/src/views/components/AudioPlayer.tsx new file mode 100644 index 0000000000..3e9da314b4 --- /dev/null +++ b/pkg/interface/src/views/components/AudioPlayer.tsx @@ -0,0 +1,77 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { Action, Text, Icon, Row } from '@tlon/indigo-react'; + +function formatTime(num: number) { + const minutes = Math.floor(num / 60); + const seconds = Math.floor(num % 60); + return `${minutes}:${seconds.toString().padStart(2, '0')}`; +} + +export function AudioPlayer(props: { url: string; title?: string }) { + const { url, title = '' } = props; + const ref = useRef(); + + const [playing, setPlaying] = useState(false); + + const playPause = useCallback(() => { + if (playing) { + ref.current.pause(); + } else { + ref.current.play(); + } + setPlaying(p => !p); + }, [ref, playing]); + + const [duration, setDuration] = useState(0); + const [progress, setProgress] = useState(0); + + useEffect(() => { + if (playing) { + // eslint-disable-next-line no-inner-declarations + function updateProgress() { + setProgress(ref.current.currentTime); + } + + const tim = setInterval(updateProgress, 250); + + return () => { + tim && clearTimeout(tim); + }; + } + }, [ref.current, playing]); + + useEffect(() => { + ref.current.addEventListener('loadedmetadata', () => { + setDuration(ref.current.duration); + }); + }, [ref.current]); + + return ( + + + {title} + + + ); +} diff --git a/pkg/interface/src/views/components/RemoteContent.tsx b/pkg/interface/src/views/components/RemoteContent.tsx index 960275eaae..97ba30d372 100644 --- a/pkg/interface/src/views/components/RemoteContent.tsx +++ b/pkg/interface/src/views/components/RemoteContent.tsx @@ -32,9 +32,9 @@ interface RemoteContentState { showArrow: boolean; } -const IMAGE_REGEX = new RegExp(/(jpg|img|png|gif|tiff|jpeg|webp|webm|svg)$/i); -const AUDIO_REGEX = new RegExp(/(mp3|wav|ogg)$/i); -const VIDEO_REGEX = new RegExp(/(mov|mp4|ogv)$/i); +export const IMAGE_REGEX = new RegExp(/(jpg|img|png|gif|tiff|jpeg|webp|webm|svg)$/i); +export const AUDIO_REGEX = new RegExp(/(mp3|wav|ogg|m4a)$/i); +export const VIDEO_REGEX = new RegExp(/(mov|mp4|ogv)$/i); const TruncatedText = styled(Text)` white-space: pre; From fab546a72a46e7bd2cf1fb9c7d9a6d79e0f38203 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Thu, 3 Jun 2021 09:36:19 +1000 Subject: [PATCH 004/237] interface: add storybook fixtures, LinkBlocks --- pkg/interface/.storybook/preview.js | 45 ++++ pkg/interface/src/logic/lib/fixtures.ts | 45 ++++ pkg/interface/src/logic/lib/test.sh | 206 ++++++++++++++++++ pkg/interface/src/logic/lib/useResize.ts | 29 +++ pkg/interface/src/logic/lib/useUrlField.tsx | 34 +++ .../src/stories/LinkBlockItem.stories.tsx | 11 +- .../src/stories/LinkDetail.stories.tsx | 60 +++++ .../src/stories/LinkListItem.stories.tsx | 1 + .../src/views/apps/links/LinkResource.tsx | 62 ++---- .../apps/links/components/LinkBlockInput.tsx | 111 ++++++++++ .../apps/links/components/LinkBlockItem.tsx | 78 +++++-- .../apps/links/components/LinkBlocks.tsx | 57 +++++ .../apps/links/components/LinkDetail.tsx | 54 +++++ .../apps/links/components/LinkListItem.tsx | 11 + pkg/interface/src/views/components/Author.tsx | 3 + .../src/views/components/CommentInput.tsx | 92 ++++++-- .../src/views/components/CommentItem.tsx | 103 +++++---- .../src/views/components/Comments.tsx | 32 ++- .../src/views/components/ImageInput.tsx | 161 +++++++------- .../src/views/components/ShipImage.tsx | 2 +- .../src/views/components/Titlebar.tsx | 110 ++++++++++ .../landscape/components/ResourceSkeleton.tsx | 11 + 22 files changed, 1099 insertions(+), 219 deletions(-) create mode 100644 pkg/interface/src/logic/lib/fixtures.ts create mode 100644 pkg/interface/src/logic/lib/test.sh create mode 100644 pkg/interface/src/logic/lib/useResize.ts create mode 100644 pkg/interface/src/logic/lib/useUrlField.tsx create mode 100644 pkg/interface/src/stories/LinkDetail.stories.tsx create mode 100644 pkg/interface/src/stories/LinkListItem.stories.tsx create mode 100644 pkg/interface/src/views/apps/links/components/LinkBlockInput.tsx create mode 100644 pkg/interface/src/views/apps/links/components/LinkBlocks.tsx create mode 100644 pkg/interface/src/views/apps/links/components/LinkDetail.tsx create mode 100644 pkg/interface/src/views/apps/links/components/LinkListItem.tsx create mode 100644 pkg/interface/src/views/components/Titlebar.tsx diff --git a/pkg/interface/.storybook/preview.js b/pkg/interface/.storybook/preview.js index fdc95f9728..b4f6628f4e 100644 --- a/pkg/interface/.storybook/preview.js +++ b/pkg/interface/.storybook/preview.js @@ -5,6 +5,7 @@ import { BrowserRouter } from 'react-router-dom'; import { ThemeProvider } from 'styled-components'; import useGraphState from '~/logic/state/graph'; import useMetadataState from '~/logic/state/metadata'; +import useContactState from '~/logic/state/contact'; import '~/views/landscape/css/custom.css'; import '~/views/css/fonts.css'; import '~/views/apps/chat/css/custom.css'; @@ -37,6 +38,31 @@ export const decorators = [ window.ship = 'sampel-palnet'; const theme = context.globals.theme === 'light' ? light : dark; + useContactState.setState({ + contacts: { + '~ridlur-figbud': { + status: 'please like and subscribe', + 'last-updated': 1616609090555, + avatar: null, + cover: null, + bio: '', + nickname: 'Gav', + color: '0x26.3e0f', + groups: [], + }, +'~sampel-palnet': { + status: 'A test status', + 'last-updated': 1616609090555, + avatar: null, + cover: null, + bio: '', + nickname: 'You', + color: '0x26.3e0f', + groups: [], + } + }, + }); + useMetadataState.setState({ associations: { groups: { @@ -65,6 +91,25 @@ export const decorators = [ }, }, graph: { + '/ship/~bitbet-bolbel/links': { + metadata: { + preview: false, + vip: '', + title: 'Link Collection', + description: '', + creator: '~darrux-landes', + picture: '', + hidden: false, + config: { + graph: 'link', + }, + 'date-created': '~2020.4.6..21.53.30..dc68', + color: '0x0', + }, + 'app-name': 'graph', + resource: '/ship/~bitbet-bolbel/links', + group: '/ship/~bitbet-bolbel/urbit-community', + }, '/ship/~darrux-landes/development': { metadata: { preview: false, diff --git a/pkg/interface/src/logic/lib/fixtures.ts b/pkg/interface/src/logic/lib/fixtures.ts new file mode 100644 index 0000000000..33b2c84ff1 --- /dev/null +++ b/pkg/interface/src/logic/lib/fixtures.ts @@ -0,0 +1,45 @@ +import { Content, GraphNode, unixToDa } from '@urbit/api'; +import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap'; +import bigInt, { BigInteger } from 'big-integer'; + +export const makeComment = ( + author: string, + time: number, + parentIndex: string, + contents: Content[] +): [BigInteger, GraphNode] => { + const da = unixToDa(time); + const index = `${parentIndex}/${da.toString()}`; + + const children = new BigIntOrderedMap().gas([ + [ + bigInt.one, + { + post: { + index: `${index}/1`, + author, + 'time-sent': time, + signatures: [], + contents: contents, + hash: null + }, + children: new BigIntOrderedMap() + } + ] + ]); + + return [ + da, + { + post: { + index, + author, + 'time-sent': time, + signatures: [], + contents: [], + hash: null + }, + children + } + ]; +}; diff --git a/pkg/interface/src/logic/lib/test.sh b/pkg/interface/src/logic/lib/test.sh new file mode 100644 index 0000000000..e3b5533ab1 --- /dev/null +++ b/pkg/interface/src/logic/lib/test.sh @@ -0,0 +1,206 @@ +#!/usr/bin/env bash + +# generated by janeway v0.14.3 +# +# shellcheck disable=SC2039,SC2034,SC1091 + +set -e +set -o pipefail +GIT="git" +SED="sed" +NPM="npm" +GSUTIL="gsutil" +GCLOUD=gcloud +HUB="hub" +MSMTP="msmtp" +URBIT="urbit" +HERB="herb" +MKTEMP="mktemp" + +if [[ ! $(type -P $GIT) ]]; then + echo "ERROR: git not found" + exit 1 +else + : +fi + +if [[ ! $(type -P $SED) ]]; then + echo "ERROR: sed not found" + exit 1 +else + : +fi + +if [[ ! $(type -P $HUB) ]]; then + echo "ERROR: hub not found" + exit 1 +else + : +fi + +if [[ ! $(type -P $GSUTIL) ]]; then + echo "ERROR: gsutil not found" + exit 1 +else + : +fi + +if [[ ! $(type -P $GCLOUD) ]]; then + echo "ERROR: gcloud not found" + exit 1 +else + : +fi + +if [[ ! $(type -P $NPM) ]]; then + echo "ERROR: npm not found" + exit 1 +else + : +fi + +if [[ ! $(type -P $URBIT) ]]; then + echo "ERROR: urbit not found" + exit 1 +else + : +fi + +if [[ ! $(type -P $HERB) ]]; then + echo "ERROR: herb not found" + exit 1 +else + : +fi + +if [[ ! $(type -P $MKTEMP) ]]; then + echo "ERROR: mktemp not found" + exit 1 +else + : +fi + +janeway() { + set -e + set -o pipefail + if [[ ! -d pkg/btc-wallet ]]; then + echo "ERROR: pkg/btc-wallet not found" + exit 1 + else + pushd pkg/btc-wallet + $NPM install --scripts-prepend-node-path && $NPM run build:prod --scripts-prepend-node-path + popd + fi + if [[ ! -d pkg/interface ]]; then + echo "ERROR: pkg/interface not found" + exit 1 + else + pushd pkg/interface + $NPM install --scripts-prepend-node-path && $NPM run build:prod --scripts-prepend-node-path + popd + fi + PIER=$($MKTEMP --dry-run "${TMPDIR:-/tmp/}fakezod.janeway.XXXXXXXXX") + if [ ! -f bin/solid.pill ]; then + echo "ERROR: bin/solid.pill not found" + exit 1 + else + SIZE=$(du -k bin/solid.pill | cut -f1) + if [ "$SIZE" -lt 10 ]; then + echo "ERROR: bin/solid.pill appears to be an LFS pointer" + exit 1 + else + $URBIT -dF zod -B bin/solid.pill -A pkg/arvo -c "$PIER" + fi + fi + $HERB "$PIER" -p glob -d "+glob/make /app/btc-wallet/js/bundle" + + GLOBPATH=$(ls "$PIER"/.urb/put/*.glob) + GLOBFILE=${GLOBPATH##*/} + GLOBBASE=${GLOBFILE%%.glob} + GLOBHASH=${GLOBBASE##glob-} + JSPATH=$(ls pkg/arvo/app/btc-wallet/js/bundle/index.*.js) + JSFILE=${JSPATH##*/} + + $SED --in-place \ + "s/^\+\+ btc-wallet-hash(.*)$/++ btc-wallet-hash $GLOBHASH :: DO NOT MOVE FROM LINE 8/" \ + pkg/arvo/app/glob.hoon + + $HERB "$PIER" -p hood -d '+hood/mount /=home=' + cp pkg/arvo/app/glob.hoon "$PIER"/home/app/glob.hoon + rm -f "$PIER"/home/app/btc-wallet/js/bundle/"$JSFILE" + $SED -E --in-place "s/index\.(js|([a-z]|[0-9])*\.js)/$JSFILE/" pkg/arvo/app/btc-wallet/index.html + cp pkg/arvo/app/btc-wallet/index.html "$PIER"/home/app/btc-wallet/index.html + $HERB "$PIER" -p hood -d '+hood/commit %home' + + if [ ! -f "$GLOBPATH" ]; then + echo "ERROR: $GLOBPATH not found" + exit 1 + else + $GSUTIL cp "$GLOBPATH" gs://bootstrap.urbit.org/"$GLOBFILE" + fi + rm "$GLOBPATH" + $HERB "$PIER" -p glob -d "+glob/make /app/landscape/js/bundle" + + GLOBPATH=$(ls "$PIER"/.urb/put/*.glob) + GLOBFILE=${GLOBPATH##*/} + GLOBBASE=${GLOBFILE%%.glob} + GLOBHASH=${GLOBBASE##glob-} + JSPATH=$(ls pkg/arvo/app/landscape/js/bundle/index.*.js) + JSFILE=${JSPATH##*/} + + $SED --in-place \ + "s/^\+\+ landscape-hash(.*)$/++ landscape-hash $GLOBHASH :: DO NOT MOVE FROM LINE 8/" \ + pkg/arvo/app/glob.hoon + + $HERB "$PIER" -p hood -d '+hood/mount /=home=' + cp pkg/arvo/app/glob.hoon "$PIER"/home/app/glob.hoon + rm -f "$PIER"/home/app/landscape/js/bundle/"$JSFILE" + $SED -E --in-place "s/index\.(js|([a-z]|[0-9])*\.js)/$JSFILE/" pkg/arvo/app/landscape/index.html + cp pkg/arvo/app/landscape/index.html "$PIER"/home/app/landscape/index.html + $HERB "$PIER" -p hood -d '+hood/commit %home' + + if [ ! -f "$GLOBPATH" ]; then + echo "ERROR: $GLOBPATH not found" + exit 1 + else + $GSUTIL cp "$GLOBPATH" gs://bootstrap.urbit.org/"$GLOBFILE" + fi + rm "$GLOBPATH" + $SED -E --in-place "8s/.*/\`$GLOBHASH\`/" pkg/btc-wallet/README.md + $HERB "$PIER" -P solid.pill -d '+solid' + mv -f solid.pill ./bin/solid.pill + $GIT -c user.name=janeway -c user.email=janeway@urbit.org \ + add pkg/arvo/app/glob.hoon + $GIT -c user.name=janeway -c user.email=janeway@urbit.org \ + add pkg/arvo/app/landscape/index.html + $GIT -c user.name=janeway -c user.email=janeway@urbit.org \ + add pkg/arvo/app/btc-wallet/index.html + $GIT -c user.name=janeway -c user.email=janeway@urbit.org \ + add pkg/btc-wallet/README.md + $GIT -c user.name=janeway -c user.email=janeway@urbit.org \ + add bin/solid.pill + $GIT -c user.name=janeway -c user.email=janeway@urbit.org \ + commit -m "glob: update to $GLOBHASH" + GLOB_RELEASE_HASH=$($GIT rev-parse --short HEAD) + $GIT -c user.name=janeway -c user.email=janeway@urbit.org \ + archive --prefix=arvo-glob-"$GLOB_RELEASE_HASH"/ \ + -o arvo-glob-"$GLOB_RELEASE_HASH".tar.gz HEAD :pkg/arvo + if [ -f arvo-glob-"$GLOB_RELEASE_HASH".tar.gz ]; then + $GSUTIL cp arvo-glob-"$GLOB_RELEASE_HASH".tar.gz gs://bootstrap.urbit.org/arvo-glob-"$GLOB_RELEASE_HASH".tar.gz + else + echo "ERROR: arvo-glob-$GLOB_RELEASE_HASH.tar.gz does not exist" + exit 1 + fi + if [ -e "$PIER"/.vere.lock ]; then + kill -3 "$(< "$PIER"/.vere.lock)" + while kill -0 "$(< "$PIER"/.vere.lock)" &> /dev/null; do + sleep 1 + done + rm -rf "$PIER" + else + rm -rf "$PIER" + fi +} +export -f janeway + +janeway diff --git a/pkg/interface/src/logic/lib/useResize.ts b/pkg/interface/src/logic/lib/useResize.ts new file mode 100644 index 0000000000..4f159883cf --- /dev/null +++ b/pkg/interface/src/logic/lib/useResize.ts @@ -0,0 +1,29 @@ +import { useEffect, useMemo, useRef } from 'react'; +import _ from 'lodash'; + +export function useResize( + callback: (entry: ResizeObserverEntry, observer: ResizeObserver) => void +) { + const ref = useRef(); + + useEffect(() => { + function observer( + entries: ResizeObserverEntry[], + observer: ResizeObserver + ) { + for (const entry of _.flatten(entries)) { + callback(entry, observer); + } + } + const resizeObs = new ResizeObserver(observer); + resizeObs.observe(ref.current, { box: 'border-box' }); + + return () => { + resizeObs.unobserve(ref.current); + }; + }, [callback]); + + const bind = useMemo(() => ({ ref }), [ref]); + + return bind; +} diff --git a/pkg/interface/src/logic/lib/useUrlField.tsx b/pkg/interface/src/logic/lib/useUrlField.tsx new file mode 100644 index 0000000000..7672e1f193 --- /dev/null +++ b/pkg/interface/src/logic/lib/useUrlField.tsx @@ -0,0 +1,34 @@ +import { useField } from 'formik'; +import { MutableRefObject, useCallback, useMemo } from 'react'; +import useStorage from './useStorage'; + +export function useUrlField( + id: string, + ref: MutableRefObject +) { + const [field, meta, helpers] = useField(id); + const { setValue, setError } = helpers; + + const storage = useStorage(); + const { uploadDefault, canUpload } = storage; + + const onImageUpload = useCallback(async () => { + const file = ref.current?.files?.item(0); + + if (!file || !canUpload) { + return; + } + try { + const url = await uploadDefault(file); + setValue(url); + } catch (e) { + setError(e.message); + } + }, [ref.current, uploadDefault, canUpload, setValue]); + const extStorage = useMemo(() => ({ ...storage, onImageUpload }), [ + storage, + onImageUpload + ]); + + return [field, meta, helpers, extStorage] as const; +} diff --git a/pkg/interface/src/stories/LinkBlockItem.stories.tsx b/pkg/interface/src/stories/LinkBlockItem.stories.tsx index b89ec4736b..41faecfa8e 100644 --- a/pkg/interface/src/stories/LinkBlockItem.stories.tsx +++ b/pkg/interface/src/stories/LinkBlockItem.stories.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Meta } from '@storybook/react'; import { withDesign } from 'storybook-addon-designs'; -import { Col } from '@tlon/indigo-react'; +import { Col, Row } from '@tlon/indigo-react'; import { LinkBlockItem } from '~/views/apps/links/components/LinkBlockItem'; export default { @@ -12,10 +12,11 @@ export default { } as Meta; export const Image = () => ( - - - - + + + + + ); Image.parameters = { diff --git a/pkg/interface/src/stories/LinkDetail.stories.tsx b/pkg/interface/src/stories/LinkDetail.stories.tsx new file mode 100644 index 0000000000..1e6893b060 --- /dev/null +++ b/pkg/interface/src/stories/LinkDetail.stories.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { Meta } from '@storybook/react'; +import { withDesign } from 'storybook-addon-designs'; + +import { Box } from '@tlon/indigo-react'; +import { LinkDetail } from '~/views/apps/links/components/LinkDetail'; +import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap'; +import { GraphNode } from '@urbit/api'; +import useMetadataState from '~/logic/state/metadata'; +import { makeComment } from '~/logic/lib/fixtures'; +import moment from 'moment'; + +export default { + title: 'Collections/LinkDetail', + component: LinkDetail, + decorators: [withDesign] +} as Meta; + +const nodeIndex = '/170141184504850861030994857749504231211'; +const node = { + post: { + index: '/170141184504850861030994857749504231211', + author: 'fabled-faster', + 'time-sent': 1609969377513, + signatures: [], + contents: [ + { text: 'IMG_20200827_150753' }, + { + url: + 'https://fabled-faster.nyc3.digitaloceanspaces.com/fabled-faster/2021.1.06..21.42.48-structure-0001.png' + } + ], + hash: null + }, + children: new BigIntOrderedMap().gas([ + makeComment('ridlur-figbud', moment().hour(12).minute(34).valueOf(), nodeIndex, [{ text: 'Beautiful' }]), + makeComment('roslet-tanner', moment().hour(12).minute(34).valueOf(), nodeIndex, [{ text: 'where did you find this?' }]), + + makeComment('fabled-faster', moment().hour(12).minute(34).valueOf(), nodeIndex, [{ text: 'I dont\'t remember lol' }]) + ]) +}; +const fakeApi = {} as any; + +export const Image = () => { + const association = useMetadataState( + s => s.associations.graph['/ship/~bitbet-bolbel/links'] + ); + return ( + + + + ); +}; +Image.parameters = { + design: { + type: 'figma', + url: + 'https://www.figma.com/file/ovD1mlsYDa0agyYTdvCmGr/Landscape?node-id=8303%3A591' + } +}; diff --git a/pkg/interface/src/stories/LinkListItem.stories.tsx b/pkg/interface/src/stories/LinkListItem.stories.tsx new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/pkg/interface/src/stories/LinkListItem.stories.tsx @@ -0,0 +1 @@ + diff --git a/pkg/interface/src/views/apps/links/LinkResource.tsx b/pkg/interface/src/views/apps/links/LinkResource.tsx index eb69ca2e9e..4a0a52fe0d 100644 --- a/pkg/interface/src/views/apps/links/LinkResource.tsx +++ b/pkg/interface/src/views/apps/links/LinkResource.tsx @@ -3,19 +3,17 @@ import { Group } from '@urbit/api'; import { Association } from '@urbit/api/metadata'; import bigInt from 'big-integer'; import React, { useEffect } from 'react'; -import { Link, Route, Switch } from 'react-router-dom'; +import { Redirect, Route, Switch } from 'react-router-dom'; import GlobalApi from '~/logic/api/global'; import useGraphState from '~/logic/state/graph'; import useMetadataState from '~/logic/state/metadata'; import { StoreState } from '~/logic/store/type'; -import { Comments } from '~/views/components/Comments'; import useGroupState from '../../../logic/state/group'; -import { LinkItem } from './components/LinkItem'; +import { LinkBlocks } from './components/LinkBlocks'; +import { LinkDetail } from './components/LinkDetail'; import './css/custom.css'; import LinkWindow from './LinkWindow'; -const emptyMeasure = () => {}; - type LinkResourceProps = StoreState & { association: Association; api: GlobalApi; @@ -58,12 +56,13 @@ export function LinkResource(props: LinkResourceProps) { return ( + { return ( - // @ts-ignore + // @ts-ignore withState typings { + return ( + // @ts-ignore wip + + ); + }} + /> + + { const index = bigInt(props.match.params.index); - const editCommentId = props.match.params.commentId || null; if (!index) { return
Malformed URL
; @@ -103,36 +112,11 @@ export function LinkResource(props: LinkResourceProps) { ); } return ( - - - {'<- Back'} - - - - + ); }} /> diff --git a/pkg/interface/src/views/apps/links/components/LinkBlockInput.tsx b/pkg/interface/src/views/apps/links/components/LinkBlockInput.tsx new file mode 100644 index 0000000000..f2462fbfad --- /dev/null +++ b/pkg/interface/src/views/apps/links/components/LinkBlockInput.tsx @@ -0,0 +1,111 @@ +import React, { ChangeEvent, useCallback, useRef, useState } from 'react'; +import { BaseInput, Box, Label, Button } from '@tlon/indigo-react'; + +import { + Prompt, + ClearButton, + UploadingStatus, + ErrorRetry, +} from '~/views/components/ImageInput'; +import useStorage from '~/logic/lib/useStorage'; + +interface LinkBlockInputProps { + size: string; + label: string; + caption: string; + id: string; + url?: string; +} +export function LinkBlockInput(props: LinkBlockInputProps) { + const { id, size, label, caption } = props; + const [url, setUrl] = useState(props.url || ''); + const [error, setError] = useState(); + + const onUrlChange = useCallback((e: ChangeEvent) => { + setUrl(e.target.value); + }, []); + + const ref = useRef(); + + const clickUploadButton = useCallback(() => { + ref.current?.click(); + }, [ref]); + + const { uploading, canUpload, uploadDefault } = useStorage(); + + const clearEvt = useCallback(() => { + setUrl(''); + }, []); + + const onImageUpload = useCallback(async () => { + const file = ref.current?.files?.item(0); + + if (!file || !canUpload) { + return; + } + try { + const url = await uploadDefault(file); + setUrl(url); + } catch (e) { + setError(e.message); + } + }, [ref.current, uploadDefault, canUpload]); + + return ( + + {label ? : null} + {caption ? ( + + ) : null} + + + + + {canUpload && ( + <> +