Add bare-bones gall app

This commit is contained in:
ronreg-ribdev 2020-11-28 00:12:21 -08:00
parent 0736a5870d
commit 05c4d61926
29 changed files with 92291 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
gall-app/node_modules

6
gall-app/.urbitrc Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
URBIT_PIERS: [
"../../zod/home",
],
URL: 'http://localhost:80'
};

150
gall-app/install.js Normal file
View File

@ -0,0 +1,150 @@
const prompt = require('prompt')
const replace = require('replace-in-file')
const fs = require('fs-extra');
var Promise = require('promise');
var path = require('path');
// Making the text input a bit legible.
prompt.colors = false
prompt.message = ""
// The text input takes a "result" object and passes it to one of two functions to do the logistics.
prompt.get([{
name: 'appName',
required: true,
description: "What's the name of your application? Lowercase and no spaces, please.",
message: "Lowercase and no spaces, please.",
conform: function(value) {
return /^[a-z0-9]+((\-[a-z0-9]+){1,})?$/g.test(value)
}
},
{
name: 'pier',
required: true,
description: "Where is your Urbit pier's desk located? For example, /Users/dev/zod/home"
}], function (err, result) {
setupFull(result)
}
)
// Migrate application to root directory.
const deleteFolderRecursive = function (path) {
if (fs.existsSync(path)) {
fs.readdirSync(path).forEach(function (file, index) {
var curPath = path + "/" + file;
if (fs.lstatSync(curPath).isDirectory()) {
deleteFolderRecursive(curPath);
} else {
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
}
};
var promiseAllWait = function (promises) {
// this is the same as Promise.all(), except that it will wait for all promises to fulfill before rejecting
var all_promises = [];
for (var i_promise = 0; i_promise < promises.length; i_promise++) {
all_promises.push(
promises[i_promise]
.then(function (res) {
return { res: res };
}).catch(function (err) {
return { err: err };
})
);
}
return Promise.all(all_promises)
.then(function (results) {
return new Promise(function (resolve, reject) {
var is_failure = false;
var i_result;
for (i_result = 0; i_result < results.length; i_result++) {
if (results[i_result].err) {
is_failure = true;
break;
} else {
results[i_result] = results[i_result].res;
}
}
if (is_failure) {
reject(results[i_result].err);
} else {
resolve(results);
}
});
});
};
var movePromiser = function (from, to, records) {
return fs.move(from, to)
.then(function () {
records.push({ from: from, to: to });
});
};
var moveDir = function (from_dir, to_dir, callback) {
return fs.readdir(from_dir)
.then(function (children) {
return fs.ensureDir(to_dir)
.then(function () {
var move_promises = [];
var moved_records = [];
var child;
for (var i_child = 0; i_child < children.length; i_child++) {
child = children[i_child];
move_promises.push(movePromiser(
path.join(from_dir, child),
path.join(to_dir, child),
moved_records
));
}
return promiseAllWait(move_promises)
.catch(function (err) {
var undo_move_promises = [];
for (var i_moved_record = 0; i_moved_record < moved_records.length; i_moved_record++) {
undo_move_promises.push(fs.move(moved_records[i_moved_record].to, moved_records[i_moved_record].from));
}
return promiseAllWait(undo_move_promises)
.then(function () {
throw err;
});
});
}).then(function () {
return fs.rmdir(from_dir);
});
}).then(callback);
};
const setupFull = function (result) {
fs.access('.DS_Store', (err) => { if (!err) fs.unlinkSync('.DS_Store') })
let deHyphenatedName = result.appName.replace(/-/g, '')
moveDir('full', './', function() {
fs.renameSync('urbit/app/smol.hoon', 'urbit/app/' + deHyphenatedName + '.hoon')
fs.renameSync('urbit/app/smol/', 'urbit/app/' + deHyphenatedName)
let urbitPierOptions = {
files: '.urbitrc',
from: "%URBITPIER%",
to: result.pier
}
replace(urbitPierOptions).then(changedFiles => console.log(changedFiles)).catch(err => console.error(err))
let appNameOptions = {
files: ['webpack.dev.js', 'webpack.prod.js', 'urbit/app/' + deHyphenatedName + '.hoon',
'src/js/api.js', 'src/js/subscription.js', 'src/js/components/root.js',
'urbit/app/' + deHyphenatedName + '/index.html'
],
from: /%APPNAME%/g,
to: deHyphenatedName
}
replace(appNameOptions).then(changedFiles => console.log(changedFiles)).catch(err => console.error(err))
})
console.log("All done! Happy hacking.")
}

9484
gall-app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

66
gall-app/package.json Normal file
View File

@ -0,0 +1,66 @@
{
"name": "create-landscape-app",
"version": "4.0.3",
"description": "Get started with a Landscape application.",
"main": "node install.js",
"scripts": {
"start": "node install.js",
"serve": "cross-env NODE_ENV=development webpack-dev-server --config webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config webpack.prod.js"
},
"author": "Tlon Corp",
"license": "MIT",
"repository": "https://github.com/urbit/create-landscape-app",
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-object-rest-spread": "^7.9.5",
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.10.5",
"@babel/preset-env": "^7.9.5",
"@babel/preset-react": "^7.9.4",
"babel-loader": "^8.1.0",
"babel-plugin-root-import": "^6.5.0",
"clean-webpack-plugin": "^3.0.0",
"cross-env": "^7.0.2",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.2.0",
"react-hot-loader": "^4.12.21",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
},
"dependencies": {
"@babel/runtime": "^7.10.5",
"@reach/disclosure": "^0.10.5",
"@reach/menu-button": "^0.10.5",
"@reach/tabs": "^0.10.5",
"@tlon/indigo-light": "^1.0.5",
"@tlon/indigo-react": "^1.2.8",
"classnames": "^2.2.6",
"css-loader": "^3.5.3",
"formik": "^2.2.0",
"fs-extra": "^8.1.0",
"lodash": "^4.17.11",
"markdown-to-jsx": "^7.0.1",
"moment": "^2.20.1",
"mousetrap": "^1.6.3",
"mv": "^2.1.1",
"promise": "^8.0.3",
"prompt": "^1.0.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.0.0",
"replace-in-file": "^4.1.1",
"style-loader": "^1.2.1",
"styled-components": "^5.2.0",
"styled-system": "^5.1.5",
"urbit-ob": "^5.0.0",
"urbit-sigil-js": "^1.3.13"
},
"resolutions": {
"natives": "1.1.3"
}
}

158
gall-app/src/css/custom.css Normal file
View File

@ -0,0 +1,158 @@
p, h1, h2, h3, h4, h5, h6, a, input, textarea, button {
margin-block-end: unset;
margin-block-start: unset;
-webkit-margin-before: unset;
-webkit-margin-after: unset;
font-family: Inter, sans-serif;
}
a {
color: #000;
text-decoration: none;
}
textarea, select, input, button {
outline: none;
-webkit-appearance: none;
border: none;
background-color: #fff;
}
.body-regular {
font-size: 16px;
line-height: 24px;
font-weight: 600;
}
.body-large {
font-size: 20px;
line-height: 24px;
}
.label-regular {
font-size: 14px;
line-height: 24px;
}
.label-small-mono {
font-size: 12px;
line-height: 24px;
font-family: "Source Code Pro", monospace;
}
.body-regular-400 {
font-size: 16px;
line-height: 24px;
font-weight: 400;
}
.plus-font {
font-size: 48px;
line-height: 24px;
}
.btn-font {
font-size: 14px;
line-height: 16px;
font-weight: 600;
}
.mono {
font-family: "Source Code Pro", monospace;
}
.inter {
font-family: Inter, sans-serif;
}
.mix-blend-diff {
mix-blend-mode: difference;
}
/* dark */
@media (prefers-color-scheme: dark) {
body {
background-color: #333;
}
.bg-black-d {
background-color: black;
}
.white-d {
color: white;
}
.gray1-d {
color: #4d4d4d;
}
.gray2-d {
color: #7f7f7f;
}
.gray3-d {
color: #b1b2b3;
}
.gray4-d {
color: #e6e6e6;
}
.bg-gray0-d {
background-color: #333;
}
.bg-gray1-d {
background-color: #4d4d4d;
}
.b--gray0-d {
border-color: #333;
}
.b--gray1-d {
border-color: #4d4d4d;
}
.b--gray2-d {
border-color: #7f7f7f;
}
.b--white-d {
border-color: #fff;
}
.bb-d {
border-bottom-width: 1px;
border-bottom-style: solid;
}
.invert-d {
filter: invert(1);
}
.o-80-d {
opacity: .8;
}
.focus-b--white-d:focus {
border-color: #fff;
}
a {
color: #fff;
}
.hover-bg-gray1-d:hover {
color: #4d4d4d;
}
}
/* responsive */
@media all and (max-width: 34.375em) {
.h-100-minus-40-s {
height: calc(100% - 40px);
}
}
@media all and (min-width: 34.375em) and (max-width: 46.875em) {
.h-100-minus-40-m {
height: calc(100% - 40px);
}
}
@media all and (min-width: 46.875em) and (max-width: 60em) {
.h-100-minus-40-l {
height: calc(100% - 40px);
}
}
@media all and (min-width: 60em) {
.h-100-minus-40-xl {
height: calc(100% - 40px);
}
}

View File

@ -0,0 +1,63 @@
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
src: url("https://media.urbit.org/fonts/Inter-Regular.woff2") format("woff2");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 400;
src: url("https://media.urbit.org/fonts/Inter-Italic.woff2") format("woff2");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
src: url("https://media.urbit.org/fonts/Inter-Bold.woff2") format("woff2");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 700;
src: url("https://media.urbit.org/fonts/Inter-BoldItalic.woff2") format("woff2");
}
@font-face {
font-family: "Source Code Pro";
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-extralight.woff");
font-weight: 200;
}
@font-face {
font-family: "Source Code Pro";
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-light.woff");
font-weight: 300;
}
@font-face {
font-family: "Source Code Pro";
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-regular.woff");
font-weight: 400;
}
@font-face {
font-family: "Source Code Pro";
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-medium.woff");
font-weight: 500;
}
@font-face {
font-family: "Source Code Pro";
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-semibold.woff");
font-weight: 600;
}
@font-face {
font-family: "Source Code Pro";
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-bold.woff");
font-weight: 700;
}

File diff suppressed because one or more lines are too long

21
gall-app/src/index.js Normal file
View File

@ -0,0 +1,21 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Root } from './js/components/root.js';
import { api } from './js/api.js';
import { subscription } from "./js/subscription.js";
import './css/indigo-static.css';
import './css/fonts.css';
import './css/custom.css';
api.setAuthTokens({
ship: window.ship
});
window.urb = new window.channel();
subscription.start();
ReactDOM.render((
<Root />
), document.querySelectorAll("#root")[0]);

47
gall-app/src/js/api.js Normal file
View File

@ -0,0 +1,47 @@
import _ from 'lodash';
class UrbitApi {
setAuthTokens(authTokens) {
this.authTokens = authTokens;
this.bindPaths = [];
}
bind(path, method, ship = this.authTokens.ship, appl = "browsermanager", success, fail) {
this.bindPaths = _.uniq([...this.bindPaths, path]);
window.subscriptionId = window.urb.subscribe(ship, appl, path,
(err) => {
fail(err);
},
(event) => {
success({
data: event,
from: {
ship,
path
}
});
},
(err) => {
fail(err);
});
}
browsermanager(data) {
this.action("browsermanager", "json", data);
}
action(appl, mark, data) {
return new Promise((resolve, reject) => {
window.urb.poke(ship, appl, mark, data,
(json) => {
resolve(json);
},
(err) => {
reject(err);
});
});
}
}
export let api = new UrbitApi();
window.api = api;

View File

@ -0,0 +1,37 @@
import React from 'react';
import { useLocation } from 'react-router-dom';
import { Row, Box, Text, Icon } from '@tlon/indigo-react';
import { StatusBarItem } from './icons/StatusBarItem';
import { Sigil } from './icons/sigil';
const HeaderBar = (props) => {
const display = (!window.location.href.includes('popout/'))
? 'grid' : 'none';
return (
<Box
display={display}
width="100%"
gridTemplateRows="30px"
gridTemplateColumns="3fr 1fr"
py={2}
>
<Row collapse>
<StatusBarItem mr={2} onClick={() => window.location.href = '/'}>
<Icon icon='Home' color='black' />
</StatusBarItem>
</Row>
<Row justifyContent="flex-end" collapse>
<StatusBarItem onClick={() => window.location.href = '/~profile'}>
<Sigil ship={window.ship} size={24} color={"#000000"} classes="dib mix-blend-diff" />
<Text ml={2} display={["none", "inline"]} fontFamily="mono">~{window.ship}</Text>
</StatusBarItem>
</Row>
</Box>
);
};
export default HeaderBar;

View File

@ -0,0 +1,41 @@
import React, { ReactNode } from "react";
import { Row as _Row, Icon } from "@tlon/indigo-react";
import styled from "styled-components";
const Row = styled(_Row)`
cursor: pointer;
`;
export function StatusBarItem({
badge,
children,
...props
}) {
return (
<Row
position="relative"
collapse
border={1}
borderRadius={2}
color="washedGray"
bg="white"
alignItems="center"
py={1}
px={2}
{...props}
>
{children}
{badge && (
<Icon
size="22px"
icon="Bullet"
fill="blue"
position="absolute"
top={"-10px"}
right={"-12px"}
/>
)}
</Row>
);
}

View File

@ -0,0 +1,9 @@
import React, { Component } from 'react';
export class IconSpinner extends Component {
render() {
return (
<div className="spinner-pending"></div>
);
}
}

View File

@ -0,0 +1,32 @@
import React, { Component } from 'react';
import { sigil, reactRenderer } from 'urbit-sigil-js';
export class Sigil extends Component {
render() {
const { props } = this;
let classes = props.classes || "";
if (props.ship.length > 14) {
return (
<div
className={"bg-black dib " + classes}
style={{ width: props.size, height: props.size }}>
</div>
);
} else {
return (
<div className={"dib " + classes} style={{ flexBasis: 32, backgroundColor: props.color }}>
{sigil({
patp: props.ship,
renderer: reactRenderer,
size: props.size,
colors: [props.color, "white"]
})}
</div>
);
}
}
}

View File

@ -0,0 +1,56 @@
import React, { Component } from 'react';
import { BrowserRouter, Route } from "react-router-dom";
import _ from 'lodash';
import HeaderBar from "./lib/header-bar.js"
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
import light from './themes/light';
import dark from './themes/dark';
import { Text, Box } from '@tlon/indigo-react';
export class Root extends Component {
constructor(props) {
super(props);
this.state = {
dark: false
}
this.updateTheme = this.updateTheme.bind(this);
}
updateTheme(updateTheme) {
this.setState({ dark: updateTheme });
}
componentDidMount() {
this.themeWatcher = window.matchMedia('(prefers-color-scheme: dark)');
this.setState({ dark: this.themeWatcher.matches });
this.themeWatcher.addListener(this.updateTheme);
}
render() {
return (
<BrowserRouter>
<ThemeProvider theme={this.state.dark ? dark : light}>
<Box display='flex' flexDirection='column' position='absolute' backgroundColor='white' height='100%' width='100%' px={[0,4]} pb={[0,4]}>
<HeaderBar/>
<Route exact path="/~browsermanager" render={ () => {
return (
<Box height='100%' p='4' display='flex' flexDirection='column' borderWidth={['none', '1px']} borderStyle="solid" borderColor="washedGray">
<Text fontSize='1'>browsermanager</Text>
<Text pt='3'>Welcome to your Landscape application.</Text>
<Text pt='3'>To get started, edit <code>src/index.js</code> or <code>urbit/app/browsermanager.hoon</code> and <code>|commit %home</code> on your Urbit ship to see your changes.</Text>
<a className="db f8 pt3" href="https://urbit.org/docs">-> Read the docs</a>
</Box>
)}}
/>
</Box>
</ThemeProvider>
</BrowserRouter>
)
}
}

View File

@ -0,0 +1,183 @@
import baseStyled from "styled-components";
const base = {
white: "rgba(255,255,255,1)",
black: "rgba(0,0,0,1)",
red: "rgba(255,65,54,1)",
yellow: "rgba(255,199,0,1)",
green: "rgba(0,159,101,1)",
blue: "rgba(0,142,255,1)",
};
const scales = {
white05: "rgba(255,255,255,0.05)",
white10: "rgba(255,255,255,0.1)",
white20: "rgba(255,255,255,0.2)",
white30: "rgba(255,255,255,0.3)",
white40: "rgba(255,255,255,0.4)",
white50: "rgba(255,255,255,0.5)",
white60: "rgba(255,255,255,0.6)",
white70: "rgba(255,255,255,0.7)",
white80: "rgba(255,255,255,0.8)",
white90: "rgba(255,255,255,0.9)",
white100: "rgba(255,255,255,1)",
black05: "rgba(0,0,0,0.05)",
black10: "rgba(0,0,0,0.1)",
black20: "rgba(0,0,0,0.2)",
black30: "rgba(0,0,0,0.3)",
black40: "rgba(0,0,0,0.4)",
black50: "rgba(0,0,0,0.5)",
black60: "rgba(0,0,0,0.6)",
black70: "rgba(0,0,0,0.7)",
black80: "rgba(0,0,0,0.8)",
black90: "rgba(0,0,0,0.9)",
black100: "rgba(0,0,0,1)",
red05: "rgba(255,65,54,0.05)",
red10: "rgba(255,65,54,0.1)",
red20: "rgba(255,65,54,0.2)",
red30: "rgba(255,65,54,0.3)",
red40: "rgba(255,65,54,0.4)",
red50: "rgba(255,65,54,0.5)",
red60: "rgba(255,65,54,0.6)",
red70: "rgba(255,65,54,0.7)",
red80: "rgba(255,65,54,0.8)",
red90: "rgba(255,65,54,0.9)",
red100: "rgba(255,65,54,1)",
yellow05: "rgba(255,199,0,0.05)",
yellow10: "rgba(255,199,0,0.1)",
yellow20: "rgba(255,199,0,0.2)",
yellow30: "rgba(255,199,0,0.3)",
yellow40: "rgba(255,199,0,0.4)",
yellow50: "rgba(255,199,0,0.5)",
yellow60: "rgba(255,199,0,0.6)",
yellow70: "rgba(255,199,0,0.7)",
yellow80: "rgba(255,199,0,0.8)",
yellow90: "rgba(255,199,0,0.9)",
yellow100: "rgba(255,199,0,1)",
green05: "rgba(0,159,101,0.05)",
green10: "rgba(0,159,101,0.1)",
green20: "rgba(0,159,101,0.2)",
green30: "rgba(0,159,101,0.3)",
green40: "rgba(0,159,101,0.4)",
green50: "rgba(0,159,101,0.5)",
green60: "rgba(0,159,101,0.6)",
green70: "rgba(0,159,101,0.7)",
green80: "rgba(0,159,101,0.8)",
green90: "rgba(0,159,101,0.9)",
green100: "rgba(0,159,101,1)",
blue05: "rgba(0,142,255,0.05)",
blue10: "rgba(0,142,255,0.1)",
blue20: "rgba(0,142,255,0.2)",
blue30: "rgba(0,142,255,0.3)",
blue40: "rgba(0,142,255,0.4)",
blue50: "rgba(0,142,255,0.5)",
blue60: "rgba(0,142,255,0.6)",
blue70: "rgba(0,142,255,0.7)",
blue80: "rgba(0,142,255,0.8)",
blue90: "rgba(0,142,255,0.9)",
blue100: "rgba(0,142,255,1)",
};
const util = {
cyan: "#00FFFF",
magenta: "#FF00FF",
yellow: "#FFFF00",
black: "#000000",
gray0: "#333333"
};
const theme = {
colors: {
white: util.gray0,
black: base.white,
gray: scales.white60,
lightGray: scales.white30,
washedGray: scales.white05,
red: base.red,
lightRed: scales.red30,
washedRed: scales.red05,
yellow: base.yellow,
lightYellow: scales.yellow30,
washedYellow: scales.yellow10,
green: base.green,
lightGreen: scales.green30,
washedGreen: scales.green10,
blue: base.blue,
lightBlue: scales.blue30,
washedBlue: scales.blue10,
none: "rgba(0,0,0,0)",
scales: scales,
util: util,
},
fonts: {
sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`,
mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`,
},
// font-size
fontSizes: [
12, // 0
16, // 1
24, // 2
32, // 3
48, // 4
64, // 5
],
// font-weight
fontWeights: {
thin: 300,
regular: 400,
bold: 600,
},
// line-height
lineHeights: {
min: 1.2,
short: 1.333333,
regular: 1.5,
tall: 1.666666,
},
// border, border-top, border-right, border-bottom, border-left
borders: ["none", "1px solid"],
// margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap
space: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// border-radius
radii: [
0, // 0
2, // 1
4, // 2
8, // 3
],
// width, height, min-width, max-width, min-height, max-height
sizes: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// z-index
zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
breakpoints: ["550px", "750px", "960px"],
};
export const styled = baseStyled;
export default theme;

View File

@ -0,0 +1,168 @@
import baseStyled from "styled-components";
const base = {
white: "rgba(255,255,255,1)",
black: "rgba(0,0,0,1)",
red: "rgba(255,65,54,1)",
yellow: "rgba(255,199,0,1)",
green: "rgba(0,159,101,1)",
blue: "rgba(0,142,255,1)",
};
const scales = {
white10: "rgba(255,255,255,0.1)",
white20: "rgba(255,255,255,0.2)",
white30: "rgba(255,255,255,0.3)",
white40: "rgba(255,255,255,0.4)",
white50: "rgba(255,255,255,0.5)",
white60: "rgba(255,255,255,0.6)",
white70: "rgba(255,255,255,0.7)",
white80: "rgba(255,255,255,0.8)",
white90: "rgba(255,255,255,0.9)",
white100: "rgba(255,255,255,1)",
black10: "rgba(0,0,0,0.1)",
black20: "rgba(0,0,0,0.2)",
black30: "rgba(0,0,0,0.3)",
black40: "rgba(0,0,0,0.4)",
black50: "rgba(0,0,0,0.5)",
black60: "rgba(0,0,0,0.6)",
black70: "rgba(0,0,0,0.7)",
black80: "rgba(0,0,0,0.8)",
black90: "rgba(0,0,0,0.9)",
black100: "rgba(0,0,0,1)",
red10: "rgba(255,65,54,0.1)",
red20: "rgba(255,65,54,0.2)",
red30: "rgba(255,65,54,0.3)",
red40: "rgba(255,65,54,0.4)",
red50: "rgba(255,65,54,0.5)",
red60: "rgba(255,65,54,0.6)",
red70: "rgba(255,65,54,0.7)",
red80: "rgba(255,65,54,0.8)",
red90: "rgba(255,65,54,0.9)",
red100: "rgba(255,65,54,1)",
yellow10: "rgba(255,199,0,0.1)",
yellow20: "rgba(255,199,0,0.2)",
yellow30: "rgba(255,199,0,0.3)",
yellow40: "rgba(255,199,0,0.4)",
yellow50: "rgba(255,199,0,0.5)",
yellow60: "rgba(255,199,0,0.6)",
yellow70: "rgba(255,199,0,0.7)",
yellow80: "rgba(255,199,0,0.8)",
yellow90: "rgba(255,199,0,0.9)",
yellow100: "rgba(255,199,0,1)",
green10: "rgba(0,159,101,0.1)",
green20: "rgba(0,159,101,0.2)",
green30: "rgba(0,159,101,0.3)",
green40: "rgba(0,159,101,0.4)",
green50: "rgba(0,159,101,0.5)",
green60: "rgba(0,159,101,0.6)",
green70: "rgba(0,159,101,0.7)",
green80: "rgba(0,159,101,0.8)",
green90: "rgba(0,159,101,0.9)",
green100: "rgba(0,159,101,1)",
blue10: "rgba(0,142,255,0.1)",
blue20: "rgba(0,142,255,0.2)",
blue30: "rgba(0,142,255,0.3)",
blue40: "rgba(0,142,255,0.4)",
blue50: "rgba(0,142,255,0.5)",
blue60: "rgba(0,142,255,0.6)",
blue70: "rgba(0,142,255,0.7)",
blue80: "rgba(0,142,255,0.8)",
blue90: "rgba(0,142,255,0.9)",
blue100: "rgba(0,142,255,1)",
};
const theme = {
colors: {
white: base.white,
black: base.black,
gray: scales.black60,
lightGray: scales.black30,
washedGray: scales.black10,
red: base.red,
lightRed: scales.red30,
washedRed: scales.red10,
yellow: base.yellow,
lightYellow: scales.yellow30,
washedYellow: scales.yellow10,
green: base.green,
lightGreen: scales.green30,
washedGreen: scales.green10,
blue: base.blue,
lightBlue: scales.blue30,
washedBlue: scales.blue10,
none: "rgba(0,0,0,0)",
scales: scales,
},
fonts: {
sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`,
mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`,
},
// font-size
fontSizes: [
12, // 0
16, // 1
24, // 2
32, // 3
48, // 4
64, // 5
],
// font-weight
fontWeights: {
thin: 300,
regular: 400,
bold: 600,
},
// line-height
lineHeights: {
min: 1.2,
short: 1.333333,
regular: 1.5,
tall: 1.666666,
},
// border, border-top, border-right, border-bottom, border-left
borders: ["none", "1px solid"],
// margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap
space: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// border-radius
radii: [
0, // 0
2, // 1
4, // 2
8, // 3
16, // 4
],
// width, height, min-width, max-width, min-height, max-height
sizes: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// z-index
zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
breakpoints: ["550px", "750px", "960px"],
};
export const styled = baseStyled;
export default theme;

View File

@ -0,0 +1,82 @@
import _ from 'lodash';
import classnames from 'classnames';
export function uuid() {
let str = "0v"
str += Math.ceil(Math.random()*8)+"."
for (var i = 0; i < 5; i++) {
let _str = Math.ceil(Math.random()*10000000).toString(32);
_str = ("00000"+_str).substr(-5,5);
str += _str+".";
}
return str.slice(0,-1);
}
export function isPatTa(str) {
const r = /^[a-z,0-9,\-,\.,_,~]+$/.exec(str)
return !!r;
}
/*
Goes from:
~2018.7.17..23.15.09..5be5 // urbit @da
To:
(javascript Date object)
*/
export function daToDate(st) {
var dub = function(n) {
return parseInt(n) < 10 ? "0" + parseInt(n) : n.toString();
};
var da = st.split('..');
var bigEnd = da[0].split('.');
var lilEnd = da[1].split('.');
var ds = `${bigEnd[0].slice(1)}-${dub(bigEnd[1])}-${dub(bigEnd[2])}T${dub(lilEnd[0])}:${dub(lilEnd[1])}:${dub(lilEnd[2])}Z`;
return new Date(ds);
}
/*
Goes from:
(javascript Date object)
To:
~2018.7.17..23.15.09..5be5 // urbit @da
*/
export function dateToDa(d, mil) {
  var fil = function(n) {
    return n >= 10 ? n : "0" + n;
  };
  return (
    `~${d.getUTCFullYear()}.` +
    `${(d.getUTCMonth() + 1)}.` +
    `${fil(d.getUTCDate())}..` +
    `${fil(d.getUTCHours())}.` +
    `${fil(d.getUTCMinutes())}.` +
    `${fil(d.getUTCSeconds())}` +
`${mil ? "..0000" : ""}`
  );
}
export function deSig(ship) {
return ship.replace('~', '');
}
// trim patps to match dojo, chat-cli
export function cite(ship) {
let patp = ship, shortened = "";
if (patp.startsWith("~")) {
patp = patp.substr(1);
}
// comet
if (patp.length === 56) {
shortened = "~" + patp.slice(0, 6) + "_" + patp.slice(50, 56);
return shortened;
}
// moon
if (patp.length === 27) {
shortened = "~" + patp.slice(14, 20) + "^" + patp.slice(21, 27);
return shortened;
}
return `~${patp}`;
}

View File

@ -0,0 +1,18 @@
import _ from 'lodash';
export class InitialReducer {
/* if we get a diff from the app that looks like this:
{ initial: {}}
it will set the state to look like the contents of "initial"
*/
reduce(json, state) {
let data = _.get(json, 'initial', false);
if (data) {
state = data;
}
}
}

View File

@ -0,0 +1,26 @@
import _ from 'lodash';
export class UpdateReducer {
/* If we get an incoming object like this:
{ update: {new: {}}}
It will replace the entire contents of the state with the incoming state enclosed in "new".
Feel free to amend the behaviour as necessary.
*/
reduce(json, state) {
let data = _.get(json, 'update', false);
if (data) {
this.reduceState(_.get(data, 'new', false), state);
}
}
reduceState(incoming, state) {
if (incoming) {
state = incoming;
}
}
}

37
gall-app/src/js/store.js Normal file
View File

@ -0,0 +1,37 @@
import { InitialReducer } from './reducers/initial';
import { UpdateReducer } from './reducers/update';
class Store {
/*
The store holds all state for the front-end. We initialise a subscription to the back-end through
subscription.js and then let the store class handle all incoming diffs, including the initial one
we get from subscribing to the back-end.
It's important that state be mutated and set in one place, so pipe changes through the handleEvent method.
*/
constructor() {
this.state = {};
this.initialReducer = new InitialReducer();
this.updateReducer = new UpdateReducer();
this.setState = () => { };
}
setStateHandler(setState) {
this.setState = setState;
}
handleEvent(data) {
let json = data.data;
console.log(json);
this.initialReducer.reduce(json, this.state);
this.updateReducer.reduce(json, this.state);
this.setState(this.state);
}
}
export let store = new Store();
window.store = store;

View File

@ -0,0 +1,36 @@
import { api } from './api';
import { store } from './store';
export class Subscription {
// uncomment the following code to start up a subscription on the '/' path
//
// see on-watch in your app's hoon file for behaviour
//
start() {
if (api.authTokens) {
// this.initializebrowsermanager();
} else {
console.error("~~~ ERROR: Must set api.authTokens before operation ~~~");
}
}
// initializebrowsermanager() {
// api.bind('/', 'PUT', api.authTokens.ship, 'browsermanager',
// this.handleEvent.bind(this),
// this.handleError.bind(this));
// }
handleEvent(diff) {
store.handleEvent(diff);
}
handleError(err) {
console.error(err);
api.bind('/', 'PUT', api.authTokens.ship, 'browsermanager',
this.handleEvent.bind(this),
this.handleError.bind(this));
}
}
export let subscription = new Subscription();

View File

@ -0,0 +1,46 @@
/+ *server, default-agent
::
|%
+$ card card:agent:gall
--
^- agent:gall
|_ bol=bowl:gall
+* this .
browsermanager-core +>
cc ~(. browsermanager-core bol)
def ~(. (default-agent this %|) bol)
::
++ on-init
^- (quip card _this)
=/ launcha [%launch-action !>([%add %browsermanager [[%basic 'browsermanager' '/~browsermanager/img/tile.png' '/~browsermanager'] %.y]])]
=/ filea [%file-server-action !>([%serve-dir /'~browsermanager' /app/browsermanager %.n %.n])]
:_ this
:~ [%pass /srv %agent [our.bol %file-server] %poke filea]
[%pass /browsermanager %agent [our.bol %launch] %poke launcha]
==
::
++ on-watch
|= =path
^- (quip card _this)
?: ?=([%http-response *] path)
`this
?. =(/ path)
(on-watch:def path)
[[%give %fact ~ %json !>(*json)]~ this]
::
++ on-agent on-agent:def
::
++ on-arvo
|= [=wire =sign-arvo]
^- (quip card _this)
?. ?=(%bound +<.sign-arvo)
(on-arvo:def wire sign-arvo)
[~ this]
::
++ on-poke on-poke:def
++ on-save on-save:def
++ on-load on-load:def
++ on-leave on-leave:def
++ on-peek on-peek:def
++ on-fail on-fail:def
--

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,15 @@
<!doctype html>
<html>
<head>
<title>browsermanager</title>
<meta charset="utf-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
</head>
<body>
<div id="root" />
<script src="/~landscape/js/channel.js"></script>
<script src="/~landscape/js/session.js"></script>
<script src="/~browsermanager/js/index.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

118
gall-app/webpack.dev.js Normal file
View File

@ -0,0 +1,118 @@
const path = require('path');
// const HtmlWebpackPlugin = require('html-webpack-plugin');
// const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const urbitrc = require('./.urbitrc');
const fs = require('fs-extra');
function copy(src,dest) {
return new Promise((res,rej) =>
fs.copy(src,dest, err => err ? rej(err) : res()));
}
class UrbitShipPlugin {
constructor(urbitrc) {
this.piers = urbitrc.URBIT_PIERS;
}
apply(compiler) {
compiler.hooks.afterEmit.tapPromise(
'UrbitShipPlugin',
async (compilation) => {
const src = './urbit/app'
return Promise.all(this.piers.map(pier => {
const dst = path.resolve(pier, 'app');
copy(src, dst).then(() => {
pier = pier.split('/');
});
}));
}
);
}
}
let devServer = {
contentBase: path.resolve('./urbit/app/browsermanager/js'),
hot: true,
port: 9000,
historyApiFallback: true,
writeToDisk: (filePath) => {
return /index.js$/.test(filePath);
}
};
if(urbitrc.URL) {
devServer = {
...devServer,
index: '',
proxy: {
'/~browsermanager/js/index.js': {
target: 'http://localhost:9000',
pathRewrite: (req, path) => '/index.js'
},
'**': {
target: urbitrc.URL,
// ensure proxy doesn't timeout channels
proxyTimeout: 0
}
}
};
}
module.exports = {
mode: 'development',
entry: {
app: './src/index.js'
},
module: {
rules: [
{
test: /\.jsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: [
'@babel/transform-runtime',
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-proposal-class-properties',
'react-hot-loader/babel'
]
}
},
exclude: /node_modules/
},
{
test: /\.css$/i,
use: [
// Creates `style` nodes from JS strings
'style-loader',
// Translates CSS into CommonJS
'css-loader',
// Compiles Sass to CSS
'sass-loader'
]
}
]
},
resolve: {
extensions: ['.js']
},
devtool: 'inline-source-map',
devServer: devServer,
plugins: [
new UrbitShipPlugin(urbitrc)
],
watch: true,
output: {
filename: 'index.js',
chunkFilename: 'index.js',
path: path.resolve('./urbit/app/browsermanager/js'),
publicPath: '/'
},
optimization: {
minimize: false,
usedExports: true
}
};

57
gall-app/webpack.prod.js Normal file
View File

@ -0,0 +1,57 @@
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const urbitrc = require('./.urbitrc');
module.exports = {
mode: 'production',
entry: {
app: './src/index.js'
},
module: {
rules: [
{
test: /\.jsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: [
'@babel/transform-runtime',
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-proposal-class-properties'
]
}
},
exclude: /node_modules/
},
{
test: /\.css$/i,
use: [
// Creates `style` nodes from JS strings
'style-loader',
// Translates CSS into CommonJS
'css-loader',
// Compiles Sass to CSS
'sass-loader'
]
}
]
},
resolve: {
extensions: ['.js']
},
devtool: 'inline-source-map',
plugins: [
new CleanWebpackPlugin()
],
output: {
filename: 'index.js',
path: path.resolve(__dirname, `${urbitrc.URBIT_PIERS[0]}/app/browsermanager/js`),
publicPath: '/',
},
optimization: {
minimize: true,
usedExports: true
}
};