Merge branch 'release/next-userspace' into la/chat-input

This commit is contained in:
Logan Allen 2020-08-06 15:01:03 -07:00
commit 012dcbda53
43 changed files with 1228 additions and 751 deletions

View File

@ -30,7 +30,7 @@ If applicable, add screenshots to help explain your problem.
**System (please supply the following information, if relevant):**
- OS: [e.g. macOS, linux64, FreeBSD]
- Vere and Urbit OS versions
- Your ship's `%base` hash (use `.^(@uv %cz /=base=)` to check)
- Your ship's `%base` hash (use `+trouble` to check)
**Additional context**
Add any other context about the problem here.

View File

@ -27,13 +27,13 @@ If applicable, add screenshots to help explain your problem. If possible, please
**Desktop (please complete the following information):**
- OS: [e.g. MacOS 10.15.3]
- Browser [e.g. chrome, safari]
- Base hash of your urbit ship. Run ` .^(@uv %cz /=base=)` in Dojo to see this.
- Base hash of your urbit ship. Run `+trouble` in Dojo to see this.
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Base hash of your urbit ship. Run ` .^(@uv %cz /=base=)` in Dojo to see this.
- Base hash of your urbit ship. Run `+trouble` in Dojo to see this.
**Additional context**
Add any other context about the problem here.

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d862132e99ed393d786fb4bdb7c487bced2d4c9efc2cb7e174a73b6acca4799d
size 6304536
oid sha256:d7088528dbfd54a913921ade093251d678c4ccebfd0ad85ef2022520266b3954
size 16451173

View File

@ -268,7 +268,7 @@
%group-store
%group-push-hook
=/ =cage
:- %group-action
:- %group-update
!> ^- action:group-store
[%change-policy rid %invite %add-invites (sy ship ~)]
[%pass / %agent [entity.rid app] %poke cage]

View File

@ -1,7 +1,7 @@
/- glob
/+ default-agent, verb, dbug
|%
++ hash 0v5.m40lm.ha96c.aavqb.57scm.222e7
++ hash 0v3.cus8h.vc64c.rfb3t.22oji.b529a
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
+$ all-states
$% state-0

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 582 B

186
pkg/interface/.eslintrc.js Normal file
View File

@ -0,0 +1,186 @@
const env = {
"browser": true,
"es6": true,
"node": true
};
const rules = {
"array-bracket-spacing": ["error", "never"],
"arrow-parens": [
"error",
"as-needed",
{
"requireForBlockBody": true
}
],
"arrow-spacing": "error",
"block-spacing": ["error", "always"],
"brace-style": ["error", "1tbs"],
"camelcase": [
"error",
{
"properties": "never"
}
],
"comma-dangle": ["error", "never"],
"eol-last": ["error", "always"],
"func-name-matching": "error",
"indent": [
"off",
2,
{
"ArrayExpression": "off",
"SwitchCase": 1,
"CallExpression": {
"arguments": "off"
},
"FunctionDeclaration": {
"parameters": "off"
},
"FunctionExpression": {
"parameters": "off"
},
"MemberExpression": "off",
"ObjectExpression": "off",
"ImportDeclaration": "off"
}
],
"handle-callback-err": "off",
"linebreak-style": ["error", "unix"],
"max-lines": [
"error",
{
"max": 300,
"skipBlankLines": true,
"skipComments": true
}
],
"max-lines-per-function": [
"warn",
{
"skipBlankLines": true,
"skipComments": true
}
],
"max-statements-per-line": [
"error",
{
"max": 1
}
],
"new-cap": [
"error",
{
"newIsCap": true,
"capIsNew": false
}
],
"new-parens": "error",
"no-buffer-constructor": "error",
"no-console": "off",
"no-extra-semi": "off",
"no-fallthrough": "off",
"no-func-assign": "off",
"no-implicit-coercion": "error",
"no-multi-assign": "error",
"no-multiple-empty-lines": [
"error",
{
"max": 1
}
],
"no-nested-ternary": "error",
"no-param-reassign": "off",
"no-return-assign": "error",
"no-return-await": "off",
"no-shadow-restricted-names": "error",
"no-tabs": "error",
"no-trailing-spaces": "error",
"no-unused-vars": [
"error",
{
"vars": "all",
"args": "none",
"ignoreRestSiblings": false
}
],
"no-use-before-define": [
"error",
{
"functions": false,
"classes": false
}
],
"no-useless-escape": "off",
"no-var": "error",
"nonblock-statement-body-position": ["error", "below"],
"object-curly-spacing": ["error", "always"],
"padded-blocks": ["error", "never"],
"prefer-arrow-callback": "error",
"prefer-const": [
"error",
{
"destructuring": "all",
"ignoreReadBeforeAssign": true
}
],
"prefer-template": "off",
"quotes": ["error", "single"],
"semi": ["error", "always"],
"spaced-comment": [
"error",
"always",
{
"exceptions": ["!"]
}
],
"space-before-blocks": "error",
"unicode-bom": ["error", "never"],
"valid-jsdoc": "error",
"wrap-iife": ["error", "inside"],
"react/jsx-closing-bracket-location": 1,
"react/jsx-tag-spacing": 1,
"react/jsx-max-props-per-line": ["error", { "maximum": 2, "when": "multiline" }],
"react/prop-types": 0
};
module.exports = {
"env": env,
"extends": [
"plugin:react/recommended",
"eslint:recommended",
],
"settings": {
"react": {
"version": "^16.5.2"
}
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 10,
"requireConfigFile": false,
"sourceType": "module"
},
"root": true,
"rules": rules,
"overrides": [
{
"files": ["**/*.ts", "**/*.tsx"],
"env": env,
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": { "jsx": true },
"ecmaVersion": 10,
"requireConfigFile": false,
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"rules": rules
}
]
};

View File

@ -1,147 +0,0 @@
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"settings": {
"react": {
"version": "^16.5.2"
}
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 10,
"requireConfigFile": false,
"sourceType": "module"
},
"root": true,
"rules": {
"array-bracket-spacing": ["error", "never"],
"arrow-parens": [
"error",
"as-needed",
{
"requireForBlockBody": true
}
],
"arrow-spacing": "error",
"block-spacing": ["error", "always"],
"brace-style": ["error", "1tbs"],
"camelcase": [
"error",
{
"properties": "never"
}
],
"comma-dangle": ["error", "never"],
"eol-last": ["error", "always"],
"func-name-matching": "error",
"indent": [
"off",
2,
{
"ArrayExpression": "off",
"SwitchCase": 1,
"CallExpression": {
"arguments": "off"
},
"FunctionDeclaration": {
"parameters": "off"
},
"FunctionExpression": {
"parameters": "off"
},
"MemberExpression": "off",
"ObjectExpression": "off",
"ImportDeclaration": "off"
}
],
"handle-callback-err": "off",
"linebreak-style": ["error", "unix"],
"max-statements-per-line": [
"error",
{
"max": 1
}
],
"new-cap": [
"error",
{
"newIsCap": true,
"capIsNew": false
}
],
"new-parens": "error",
"no-buffer-constructor": "error",
"no-console": "off",
"no-extra-semi": "off",
"no-fallthrough": "off",
"no-func-assign": "off",
"no-implicit-coercion": "error",
"no-multi-assign": "error",
"no-multiple-empty-lines": [
"error",
{
"max": 1
}
],
"no-nested-ternary": "error",
"no-param-reassign": "off",
"no-return-assign": "error",
"no-return-await": "off",
"no-shadow-restricted-names": "error",
"no-tabs": "error",
"no-trailing-spaces": "error",
"no-unused-vars": [
"error",
{
"vars": "all",
"args": "none",
"ignoreRestSiblings": false
}
],
"no-use-before-define": [
"error",
{
"functions": false,
"classes": false
}
],
"no-useless-escape": "off",
"no-var": "error",
"nonblock-statement-body-position": ["error", "below"],
"object-curly-spacing": ["error", "always"],
"padded-blocks": ["error", "never"],
"prefer-arrow-callback": "error",
"prefer-const": [
"error",
{
"destructuring": "all",
"ignoreReadBeforeAssign": true
}
],
"prefer-template": "off",
"quotes": ["error", "single"],
"semi": ["error", "always"],
"spaced-comment": [
"error",
"always",
{
"exceptions": ["!"]
}
],
"space-before-blocks": "error",
"unicode-bom": ["error", "never"],
"valid-jsdoc": "error",
"wrap-iife": ["error", "inside"],
"react/jsx-closing-bracket-location": 1,
"react/jsx-tag-spacing": 1,
"react/jsx-max-props-per-line": ["error", { "maximum": 2, "when": "multiline" }],
"react/prop-types": 0
}
}

View File

@ -1660,6 +1660,12 @@
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
"dev": true
},
"@types/eslint-visitor-keys": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
"integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==",
"dev": true
},
"@types/events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
@ -1689,6 +1695,12 @@
"integrity": "sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA==",
"dev": true
},
"@types/json-schema": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz",
"integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==",
"dev": true
},
"@types/lodash": {
"version": "4.14.155",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.155.tgz",
@ -1795,6 +1807,93 @@
"source-map": "^0.6.1"
}
},
"@typescript-eslint/eslint-plugin": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.8.0.tgz",
"integrity": "sha512-lFb4VCDleFSR+eo4Ew+HvrJ37ZH1Y9ZyE+qyP7EiwBpcCVxwmUc5PAqhShCQ8N8U5vqYydm74nss+a0wrrCErw==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "3.8.0",
"debug": "^4.1.1",
"functional-red-black-tree": "^1.0.1",
"regexpp": "^3.0.0",
"semver": "^7.3.2",
"tsutils": "^3.17.1"
},
"dependencies": {
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
"dev": true
}
}
},
"@typescript-eslint/experimental-utils": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.8.0.tgz",
"integrity": "sha512-o8T1blo1lAJE0QDsW7nSyvZHbiDzQDjINJKyB44Z3sSL39qBy5L10ScI/XwDtaiunoyKGLiY9bzRk4YjsUZl8w==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.3",
"@typescript-eslint/types": "3.8.0",
"@typescript-eslint/typescript-estree": "3.8.0",
"eslint-scope": "^5.0.0",
"eslint-utils": "^2.0.0"
}
},
"@typescript-eslint/parser": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.8.0.tgz",
"integrity": "sha512-u5vjOBaCsnMVQOvkKCXAmmOhyyMmFFf5dbkM3TIbg3MZ2pyv5peE4gj81UAbTHwTOXEwf7eCQTUMKrDl/+qGnA==",
"dev": true,
"requires": {
"@types/eslint-visitor-keys": "^1.0.0",
"@typescript-eslint/experimental-utils": "3.8.0",
"@typescript-eslint/types": "3.8.0",
"@typescript-eslint/typescript-estree": "3.8.0",
"eslint-visitor-keys": "^1.1.0"
}
},
"@typescript-eslint/types": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.8.0.tgz",
"integrity": "sha512-8kROmEQkv6ss9kdQ44vCN1dTrgu4Qxrd2kXr10kz2NP5T8/7JnEfYNxCpPkArbLIhhkGLZV3aVMplH1RXQRF7Q==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.8.0.tgz",
"integrity": "sha512-MTv9nPDhlKfclwnplRNDL44mP2SY96YmPGxmMbMy6x12I+pERcxpIUht7DXZaj4mOKKtet53wYYXU0ABaiXrLw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "3.8.0",
"@typescript-eslint/visitor-keys": "3.8.0",
"debug": "^4.1.1",
"glob": "^7.1.6",
"is-glob": "^4.0.1",
"lodash": "^4.17.15",
"semver": "^7.3.2",
"tsutils": "^3.17.1"
},
"dependencies": {
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
"dev": true
}
}
},
"@typescript-eslint/visitor-keys": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.8.0.tgz",
"integrity": "sha512-gfqQWyVPpT9NpLREXNR820AYwgz+Kr1GuF3nf1wxpHD6hdxI62tq03ToomFnDxY0m3pUB39IF7sil7D5TQexLA==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
},
"@webassemblyjs/ast": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@ -1993,9 +2092,9 @@
}
},
"acorn": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
"integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz",
"integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==",
"dev": true
},
"acorn-jsx": {
@ -2931,9 +3030,9 @@
}
},
"cli-width": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz",
"integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
"integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
"dev": true
},
"cliui": {
@ -3960,6 +4059,15 @@
}
}
},
"eslint-utils": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
"integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
},
"globals": {
"version": "12.4.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
@ -3975,6 +4083,12 @@
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
"dev": true
},
"regexpp": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
"integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
"dev": true
},
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
@ -4033,9 +4147,9 @@
}
},
"eslint-scope": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
"integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz",
"integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==",
"dev": true,
"requires": {
"esrecurse": "^4.1.0",
@ -4043,9 +4157,9 @@
}
},
"eslint-utils": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
"integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.1.0"
@ -4084,9 +4198,9 @@
},
"dependencies": {
"estraverse": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz",
"integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==",
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
"dev": true
}
}
@ -5409,21 +5523,21 @@
"dev": true
},
"inquirer": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz",
"integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==",
"version": "7.3.3",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz",
"integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==",
"dev": true,
"requires": {
"ansi-escapes": "^4.2.1",
"chalk": "^3.0.0",
"chalk": "^4.1.0",
"cli-cursor": "^3.1.0",
"cli-width": "^2.0.0",
"cli-width": "^3.0.0",
"external-editor": "^3.0.3",
"figures": "^3.0.0",
"lodash": "^4.17.15",
"lodash": "^4.17.19",
"mute-stream": "0.0.8",
"run-async": "^2.4.0",
"rxjs": "^6.5.3",
"rxjs": "^6.6.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0",
"through": "^2.3.6"
@ -5440,9 +5554,9 @@
}
},
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
@ -5470,6 +5584,12 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
"dev": true
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
@ -5792,9 +5912,9 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-yaml": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
@ -6380,6 +6500,11 @@
"resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz",
"integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA=="
},
"mousetrap-global-bind": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/mousetrap-global-bind/-/mousetrap-global-bind-1.1.0.tgz",
"integrity": "sha1-zX3pIivQZG+i4BDVTISnTCaojt0="
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@ -6774,9 +6899,9 @@
}
},
"onetime": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
"integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.1.tgz",
"integrity": "sha512-ZpZpjcJeugQfWsfyQlshVoowIIQ1qBGSVll4rfDq6JJVO//fesjoX808hXWfBjY+ROZgpKDI5TRSRBSoJiZ8eg==",
"dev": true,
"requires": {
"mimic-fn": "^2.1.0"
@ -7675,9 +7800,9 @@
}
},
"regexpp": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
"integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
"integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
"dev": true
},
"regexpu-core": {
@ -7996,9 +8121,9 @@
}
},
"rxjs": {
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
"integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==",
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz",
"integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
@ -8840,9 +8965,9 @@
"dev": true
},
"strip-json-comments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz",
"integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true
},
"style-loader": {
@ -9201,6 +9326,15 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
"integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA=="
},
"tsutils": {
"version": "3.17.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
"integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
}
},
"tty-browserify": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
@ -9238,6 +9372,12 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
"dev": true
},
"typescript": {
"version": "3.9.7",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
"dev": true
},
"unherit": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz",
@ -9511,9 +9651,9 @@
"dev": true
},
"v8-compile-cache": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
"integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
"integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
"dev": true
},
"value-equal": {

View File

@ -18,6 +18,7 @@
"markdown-to-jsx": "^6.11.4",
"moment": "^2.20.1",
"mousetrap": "^1.6.5",
"mousetrap-global-bind": "^1.1.0",
"prop-types": "^15.7.2",
"react": "^16.5.2",
"react-codemirror2": "^6.0.1",
@ -45,6 +46,8 @@
"@types/lodash": "^4.14.155",
"@types/react": "^16.9.38",
"@types/react-router-dom": "^5.1.5",
"@typescript-eslint/eslint-plugin": "^3.8.0",
"@typescript-eslint/parser": "^3.8.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
@ -56,12 +59,13 @@
"react-hot-loader": "^4.12.21",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"typescript": "^3.9.7",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
},
"scripts": {
"lint": "eslint ./**/*.js",
"lint": "eslint ./src/**/*.{js,ts,tsx}",
"lint-file": "eslint",
"tsc": "tsc",
"tsc:watch": "tsc --watch",

View File

@ -3,6 +3,10 @@ import 'react-hot-loader';
import * as React from 'react';
import { BrowserRouter as Router, Route, withRouter, Switch } from 'react-router-dom';
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
import { sigil as sigiljs, stringRenderer } from 'urbit-sigil-js';
import Mousetrap from 'mousetrap';
import 'mousetrap-global-bind';
import './css/indigo-static.css';
import './css/fonts.css';
@ -17,11 +21,14 @@ import LinksApp from './apps/links/app';
import PublishApp from './apps/publish/app';
import StatusBar from './components/StatusBar';
import Omnibox from './components/Omnibox';
import ErrorComponent from './components/Error';
import GlobalStore from './store/store';
import GlobalSubscription from './subscription/global';
import GlobalApi from './api/global';
import { uxToHex } from './lib/util';
import { Sigil } from './lib/sigil';
// const Style = createGlobalStyle`
// ${cssReset}
@ -62,6 +69,7 @@ class App extends React.Component {
new GlobalSubscription(this.store, this.api, this.appChannel);
this.updateTheme = this.updateTheme.bind(this);
this.setFavicon = this.setFavicon.bind(this);
}
componentDidMount() {
@ -70,21 +78,49 @@ class App extends React.Component {
this.api.local.setDark(this.themeWatcher.matches);
this.themeWatcher.addListener(this.updateTheme);
this.api.local.getBaseHash();
Mousetrap.bindGlobal(['command+/', 'ctrl+/'], (e) => {
e.preventDefault();
this.api.local.setOmnibox();
});
this.setFavicon();
}
componentWillUnmount() {
this.themeWatcher.removeListener(this.updateTheme);
}
componentDidUpdate(prevProps, prevState, snapshot) {
this.setFavicon();
}
updateTheme(e) {
this.api.local.setDark(e.matches);
}
setFavicon() {
if (window.ship.length < 14) {
let background = '#ffffff';
if (this.state.contacts.hasOwnProperty('/~/default')) {
background = `#${uxToHex(this.state.contacts['/~/default'][window.ship].color)}`;
}
const foreground = Sigil.foregroundFromBackground(background);
const svg = sigiljs({
patp: window.ship,
renderer: stringRenderer,
size: 16,
colors: [background, foreground]
});
const dataurl = 'data:image/svg+xml;base64,' + btoa(svg);
const favicon = document.querySelector('[rel=icon]');
favicon.href = dataurl;
favicon.type = 'image/svg+xml';
}
}
render() {
const channel = window.channel;
const associations = this.state.associations ? this.state.associations : { contacts: {} };
const selectedGroups = this.state.selectedGroups ? this.state.selectedGroups : [];
const { state } = this;
const theme = state.dark ? dark : light;
@ -92,81 +128,99 @@ class App extends React.Component {
<ThemeProvider theme={theme}>
<Root>
<Router>
<StatusBarWithRouter props={this.props}
associations={associations}
invites={this.state.invites}
api={this.api}
connection={this.state.connection}
subscription={this.subscription}
<StatusBarWithRouter
props={this.props}
associations={associations}
invites={this.state.invites}
api={this.api}
connection={this.state.connection}
subscription={this.subscription}
/>
<Omnibox
associations={state.associations}
apps={state.launch}
api={this.api}
dark={state.dark}
show={state.omniboxShown}
/>
<Content>
<Switch>
<Route exact path="/"
render={ p => (
<LaunchApp
ship={this.ship}
api={this.api}
{...state}
{...p}
<Switch>
<Route
exact
path='/'
render={p => (
<LaunchApp
ship={this.ship}
api={this.api}
{...state}
{...p}
/>
)}
/>
)}
/>
<Route path="/~chat" render={ p => (
<ChatApp
ship={this.ship}
api={this.api}
subscription={this.subscription}
{...state}
{...p}
<Route
path='/~chat'
render={p => (
<ChatApp
ship={this.ship}
api={this.api}
subscription={this.subscription}
{...state}
{...p}
/>
)}
/>
)}
/>
<Route path="/~dojo" render={ p => (
<DojoApp
ship={this.ship}
channel={channel}
selectedGroups={selectedGroups}
subscription={this.subscription}
{...p}
<Route
path='/~dojo'
render={p => (
<DojoApp
ship={this.ship}
channel={channel}
subscription={this.subscription}
{...p}
/>
)}
/>
)}
/>
<Route path="/~groups" render={ p => (
<GroupsApp
ship={this.ship}
api={this.api}
subscription={this.subscription}
{...state}
{...p}
<Route
path='/~groups'
render={p => (
<GroupsApp
ship={this.ship}
api={this.api}
subscription={this.subscription}
{...state}
{...p}
/>
)}
/>
)}
/>
<Route path="/~link" render={ p => (
<LinksApp
ship={this.ship}
ship={this.ship}
api={this.api}
subscription={this.subscription}
{...state}
{...p}
<Route
path='/~link'
render={p => (
<LinksApp
ship={this.ship}
api={this.api}
subscription={this.subscription}
{...state}
{...p}
/>
)}
/>
)}
/>
<Route path="/~publish" render={ p => (
<PublishApp
ship={this.ship}
api={this.api}
subscription={this.subscription}
{...state}
{...p}
<Route
path='/~publish'
render={p => (
<PublishApp
ship={this.ship}
api={this.api}
subscription={this.subscription}
{...state}
{...p}
/>
)}
/>
)}
/>
<Route
render={(props) => (
render={props => (
<ErrorComponent {...props} code={404} description="Not Found" />
)}
/>
/>
</Switch>
</Content>
</Router>

View File

@ -1,6 +1,5 @@
import BaseApi from "./base";
import { StoreState } from "../store/type";
import { SelectedGroup } from "../types/local-update";
export default class LocalApi extends BaseApi<StoreState> {
getBaseHash() {
@ -9,16 +8,6 @@ export default class LocalApi extends BaseApi<StoreState> {
});
}
setSelected(selected: SelectedGroup[]) {
this.store.handleEvent({
data: {
local: {
selected
}
}
})
}
sidebarToggle() {
this.store.handleEvent({
data: {
@ -39,4 +28,14 @@ export default class LocalApi extends BaseApi<StoreState> {
});
}
setOmnibox() {
this.store.handleEvent({
data: {
local: {
omniboxShown: true
},
},
});
}
}

View File

@ -53,7 +53,6 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
const unreads = {};
let totalUnreads = 0;
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
const associations = props.associations
? props.associations
: { chat: {}, contacts: {} };
@ -74,14 +73,7 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
unreads[stat] = Boolean(unread);
if (
unread &&
stat in associations.chat &&
(selectedGroups.length === 0 ||
selectedGroups
.map((e) => {
return e[0];
})
.includes(associations.chat?.[stat]?.['group-path']) ||
props.groups[associations.chat?.[stat]?.['group-path']]?.hidden)
stat in associations.chat
) {
totalUnreads += unread;
}
@ -111,7 +103,6 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
inbox={inbox}
messagePreviews={messagePreviews}
associations={associations}
selectedGroups={selectedGroups}
contacts={contacts}
invites={invites['/chat'] || {}}
unreads={unreads}

View File

@ -5,7 +5,6 @@ import { S3Upload } from './s3-upload';
import { uxToHex } from '../../../../lib/util';
const URL_REGEX = new RegExp(String(/^((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+)/.source));
export class ChatInput extends Component {
@ -91,10 +90,12 @@ export class ChatInput extends Component {
return;
}
let messages = [];
let message = [];
let isInCodeBlock = false;
let endOfCodeBlock = false;
text.split(/\r?\n/).forEach((line) => {
message.push('\n');
// A line of backticks enters and exits a codeblock
if (line.startsWith('```')) {
// But we need to check if we've ended a codeblock
@ -103,10 +104,9 @@ export class ChatInput extends Component {
} else {
endOfCodeBlock = false;
}
if (isInCodeBlock) {
message.push(`\n${line}`);
} else if (endOfCodeBlock) {
message.push(`\n${line}\n`);
if (isInCodeBlock || endOfCodeBlock) {
message.push(line);
} else {
line.split(/\s/).forEach((str) => {
if (
@ -120,45 +120,92 @@ export class ChatInput extends Component {
) {
isInCodeBlock = false;
}
if (this.isUrl(str) && !isInCodeBlock) {
if (message.length > 0) {
message = message.join(' ');
message = this.getLetterType(message);
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
message
);
// If we're in the middle of a message, add it to the stack and reset
messages.push(message);
message = [];
}
const URL = this.getLetterType(str);
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
URL
);
messages.push([str]);
message = [];
} else {
message.push(str);
}
});
}
});
if (message.length > 0) {
message = message.join(' ');
message = this.getLetterType(message);
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
message
);
message = [];
if (message.length) {
// Add any remaining message
messages.push(message);
}
messages.forEach((message) => {
if (message.length > 0) {
message = this.getLetterType(message.join(' '));
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
message
);
}
});
// perf testing:
/*let closure = () => {
let x = 0;
for (var i = 0; i < 30; i++) {
x++;
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
{
text: `${x}`
}
);
}
setTimeout(closure, 1000);
};
this.closure = closure.bind(this);
setTimeout(this.closure, 2000);*/
this.editor.setValue('');
}
toggleCode() {
if(this.state.code) {
this.setState({ code: false });
this.editor.setOption('mode', MARKDOWN_CONFIG);
this.editor.setOption('placeholder', this.props.placeholder);
} else {
this.setState({ code: true });
this.editor.setOption('mode', null);
this.editor.setOption('placeholder', 'Code...');
}
const value = this.editor.getValue();
// Force redraw of placeholder
if(value.length === 0) {
this.editor.setValue(' ');
this.editor.setValue('');
}
}
uploadSuccess(url) {
const { props } = this;
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
{ url }
);
}
uploadError(error) {
// no-op for now
}
render() {

View File

@ -3,6 +3,7 @@ import { Link } from 'react-router-dom';
import ReactMarkdown from 'react-markdown';
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
import urbitOb from 'urbit-ob';
import { Link } from 'react-router-dom';
const DISABLED_BLOCK_TOKENS = [
'indentedCode',

View File

@ -77,7 +77,7 @@ const renderWithSigil = (props, timestamp) => {
<p className={`v-mid f9 gray2 dib mr3 c-default`}>
<span
className={
'mw5 dib truncate pointer ' +
'mw5 db truncate pointer ' +
(contact.nickname ? '' : 'mono')
}
onClick={() => {

View File

@ -13,8 +13,6 @@ export class Sidebar extends Component {
render() {
const { props } = this;
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
const contactAssoc =
(props.associations && 'contacts' in props.associations)
? alphabetiseAssociations(props.associations.contacts) : {};
@ -61,15 +59,6 @@ export class Sidebar extends Component {
const groupedItems = Object.keys(contactAssoc)
.filter(each => (groupedChannels[each] || []).length !== 0)
.filter((each) => {
if (selectedGroups.length === 0) {
return true;
}
const selectedPaths = selectedGroups.map((e) => {
return e[0];
});
return selectedPaths.includes(each);
})
.map((each, i) => {
const channels = groupedChannels[each] || [];
return(

View File

@ -48,7 +48,6 @@ export default class GroupsApp extends Component<GroupsAppProps, {}> {
const invites =
(Boolean(props.invites) && '/contacts' in props.invites) ?
props.invites['/contacts'] : {};
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
const s3 = props.s3 ? props.s3 : {};
const groups = props.groups || {};
const associations = props.associations || {};
@ -62,7 +61,6 @@ export default class GroupsApp extends Component<GroupsAppProps, {}> {
return (
<Skeleton
activeDrawer="groups"
selectedGroups={selectedGroups}
history={props.history}
api={api}
contacts={contacts}
@ -86,7 +84,6 @@ export default class GroupsApp extends Component<GroupsAppProps, {}> {
return (
<Skeleton
history={props.history}
selectedGroups={selectedGroups}
api={api}
contacts={contacts}
groups={groups}
@ -111,7 +108,6 @@ export default class GroupsApp extends Component<GroupsAppProps, {}> {
return (
<Skeleton
history={props.history}
selectedGroups={selectedGroups}
api={api}
contacts={contacts}
groups={groups}
@ -150,7 +146,6 @@ export default class GroupsApp extends Component<GroupsAppProps, {}> {
return (
<Skeleton
history={props.history}
selectedGroups={selectedGroups}
api={api}
contacts={contacts}
invites={invites}
@ -198,7 +193,6 @@ export default class GroupsApp extends Component<GroupsAppProps, {}> {
return (
<Skeleton
history={props.history}
selectedGroups={selectedGroups}
api={api}
contacts={contacts}
groups={groups}
@ -248,7 +242,6 @@ export default class GroupsApp extends Component<GroupsAppProps, {}> {
<Skeleton
history={props.history}
api={api}
selectedGroups={selectedGroups}
contacts={contacts}
groups={groups}
invites={invites}
@ -305,7 +298,6 @@ export default class GroupsApp extends Component<GroupsAppProps, {}> {
<Skeleton
history={props.history}
api={api}
selectedGroups={selectedGroups}
contacts={contacts}
groups={groups}
invites={invites}
@ -345,7 +337,6 @@ export default class GroupsApp extends Component<GroupsAppProps, {}> {
<Skeleton
history={props.history}
api={api}
selectedGroups={selectedGroups}
contacts={contacts}
groups={groups}
invites={invites}

View File

@ -103,6 +103,7 @@ export class JoinScreen extends Component {
spellCheck="false"
rows={1}
cols={32}
autoFocus={true}
onKeyPress={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
@ -110,7 +111,7 @@ export class JoinScreen extends Component {
}
}}
style={{
resize: 'none',
resize: 'none'
}}
onChange={this.groupChange}
value={this.state.group}

View File

@ -72,16 +72,6 @@ export class GroupSidebar extends Component {
(path in props.groups)
);
})
.filter((path) => {
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
if (selectedGroups.length === 0) {
return true;
}
const selectedPaths = selectedGroups.map(((e) => {
return e[0];
}));
return (selectedPaths.includes(path));
})
.sort((a, b) => {
let aName = a.substr(1);
let bName = b.substr(1);

View File

@ -17,7 +17,6 @@ export class Skeleton extends Component {
invites={props.invites}
activeDrawer={props.activeDrawer}
selected={props.selected}
selectedGroups={props.selectedGroups}
history={props.history}
api={props.api}
associations={props.associations}

View File

@ -1,6 +1,7 @@
import React from 'react';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
import defaultApps from '../../../../lib/default-apps';
import Tile from './tile';
@ -29,7 +30,9 @@ export default class BasicTile extends React.PureComponent {
</span>
);
const routeList = ['/~chat', '/~publish', '/~link', '/~groups', '/~dojo'];
const routeList = defaultApps.map((e) => {
return `/~${e}`;
});
const tile = ( routeList.indexOf(props.linkedUrl) !== -1 ) ? (
<Link className="w-100 h-100 db pa2 no-underline" to={props.linkedUrl}>

View File

@ -41,7 +41,7 @@ export class LinksApp extends Component {
render() {
const { props } = this;
const contacts = props.contacts ? props.contacts : {};
const contacts = props.contacts ? props.contacts : {};
const groups = props.groups ? props.groups : {};
@ -51,18 +51,9 @@ export class LinksApp extends Component {
const seen = props.linksSeen ? props.linksSeen : {};
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
const selGroupPaths = selectedGroups.map(g => g[0]);
const totalUnseen = _.reduce(
links,
(acc, collection, path) => {
if(selGroupPaths.length > 0
&& !selGroupPaths.includes(associations.link?.[path]?.['group-path'])) {
return acc;
}
return acc + collection.unseenCount;
},
(acc, collection) => acc + collection.unseenCount,
0
);
@ -91,7 +82,6 @@ export class LinksApp extends Component {
groups={groups}
rightPanelHide={true}
sidebarShown={sidebarShown}
selectedGroups={selectedGroups}
links={links}
listening={listening}
api={api}
@ -109,7 +99,6 @@ export class LinksApp extends Component {
invites={invites}
groups={groups}
sidebarShown={sidebarShown}
selectedGroups={selectedGroups}
links={links}
listening={listening}
api={api}
@ -157,7 +146,6 @@ export class LinksApp extends Component {
groups={groups}
selected={resourcePath}
sidebarShown={sidebarShown}
selectedGroups={selectedGroups}
links={links}
listening={listening}
api={api}
@ -198,7 +186,6 @@ export class LinksApp extends Component {
groups={groups}
selected={resourcePath}
sidebarShown={sidebarShown}
selectedGroups={selectedGroups}
popout={popout}
links={links}
listening={listening}
@ -253,7 +240,6 @@ export class LinksApp extends Component {
groups={groups}
selected={resourcePath}
sidebarShown={sidebarShown}
selectedGroups={selectedGroups}
sidebarHideMobile={true}
popout={popout}
links={links}
@ -311,7 +297,6 @@ export class LinksApp extends Component {
groups={groups}
selected={resourcePath}
sidebarShown={sidebarShown}
selectedGroups={selectedGroups}
sidebarHideMobile={true}
popout={popout}
links={links}

View File

@ -51,24 +51,14 @@ export class ChannelsSidebar extends Component {
}
});
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
let i = -1;
const groupedItems = Object.keys(associations)
.filter((each) => {
if (selectedGroups.length === 0) {
return true;
};
const selectedPaths = selectedGroups.map((e) => {
return e[0];
});
return selectedPaths.includes(each);
})
.map((each) => {
const channels = groupedChannels[each];
if (!channels || channels.length === 0)
return;
i++;
if ((selectedGroups.length === 0) && groupedChannels['/~/'] && groupedChannels['/~/'].length !== 0) {
if (groupedChannels['/~/'] && groupedChannels['/~/'].length !== 0) {
i++;
}
@ -84,7 +74,7 @@ export class ChannelsSidebar extends Component {
/>
);
});
if ((selectedGroups.length === 0) && groupedChannels['/~/'] && groupedChannels['/~/'].length !== 0) {
if (groupedChannels['/~/'] && groupedChannels['/~/'].length !== 0) {
groupedItems.unshift(
<GroupItem
key={'/~/'}

View File

@ -31,7 +31,6 @@ export class Skeleton extends Component {
invites={linkInvites}
groups={props.groups}
selected={props.selected}
selectedGroups={props.selectedGroups}
sidebarShown={props.sidebarShown}
links={props.links}
listening={props.listening}

View File

@ -42,7 +42,6 @@ export default class PublishApp extends React.Component {
const contacts = props.contacts ? props.contacts : {};
const associations = props.associations ? props.associations : { contacts: {} };
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
const notebooks = props.notebooks ? props.notebooks : {};
@ -50,12 +49,6 @@ export default class PublishApp extends React.Component {
.values()
.map(_.values)
.flatten() // flatten into array of notebooks
.filter((each) => {
return ((selectedGroups.map((e) => {
return e[0];
}).includes(each?.['writers-group-path'])) ||
(selectedGroups.length === 0));
})
.map('num-unread')
.reduce((acc, count) => acc + count, 0)
.value();
@ -80,7 +73,6 @@ export default class PublishApp extends React.Component {
invites={invites}
notebooks={notebooks}
associations={associations}
selectedGroups={selectedGroups}
contacts={contacts}
api={api}
>
@ -111,7 +103,6 @@ export default class PublishApp extends React.Component {
invites={invites}
notebooks={notebooks}
associations={associations}
selectedGroups={selectedGroups}
contacts={contacts}
api={api}
>
@ -142,7 +133,6 @@ export default class PublishApp extends React.Component {
invites={invites}
notebooks={notebooks}
associations={associations}
selectedGroups={selectedGroups}
contacts={contacts}
api={api}
>
@ -188,7 +178,6 @@ export default class PublishApp extends React.Component {
invites={invites}
notebooks={notebooks}
associations={associations}
selectedGroups={selectedGroups}
contacts={contacts}
path={path}
api={api}
@ -215,7 +204,6 @@ export default class PublishApp extends React.Component {
notebooks={notebooks}
associations={associations}
contacts={contacts}
selectedGroups={selectedGroups}
path={path}
api={api}
>
@ -265,7 +253,6 @@ export default class PublishApp extends React.Component {
sidebarShown={sidebarShown}
invites={invites}
notebooks={notebooks}
selectedGroups={selectedGroups}
associations={associations}
contacts={contacts}
path={path}
@ -293,7 +280,6 @@ export default class PublishApp extends React.Component {
invites={invites}
notebooks={notebooks}
associations={associations}
selectedGroups={selectedGroups}
contacts={contacts}
path={path}
api={api}

View File

@ -64,23 +64,12 @@ export class Sidebar extends Component {
}
});
const selectedGroups = props.selectedGroups ? props.selectedGroups: [];
const groupedItems = Object.keys(associations)
.filter((each) => {
if (selectedGroups.length === 0) {
return true;
}
const selectedPaths = selectedGroups.map((e) => {
return e[0];
});
return (selectedPaths.includes(each));
})
.map((each, i) => {
const books = groupedNotebooks[each] || [];
if (books.length === 0)
return;
if ((selectedGroups.length === 0) &&
groupedNotebooks['/~/'] &&
if (groupedNotebooks['/~/'] &&
groupedNotebooks['/~/'].length !== 0) {
i = i + 1;
}
@ -95,8 +84,7 @@ export class Sidebar extends Component {
/>
);
});
if ((selectedGroups.length === 0) &&
groupedNotebooks['/~/'] &&
if (groupedNotebooks['/~/'] &&
groupedNotebooks['/~/'].length !== 0) {
groupedItems.unshift(
<GroupItem

View File

@ -30,7 +30,6 @@ export class Skeleton extends Component {
path={props.path}
invites={props.invites}
associations={props.associations}
selectedGroups={props.selectedGroups}
api={this.props.api}
/>
<div className={'h-100 w-100 relative white-d flex-auto ' + rightPanelHide} style={{

View File

@ -1,249 +0,0 @@
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
export default class GroupFilter extends Component {
constructor(props) {
super(props);
this.state = {
open: false,
selected: [],
groups: [],
searchTerm: '',
results: []
};
this.toggleOpen = this.toggleOpen.bind(this);
this.handleClickOutside = this.handleClickOutside.bind(this);
this.groupIndex = this.groupIndex.bind(this);
this.search = this.search.bind(this);
this.addGroup = this.addGroup.bind(this);
this.deleteGroup = this.deleteGroup.bind(this);
}
componentDidMount() {
document.addEventListener('mousedown', this.handleClickOutside);
this.groupIndex();
const selected = localStorage.getItem('urbit-selectedGroups');
if (selected) {
this.setState({ selected: JSON.parse(selected) }, (() => {
this.props.api.local.setSelected(this.state.selected);
}));
}
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.handleClickOutside);
}
componentDidUpdate(prevProps) {
if (prevProps !== this.props) {
this.groupIndex();
}
}
handleClickOutside(evt) {
if ((this.dropdown && !this.dropdown.contains(evt.target))
&& (this.toggleButton && !this.toggleButton.contains(evt.target))) {
this.setState({ open: false });
}
}
toggleOpen() {
this.setState({ open: !this.state.open });
}
groupIndex() {
const { props } = this;
let index = [];
const associations =
(props.associations && 'contacts' in props.associations) ?
props.associations.contacts : {};
index = Object.keys(associations).map((each) => {
const eachGroup = [];
eachGroup.push(each);
let name = each;
if (associations[each].metadata) {
name = (associations[each].metadata.title !== '')
? associations[each].metadata.title : name;
}
eachGroup.push(name);
return eachGroup;
});
this.setState({ groups: index });
}
search(evt) {
this.setState({ searchTerm: evt.target.value });
const term = evt.target.value.toLowerCase();
if (term.length < 3) {
return this.setState({ results: [] });
}
let groupMatches = [];
groupMatches = this.state.groups.filter((e) => {
return (e[0].includes(term) || e[1].includes(term));
});
this.setState({ results: groupMatches });
}
addGroup(group) {
const selected = this.state.selected;
if (!(group in selected)) {
selected.push(group);
}
this.setState({
searchTerm: '',
selected: selected,
results: []
}, (() => {
this.props.api.local.setSelected(this.state.selected);
localStorage.setItem('urbit-selectedGroups', JSON.stringify(this.state.selected));
}));
}
deleteGroup(group) {
let selected = this.state.selected;
selected = selected.filter((e) => {
return e !== group;
});
this.setState({ selected: selected }, (() => {
this.props.api.local.setSelected(this.state.selected);
localStorage.setItem('urbit-selectedGroups', JSON.stringify(this.state.selected));
}));
}
render() {
const { props, state } = this;
let currentGroup = 'All Groups';
if (state.selected.length > 0) {
const titles = state.selected.map((each) => {
return each[1];
});
currentGroup = titles.join(' + ');
}
const buttonOpened = (state.open)
? 'bg-gray5 bg-gray1-d white-d' : 'hover-bg-gray5 hover-bg-gray1-d white-d';
const dropdownClass = (state.open)
? 'absolute db z-2 bg-white bg-gray0-d white-d ba b--gray3 b--gray1-d'
: 'dn';
const inviteCount = (props.invites && Object.keys(props.invites).length > 0)
? <template className="dib fr">
<p className="dib bg-green2 bg-gray2-d white fw6 ph1 br1 v-mid" style={{ marginBottom: 2 }}>
{Object.keys(props.invites).length}
</p>
<span className="dib v-mid ml1">
<img
className="v-mid"
src="/~landscape/img/Chevron.png"
style={{ height: 16, width: 16, paddingBottom: 1 }}
/>
</span>
</template>
: <template className="dib fr">
<span className="dib v-top ml1">
<img className="v-mid"
src="/~landscape/img/Chevron.png"
style={{ height: 16, width: 16, paddingBottom: 1 }}
/>
</span>
</template>;
let selectedGroups = <div />;
let searchResults = <div />;
if (state.results.length > 0) {
const groupResults = state.results.map(((group) => {
return(
<li
key={group[0]}
className="tl list white-d f9 pv2 ph3 pointer hover-bg-gray4 hover-bg-gray1-d inter"
onClick={() => this.addGroup(group)}
>
<span className="mix-blend-diff white">{(group[1]) ? group[1] : group[0]}</span>
</li>
);
}));
searchResults = (
<div className={'tl absolute bg-white bg-gray0-d white-d pv3 z-1 w-100 ba b--gray4 b--white-d overflow-y-scroll'} style={{ maxWidth: '15.67rem', maxHeight: '8rem' }}>
<p className="f9 tl gray2 ph3 pb2">Groups</p>
{groupResults}
</div>
);
}
if (state.selected.length > 0) {
const allSelected = this.state.selected.map((each) => {
const name = each[1];
return(
<span
key={each[0]}
className={'f9 inter black pa2 bg-gray5 bg-gray1-d ' +
'ba b--gray4 b--gray2-d white-d dib mr2 mt2 c-default'}
>
{name}
<span
className="white-d ml3 mono pointer"
onClick={e => this.deleteGroup(each)}
>
x
</span>
</span>
);
});
selectedGroups = (
<div className={
'f9 gray2 bb bl br b--gray3 b--gray2-d bg-gray0-d ' +
'white-d pa3 db w-100 inter bg-gray5 lh-solid tl'
}
style={{ width: 251 }}
>
{allSelected}
</div>
);
}
return (
<div className="ml1 dib">
<div className={buttonOpened}
onClick={() => this.toggleOpen()}
ref={el => this.toggleButton = el}
>
<p className="inter dib f9 pointer pv1 ph2 mw5 truncate v-mid">{currentGroup}</p>
</div>
<div className={dropdownClass}
style={{ maxHeight: '24rem', width: 285 }}
ref={(el) => {
this.dropdown = el;
}}
>
<p className="tc bb b--gray3 b--gray1-d gray3 pv4 f9">Group Select and Filter</p>
<Link to="/~groups"
className="ma4 bg-gray5 bg-gray1-d f9 tl pa1 br1 db no-underline"
style={{ paddingLeft: '6.5px', paddingRight: '6.5px' }}
onClick={() => this.setState({ open: false })}
>
Manage all Groups
{inviteCount}
</Link>
<p className="pt4 gray3 f9 tl mh4">Filter Groups</p>
<div className="relative w-100 ph4 pt2 pb4">
<input className="ba b--gray3 white-d bg-gray0-d inter w-100 f9 pa2"
style={{ boxSizing: 'border-box' }}
placeholder="Group name..."
onChange={this.search}
value={state.searchTerm}
/>
{searchResults}
{selectedGroups}
</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,249 @@
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { Box, Row, Rule, Text } from '@tlon/indigo-react';
import index from '../lib/omnibox';
import Mousetrap from 'mousetrap';
import OmniboxInput from './OmniboxInput';
import OmniboxResult from './OmniboxResult';
import { cite } from '../lib/util';
export class Omnibox extends Component {
constructor(props) {
super(props);
this.state = {
index: new Map([]),
query: '',
results: this.initialResults(),
selected: ''
};
this.handleClickOutside = this.handleClickOutside.bind(this);
this.search = this.search.bind(this);
this.navigate = this.navigate.bind(this);
this.control - this.control.bind(this);
this.setPreviousSelected = this.setPreviousSelected.bind(this);
this.setNextSelected = this.setNextSelected.bind(this);
}
componentDidUpdate(prevProps) {
if (prevProps !== this.props) {
this.setState({ index: index(this.props.associations, this.props.apps.tiles) });
}
if (prevProps && this.props.show && prevProps.show !== this.props.show) {
Mousetrap.bind('escape', () => this.props.api.local.setOmnibox());
document.addEventListener('mousedown', this.handleClickOutside);
const touchstart = new Event('touchstart');
this.omniInput.input.dispatchEvent(touchstart);
this.omniInput.input.focus();
}
}
componentWillUpdate(prevProps) {
if (this.props.show && prevProps.show !== this.props.show) {
Mousetrap.unbind('escape');
document.removeEventListener('mousedown', this.handleClickOutside);
}
}
control(evt) {
if (evt.key === 'Escape') {
if (this.state.query.length > 0) {
this.setState({ query: '', results: this.initialResults() });
} else if (this.props.show) {
this.props.api.local.setOmnibox();
}
};
if (
evt.key === 'ArrowUp' ||
(evt.shiftKey && evt.key === 'Tab')) {
evt.preventDefault();
return this.setPreviousSelected();
}
if (evt.key === 'ArrowDown' || evt.key === 'Tab') {
evt.preventDefault();
this.setNextSelected();
}
if (evt.key === 'Enter') {
if (this.state.selected !== '') {
this.navigate(this.state.selected);
} else {
this.navigate(Array.from(this.state.results.values()).flat()[0].link);
}
}
}
handleClickOutside(evt) {
if (this.props.show && !this.omniBox.contains(evt.target)) {
this.setState({ results: this.initialResults(), query: '' }, () => {
this.props.api.local.setOmnibox();
});
}
}
initialResults() {
return new Map([
['commands', []],
['subscriptions', []],
['groups', []],
['apps', []]
]);
}
navigate(link) {
const { props } = this;
this.setState({ results: this.initialResults(), query: '' }, () => {
props.api.local.setOmnibox();
props.history.push(link);
});
}
search(event) {
const { state } = this;
const query = event.target.value;
const results = this.initialResults();
this.setState({ query: query });
// wipe results if backspacing
if (query.length === 0) {
this.setState({ results: results, selected: '' });
return;
}
// don't search for single characters
if (query.length === 1) {
return;
}
['commands', 'subscriptions', 'groups', 'apps'].map((category) => {
const categoryIndex = state.index.get(category);
results.set(category,
categoryIndex.filter((result) => {
return (
result.title.toLowerCase().includes(query) ||
result.link.toLowerCase().includes(query) ||
result.app.toLowerCase().includes(query) ||
(result.host !== null ? result.host.includes(query) : false)
);
})
);
});
this.setState({ results: results });
}
setPreviousSelected() {
const current = this.state.selected;
const flattenedResults = Array.from(this.state.results.values()).flat();
const totalLength = flattenedResults.length;
if (current !== '') {
const currentIndex = flattenedResults.indexOf(
...flattenedResults.filter((e) => {
return e.link === current;
})
);
if (currentIndex > 0) {
const nextLink = flattenedResults[currentIndex - 1].link;
this.setState({ selected: nextLink });
} else {
const nextLink = flattenedResults[totalLength - 1].link;
this.setState({ selected: nextLink });
}
} else {
const nextLink = flattenedResults[totalLength - 1].link;
this.setState({ selected: nextLink });
}
}
setNextSelected() {
const current = this.state.selected;
const flattenedResults = Array.from(this.state.results.values()).flat();
if (current !== '') {
const currentIndex = flattenedResults.indexOf(
...flattenedResults.filter((e) => {
return e.link === current;
})
);
if (currentIndex < flattenedResults.length - 1) {
const nextLink = flattenedResults[currentIndex + 1].link;
this.setState({ selected: nextLink });
} else {
const nextLink = flattenedResults[0].link;
this.setState({ selected: nextLink });
}
} else {
const nextLink = flattenedResults[0].link;
this.setState({ selected: nextLink });
}
}
render() {
const { props, state } = this;
const categoryResult = [];
const renderResults = <Box maxHeight="400px" overflowY="scroll" overflowX="hidden">
{categoryResult}
</Box>;
['commands', 'subscriptions', 'groups', 'apps'].map((category, i) => {
const categoryResults = state.results.get(category);
if (categoryResults.length > 0) {
const each = categoryResults.map((result, i) => {
return <OmniboxResult
key={i}
icon={result.app}
text={result.title}
subtext={cite(result.host)}
link={result.link}
navigate={() => this.navigate(result.link)}
selected={this.state.selected}
dark={props.dark}
/>;
});
categoryResult.push(<Box key={i} width='max(50vw, 300px)' maxWidth='600px'>
<Rule borderTopWidth="0.5px" color="washedGray" />
<Text gray ml={2}>{category.charAt(0).toUpperCase() + category.slice(1)}</Text>
{each}
</Box>);
}
});
return (
<Box
backgroundColor='lightGray'
width='100vw'
height='100vh'
position='absolute'
top='0'
right='0'
zIndex='9'
display={props.show ? 'block' : 'none'}>
<Row justifyContent='center'>
<Box
mt='20vh'
width='max(50vw, 300px)'
maxWidth='600px'
borderRadius='2'
backgroundColor='white'
ref={(el) => {
this.omniBox = el;
}}>
<OmniboxInput
ref={(el) => { this.omniInput = el; }}
control={e => this.control(e)}
search={this.search}
query={state.query}
/>
{renderResults}
</Box>
</Row>
</Box>
);
}
}
export default withRouter(Omnibox);

View File

@ -0,0 +1,25 @@
import React, { Component } from 'react';
export class OmniboxInput extends Component {
render() {
const { props } = this;
return (
<input
ref={(el) => {
this.input = el;
}
}
className='ba b--transparent w-100 br2 white-d bg-gray0-d inter f9 pa2'
style={{ maxWidth: 'calc(600px - 1.15rem)', boxSizing: 'border-box' }}
placeholder='Search...'
onKeyDown={props.control}
onChange={props.search}
value={props.query}
/>
);
}
}
export default OmniboxInput;

View File

@ -0,0 +1,83 @@
import React, { Component } from 'react';
import { Row, Icon, Text } from '@tlon/indigo-react';
import defaultApps from '../lib/default-apps';
export class OmniboxResult extends Component {
constructor(props) {
super(props);
this.state = {
isSelected: false,
hovered: false
};
this.setHover = this.setHover.bind(this);
}
setHover(boolean) {
this.setState({ hovered: boolean });
}
render() {
const { icon, text, subtext, link, navigate, selected, dark } = this.props;
let invertGraphic = {};
if (icon.toLowerCase() !== 'dojo') {
invertGraphic = (!dark && this.state.hovered) ||
selected === link ||
(dark && !(this.state.hovered || selected === link))
? { filter: 'invert(1)', paddingTop: 2 }
: { filter: 'invert(0)', paddingTop: 2 };
} else {
invertGraphic =
(!dark && this.state.hovered) ||
selected === link ||
(dark && !(this.state.hovered || selected === link))
? { filter: 'invert(0)', paddingTop: 2 }
: { filter: 'invert(1)', paddingTop: 2 };
}
let graphic = <div />;
if (defaultApps.includes(icon.toLowerCase()) || icon.toLowerCase() === 'links') {
graphic = <img className="mr2 v-mid" height="12" width="12" src={`/~landscape/img/${icon.toLowerCase()}.png`} style={invertGraphic} />;
} else {
graphic = <Icon verticalAlign="middle" mr={2} size="12px" />;
}
return (
<Row
py='2'
px='2'
display='flex'
flexDirection='row'
style={{ cursor: 'pointer' }}
onMouseEnter={() => this.setHover(true)}
onMouseLeave={() => this.setHover(false)}
backgroundColor={
this.state.hovered || selected === link ? 'blue' : 'white'
}
onClick={navigate}
width="100%"
>
{this.state.hovered || selected === link ? (
<>
{graphic}
<Text color='white' mr='1' style={{ 'flex-shrink': 0 }}>
{text}
</Text>
<Text pr='2' color='white' width='100%' textAlign='right'>
{subtext}
</Text>
</>
) : (
<>
{graphic}
<Text mr='1' style={{ 'flex-shrink': 0 }}>{text}</Text>
<Text pr='2' gray width='100%' textAlign='right'>
{subtext}
</Text>
</>
)}
</Row>
);
}
}
export default OmniboxResult;

View File

@ -0,0 +1,46 @@
import React from 'react';
import { Box, Text } from '@tlon/indigo-react';
const ReconnectButton = ({ connection, subscription }) => {
const connectedStatus = connection || 'connected';
const reconnect = subscription.restart.bind(subscription);
if (connectedStatus === 'disconnected') {
return (
<>
<Box
ml={4}
px={2}
py={1}
display='inline-block'
color='red'
border={1}
lineHeight='min'
borderRadius={2}
style={{ cursor: 'pointer' }}
onClick={reconnect}>
<Text color='red'>Reconnect </Text>
</Box>
</>
);
} else if (connectedStatus === 'reconnecting') {
return (
<>
<Box
ml={4}
px={2}
py={1}
lineHeight="min"
display='inline-block'
color='yellow'
border={1}
borderRadius={2}>
<Text color='yellow'>Reconnecting</Text>
</Box>
</>
);
} else {
return null;
}
};
export default ReconnectButton;

View File

@ -1,82 +1,103 @@
import React from 'react';
import { useLocation, Link } from 'react-router-dom';
import GroupFilter from './GroupFilter';
import { Sigil } from '../lib/sigil';
const getLocationName = (basePath) => {
if (basePath === '~chat')
return 'Chat';
else if (basePath === '~dojo')
return 'Dojo';
else if (basePath === '~groups')
return 'Groups';
else if (basePath === '~link')
return 'Links';
else if (basePath === '~publish')
return 'Publish';
else
return 'Unknown';
};
import { useLocation } from 'react-router-dom';
import { Box, Text, Icon } from '@tlon/indigo-react';
import ReconnectButton from './ReconnectButton';
const StatusBar = (props) => {
const location = useLocation();
const basePath = location.pathname.split('/')[1];
const locationName = location.pathname === '/'
? 'Home'
: getLocationName(basePath);
const atHome = Boolean(location.pathname === '/');
const display = (!window.location.href.includes('popout/') &&
(locationName !== 'Unknown'))
const display = (!window.location.href.includes('popout/'))
? 'db' : 'dn';
const invites = (props.invites && props.invites['/contacts'])
? props.invites['/contacts']
: {};
const connection = props.connection || 'connected';
const reconnect = props.subscription.restart.bind(props.subscription);
const Notification = (Object.keys(invites).length > 0)
? <Icon size="22px" icon="Bullet"
fill="blue" position="absolute"
top={'-8px'} right={'7px'}
/>
: null;
const metaKey = (window.navigator.platform.includes('Mac')) ? '⌘' : 'Ctrl+';
const mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(
navigator.userAgent
);
return (
<div
className={
'bg-white bg-gray0-d w-100 justify-between relative tc pt3 ' + display
}
style={{ height: 45 }}
>
<div className="fl lh-copy absolute left-0 pl4" style={{ top: 8 }}>
<Link to="/~groups/me"
className="dib v-top" style={{ lineHeight: 0, paddingTop: 6 }}>
<Sigil
ship={'~' + window.ship}
classes="v-mid mix-blend-diff"
size={16}
color={'#000000'}
style={{ height: 45 }}>
<div className='absolute left-0 pl4' style={{ top: 10 }}>
{atHome ? null : (
<Box
style={{ cursor: 'pointer' }}
display='inline-block'
borderRadius={2}
color='washedGray'
border={1}
py={1}
px={2}
mr={2}
onClick={() => props.history.push('/')}>
<img
className='invert-d'
src='/~landscape/img/icon-home.png'
height='12'
width='12'
/>
</Box>
)}
<Box
border={1}
borderRadius={2}
color='washedGray'
display='inline-block'
style={{ cursor: 'pointer' }}
lineHeight='min'
py={1}
px={2}
onClick={() => props.api.local.setOmnibox()}>
<Text display='inline-block' style={{ transform: 'rotate(180deg)' }}>
</Text>
<Text ml={2} color='black'>
Leap
</Text>
<Text display={mobile ? 'none' : 'inline-block'} ml={4} color='gray'>
{metaKey}/
</Text>
</Box>
<ReconnectButton
connection={props.connection}
subscription={props.subscription}
/>
</div>
<div className='fl absolute relative right-0 pr4' style={{ top: 10 }}>
<Box
style={{ cursor: 'pointer' }}
display='inline-block'
borderRadius={2}
color='washedGray'
lineHeight='min'
border={1}
px={2}
py={1}
onClick={() => props.history.push('/~groups')}>
<img
className='invert-d v-mid mr1'
src='/~landscape/img/groups.png'
height='16'
width='16'
/>
</Link>
<GroupFilter invites={invites} associations={props.associations} api={props.api} />
<span className="dib f9 v-mid gray2 ml1 mr1 c-default inter">/</span>
{
location.pathname === '/'
? null
: <Link
className="dib f9 v-mid inter ml2 no-underline white-d"
to="/"
style={{ top: 14 }}
>
</Link>
}
<p className="dib f9 v-mid inter ml2 white-d">{locationName}</p>
{ connection === 'disconnected' &&
(<span
onClick={reconnect}
className="ml4 ph2 dib f9 v-mid red2 inter ba b-red2 br1 pointer"
>Reconnect </span> )
}
{ connection === 'reconnecting' &&
(<span className="ml4 ph2 dib f9 v-mid yellow2 inter ba b-yellow2 br1">Reconnecting</span> )
}
{Notification}
<Text ml={1}>Groups</Text>
</Box>
</div>
</div>
);

View File

@ -0,0 +1,3 @@
const defaultApps = ['chat', 'dojo', 'groups', 'link', 'publish'];
export default defaultApps;

View File

@ -0,0 +1,106 @@
import defaultApps from './default-apps';
export default function index(associations, apps) {
const index = new Map([
['commands', []],
['subscriptions', []],
['groups', []],
['apps', []]
]);
// result schematic
const result = function(title, link, app, host) {
return {
'title': title,
'link': link,
'app': app,
'host': host
};
};
// commands are special cased for default suite
const commands = [];
defaultApps.filter((e) => {
return (e !== 'dojo');
}).map((e) => {
let title = e;
if (e === 'link') {
title = 'Links';
}
title = title.charAt(0).toUpperCase() + title.slice(1);
let obj = result(`${title}: Create`, `/~${e}/new`, e, null);
commands.push(obj);
if (title === 'Groups') {
obj = result(`${title}: Join Group`, `/~${e}/join`, title, null);
commands.push(obj);
}
});
index.set('commands', commands);
// all metadata from all apps is indexed
// into subscriptions and groups
const subscriptions = [];
const groups = [];
Object.keys(associations).filter((e) => {
// skip apps with no metadata
return Object.keys(associations[e]).length > 0;
}).map((e) => {
// iterate through each app's metadata object
Object.keys(associations[e]).map((association) => {
const each = associations[e][association];
let title = each['app-path'];
if (each.metadata.title !== '') {
title = each.metadata.title;
}
let app = each['app-name'];
if (each['app-name'] === 'contacts') {
app = 'groups';
};
const shipStart = each['app-path'].substr(each['app-path'].indexOf('~'));
if (app === 'groups') {
const obj = result(
title,
`/~${app}${each['app-path']}`,
app.charAt(0).toUpperCase() + app.slice(1),
shipStart.slice(0, shipStart.indexOf('/'))
);
groups.push(obj);
} else {
let endpoint = '';
if (app === 'chat') {
endpoint = '/room';
} else if (app === 'publish') {
endpoint = '/notebook';
}
const obj = result(
title,
`/~${each['app-name']}${endpoint}${each['app-path']}`,
app.charAt(0).toUpperCase() + app.slice(1),
shipStart.slice(0, shipStart.indexOf('/'))
);
subscriptions.push(obj);
}
});
});
index.set('subscriptions', subscriptions);
index.set('groups', groups);
// all apps are indexed from launch data
// indexed into 'apps'
const applications = [];
Object.keys(apps).filter((e) => {
return (apps[e]?.type?.basic);
}).map((e) => {
const obj = result(apps[e].type.basic.title, apps[e].type.basic.linkedUrl, apps[e].type.basic.title, null);
applications.push(obj);
});
index.set('apps', applications);
return index;
};

View File

@ -2,24 +2,24 @@ import React, { Component } from 'react';
import { sigil, reactRenderer } from 'urbit-sigil-js';
export class Sigil extends Component {
static foregroundFromBackground(background) {
const rgb = {
r: parseInt(background.slice(1, 3), 16),
g: parseInt(background.slice(3, 5), 16),
b: parseInt(background.slice(5, 7), 16)
};
const brightness = ((299 * rgb.r) + (587 * rgb.g) + (114 * rgb.b)) / 1000;
const whiteBrightness = 255;
return ((whiteBrightness - brightness) < 50) ? 'black' : 'white';
}
render() {
const { props } = this;
const classes = props.classes || '';
const rgb = {
r: parseInt(props.color.slice(1, 3), 16),
g: parseInt(props.color.slice(3, 5), 16),
b: parseInt(props.color.slice(5, 7), 16)
};
const brightness = ((299 * rgb.r) + (587 * rgb.g) + (114 * rgb.b)) / 1000;
const whiteBrightness = 255;
let foreground = 'white';
if ((whiteBrightness - brightness) < 50) {
foreground = 'black';
}
const foreground = Sigil.foregroundFromBackground(props.color);
if (props.ship.length > 14) {
return (

View File

@ -119,6 +119,9 @@ export function writeText(str) {
// trim patps to match dojo, chat-cli
export function cite(ship) {
let patp = ship, shortened = '';
if (patp === null || patp === '') {
return null;
}
if (patp.startsWith('~')) {
patp = patp.substr(1);
}

View File

@ -3,16 +3,16 @@ import { StoreState } from '../store/type';
import { Cage } from '../types/cage';
import { LocalUpdate } from '../types/local-update';
type LocalState = Pick<StoreState, 'sidebarShown' | 'selectedGroups' | 'dark' | 'baseHash'>;
type LocalState = Pick<StoreState, 'sidebarShown' | 'omniboxShown' | 'dark' | 'baseHash'>;
export default class LocalReducer<S extends LocalState> {
reduce(json: Cage, state: S) {
const data = json['local'];
if (data) {
this.sidebarToggle(data, state);
this.setSelected(data, state);
this.setDark(data, state);
this.baseHash(data, state);
this.omniboxShown(data, state);
}
}
baseHash(obj: LocalUpdate, state: S) {
@ -21,18 +21,18 @@ export default class LocalReducer<S extends LocalState> {
}
}
omniboxShown(obj: LocalUpdate, state: S) {
if ('omniboxShown' in obj) {
state.omniboxShown = !state.omniboxShown;
}
}
sidebarToggle(obj: LocalUpdate, state: S) {
if ('sidebarToggle' in obj) {
state.sidebarShown = !state.sidebarShown;
}
}
setSelected(obj: LocalUpdate, state: S) {
if ('selected' in obj) {
state.selectedGroups = obj.selected;
}
}
setDark(obj: LocalUpdate, state: S) {
if('setDark' in obj) {
state.dark = obj.setDark;

View File

@ -41,6 +41,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
chatInitialized: false,
connection: 'connected',
sidebarShown: true,
omniboxShown: false,
baseHash: null,
invites: {},
associations: {
@ -72,7 +73,6 @@ export default class GlobalStore extends BaseStore<StoreState> {
linkComments: {},
notebooks: {},
contacts: {},
selectedGroups: [],
dark: false,
inbox: {},
chatSynced: null,

View File

@ -2,7 +2,6 @@ import { Inbox, Envelope } from '../types/chat-update';
import { ChatHookUpdate } from '../types/chat-hook-update';
import { Path } from '../types/noun';
import { Invites } from '../types/invite-update';
import { SelectedGroup } from '../types/local-update';
import { Associations } from '../types/metadata-update';
import { Rolodex } from '../types/contact-update';
import { Notebooks } from '../types/publish-update';
@ -16,7 +15,7 @@ import { ConnectionStatus } from '../types/connection';
export interface StoreState {
// local state
sidebarShown: boolean;
selectedGroups: SelectedGroup[];
omniboxShown: boolean;
dark: boolean;
connection: ConnectionStatus;
baseHash: string | null;

View File

@ -1,19 +1,13 @@
import { Path } from './noun';
export type LocalUpdate =
LocalUpdateSidebarToggle
| LocalUpdateSelectedGroups
| LocalUpdateSetDark
| LocalUpdateSetOmniboxShown
| LocalUpdateBaseHash;
interface LocalUpdateSidebarToggle {
sidebarToggle: boolean;
}
interface LocalUpdateSelectedGroups {
selected: SelectedGroup[];
}
interface LocalUpdateSetDark {
setDark: boolean;
}
@ -22,4 +16,6 @@ interface LocalUpdateBaseHash {
baseHash: string;
}
export type SelectedGroup = [Path, string];
interface LocalUpdateSetOmniboxShown {
omniboxShown: boolean;
}