Merge branch 'release/next-userspace' into mp/landscape/dm-restoration

This commit is contained in:
Matilde Park 2020-10-22 16:54:32 -04:00
commit 3b39122984
16 changed files with 400 additions and 404 deletions

View File

@ -0,0 +1,20 @@
:: :metadata-store|remove: remove resource from group
:: Usage:
:: :metadata-store|remove
:: <group-name> <app-name> <channel-path>
:: %urbit-community %chat /~darrux-landes/general-503
::
:: You can acquire the channel-path with
:: :metadata-store +dbug [%state '(~(got by group-indices) <group-path>)'
:: and looking for the entry with an app-path that is similar to the
:: title of the channel
::
/- *metadata-store
/+ resource
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[group=term app=term =path ~] ~]
==
:- %metadata-action
^- metadata-action
[%remove (en-path:resource [p.beak group]) app path]

View File

@ -3,6 +3,10 @@ import f from 'lodash/fp';
export const MOBILE_BROWSER_REGEX = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i;
export function parentPath(path) {
return _.dropRight(path.split('/'), 1).join('/');
}
export function clamp(x,min,max) {
return Math.max(min, Math.min(max, x));
}

View File

@ -49,6 +49,7 @@ const Root = styled.div`
/* Works on Chrome/Edge/Safari */
*::-webkit-scrollbar {
width: 6px;
height: 6px;
}
*::-webkit-scrollbar-track {
background: transparent;

View File

@ -1,7 +1,7 @@
import React, { Component, PureComponent } from "react";
import moment from "moment";
import _ from "lodash";
import { Box, Row, Text } from "@tlon/indigo-react";
import { Box, Row, Text, Rule } from "@tlon/indigo-react";
import { OverlaySigil } from './overlay-sigil';
import { uxToHex, cite, writeText } from '~/logic/lib/util';
@ -14,15 +14,15 @@ import RemoteContent from '~/views/components/RemoteContent';
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
export const UnreadMarker = React.forwardRef(({ dayBreak, when }, ref) => (
<div ref={ref} style={{ color: "#219dff" }} className="flex items-center f9 absolute w-100 left-0 pv0">
<hr style={{ borderColor: "#219dff" }} className="dn-s ma0 w2 bt-0" />
<p className="mh4 z-2" style={{ whiteSpace: 'normal' }}>New messages below</p>
<hr style={{ borderColor: "#219dff" }} className="ma0 flex-grow-1 bt-0" />
<Row ref={ref} color='blue' alignItems='center' fontSize='0' position='absolute' width='100%' py='2'>
<Rule borderColor='blue' display={['none', 'block']} m='0' width='2rem' />
<Text flexShrink='0' display='block' zIndex='2' mx='4' color='blue'>New messages below</Text>
<Rule borderColor='blue' flexGrow='1' m='0'/>
{dayBreak
? <p className="gray2 mh4">{moment(when).calendar()}</p>
? <Text display='block' gray mx='4'>{moment(when).calendar()}</Text>
: null}
<hr style={{ width: "calc(50% - 48px)" }} style={{ borderColor: "#219dff" }} className="ma0 bt-0" />
</div>
<Rule style={{ width: "calc(50% - 48px)" }} borderColor='blue' m='0' />
</Row>
));
export const DayBreak = ({ when }) => (
@ -95,7 +95,7 @@ export default class ChatMessage extends Component<ChatMessageProps> {
const containerClass = `${renderSigil
? `cf pt2 pl3 lh-copy`
: `items-center cf hide-child`} ${isPending ? 'o-40' : ''} ${className}`
: `items-top cf hide-child`} ${isPending ? 'o-40' : ''} ${className}`
const timestamp = moment.unix(msg.when / 1000).format(renderSigil ? 'hh:mm a' : 'hh:mm');
@ -122,7 +122,7 @@ export default class ChatMessage extends Component<ChatMessageProps> {
};
const unreadContainerStyle = {
height: isLastRead ? '1.66em' : '0',
height: isLastRead ? '2rem' : '0',
};
return (
@ -249,7 +249,7 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
export const MessageWithoutSigil = ({ timestamp, msg, remoteContentPolicy, measure }) => (
<>
<p className="child pr1 mono f9 gray2 dib">{timestamp}</p>
<Text mono gray display='inline-block' pr='1' pt='2px' lineHeight='tall' className="child">{timestamp}</Text>
<Box fontSize='14px' className="clamp-message" style={{ flexGrow: 1 }}>
<MessageContent content={msg.letter} remoteContentPolicy={remoteContentPolicy} measure={measure}/>
</Box>

View File

@ -3,7 +3,7 @@ import { UnControlled as CodeEditor } from 'react-codemirror2';
import { MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
import CodeMirror from 'codemirror';
import { Row } from '@tlon/indigo-react';
import { Row, BaseInput } from '@tlon/indigo-react';
import 'codemirror/mode/markdown/markdown';
import 'codemirror/addon/display/placeholder';
@ -36,6 +36,25 @@ const MARKDOWN_CONFIG = {
}
};
// Until CodeMirror supports options.inputStyle = 'textarea' on mobile,
// we need to hack this into a regular input that has some funny behaviors
const inputProxy = (input) => new Proxy(input, {
get(target, property) {
if (property in target) {
return target[property];
}
if (property === 'setOption') {
return () => {};
}
if (property === 'getValue') {
return () => target.value;
}
if (property === 'setValue') {
return (val) => target.value = val;
}
}
});
export default class ChatEditor extends Component {
constructor(props) {
super(props);
@ -129,7 +148,11 @@ export default class ChatEditor extends Component {
'Esc': () => {
this.editor?.getInputField().blur();
}
}
},
// The below will ony work once codemirror's bug is fixed
spellcheck: !!MOBILE_BROWSER_REGEX.test(navigator.userAgent),
autocorrect: !!MOBILE_BROWSER_REGEX.test(navigator.userAgent),
autocapitalize: !!MOBILE_BROWSER_REGEX.test(navigator.userAgent)
};
return (
@ -142,19 +165,39 @@ export default class ChatEditor extends Component {
paddingTop='8px'
width='calc(100% - 88px)'
className={inCodeMode ? 'chat code' : 'chat'}
color="black"
>
<CodeEditor
{MOBILE_BROWSER_REGEX.test(navigator.userAgent)
? <BaseInput
fontFamily={inCodeMode ? 'Source Code Pro' : 'Inter'}
fontSize="14px"
style={{ width: '100%', marginTop: '-8px', background: 'transparent', color: 'currentColor' }}
placeholder={inCodeMode ? "Code..." : "Message..."}
onKeyUp={event => {
if (event.key === 'Enter') {
this.submit();
} else {
this.messageChange(null, null, event.target.value);
}
}}
ref={input => {
if (!input) return;
this.editor = inputProxy(input);
}}
{...props}
/>
: <CodeEditor
value={message}
options={options}
onChange={(e, d, v) => this.messageChange(e, d, v)}
editorDidMount={(editor) => {
this.editor = editor;
if (!MOBILE_BROWSER_REGEX.test(navigator.userAgent)) {
editor.focus();
}
editor.focus();
}}
{...props}
/>
}
</Row>
);
}

View File

@ -19,6 +19,7 @@ export default class CodeContent extends Component {
overflow='auto'
maxHeight='10em'
maxWidth='100%'
style={{ whiteSpace: 'pre' }}
backgroundColor='scales.black10'
>
{content.code.output[0].join('\n')}
@ -36,6 +37,7 @@ export default class CodeContent extends Component {
overflow='auto'
maxHeight='10em'
maxWidth='100%'
style={{ whiteSpace: 'pre' }}
>
{content.code.expression}
</Text>

View File

@ -1,24 +1,21 @@
import React from 'react';
import Helmet from 'react-helmet';
import { Link } from 'react-router-dom';
import { Box, Row, Icon, Text, Center } from '@tlon/indigo-react';
import { uxToHex, adjustHex } from "~/logic/lib/util";
import { uxToHex, adjustHex } from '~/logic/lib/util';
import './css/custom.css';
import { Sigil } from "~/logic/lib/sigil";
import { Sigil } from '~/logic/lib/sigil';
import Tiles from './components/tiles';
import Tile from './components/tiles/tile';
import Welcome from './components/welcome';
import Groups from './components/Groups';
export default class LaunchApp extends React.Component {
componentDidMount() {
// preload spinner asset
new Image().src = '/~landscape/img/Spinner.png';
}
render() {
@ -27,16 +24,15 @@ export default class LaunchApp extends React.Component {
const sigilColor = contact?.color
? `#${uxToHex(contact.color)}`
: props.dark
? "#FFFFFF"
: "#000000";
? '#FFFFFF'
: '#000000';
return (
<>
<Helmet>
<title>OS1 - Home</title>
</Helmet>
<div className="h-100 overflow-y-scroll">
<Box height='100%' overflowY='scroll'>
<Welcome firstTime={props.launch.firstTime} api={props.api} />
<Box
ml='2'
@ -80,7 +76,7 @@ export default class LaunchApp extends React.Component {
weather={props.weather}
/>
</Box>
<Groups associations={props.associations} invites={props.invites} api={props.api}/>
<Groups associations={props.associations} invites={props.invites} api={props.api} />
<Box
position="absolute"
fontFamily="mono"
@ -92,10 +88,11 @@ export default class LaunchApp extends React.Component {
mb={3}
borderRadius={2}
fontSize={0}
p={2}>
p={2}
>
{props.baseHash}
</Box>
</div>
</Box>
</>
);
}

View File

@ -1,5 +1,4 @@
import React from 'react';
import classnames from 'classnames';
import { Text, Icon } from '@tlon/indigo-react';
import Tile from './tile';
@ -23,7 +22,7 @@ export default class BasicTile extends React.PureComponent {
verticalAlign='top'
pt='5px'
pr='2px'
/>
/>
: null
}{props.title}
</Text>

View File

@ -1,38 +1,28 @@
import React from 'react';
import moment from 'moment';
import SunCalc from 'suncalc';
import styled from 'styled-components';
import Tile from './tile';
const innerSize = 124; // clock size
const VIEWBOX_SIZE = 100;
const CX = VIEWBOX_SIZE / 2;
const CY = VIEWBOX_SIZE / 2;
const RADIUS = VIEWBOX_SIZE / 2;
const CELESTIAL_BODY_SIZE = 16;
// polar to cartesian
// var ptc = function(r, theta) {
// return {
// x: r * Math.cos(theta),
// y: r * Math.sin(theta)
// }
// }
let timeTextColor = '#000000', dateTextColor = '#333333', background = '#ffffff';
const dark = window.matchMedia('(prefers-color-scheme: dark)');
if (dark.matches) {
timeTextColor = '#ffffff';
dateTextColor = '#7f7f7f';
background = '#333';
}
function darkColors(dark) {
if (dark.matches) {
timeTextColor = '#ffffff';
dateTextColor = '#7f7f7f';
background = '#ffffff';
const ApplyClockBg = styled.div`
.background {
fill: ${p => p.theme.colors.white};
}
}
dark.addListener(darkColors);
.time {
fill: ${p => p.theme.colors.black};
color: ${p => p.theme.colors.black};
}
.date {
fill: ${p => p.theme.colors.gray};
}
`;
const toRelativeTime = (date, referenceTime, unit) => moment(date)
.diff(referenceTime, unit);
@ -42,10 +32,6 @@ const minsToDegs = (mins) => {
return (mins / 1440) * 360;
};
const splitArc = (start, end) => end + ((start - end) * 0.5);
const isOdd = n => Math.abs(n % 2) == 1;
const radToDeg = rad => rad * (180 / Math.PI);
const degToRad = deg => deg * (Math.PI / 180);
@ -54,58 +40,137 @@ const convert = (date, referenceTime) => {
return minsToDegs(toRelativeTime(date, referenceTime, 'minutes'));
};
const circle = (ctx, x, y, r, from, to, fill) => {
ctx.beginPath();
ctx.arc( x, y, r, from, to );
ctx.strokeStyle = 'rgba(0,0,0,0)';
ctx.fillStyle = fill || 'rgba(0,0,0,0)';
ctx.fill();
};
const circleClip = (ctx, x, y, r, from, to, fill) => {
ctx.globalCompositeOperation = 'xor';
circle(ctx, x, y, r, from, to, fill);
ctx.globalCompositeOperation = 'source-over';
};
const circleOutline = (ctx, x, y, r, from, to, stroke, lineWidth) => {
ctx.beginPath();
ctx.arc( x, y, r, from, to );
ctx.fillStyle = 'rgba(0,0,0,0)';
ctx.lineWidth = lineWidth;
ctx.strokeStyle = stroke || 'rgba(0,0,0,0)';
if (lineWidth) {
ctx.stroke();
// https://github.com/tingletech/moon-phase
export const dFromPhase = (moonPhase) => {
let mag, sweep, d = "m50,0";
if (moonPhase <= 0.25) {
sweep = [ 1, 0 ];
mag = 20 - 20 * moonPhase * 4;
} else if (moonPhase <= 0.50) {
sweep = [ 0, 0 ];
mag = 20 * (moonPhase - 0.25) * 4;
} else if (moonPhase <= 0.75) {
sweep = [ 1, 1 ];
mag = 20 - 20 * (moonPhase - 0.50) * 4;
} else if (moonPhase <= 1) {
sweep = [ 0, 1 ];
mag = 20 * (moonPhase - 0.75) * 4;
}
};
const arc = (ctx, x, y, r, from, to, fill) => {
ctx.beginPath();
ctx.arc( x, y, r, from, to );
ctx.fillStyle = 'rgba(0,0,0,0)';
ctx.lineWidth = r * 2;
ctx.strokeStyle = fill || 'rgba(0,0,0,0)';
ctx.stroke();
};
d = d + "a" + mag + ",20 0 1," + sweep[0] + " 0,100 ";
d = d + "a20,20 0 1," + sweep[1] + " 0,-100";
return d;
}
const degArc = (ctx, x, y, r, from, to, fill) => {
ctx.beginPath();
ctx.arc( x, y, r, degToRad(from), degToRad(to));
ctx.fillStyle = 'rgba(0,0,0,0)';
ctx.lineWidth = r * 2;
ctx.strokeStyle = fill || 'rgba(0,0,0,0)';
ctx.stroke();
};
const Moon = ({ angle, ...props }) => {
const phase = SunCalc.getMoonIllumination(moment().toDate()).phase.toFixed(2);
const cx = CX + (RADIUS - 12) * Math.cos(degToRad(angle)) - (CELESTIAL_BODY_SIZE / 2);
const cy = CY + (RADIUS - 12) * Math.sin(degToRad(angle)) - (CELESTIAL_BODY_SIZE / 2);
return (
<g>
<mask id="umbra">
<rect x="-50" y="-50" height="200" width="200" fill="black" />
<path d={dFromPhase(phase)} fill="white"/>
</mask>
<use
width={CELESTIAL_BODY_SIZE}
height={CELESTIAL_BODY_SIZE}
xlinkHref="#Moon-symbol"
x={cx}
y={cy}
transform={`rotate(${angle} ${cx + (CELESTIAL_BODY_SIZE / 2)} ${cy + (CELESTIAL_BODY_SIZE / 2)})`}
/>
</g>
);
}
class Clock extends React.Component {
const Sun = ({ angle, ...props}) => (
<circle
id="sun"
cx={CX + (RADIUS - 12) * Math.cos(degToRad(angle))}
cy={CY + (RADIUS - 12) * Math.sin(degToRad(angle))}
fill="#FCC440"
stroke="rgba(0,0,0,0.1)"
r={CELESTIAL_BODY_SIZE / 2}
{...props}
></circle>
);
const SvgArc = ({ start, end, ...rest }) => {
const x1 = CX + RADIUS * Math.cos(degToRad(start));
const y1 = CY + RADIUS * Math.sin(degToRad(start));
const x2 = CX + RADIUS * Math.cos(degToRad(end));
const y2 = CY + RADIUS * Math.sin(degToRad(end));
const isLarge = Math.abs((start > 360 ? start - 360 : start) - end) > 180;
const d = [
'M', CX, CY,
'L', x1, y1,
'A', RADIUS, RADIUS, '0', (isLarge ? '1' : '0'), '1', x2, y2, 'z'
].join(' ');
return <path d={d} {...rest} />;
}
class ClockText extends React.Component {
constructor(props) {
super(props);
this.state = {
time: Date.now()
}
}
componentDidMount() {
this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
const now = moment(this.state.time);
return (
<>
<use xlinkHref="#clock-center" className="background" />
<text
textAnchor="middle"
x={CX}
y={CY - 2}
fontSize="10"
fontFamily="Inter"
className="time"
>
<tspan>{now.format('h')}</tspan>
<tspan>:<animate attributeName="fill"
values="currentColor;transparent"
begin="0s"
dur="1s"
calcMode="discrete"
repeatCount="indefinite"/>
</tspan>
<tspan>{now.format('mm A')}</tspan>
</text>
<text
textAnchor="middle"
x={CX}
y={CY + 11}
fontSize="10"
fontFamily="Inter"
className="date"
>{now.format('MMM D')}<tspan style={{ fontFeatureSettings: "'sups' 1" }}>{now.format('Do').replace(now.format('D'), '')}</tspan></text>
</>
);
}
}
class Clock extends React.PureComponent {
constructor(props) {
super(props);
this.animate = this.animate.bind(this);
this.canvasRef = React.createRef();
this.canvas = null;
this.angle = 0;
this.referenceTime = moment().startOf('day').subtract(6, 'hours');
this.state = {
time: Date.now(),
lat: 0,
lon: 0,
geolocationSuccess: false,
@ -164,246 +229,84 @@ class Clock extends React.Component {
}
componentDidMount() {
this.canvas = initCanvas(
this.canvasRef,
{ x: innerSize, y: innerSize },
4
);
this.initGeolocation();
this.animate();
this.interval = setInterval(() => this.setState({ time: Date.now() }), 60000);
}
componentWillUnmount() {
if (this.animationTimer) {
window.clearTimeout(this.animationTimer);
}
}
animate() {
this.animationTimer =
window.setTimeout(() => window.requestAnimationFrame(this.animate), 1000);
const { state } = this;
const time = new Date();
const ctx = this.canvas.getContext('2d');
ctx.clearRect(0, 0, ctx.width, ctx.height);
ctx.save();
const ctr = innerSize / 2;
// Sun+moon calculations
const cx = ctr;
const cy = ctr;
this.angle = degToRad(convert(time, this.referenceTime));
const newX = cx + (ctr - 15) * Math.cos(this.angle);
const newY = cy + (ctr - 15) * Math.sin(this.angle);
// Center white circle with time and date
circle(
ctx,
ctr,
ctr,
ctr,
-1,
2 * Math.PI,
background
);
// Day
degArc(
ctx,
ctr,
ctr,
ctr / 2,
state.sunriseEnd,
state.sunset,
'rgba(33, 157, 255, .2)'
);
// Sunrise
degArc(
ctx,
ctr,
ctr,
ctr / 2,
state.sunsetStart,
state.sunriseEnd,
'#FFC700'
);
// Sunset
degArc(
ctx,
ctr,
ctr,
ctr / 2,
state.dusk,
state.dawn,
'rgba(255, 65, 54, .8)'
);
// Night
degArc(
ctx,
ctr,
ctr,
ctr / 2,
state.night,
state.nightEnd,
'rgba(0, 0, 0, .8)'
);
if (
radToDeg(this.angle) > splitArc(state.sunriseEnd, state.nightEnd)
&& radToDeg(this.angle) < splitArc(state.sunset, state.night)
) {
// Sun circle
circle(
ctx,
newX-1/2,
newY-1/2,
8,
0,
2 * Math.PI,
'#FCC440'
);
// Sun circle border
circleOutline(
ctx,
newX-1/2,
newY-1/2,
8,
0,
2 * Math.PI,
'rgba(0,0,0,0.1)',
1
);
} else {
// Moon circle
circle(
ctx,
newX-1/2,
newY-1/2,
8,
0,
2 * Math.PI,
'#FFFFFF'
);
// Moon circle outline
circleOutline(
ctx,
newX-1/2,
newY-1/2,
8,
0,
2 * Math.PI,
'#000000',
1
);
}
// Outer borders
circleOutline(
ctx,
ctr,
ctr,
ctr-1,
-1,
2 * Math.PI,
'none',
0
);
// Center white circle border
circleOutline(
ctx,
ctr,
ctr,
ctr/1.85,
-1,
2 * Math.PI,
'none',
0
);
// Inner hole
circle(
ctx,
ctr,
ctr,
ctr/1.85,
-1,
2 * Math.PI,
background
);
// Text for time and date
const timeText = isOdd(time.getSeconds())
? moment().format('h mm A')
: moment().format('h:mm A');
const dateText = moment().format('MMM Do');
ctx.textAlign = 'center';
ctx.fillStyle = timeTextColor;
ctx.font = '12px Inter';
ctx.fillText(timeText, ctr, ctr + 6 - 7);
ctx.fillStyle = dateTextColor;
ctx.font = '12px Inter';
ctx.fillText(dateText, ctr, ctr + 6 + 7);
ctx.restore();
clearInterval(this.interval);
}
render() {
const now = moment(this.state.time);
const angle = convert(now, this.referenceTime);
return (
<canvas
style={{ height: '100%', width: '100%'}}
ref={ canvasRef => this.canvasRef = canvasRef }
id="clock-canvas"
/>
<ApplyClockBg>
<svg
style={{ height: '100%', width: '100%'}}
viewBox={`0 0 ${VIEWBOX_SIZE} ${VIEWBOX_SIZE}`}
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<defs>
<symbol id="clock-center">
<circle r={VIEWBOX_SIZE / 1.85 / 2} cx={CX} cy={CY} />
</symbol>
<mask id="center-mask">
<rect x="0" y="0" width={VIEWBOX_SIZE} height={VIEWBOX_SIZE} fill="white" />
<use xlinkHref="#clock-center" fill="black"/>
</mask>
<symbol id="Moon-symbol" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<g>
<path mask="url(#umbra)" d="m50,0 a20,20 0 1,1 0,100 a20,20 0 1,1 0,-100" fill="#fff" stroke="#000"/>
</g>
</symbol>
</defs>
<g mask="url(#center-mask)">
<circle cx={CY} cy={CY} r={RADIUS} className="background" />
<SvgArc
id="day"
start={this.state.sunriseEnd}
end={this.state.sunset}
fill="rgba(33, 157, 255, .2)"
/>
<SvgArc
id="sunrise"
start={this.state.sunsetStart}
end={this.state.sunriseEnd}
fill="#FFC700"
/>
<SvgArc
id="sunset"
start={this.state.dusk}
end={this.state.dawn}
fill="rgba(255, 65, 54, .8)"
/>
<SvgArc
id="night"
start={this.state.night}
end={this.state.nightEnd}
fill="rgba(0, 0, 0, .8)"
/>
{angle > this.state.nightEnd && angle < this.state.sunset
? <Sun angle={angle} />
: <Moon angle={angle} />
}
</g>
<ClockText />
</svg>
</ApplyClockBg>
);
}
}
export default class ClockTile extends React.Component {
constructor(props) {
super(props);
}
renderWrapper(child) {
return (
<Tile p={0} border={0} bg='transparent' boxShadow='none'>
{child}
</Tile>
);
}
render() {
const data = this.props.location ? this.props.location : {};
return this.renderWrapper((
<Clock data={data} />
));
}
}
const initCanvas = (canvas, size, ratio) => {
const { x, y } = size;
// let ratio = ctx.webkitBackingStorePixelRatio < 2
// ? window.devicePixelRatio
// : 1;
// default for high print resolution.
// ratio = ratio * resMult;
canvas.width = x * ratio;
canvas.height = y * ratio;
canvas.style.width = x + 'px';
canvas.style.height = y + 'px';
canvas.getContext('2d').scale(ratio, ratio);
return canvas;
};
const ClockTile = ({ location = {} }) => (
<Tile p={0} border={0} bg='transparent' boxShadow='none'>
<Clock data={location} />
</Tile>
);
export default ClockTile;

View File

@ -1,26 +1,30 @@
import React from 'react';
import classnames from 'classnames';
import { Box, BaseImage } from '@tlon/indigo-react';
import Tile from './tile';
export default class CustomTile extends React.PureComponent {
render() {
const { props } = this;
return (
<Tile>
<div className={"w-100 h-100 relative bg-white bg-gray0-d ba " +
"b--black br2 b--gray1-d"}>
<img
className="absolute invert-d"
<Box
width='100%'
height='100%'
position='relative'
backgroundColor='white'
border='1px solid'
borderColor='washedGray'
borderRadius='2'
>
<BaseImage
position='absolute'
className="invert-d"
style={{ left: 38, top: 38 }}
src={'/~launch/img/UnknownCustomTile.png'}
width={48}
height={48} />
</div>
src='/~launch/img/UnknownCustomTile.png'
width='48px'
height='48px'
/>
</Box>
</Tile>
);
}
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import moment from 'moment';
import { Box, Icon, Text } from '@tlon/indigo-react';
import { Box, Icon, Text, BaseAnchor, BaseInput } from '@tlon/indigo-react';
import Tile from './tile';
@ -103,14 +103,15 @@ export default class WeatherTile extends React.Component {
let secureCheck;
let error;
if (this.state.error === true) {
error = <p className="f9 red2 pt1">Please try again.</p>;
error = <Text display='block' color='red' pt='1'>Please try again.</Text>;
}
if (location.protocol === 'https:') {
secureCheck = (
<a className="black white-d f9 pointer"
onClick={() => this.locationSubmit()}>
<Text color='black' cursor='pointer'
onClick={() => this.locationSubmit()}
>
Detect ->
</a>
</Text>
);
}
return this.renderWrapper(
@ -120,32 +121,38 @@ export default class WeatherTile extends React.Component {
justifyContent='space-between'
height='100%'
>
<a
className="f9 black white-d pointer"
<Text
color='black'
cursor='pointer'
onClick={() =>
this.setState({ manualEntry: !this.state.manualEntry })
}
>
&lt;&#45;
</a>
</Text>
{secureCheck}
<Text pb={1} mb='auto'>
Please enter your{' '}
<a
className="bb"
<BaseAnchor
borderBottom='1px solid'
color='black'
href="https://latitudeandlongitude.org/"
target="_blank"
>
latitude and longitude
</a>
</BaseAnchor>
.
</Text>
{error}
<form mt='auto' className="flex" style={{ marginBlockEnd: 0}}>
<input
<Box mt='auto' display='flex' marginBlockEnd='0'>
<BaseInput
id="gps"
size="10"
className="w-100 black white-d bg-transparent bn f9"
width='100%'
color='black'
fontSize='0'
backgroundColor='transparent'
border='0'
type="text"
placeholder="29.55, -95.08"
onKeyDown={(e) => {
@ -153,15 +160,21 @@ export default class WeatherTile extends React.Component {
e.preventDefault();
this.manualLocationSubmit(e.target.value);
}
}} />
<input
className={'bg-transparent black white-d bn pointer ' +
'f9 flex-shrink-0 pr1'}
}}
/>
<BaseInput
backgroundColor='transparent'
color='black'
cursor='pointer'
flexShrink='0'
pl='1'
fontSize='0'
border='0'
type="submit"
onClick={() => this.manualLocationSubmit()}
value="->"
/>
</form>
</Box>
</Box>
);
}
@ -216,17 +229,17 @@ export default class WeatherTile extends React.Component {
alignItems='space-between'
>
<Text color={weatherStyle.text}>
<Icon icon='Weather' color={weatherStyle.text} display='inline' style={{ position: 'relative', top: '.3em'}} />
<Icon icon='Weather' color={weatherStyle.text} display='inline' style={{ position: 'relative', top: '.3em' }} />
Weather
<a
style={{ color: weatherStyle.text }}
className='pointer'
onClick={() =>
this.setState({ manualEntry: !this.state.manualEntry })
}
>
->
</a>
<Text
color={weatherStyle.text}
cursor='pointer'
onClick={() =>
this.setState({ manualEntry: !this.state.manualEntry })
}
>
->
</Text>
</Text>
<Box
@ -257,15 +270,19 @@ export default class WeatherTile extends React.Component {
if (this.props.location) {
return this.renderWrapper((
<div
className={'pa2 w-100 h-100 ' +
'bg-white bg-gray0-d black white-d'}>
<Box
p='2'
width='100%'
height='100%'
backgroundColor='white'
color='black'
>
<Icon icon='Weather' color='black' display='inline' style={{ position: 'relative', top: '.3em' }} />
<Text>Weather</Text>
<p className="w-100 flex-col f9">
<Text pt='2' width='100%' display='flex' flexDirection='column'>
Loading, please check again later...
</p>
</div>
</Text>
</Box>
));
}
return this.renderNoData();

View File

@ -60,6 +60,7 @@ export function MarkdownEditor(
border={1}
borderColor="lightGray"
borderRadius={2}
height={['calc(100% - 22vh)', '100%']}
{...boxProps}
>
<CodeEditor

View File

@ -24,6 +24,7 @@ export const MarkdownField = ({
return (
<Box
overflowY="hidden"
height='100%'
width="100%"
display="flex"
flexDirection="column"

View File

@ -41,13 +41,13 @@ export function PostForm(props: PostFormProps) {
onSubmit={onSubmit}
validateOnBlur
>
<Form style={{ display: "contents" }}>
<Row flexDirection={["column-reverse", "row"]} mb={4} gapX={4} justifyContent='space-between'>
<Input maxWidth='40rem' flexGrow={1} placeholder="Post Title" id="title" />
<Form style={{ display: "contents"}}>
<Row flexShrink='0' flexDirection={["column-reverse", "row"]} mb={4} gapX={4} justifyContent='space-between'>
<Input maxWidth='40rem' width='100%' flexShrink={[0, 1]} placeholder="Post Title" id="title" />
<AsyncButton
ml={[0,2]}
mb={[4,0]}
flexShrink={1}
flexShrink={0}
primary
loadingText={loadingText}
>

View File

@ -168,7 +168,7 @@ export function GroupsPane(props: GroupsPaneProps) {
render={(routeProps) => {
const newUrl = `${baseUrl}/new`;
return (
<Skeleton recentGroups={recentGroups} {...props} baseUrl={baseUrl}>
<Skeleton mobileHide recentGroups={recentGroups} {...props} baseUrl={baseUrl}>
<NewChannel
{...routeProps}
api={api}

View File

@ -4,26 +4,27 @@ import {
ManagedTextInputField as Input,
Col,
ManagedRadioButtonField as Radio,
Text
} from '@tlon/indigo-react';
import { Formik, Form } from 'formik';
import * as Yup from 'yup';
import GlobalApi from '~/logic/api/global';
import { AsyncButton } from '~/views/components/AsyncButton';
import { FormError } from '~/views/components/FormError';
import { RouteComponentProps } from 'react-router-dom';
import { stringToSymbol } from '~/logic/lib/util';
import { Associations } from '~/types/metadata-update';
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
import { Groups } from '~/types/group-update';
import { ShipSearch } from '~/views/components/ShipSearch';
import { Rolodex } from '~/types';
Text,
} from "@tlon/indigo-react";
import { Formik, Form } from "formik";
import * as Yup from "yup";
import GlobalApi from "~/logic/api/global";
import { AsyncButton } from "~/views/components/AsyncButton";
import { FormError } from "~/views/components/FormError";
import { RouteComponentProps } from "react-router-dom";
import { stringToSymbol, parentPath } from "~/logic/lib/util";
import GroupSearch from "~/views/components/GroupSearch";
import { Associations } from "~/types/metadata-update";
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
import { Groups } from "~/types/group-update";
import { ShipSearch } from "~/views/components/ShipSearch";
import { Rolodex, Workspace } from "~/types";
interface FormSchema {
name: string;
description: string;
ships: string[];
type: 'chat' | 'publish' | 'links';
type: "chat" | "publish" | "link";
}
const formSchema = Yup.object({
@ -39,6 +40,7 @@ interface NewChannelProps {
contacts: Rolodex;
groups: Groups;
group?: string;
workspace: Workspace;
}
export function NewChannel(props: NewChannelProps & RouteComponentProps) {
@ -69,7 +71,7 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) {
case 'publish':
await props.api.publish.newBook(resId, name, description, group);
break;
case 'links':
case "link":
if (group) {
await api.graph.createManagedGraph(
resId,
@ -97,6 +99,8 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) {
await waiter(p => Boolean(p?.groups?.[`/ship/~${window.ship}/${resId}`]));
}
actions.setStatus({ success: null });
const resourceUrl = parentPath(location.pathname);
history.push(`${resourceUrl}/resource/${type}${type === 'link' ? '/ship' : ''}/~${window.ship}/${resId}`);
} catch (e) {
console.error(e);
actions.setStatus({ error: 'Channel creation failed' });
@ -129,7 +133,7 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) {
<Box color="black" mb={2}>Channel Type</Box>
<Radio label="Chat" id="chat" name="type" />
<Radio label="Notebook" id="publish" name="type" />
<Radio label="Collection" id="links" name="type" />
<Radio label="Collection" id="link" name="type" />
</Col>
<Input
id="name"