diff --git a/pkg/arvo/app/landscape/fonts/sourcecodepro-bold.woff2 b/pkg/arvo/app/landscape/fonts/sourcecodepro-bold.woff2 new file mode 100644 index 000000000..4efbf2991 Binary files /dev/null and b/pkg/arvo/app/landscape/fonts/sourcecodepro-bold.woff2 differ diff --git a/pkg/arvo/app/landscape/fonts/sourcecodepro-extralight.woff2 b/pkg/arvo/app/landscape/fonts/sourcecodepro-extralight.woff2 index 1c2a39290..4af3968e0 100644 Binary files a/pkg/arvo/app/landscape/fonts/sourcecodepro-extralight.woff2 and b/pkg/arvo/app/landscape/fonts/sourcecodepro-extralight.woff2 differ diff --git a/pkg/arvo/app/landscape/fonts/sourcecodepro-light.woff2 b/pkg/arvo/app/landscape/fonts/sourcecodepro-light.woff2 index 8f7849083..289b6c97c 100644 Binary files a/pkg/arvo/app/landscape/fonts/sourcecodepro-light.woff2 and b/pkg/arvo/app/landscape/fonts/sourcecodepro-light.woff2 differ diff --git a/pkg/arvo/app/landscape/fonts/sourcecodepro-medium.woff2 b/pkg/arvo/app/landscape/fonts/sourcecodepro-medium.woff2 index 7dfd91c5b..0cce5e955 100644 Binary files a/pkg/arvo/app/landscape/fonts/sourcecodepro-medium.woff2 and b/pkg/arvo/app/landscape/fonts/sourcecodepro-medium.woff2 differ diff --git a/pkg/arvo/app/landscape/fonts/sourcecodepro-regular.woff2 b/pkg/arvo/app/landscape/fonts/sourcecodepro-regular.woff2 index c10f38d32..aae63a946 100644 Binary files a/pkg/arvo/app/landscape/fonts/sourcecodepro-regular.woff2 and b/pkg/arvo/app/landscape/fonts/sourcecodepro-regular.woff2 differ diff --git a/pkg/arvo/app/landscape/fonts/sourcecodepro-semibold.woff2 b/pkg/arvo/app/landscape/fonts/sourcecodepro-semibold.woff2 index b2b00d64a..3a24fb6ec 100644 Binary files a/pkg/arvo/app/landscape/fonts/sourcecodepro-semibold.woff2 and b/pkg/arvo/app/landscape/fonts/sourcecodepro-semibold.woff2 differ diff --git a/pkg/arvo/app/landscape/fonts/sourcecodeprop-bold.woff2 b/pkg/arvo/app/landscape/fonts/sourcecodeprop-bold.woff2 deleted file mode 100644 index 45ee40a2b..000000000 Binary files a/pkg/arvo/app/landscape/fonts/sourcecodeprop-bold.woff2 and /dev/null differ diff --git a/pkg/interface/.eslintrc.js b/pkg/interface/.eslintrc.js index 0f63724ca..fba1ccabc 100644 --- a/pkg/interface/.eslintrc.js +++ b/pkg/interface/.eslintrc.js @@ -1,186 +1,3 @@ -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 - } - ] -}; + extends: "@urbit" +}; \ No newline at end of file diff --git a/pkg/interface/package-lock.json b/pkg/interface/package-lock.json index d4a06d132..a562d5080 100644 --- a/pkg/interface/package-lock.json +++ b/pkg/interface/package-lock.json @@ -1165,6 +1165,67 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "@eslint/eslintrc": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", + "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.20", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + } + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.4", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.4", + "fastq": "^1.6.0" + } + }, "@reach/auto-id": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.10.5.tgz", @@ -1403,12 +1464,6 @@ "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", "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/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", @@ -1596,14 +1651,16 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz", - "integrity": "sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.15.0.tgz", + "integrity": "sha512-DJgdGZW+8CFUTz5C/dnn4ONcUm2h2T0itWD85Ob5/V27Ndie8hUoX5HKyGssvR8sUMkAIlUc/AMK67Lqa3kBIQ==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "3.10.1", + "@typescript-eslint/experimental-utils": "4.15.0", + "@typescript-eslint/scope-manager": "4.15.0", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", + "lodash": "^4.17.15", "regexpp": "^3.0.0", "semver": "^7.3.2", "tsutils": "^3.17.1" @@ -1621,53 +1678,70 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", - "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.0.tgz", + "integrity": "sha512-V4vaDWvxA2zgesg4KPgEGiomWEBpJXvY4ZX34Y3qxK8LUm5I87L+qGIOTd9tHZOARXNRt9pLbblSKiYBlGMawg==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/typescript-estree": "3.10.1", + "@typescript-eslint/scope-manager": "4.15.0", + "@typescript-eslint/types": "4.15.0", + "@typescript-eslint/typescript-estree": "4.15.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" } }, - "@typescript-eslint/parser": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz", - "integrity": "sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==", + "@typescript-eslint/scope-manager": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.0.tgz", + "integrity": "sha512-CSNBZnCC2jEA/a+pR9Ljh8Y+5TY5qgbPz7ICEk9WCpSEgT6Pi7H2RIjxfrrbUXvotd6ta+i27sssKEH8Azm75g==", "dev": true, "requires": { - "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "3.10.1", - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/typescript-estree": "3.10.1", - "eslint-visitor-keys": "^1.1.0" + "@typescript-eslint/types": "4.15.0", + "@typescript-eslint/visitor-keys": "4.15.0" } }, "@typescript-eslint/types": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", - "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.0.tgz", + "integrity": "sha512-su4RHkJhS+iFwyqyXHcS8EGPlUVoC+XREfy5daivjLur9JP8GhvTmDipuRpcujtGC4M+GYhUOJCPDE3rC5NJrg==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", - "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.0.tgz", + "integrity": "sha512-jG6xTmcNbi6xzZq0SdWh7wQ9cMb2pqXaUp6bUZOMsIlu5aOlxGxgE/t6L/gPybybQGvdguajXGkZKSndZJpksA==", "dev": true, "requires": { - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/visitor-keys": "3.10.1", + "@typescript-eslint/types": "4.15.0", + "@typescript-eslint/visitor-keys": "4.15.0", "debug": "^4.1.1", - "glob": "^7.1.6", + "globby": "^11.0.1", "is-glob": "^4.0.1", - "lodash": "^4.17.15", "semver": "^7.3.2", "tsutils": "^3.17.1" }, "dependencies": { + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "globby": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", + "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, "semver": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", @@ -1680,14 +1754,64 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", - "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.0.tgz", + "integrity": "sha512-RnDtJwOwFucWFAMjG3ghCG/ikImFJFEg20DI7mn4pHEx3vC48lIAoyjhffvfHmErRDboUPC7p9Z2il4CLb7qxA==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.1.0" + "@typescript-eslint/types": "4.15.0", + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "dev": true + } } }, + "@urbit/api": { + "version": "file:../npm/api", + "requires": { + "@babel/runtime": "^7.12.5", + "@urbit/eslint-config": "^1.0.0", + "big-integer": "^1.6.48", + "lodash": "^4.17.20" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", + "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@urbit/eslint-config": { + "version": "1.0.0" + }, + "big-integer": { + "version": "1.6.48", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", + "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==" + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@urbit/eslint-config": { + "version": "file:../npm/eslint-config", + "dev": true + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -1925,23 +2049,6 @@ "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", "dev": true }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, - "requires": { - "type-fest": "^0.11.0" - }, - "dependencies": { - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true - } - } - }, "ansi-html": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", @@ -2103,9 +2210,9 @@ "dev": true }, "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, "async": { @@ -2804,12 +2911,6 @@ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, "chokidar": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", @@ -2906,21 +3007,6 @@ "del": "^4.1.1" } }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-width": { - "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": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -3583,6 +3669,15 @@ "randombytes": "^2.0.0" } }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -3609,9 +3704,9 @@ } }, "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { "esutils": "^2.0.2" @@ -3839,6 +3934,23 @@ } } }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + }, + "dependencies": { + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + } + } + }, "entities": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", @@ -3904,80 +4016,99 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.19.0.tgz", + "integrity": "sha512-CGlMgJY56JZ9ZSYhJuhow61lMPPjUzWmChFya71Z/jilVos7mR/jPgaEfVGgMBY5DshbKdG8Ezb8FDCHcoMEMg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", + "@eslint/eslintrc": "^0.3.0", "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.2.0", "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", + "file-entry-cache": "^6.0.0", "functional-red-black-tree": "^1.0.1", "glob-parent": "^5.0.0", "globals": "^12.1.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", + "levn": "^0.4.1", + "lodash": "^4.17.20", "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.3", + "optionator": "^0.9.1", "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.4", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "color-convert": "^2.0.1" } }, - "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==", + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.1.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "eslint-visitor-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "dev": true + }, "globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", @@ -3987,40 +4118,43 @@ "type-fest": "^0.8.1" } }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "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==", + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "lru-cache": "^6.0.0" } }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "isexe": "^2.0.0" + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } } } @@ -4042,17 +4176,6 @@ "prop-types": "^15.7.2", "resolve": "^1.18.1", "string.prototype.matchall": "^4.0.2" - }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - } } }, "eslint-scope": { @@ -4081,14 +4204,14 @@ "dev": true }, "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "requires": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" } }, "esprima": { @@ -4098,9 +4221,9 @@ "dev": true }, "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -4396,17 +4519,6 @@ } } }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -4483,6 +4595,32 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-glob": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + }, + "dependencies": { + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + } + } + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4494,6 +4632,15 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fastq": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.1.tgz", + "integrity": "sha512-AWuv6Ery3pM+dY7LYS8YIaCiQvUaos9OB1RyNgaOWnaX+Tik7Onvcsf8x8c+YtDeT0maYLniBip2hox5KtEXXA==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, "faye-websocket": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", @@ -4509,22 +4656,13 @@ "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", "dev": true }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz", + "integrity": "sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==", "dev": true, "requires": { - "flat-cache": "^2.0.1" + "flat-cache": "^3.0.4" } }, "file-loader": { @@ -4657,20 +4795,19 @@ } }, "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" + "flatted": "^3.1.0", + "rimraf": "^3.0.2" }, "dependencies": { "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" @@ -4679,9 +4816,9 @@ } }, "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", "dev": true }, "flush-write-stream": { @@ -5375,9 +5512,9 @@ "dev": true }, "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true }, "immer": { @@ -5488,87 +5625,6 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, - "inquirer": { - "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": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "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", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "internal-ip": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", @@ -5580,33 +5636,25 @@ } }, "internal-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", - "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", "dev": true, "requires": { - "es-abstract": "^1.17.0-next.1", + "get-intrinsic": "^1.1.0", "has": "^1.0.3", - "side-channel": "^1.0.2" + "side-channel": "^1.0.4" }, "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", "dev": true, "requires": { - "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" + "has-symbols": "^1.0.1" } } } @@ -5979,13 +6027,13 @@ } }, "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" } }, "loader-runner": { @@ -6181,6 +6229,12 @@ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -6329,12 +6383,6 @@ "mime-db": "1.45.0" } }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, "min-document": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", @@ -6486,12 +6534,6 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, "nan": { "version": "2.14.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", @@ -6861,15 +6903,6 @@ "wrappy": "1" } }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, "opn": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", @@ -6880,17 +6913,17 @@ } }, "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" } }, "original": { @@ -6908,12 +6941,6 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -7132,6 +7159,12 @@ } } }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, "pbkdf2": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", @@ -7273,9 +7306,9 @@ "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" }, "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "pretty-error": { @@ -7418,6 +7451,12 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "queue-microtask": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.2.tgz", + "integrity": "sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg==", + "dev": true + }, "ramda": { "version": "0.27.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", @@ -7920,6 +7959,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -8010,16 +8055,6 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -8032,6 +8067,12 @@ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -8051,11 +8092,14 @@ "inherits": "^2.0.1" } }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } }, "run-queue": { "version": "1.0.3", @@ -8066,23 +8110,6 @@ "aproba": "^1.1.1" } }, - "rxjs": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", - "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -8405,20 +8432,38 @@ "dev": true }, "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" }, "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true } } @@ -9017,39 +9062,34 @@ "integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ==" }, "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", "dev": true, "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" + "ajv": "^7.0.2", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" }, "dependencies": { - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "ajv": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.0.tgz", + "integrity": "sha512-svS9uILze/cXbH0z2myCK2Brqprx/+JJYK5pHicT/GQiBfzzhUVAIT6MwqJg8y4xV/zoGsUeuPuwtoiKSGE15g==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true } } }, @@ -9184,12 +9224,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -9251,15 +9285,6 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -9338,9 +9363,9 @@ "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" }, "tsutils": { - "version": "3.19.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.19.1.tgz", - "integrity": "sha512-GEdoBf5XI324lu7ycad7s6laADfnAqCw6wLGI+knxvw9vsIYBaJfYdmeCEG3FMMUiSm3OGgNb+m6utsWf5h9Vw==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz", + "integrity": "sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==", "dev": true, "requires": { "tslib": "^1.8.1" @@ -9361,12 +9386,12 @@ "dev": true }, "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "^1.2.1" } }, "type-fest": { @@ -10458,15 +10483,6 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, "ws": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", diff --git a/pkg/interface/package.json b/pkg/interface/package.json index a65578c43..bf58224c4 100644 --- a/pkg/interface/package.json +++ b/pkg/interface/package.json @@ -12,6 +12,7 @@ "@tlon/indigo-light": "^1.0.6", "@tlon/indigo-react": "1.2.17", "@tlon/sigil-js": "^1.4.3", + "@urbit/api": "file:../npm/api", "aws-sdk": "^2.830.0", "big-integer": "^1.6.48", "classnames": "^2.2.6", @@ -63,15 +64,15 @@ "@types/styled-components": "^5.1.7", "@types/styled-system": "^5.1.10", "@types/yup": "^0.29.11", - "@typescript-eslint/eslint-plugin": "^3.10.1", - "@typescript-eslint/parser": "^3.10.1", + "@typescript-eslint/eslint-plugin": "^4.15.0", + "@urbit/eslint-config": "file:../npm/eslint-config", "babel-eslint": "^10.1.0", "babel-loader": "^8.2.2", "babel-plugin-lodash": "^3.3.4", "babel-plugin-root-import": "^6.6.0", "clean-webpack-plugin": "^3.0.0", "cross-env": "^7.0.3", - "eslint": "^6.8.0", + "eslint": "^7.19.0", "eslint-plugin-react": "^7.22.0", "file-loader": "^6.2.0", "html-webpack-plugin": "^4.5.1", @@ -85,7 +86,7 @@ "webpack-dev-server": "^3.11.2" }, "scripts": { - "lint": "eslint ./src/**/*.{js,ts,tsx}", + "lint": "eslint ./src/**/*.{ts,tsx}", "lint-file": "eslint", "tsc": "tsc", "tsc:watch": "tsc --watch", diff --git a/pkg/interface/src/logic/api/base.ts b/pkg/interface/src/logic/api/base.ts index 085c4cc32..01b70fabc 100644 --- a/pkg/interface/src/logic/api/base.ts +++ b/pkg/interface/src/logic/api/base.ts @@ -1,6 +1,5 @@ -import _ from "lodash"; -import { uuid } from "../lib/util"; -import { Patp, Path } from "~/types/noun"; +import _ from 'lodash'; +import { Patp, Path } from '@urbit/api'; import BaseStore from '../store/base'; export default class BaseApi { @@ -26,8 +25,8 @@ export default class BaseApi { data: event, from: { ship, - path, - }, + path + } }); }, (qui) => { @@ -50,8 +49,12 @@ export default class BaseApi { appl, mark, data, - (json) => { resolve(json); }, - (err) => { reject(err); } + (json) => { + resolve(json); +}, + (err) => { + reject(err); +} ); }); } @@ -69,5 +72,4 @@ export default class BaseApi { return res.json(); } - } diff --git a/pkg/interface/src/logic/api/contacts.ts b/pkg/interface/src/logic/api/contacts.ts index ca1f0a338..ae6a6972a 100644 --- a/pkg/interface/src/logic/api/contacts.ts +++ b/pkg/interface/src/logic/api/contacts.ts @@ -1,8 +1,7 @@ import BaseApi from './base'; import { StoreState } from '../store/type'; -import { Patp, Path, Enc } from '~/types/noun'; -import { Contact, ContactEdit } from '~/types/contact-update'; -import { GroupPolicy, Resource } from '~/types/group-update'; +import { Patp } from '@urbit/api'; +import { ContactEdit } from '@urbit/api/contacts'; export default class ContactsApi extends BaseApi { add(ship: Patp, contact: any) { @@ -31,7 +30,7 @@ export default class ContactsApi extends BaseApi { ship, 'edit-field': editField, timestamp: Date.now() - }, + } }); } @@ -62,7 +61,7 @@ export default class ContactsApi extends BaseApi { return this.action( 'contact-push-hook', 'contact-share', - { share: recipient }, + { share: recipient } ); } @@ -85,7 +84,7 @@ export default class ContactsApi extends BaseApi { } private storeAction(action: any): Promise { - return this.action('contact-store', 'contact-update', action) + return this.action('contact-store', 'contact-update', action); } private viewAction(threadName: string, action: any) { diff --git a/pkg/interface/src/logic/api/global.ts b/pkg/interface/src/logic/api/global.ts index 8ed02020b..cc7c1291c 100644 --- a/pkg/interface/src/logic/api/global.ts +++ b/pkg/interface/src/logic/api/global.ts @@ -1,4 +1,4 @@ -import { Patp } from '~/types/noun'; +import { Patp } from '@urbit/api'; import BaseApi from './base'; import { StoreState } from '../store/type'; import GlobalStore from '../store/store'; @@ -10,7 +10,7 @@ import GroupsApi from './groups'; import LaunchApi from './launch'; import GraphApi from './graph'; import S3Api from './s3'; -import {HarkApi} from './hark'; +import { HarkApi } from './hark'; import SettingsApi from './settings'; export default class GlobalApi extends BaseApi { diff --git a/pkg/interface/src/logic/api/graph.ts b/pkg/interface/src/logic/api/graph.ts index 0606a2c1e..58b330da1 100644 --- a/pkg/interface/src/logic/api/graph.ts +++ b/pkg/interface/src/logic/api/graph.ts @@ -1,14 +1,14 @@ import BaseApi from './base'; import { StoreState } from '../store/type'; -import { Patp, Path, PatpNoSig } from '~/types/noun'; +import { Patp, Path } from '@urbit/api'; import _ from 'lodash'; -import {makeResource, resourceFromPath} from '../lib/group'; -import {GroupPolicy, Enc, Post, NodeMap, Content, Resource} from '~/types'; +import { makeResource, resourceFromPath } from '../lib/group'; +import { GroupPolicy, Enc, Post, Content } from '@urbit/api'; import { numToUd, unixToDa, decToUd, deSig, resourceAsPath } from '~/logic/lib/util'; export const createBlankNodeWithChildPost = ( - parentIndex: string = '', - childIndex: string = '', + parentIndex = '', + childIndex = '', contents: Content[] ) => { const date = unixToDa(Date.now()).toString(); @@ -37,11 +37,11 @@ export const createBlankNodeWithChildPost = ( signatures: [] }, children: childGraph - }; + }; }; function markPending(nodes: any) { - _.forEach(nodes, node => { + _.forEach(nodes, (node) => { node.post.author = deSig(node.post.author); node.post.pending = true; markPending(node.children || {}); @@ -50,8 +50,8 @@ function markPending(nodes: any) { export const createPost = ( contents: Content[], - parentIndex: string = '', - childIndex:string = 'DATE_PLACEHOLDER' + parentIndex = '', + childIndex = 'DATE_PLACEHOLDER' ) => { if (childIndex === 'DATE_PLACEHOLDER') { childIndex = unixToDa(Date.now()).toString(); @@ -80,11 +80,10 @@ function moduleToMark(mod: string): string | undefined { } export default class GraphApi extends BaseApi { - joiningGraphs = new Set(); private storeAction(action: any): Promise { - return this.action('graph-store', 'graph-update', action) + return this.action('graph-store', 'graph-update', action); } private viewAction(threadName: string, action: any) { @@ -106,12 +105,12 @@ export default class GraphApi extends BaseApi { const resource = makeResource(`~${window.ship}`, name); return this.viewAction('graph-create', { - "create": { + 'create': { resource, title, description, associated, - "module": mod, + 'module': mod, mark: moduleToMark(mod) } }); @@ -127,12 +126,12 @@ export default class GraphApi extends BaseApi { const resource = makeResource(`~${window.ship}`, name); return this.viewAction('graph-create', { - "create": { + 'create': { resource, title, description, associated: { policy }, - "module": mod, + 'module': mod, mark: moduleToMark(mod) } }); @@ -148,9 +147,9 @@ export default class GraphApi extends BaseApi { return this.viewAction('graph-join', { join: { resource, - ship, + ship } - }).then(res => { + }).then((res) => { this.joiningGraphs.delete(rid); return res; }); @@ -159,7 +158,7 @@ export default class GraphApi extends BaseApi { deleteGraph(name: string) { const resource = makeResource(`~${window.ship}`, name); return this.viewAction('graph-delete', { - "delete": { + 'delete': { resource } }); @@ -168,7 +167,7 @@ export default class GraphApi extends BaseApi { leaveGraph(ship: Patp, name: string) { const resource = makeResource(ship, name); return this.viewAction('graph-leave', { - "leave": { + 'leave': { resource } }); @@ -203,7 +202,7 @@ export default class GraphApi extends BaseApi { } addPost(ship: Patp, name: string, post: Post) { - let nodes = {}; + const nodes = {}; nodes[post.index] = { post, children: null @@ -212,7 +211,7 @@ export default class GraphApi extends BaseApi { } addNode(ship: Patp, name: string, node: Object) { - let nodes = {}; + const nodes = {}; nodes[node.post.index] = node; return this.addNodes(ship, name, nodes); @@ -300,7 +299,6 @@ export default class GraphApi extends BaseApi { this.store.handleEvent({ data }); } - getGraphSubset(ship: string, resource: string, start: string, end: string) { return this.scry( 'graph-store', diff --git a/pkg/interface/src/logic/api/groups.ts b/pkg/interface/src/logic/api/groups.ts index de2f4b488..f0bab91f0 100644 --- a/pkg/interface/src/logic/api/groups.ts +++ b/pkg/interface/src/logic/api/groups.ts @@ -1,14 +1,14 @@ import BaseApi from './base'; import { StoreState } from '../store/type'; -import { Path, Patp, Enc } from '~/types/noun'; +import { Path, Patp, Enc } from '@urbit/api'; import { GroupAction, GroupPolicy, Resource, Tag, - GroupPolicyDiff, -} from '~/types/group-update'; -import {makeResource} from '../lib/group'; + GroupPolicyDiff +} from '@urbit/api/groups'; +import { makeResource } from '../lib/group'; export default class GroupsApi extends BaseApi { remove(resource: Resource, ships: Patp[]) { @@ -38,7 +38,7 @@ export default class GroupsApi extends BaseApi { join(ship: string, name: string) { const resource = makeResource(ship, name); - return this.viewAction({ join: { resource, ship }}); + return this.viewAction({ join: { resource, ship } }); } create(name: string, policy: Enc, title: string, description: string) { @@ -76,7 +76,6 @@ export default class GroupsApi extends BaseApi { description } }); - } private proxyAction(action: GroupAction) { @@ -93,6 +92,5 @@ export default class GroupsApi extends BaseApi { private viewAction(action: any) { return this.action('group-view', 'group-view-action', action); - } } diff --git a/pkg/interface/src/logic/api/hark.ts b/pkg/interface/src/logic/api/hark.ts index 4dadeb169..b807201a1 100644 --- a/pkg/interface/src/logic/api/hark.ts +++ b/pkg/interface/src/logic/api/hark.ts @@ -1,24 +1,23 @@ -import BaseApi from "./base"; -import { StoreState } from "../store/type"; -import { dateToDa, decToUd } from "../lib/util"; -import {NotifIndex, IndexedNotification, Association, GraphNotifDescription} from "~/types"; +import BaseApi from './base'; +import { StoreState } from '../store/type'; +import { dateToDa, decToUd } from '../lib/util'; +import { NotifIndex, IndexedNotification, Association, GraphNotifDescription } from '@urbit/api'; import { BigInteger } from 'big-integer'; -import {getParentIndex} from "../lib/notification"; +import { getParentIndex } from '../lib/notification'; export class HarkApi extends BaseApi { private harkAction(action: any): Promise { - return this.action("hark-store", "hark-action", action); + return this.action('hark-store', 'hark-action', action); } private graphHookAction(action: any) { - return this.action("hark-graph-hook", "hark-graph-hook-action", action); + return this.action('hark-graph-hook', 'hark-graph-hook-action', action); } private groupHookAction(action: any) { - return this.action("hark-group-hook", "hark-group-hook-action", action); + return this.action('hark-group-hook', 'hark-group-hook-action', action); } - private actOnNotification(frond: string, intTime: BigInteger, index: NotifIndex) { const time = decToUd(intTime.toString()); return this.harkAction({ @@ -74,12 +73,10 @@ export class HarkApi extends BaseApi { module: association.metadata.module, description, index: parent - } }, + } } }); } - - markEachAsRead(association: Association, parent: string, child: string, description: GraphNotifDescription, mod: string) { return this.harkAction({ 'read-each': { @@ -116,7 +113,7 @@ export class HarkApi extends BaseApi { mute(notif: IndexedNotification) { if('graph' in notif.index && 'graph' in notif.notification.contents) { const { index } = notif; - const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph) + const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph); if(!parentIndex) { return Promise.resolve(); } @@ -132,7 +129,7 @@ export class HarkApi extends BaseApi { unmute(notif: IndexedNotification) { if('graph' in notif.index && 'graph' in notif.notification.contents) { const { index } = notif; - const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph) + const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph); if(!parentIndex) { return Promise.resolve(); } @@ -147,7 +144,7 @@ export class HarkApi extends BaseApi { ignoreGroup(group: string) { return this.groupHookAction({ ignore: group - }) + }); } ignoreGraph(graph: string, index: string) { @@ -156,13 +153,13 @@ export class HarkApi extends BaseApi { graph, index } - }) + }); } listenGroup(group: string) { return this.groupHookAction({ listen: group - }) + }); } listenGraph(graph: string, index: string) { @@ -171,7 +168,7 @@ export class HarkApi extends BaseApi { graph, index } - }) + }); } async getMore(): Promise { @@ -183,16 +180,16 @@ export class HarkApi extends BaseApi { async getSubset(offset:number, count:number, isArchive: boolean) { const where = isArchive ? 'archive' : 'inbox'; - const data = await this.scry("hark-store", `/recent/${where}/${offset}/${count}`); + const data = await this.scry('hark-store', `/recent/${where}/${offset}/${count}`); this.store.handleEvent({ data }); } async getTimeSubset(start?: Date, end?: Date) { - const s = start ? dateToDa(start) : "-"; - const e = end ? dateToDa(end) : "-"; - const result = await this.scry("hark-hook", `/recent/${s}/${e}`); + const s = start ? dateToDa(start) : '-'; + const e = end ? dateToDa(end) : '-'; + const result = await this.scry('hark-hook', `/recent/${s}/${e}`); this.store.handleEvent({ - data: result, + data: result }); } } diff --git a/pkg/interface/src/logic/api/invite.ts b/pkg/interface/src/logic/api/invite.ts index 89a730768..dc390d12d 100644 --- a/pkg/interface/src/logic/api/invite.ts +++ b/pkg/interface/src/logic/api/invite.ts @@ -1,6 +1,6 @@ -import BaseApi from "./base"; -import { StoreState } from "../store/type"; -import { Serial, Path } from "~/types/noun"; +import BaseApi from './base'; +import { StoreState } from '../store/type'; +import { Serial, Path } from '@urbit/api'; export default class InviteApi extends BaseApi { accept(app: string, uid: Serial) { diff --git a/pkg/interface/src/logic/api/launch.ts b/pkg/interface/src/logic/api/launch.ts index ce0a09a1c..3bba3b3cc 100644 --- a/pkg/interface/src/logic/api/launch.ts +++ b/pkg/interface/src/logic/api/launch.ts @@ -2,7 +2,7 @@ import BaseApi from './base'; import { StoreState } from '../store/type'; export default class LaunchApi extends BaseApi { - add(name: string, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' }}) { + add(name: string, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' } }) { return this.launchAction({ add: { name, tile } }); } @@ -15,7 +15,7 @@ export default class LaunchApi extends BaseApi { } changeIsShown(name: string, isShown = true) { - return this.launchAction({ 'change-is-shown': { name, isShown }}); + return this.launchAction({ 'change-is-shown': { name, isShown } }); } weather(location: string) { diff --git a/pkg/interface/src/logic/api/local.ts b/pkg/interface/src/logic/api/local.ts index 8446f27ce..e5bad49a3 100644 --- a/pkg/interface/src/logic/api/local.ts +++ b/pkg/interface/src/logic/api/local.ts @@ -1,9 +1,9 @@ -import BaseApi from "./base"; -import { StoreState } from "../store/type"; +import BaseApi from './base'; +import { StoreState } from '../store/type'; export default class LocalApi extends BaseApi { getBaseHash() { - this.scry('file-server', '/clay/base/hash').then(baseHash => { + this.scry('file-server', '/clay/base/hash').then((baseHash) => { this.store.handleEvent({ data: { local: { baseHash } } }); }); } @@ -11,5 +11,4 @@ export default class LocalApi extends BaseApi { dehydrate() { this.store.dehydrate(); } - } diff --git a/pkg/interface/src/logic/api/metadata.ts b/pkg/interface/src/logic/api/metadata.ts index c4e20ae2f..c2d388dfc 100644 --- a/pkg/interface/src/logic/api/metadata.ts +++ b/pkg/interface/src/logic/api/metadata.ts @@ -1,12 +1,10 @@ import BaseApi from './base'; import { StoreState } from '../store/type'; -import { Path, Patp, Association, Metadata, MetadataUpdatePreview } from '~/types'; -import {uxToHex} from '../lib/util'; +import { Path, Patp, Association, Metadata, MetadataUpdatePreview } from '@urbit/api'; +import { uxToHex } from '../lib/util'; export default class MetadataApi extends BaseApi { - - metadataAdd(appName: string, resource: Path, group: Path, title: string, description: string, dateCreated: string, color: string, moduleName: string) { const creator = `~${this.ship}`; return this.metadataAction({ @@ -44,9 +42,9 @@ export default class MetadataApi extends BaseApi { } update(association: Association, newMetadata: Partial) { - const metadata = {...association.metadata, ...newMetadata }; + const metadata = { ...association.metadata, ...newMetadata }; metadata.color = uxToHex(metadata.color); - return this.metadataAction({ + return this.metadataAction({ add: { group: association.group, resource: { @@ -69,10 +67,10 @@ export default class MetadataApi extends BaseApi { } done = true; tempChannel.delete(); - reject(new Error("offline")) + reject(new Error('offline')); }, 15000); - tempChannel.subscribe(window.ship, "metadata-pull-hook", `/preview${group}`, + tempChannel.subscribe(window.ship, 'metadata-pull-hook', `/preview${group}`, (err) => { console.error(err); reject(err); @@ -88,24 +86,22 @@ export default class MetadataApi extends BaseApi { } else { done = true; tempChannel.delete(); - reject(new Error("no-permissions")); + reject(new Error('no-permissions')); } }, (quit) => { tempChannel.delete(); if(!done) { - reject(new Error("offline")) + reject(new Error('offline')); } }, (a) => { console.log(a); } ); - }) + }); } - - private metadataAction(data) { return this.action('metadata-push-hook', 'metadata-update', data); } diff --git a/pkg/interface/src/logic/api/s3.ts b/pkg/interface/src/logic/api/s3.ts index 834cc6ffe..1d775ef77 100644 --- a/pkg/interface/src/logic/api/s3.ts +++ b/pkg/interface/src/logic/api/s3.ts @@ -1,10 +1,8 @@ import BaseApi from './base'; import { StoreState } from '../store/type'; -import {S3Update} from '../../types/s3-update'; - +import { S3Update } from '../../types/s3-update'; export default class S3Api extends BaseApi { - setCurrentBucket(bucket: string) { return this.s3Action({ 'set-current-bucket': bucket }); } @@ -32,6 +30,5 @@ export default class S3Api extends BaseApi { private s3Action(data: any) { return this.action('s3-store', 's3-action', data); } - } diff --git a/pkg/interface/src/logic/api/settings.ts b/pkg/interface/src/logic/api/settings.ts index cd0cd736f..3f98d801c 100644 --- a/pkg/interface/src/logic/api/settings.ts +++ b/pkg/interface/src/logic/api/settings.ts @@ -1,13 +1,9 @@ import BaseApi from './base'; import { StoreState } from '../store/type'; -import { - SettingsUpdate, - SettingsData, - Key, +import { Key, Value, - Bucket, -} from '~/types/settings'; - + Bucket +} from '@urbit/api/settings'; export default class SettingsApi extends BaseApi { private storeAction(action: SettingsEvent): Promise { @@ -16,59 +12,59 @@ export default class SettingsApi extends BaseApi { putBucket(key: Key, bucket: Bucket) { this.storeAction({ - "put-bucket": { - "bucket-key": key, - "bucket": bucket, + 'put-bucket': { + 'bucket-key': key, + 'bucket': bucket } }); } delBucket(key: Key) { this.storeAction({ - "del-bucket": { - "bucket-key": key, + 'del-bucket': { + 'bucket-key': key } }); } putEntry(buc: Key, key: Key, val: Value) { return this.storeAction({ - "put-entry": { - "bucket-key": buc, - "entry-key": key, - "value": val, + 'put-entry': { + 'bucket-key': buc, + 'entry-key': key, + 'value': val } }); } delEntry(buc: Key, key: Key) { this.storeAction({ - "put-entry": { - "bucket-key": buc, - "entry-key": key, + 'put-entry': { + 'bucket-key': buc, + 'entry-key': key } }); } async getAll() { - const data = await this.scry("settings-store", "/all"); - this.store.handleEvent({data: {"settings-data": data.all}}); + const data = await this.scry('settings-store', '/all'); + this.store.handleEvent({ data: { 'settings-data': data.all } }); } async getBucket(bucket: Key) { const data = await this.scry('settings-store', `/bucket/${bucket}`); - this.store.handleEvent({data: {"settings-data": { - "bucket-key": bucket, - "bucket": data.bucket, - }}}); + this.store.handleEvent({ data: { 'settings-data': { + 'bucket-key': bucket, + 'bucket': data.bucket + } } }); } async getEntry(bucket: Key, entry: Key) { const data = await this.scry('settings-store', `/entry/${bucket}/${entry}`); - this.store.handleEvent({data: {"settings-data": { - "bucket-key": bucket, - "entry-key": entry, - "entry": data.entry, - }}}); + this.store.handleEvent({ data: { 'settings-data': { + 'bucket-key': bucket, + 'entry-key': entry, + 'entry': data.entry + } } }); } } diff --git a/pkg/interface/src/logic/lib/BigIntOrderedMap.ts b/pkg/interface/src/logic/lib/BigIntOrderedMap.ts index 069d52002..21fc1c8ca 100644 --- a/pkg/interface/src/logic/lib/BigIntOrderedMap.ts +++ b/pkg/interface/src/logic/lib/BigIntOrderedMap.ts @@ -1,4 +1,4 @@ -import bigInt, { BigInteger } from "big-integer"; +import bigInt, { BigInteger } from 'big-integer'; interface NonemptyNode { n: [BigInteger, V]; @@ -14,7 +14,7 @@ type MapNode = NonemptyNode | null; */ export class BigIntOrderedMap implements Iterable<[BigInteger, V]> { private root: MapNode = null; - size: number = 0; + size = 0; constructor(initial: [BigInteger, V][] = []) { initial.forEach(([key, val]) => { @@ -48,13 +48,12 @@ export class BigIntOrderedMap implements Iterable<[BigInteger, V]> { * Put an item by a key */ set(key: BigInteger, value: V): void { - const inner = (node: MapNode) => { if (!node) { return { n: [key, value], l: null, - r: null, + r: null }; } const [k] = node.n; @@ -62,22 +61,22 @@ export class BigIntOrderedMap implements Iterable<[BigInteger, V]> { this.size--; return { ...node, - n: [k, value], + n: [k, value] }; } if (key.gt(k)) { const l = inner(node.l); if (!l) { - throw new Error("invariant violation"); + throw new Error('invariant violation'); } return { ...node, - l, + l }; } const r = inner(node.r); if (!r) { - throw new Error("invariant violation"); + throw new Error('invariant violation'); } return { ...node, r }; @@ -133,8 +132,8 @@ export class BigIntOrderedMap implements Iterable<[BigInteger, V]> { bool, { ...node, - l, - }, + l + } ]; } @@ -143,8 +142,8 @@ export class BigIntOrderedMap implements Iterable<[BigInteger, V]> { bool, { ...node, - r, - }, + r + } ]; }; const [ret, newRoot] = inner(this.root); @@ -165,12 +164,12 @@ export class BigIntOrderedMap implements Iterable<[BigInteger, V]> { } return { ...node.l, - r: inner(node.r), + r: inner(node.r) }; }; return inner(nod); } - + peekLargest(): [BigInteger, V] | undefined { const inner = (node: MapNode) => { if(!node) { @@ -180,7 +179,7 @@ export class BigIntOrderedMap implements Iterable<[BigInteger, V]> { return inner(node.l); } return node.n; - } + }; return inner(this.root); } @@ -193,7 +192,7 @@ export class BigIntOrderedMap implements Iterable<[BigInteger, V]> { return inner(node.r); } return node.n; - } + }; return inner(this.root); } @@ -208,7 +207,7 @@ export class BigIntOrderedMap implements Iterable<[BigInteger, V]> { } [Symbol.iterator](): IterableIterator<[BigInteger, V]> { - let result: [BigInteger, V][] = []; + const result: [BigInteger, V][] = []; const inner = (node: MapNode) => { if (!node) { return; @@ -227,7 +226,7 @@ export class BigIntOrderedMap implements Iterable<[BigInteger, V]> { return { value: result[idx++], done: false }; } return { done: true, value: null }; - }, + } }; } } diff --git a/pkg/interface/src/logic/lib/OrderedMap.ts b/pkg/interface/src/logic/lib/OrderedMap.ts index d66346ff6..91ec52b78 100644 --- a/pkg/interface/src/logic/lib/OrderedMap.ts +++ b/pkg/interface/src/logic/lib/OrderedMap.ts @@ -1,7 +1,6 @@ export class OrderedMap extends Map implements Iterable<[number, V]> { - [Symbol.iterator](): IterableIterator<[number, V]> { const sorted = Array.from(super[Symbol.iterator]()).sort( ([a], [b]) => b - a @@ -15,7 +14,7 @@ export class OrderedMap extends Map } else { return { done: true, value: null }; } - }, + } }; } } diff --git a/pkg/interface/src/logic/lib/bigInt.ts b/pkg/interface/src/logic/lib/bigInt.ts index ecc6dda0a..01da28b45 100644 --- a/pkg/interface/src/logic/lib/bigInt.ts +++ b/pkg/interface/src/logic/lib/bigInt.ts @@ -1,4 +1,4 @@ -import bigInt, { BigInteger } from "big-integer"; +import bigInt, { BigInteger } from 'big-integer'; export function max(a: BigInteger, b: BigInteger) { return a.gt(b) ? a : b; diff --git a/pkg/interface/src/logic/lib/group.ts b/pkg/interface/src/logic/lib/group.ts index edc525d89..2eb81e015 100644 --- a/pkg/interface/src/logic/lib/group.ts +++ b/pkg/interface/src/logic/lib/group.ts @@ -1,7 +1,7 @@ -import _ from "lodash"; -import { roleTags, RoleTags, Group, Resource } from "~/types/group-update"; -import { PatpNoSig, Path } from "~/types/noun"; -import {deSig} from "./util"; +import _ from 'lodash'; +import { roleTags, RoleTags, Group, Resource } from '@urbit/api/groups'; +import { PatpNoSig, Path } from '@urbit/api'; +import { deSig } from './util'; export function roleForShip( group: Group, @@ -14,7 +14,7 @@ export function roleForShip( } export function resourceFromPath(path: Path): Resource { - const [, , ship, name] = path.split("/"); + const [, , ship, name] = path.split('/'); return { ship, name }; } @@ -25,7 +25,7 @@ export function makeResource(ship: string, name: string) { export function isWriter(group: Group, resource: string) { const writers: Set | undefined = _.get( group, - ["tags", "graph", resource, "writers"], + ['tags', 'graph', resource, 'writers'], undefined ); const admins = group?.tags?.role?.admin ?? new Set(); @@ -36,18 +36,18 @@ export function isWriter(group: Group, resource: string) { } } -export function isChannelAdmin(group: Group, resource: string, ship: string = `~${window.ship}`) { +export function isChannelAdmin(group: Group, resource: string, ship = `~${window.ship}`) { const role = roleForShip(group, ship.slice(1)); return ( isHost(resource, ship) || - role === "admin" || - role === "moderator" + role === 'admin' || + role === 'moderator' ); } -export function isHost(resource: string, ship: string = `~${window.ship}`) { - const [, , host] = resource.split("/"); +export function isHost(resource: string, ship = `~${window.ship}`) { + const [, , host] = resource.split('/'); return ship === host; } diff --git a/pkg/interface/src/logic/lib/hark.ts b/pkg/interface/src/logic/lib/hark.ts index ee5ecd761..b65a09603 100644 --- a/pkg/interface/src/logic/lib/hark.ts +++ b/pkg/interface/src/logic/lib/hark.ts @@ -1,6 +1,6 @@ -import bigInt, { BigInteger } from "big-integer"; -import f from "lodash/fp"; -import { Unreads } from "~/types"; +import bigInt, { BigInteger } from 'big-integer'; +import f from 'lodash/fp'; +import { Unreads } from '@urbit/api'; export function getLastSeen( unreads: Unreads, @@ -8,10 +8,10 @@ export function getLastSeen( index: string ): BigInteger | undefined { const lastSeenIdx = unreads.graph?.[path]?.[index]?.unreads; - if (!(typeof lastSeenIdx === "string")) { + if (!(typeof lastSeenIdx === 'string')) { return bigInt.zero; } - return f.flow(f.split("/"), f.last, (x) => (!!x ? bigInt(x) : undefined))( + return f.flow(f.split('/'), f.last, x => (x ? bigInt(x) : undefined))( lastSeenIdx ); } diff --git a/pkg/interface/src/logic/lib/notification.ts b/pkg/interface/src/logic/lib/notification.ts index 87288d83d..bd2979f40 100644 --- a/pkg/interface/src/logic/lib/notification.ts +++ b/pkg/interface/src/logic/lib/notification.ts @@ -1,19 +1,19 @@ -import { GraphNotifIndex, GraphNotificationContents } from "~/types"; +import { GraphNotifIndex, GraphNotificationContents } from '@urbit/api'; export function getParentIndex( idx: GraphNotifIndex, contents: GraphNotificationContents ) { - const origIndex = contents[0].index.slice(1).split("/"); - const ret = (i: string[]) => `/${i.join("/")}`; + const origIndex = contents[0].index.slice(1).split('/'); + const ret = (i: string[]) => `/${i.join('/')}`; switch (idx.description) { - case "link": - return "/"; - case "comment": + case 'link': + return '/'; + case 'comment': return ret(origIndex.slice(0, 1)); - case "note": - return "/"; - case "mention": + case 'note': + return '/'; + case 'mention': return undefined; default: return undefined; diff --git a/pkg/interface/src/logic/lib/post.ts b/pkg/interface/src/logic/lib/post.ts index 1a311de55..397563283 100644 --- a/pkg/interface/src/logic/lib/post.ts +++ b/pkg/interface/src/logic/lib/post.ts @@ -1,4 +1,4 @@ -import { Post, GraphNode } from "~/types"; +import { Post, GraphNode } from '@urbit/api'; export const buntPost = (): Post => ({ author: '', @@ -10,7 +10,7 @@ export const buntPost = (): Post => ({ }); export function makeNodeMap(posts: Post[]): Record { - let nodes = {}; + const nodes = {}; posts.forEach((p) => { nodes[p.index] = { children: { empty: null }, post: p }; }); diff --git a/pkg/interface/src/logic/lib/publish.ts b/pkg/interface/src/logic/lib/publish.ts index 10c64aecf..ce616b3cf 100644 --- a/pkg/interface/src/logic/lib/publish.ts +++ b/pkg/interface/src/logic/lib/publish.ts @@ -1,8 +1,8 @@ -import { Post, GraphNode, TextContent, Graph, NodeMap } from "~/types"; +import { Post, GraphNode, TextContent, Graph, NodeMap } from '@urbit/api'; import { buntPost } from '~/logic/lib/post'; -import { unixToDa } from "~/logic/lib/util"; -import {BigIntOrderedMap} from "./BigIntOrderedMap"; -import bigInt, {BigInteger} from 'big-integer'; +import { unixToDa } from '~/logic/lib/util'; +import { BigIntOrderedMap } from './BigIntOrderedMap'; +import bigInt, { BigInteger } from 'big-integer'; export function newPost( title: string, @@ -12,20 +12,20 @@ export function newPost( const nowDa = unixToDa(now); const root: Post = { author: `~${window.ship}`, - index: "/" + nowDa.toString(), - "time-sent": now, + index: '/' + nowDa.toString(), + 'time-sent': now, contents: [], hash: null, - signatures: [], + signatures: [] }; - const revContainer: Post = { ...root, index: root.index + "/1" }; - const commentsContainer = { ...root, index: root.index + "/2" }; + const revContainer: Post = { ...root, index: root.index + '/1' }; + const commentsContainer = { ...root, index: root.index + '/2' }; const firstRevision: Post = { ...revContainer, - index: revContainer.index + "/1", - contents: [{ text: title }, { text: body }], + index: revContainer.index + '/1', + contents: [{ text: title }, { text: body }] }; const nodes = { @@ -37,16 +37,16 @@ export function newPost( children: { 1: { post: firstRevision, - children: null, - }, - }, + children: null + } + } }, 2: { post: commentsContainer, children: null - }, - }, - }, + } + } + } }; return [nowDa, nodes]; @@ -57,15 +57,15 @@ export function editPost(rev: number, noteId: BigInteger, title: string, body: s const newRev: Post = { author: `~${window.ship}`, index: `/${noteId.toString()}/1/${rev}`, - "time-sent": now, + 'time-sent': now, contents: [{ text: title }, { text: body }], hash: null, - signatures: [], + signatures: [] }; const nodes = { [newRev.index]: { post: newRev, - children: null + children: null } }; @@ -74,7 +74,7 @@ export function editPost(rev: number, noteId: BigInteger, title: string, body: s export function getLatestRevision(node: GraphNode): [number, string, string, Post] { const revs = node.children.get(bigInt(1)); - const empty = [1, "", "", buntPost()] as [number, string, string, Post]; + const empty = [1, '', '', buntPost()] as [number, string, string, Post]; if(!revs) { return empty; } @@ -98,17 +98,16 @@ export function getLatestCommentRevision(node: GraphNode): [number, Post] { return [revNum.toJSNumber(), rev.post]; } - export function getComments(node: GraphNode): GraphNode { const comments = node.children.get(bigInt(2)); if(!comments) { - return { post: buntPost(), children: new BigIntOrderedMap() } + return { post: buntPost(), children: new BigIntOrderedMap() }; } return comments; } export function getSnippet(body: string) { const start = body.slice(0, body.indexOf('\n', 2)); - return (start === body || start.startsWith("![")) ? start : `${start}...`; + return (start === body || start.startsWith('![')) ? start : `${start}...`; } diff --git a/pkg/interface/src/logic/lib/relativePosition.tsx b/pkg/interface/src/logic/lib/relativePosition.tsx index 8ad1618e5..b2c4b751e 100644 --- a/pkg/interface/src/logic/lib/relativePosition.tsx +++ b/pkg/interface/src/logic/lib/relativePosition.tsx @@ -1,16 +1,16 @@ -import _ from "lodash"; +import _ from 'lodash'; -export const alignY = ["top", "bottom"] as const; +export const alignY = ['top', 'bottom'] as const; export type AlignY = typeof alignY[number]; -export const alignX = ["left", "right"] as const; +export const alignX = ['left', 'right'] as const; export type AlignX = typeof alignX[number]; export function getRelativePosition( relativeTo: HTMLElement | null, alignX: AlignX | AlignX[], alignY: AlignY | AlignY[], - offsetX: number = 0, - offsetY: number = 0 + offsetX = 0, + offsetY = 0 ) { const rect = relativeTo?.getBoundingClientRect(); if (!rect) { @@ -20,7 +20,7 @@ export function getRelativePosition( top: rect.top - offsetY, left: rect.left - offsetX, bottom: document.documentElement.clientHeight - rect.bottom - offsetY, - right: document.documentElement.clientWidth - rect.right - offsetX, + right: document.documentElement.clientWidth - rect.right - offsetX }; const alignXArr = _.isArray(alignX) ? alignX : [alignX]; const alignYArr = _.isArray(alignY) ? alignY : [alignY]; @@ -34,7 +34,7 @@ export function getRelativePosition( [...Array(idx), `${bounds[a]}px`], acc[a] || [], (a, b) => a || b || null - ), + ) }), {} ), @@ -46,10 +46,10 @@ export function getRelativePosition( [...Array(idx), `${bounds[a]}px`], acc[a] || [], (a, b) => a || b || null - ), + ) }), {} - ), + ) } as Record; } diff --git a/pkg/interface/src/logic/lib/tutorialModal.ts b/pkg/interface/src/logic/lib/tutorialModal.ts index ef1d753b1..53e759838 100644 --- a/pkg/interface/src/logic/lib/tutorialModal.ts +++ b/pkg/interface/src/logic/lib/tutorialModal.ts @@ -1,6 +1,6 @@ -import { TutorialProgress, Associations } from "~/types"; -import { AlignX, AlignY } from "~/logic/lib/relativePosition"; -import { Direction } from "~/views/components/Triangle"; +import { TutorialProgress, Associations } from '@urbit/api'; +import { AlignX, AlignY } from '~/logic/lib/relativePosition'; +import { Direction } from '~/views/components/Triangle'; export const MODAL_WIDTH = 256; export const MODAL_HEIGHT = 256; @@ -43,7 +43,7 @@ export const getTrianglePosition = (dir: Direction) => { return { top: midY, left: '-32px' - } + }; case 'North': return { top: '-32px', @@ -55,117 +55,117 @@ export const getTrianglePosition = (dir: Direction) => { left: midX }; } -} +}; export const progressDetails: Record = { hidden: {} as any, exit: {} as any, done: { - title: "End", + title: 'End', description: - "This tutorial is finished. Would you like to leave Beginner Island?", - url: "/", - alignX: "right", - alignY: "top", + 'This tutorial is finished. Would you like to leave Beginner Island?', + url: '/', + alignX: 'right', + alignY: 'top', offsetX: MODAL_WIDTH + 8, - offsetY: 0, + offsetY: 0 }, start: { - title: "New Group added", + title: 'New Group added', description: - "We just added you to the Beginner island group to show you around. This group is public, but other groups can be private", - url: "/", - alignX: "right", - alignY: "top", - arrow: "West", + 'We just added you to the Beginner island group to show you around. This group is public, but other groups can be private', + url: '/', + alignX: 'right', + alignY: 'top', + arrow: 'West', offsetX: MODAL_WIDTH + 24, - offsetY: 64, + offsetY: 64 }, - "group-desc": { - title: "What's a group", + 'group-desc': { + title: 'What\'s a group', description: - "A group contains members and tends to be centered around a topic or multiple topics.", + 'A group contains members and tends to be centered around a topic or multiple topics.', url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`, - alignX: "left", - alignY: "top", - arrow: "East", + alignX: 'left', + alignY: 'top', + arrow: 'East', offsetX: MODAL_WIDTH + 24, - offsetY: MODAL_HEIGHT / 2 - 8, + offsetY: MODAL_HEIGHT / 2 - 8 }, channels: { - title: "Channels", + title: 'Channels', description: - "Inside a group you have three types of Channels: Chat, Collection, or Notebook. Mix and match these depending on your group context!", + 'Inside a group you have three types of Channels: Chat, Collection, or Notebook. Mix and match these depending on your group context!', url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`, - alignY: "top", - alignX: "right", - arrow: "West", + alignY: 'top', + alignX: 'right', + arrow: 'West', offsetX: MODAL_WIDTH + 24, - offsetY: -8, + offsetY: -8 }, chat: { - title: "Chat", + title: 'Chat', description: - "Chat channels are for messaging within your group. Direct Messages are also supported, and are accessible from the “DMs” tile on the homescreen", + 'Chat channels are for messaging within your group. Direct Messages are also supported, and are accessible from the “DMs” tile on the homescreen', url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/chat/ship/${TUTORIAL_HOST}/${TUTORIAL_CHAT}`, - alignY: "top", - arrow: "North", - alignX: "right", + alignY: 'top', + arrow: 'North', + alignX: 'right', offsetY: -56, - offsetX: -8, + offsetX: -8 }, link: { - title: "Collection", + title: 'Collection', description: - "A collection is where you can share and view links, images, and other media within your group. Every item in a Collection can have it’s own comment thread.", + 'A collection is where you can share and view links, images, and other media within your group. Every item in a Collection can have it’s own comment thread.', url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/link/ship/${TUTORIAL_HOST}/${TUTORIAL_LINKS}`, - alignY: "top", - alignX: "right", - arrow: "North", + alignY: 'top', + alignX: 'right', + arrow: 'North', offsetX: -8, - offsetY: -56, + offsetY: -56 }, publish: { - title: "Notebook", + title: 'Notebook', description: - "Notebooks are for creating long-form content within your group. Use markdown to create rich posts with headers, lists and images.", + 'Notebooks are for creating long-form content within your group. Use markdown to create rich posts with headers, lists and images.', url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/publish/ship/${TUTORIAL_HOST}/${TUTORIAL_BOOK}`, - alignY: "top", - alignX: "right", - arrow: "North", + alignY: 'top', + alignX: 'right', + arrow: 'North', offsetX: -8, - offsetY: -56, + offsetY: -56 }, notifications: { - title: "Notifications", - description: "You will get updates from subscribed channels and mentions here. You can access Notifications through Leap.", + title: 'Notifications', + description: 'You will get updates from subscribed channels and mentions here. You can access Notifications through Leap.', url: '/~notifications', - alignY: "top", - alignX: "left", - arrow: "North", + alignY: 'top', + alignX: 'left', + arrow: 'North', offsetX: (MODAL_WIDTH / 2) - 16, - offsetY: -48, + offsetY: -48 }, profile: { - title: "Profile", + title: 'Profile', description: - "Your profile is customizable and can be shared with other ships. Enter as much or as little information as you’d like.", + 'Your profile is customizable and can be shared with other ships. Enter as much or as little information as you’d like.', url: `/~profile/~${window.ship}`, - alignY: "top", - alignX: "right", - arrow: "South", + alignY: 'top', + alignX: 'right', + arrow: 'South', offsetX: -300 + MODAL_WIDTH / 2, - offsetY: -120 + MODAL_HEIGHT / 2, + offsetY: -120 + MODAL_HEIGHT / 2 }, leap: { - title: "Leap", + title: 'Leap', description: - "Leap allows you to go to a specific channel, message, collection, profile or group simply by typing in a command or selecting a shortcut from the dropdown menu.", + 'Leap allows you to go to a specific channel, message, collection, profile or group simply by typing in a command or selecting a shortcut from the dropdown menu.', url: `/~profile/~${window.ship}`, - alignY: "top", - alignX: "left", - arrow: "North", + alignY: 'top', + alignX: 'left', + arrow: 'North', offsetX: 0.3 *MODAL_HEIGHT, - offsetY: -48, - }, + offsetY: -48 + } }; diff --git a/pkg/interface/src/logic/lib/useDrag.ts b/pkg/interface/src/logic/lib/useDrag.ts index e2aa496a8..fb17928e9 100644 --- a/pkg/interface/src/logic/lib/useDrag.ts +++ b/pkg/interface/src/logic/lib/useDrag.ts @@ -1,4 +1,4 @@ -import { useState, useCallback, useMemo, useEffect } from "react"; +import { useState, useCallback, useMemo, useEffect } from 'react'; function validateDragEvent(e: DragEvent): FileList | File[] | true | null { const files: File[] = []; @@ -8,8 +8,8 @@ function validateDragEvent(e: DragEvent): FileList | File[] | true | null { } if (e.dataTransfer?.items) { Array.from(e.dataTransfer.items || []) - .filter((i) => i.kind === 'file') - .forEach(f => { + .filter(i => i.kind === 'file') + .forEach((f) => { valid = true; // Valid if file exists, but on DragOver, won't reveal its contents for security const data = f.getAsFile(); if (data) { @@ -89,14 +89,14 @@ export function useFileDrag(dragged: (f: FileList | File[], e: DragEvent) => voi document.body.addEventListener('mouseout', mouseleave); return () => { document.body.removeEventListener('mouseout', mouseleave); - } + }; }, []); const bind = { onDragLeave, onDragOver, onDrop, - onDragEnter, + onDragEnter }; return { bind, dragging }; diff --git a/pkg/interface/src/logic/lib/useDropdown.ts b/pkg/interface/src/logic/lib/useDropdown.ts index 0e0b37e0d..98a7359f3 100644 --- a/pkg/interface/src/logic/lib/useDropdown.ts +++ b/pkg/interface/src/logic/lib/useDropdown.ts @@ -1,4 +1,4 @@ -import { useState, useEffect, useMemo, useCallback } from "react"; +import { useState, useEffect, useMemo, useCallback } from 'react'; export function useDropdown( candidates: C[], @@ -12,10 +12,10 @@ export function useDropdown( (s: string) => { const exactMatch = isExact(s); const exact = exactMatch ? [exactMatch] : []; - const opts = [...new Set([...exact, ...candidates.filter((c) => searchPred(s, c))])]; + const opts = [...new Set([...exact, ...candidates.filter(c => searchPred(s, c))])]; setOptions(opts); if (selected) { - const idx = opts.findIndex((c) => key(c) === key(selected)); + const idx = opts.findIndex(c => key(c) === key(selected)); if (idx < 0) { setSelected(undefined); } @@ -29,9 +29,11 @@ export function useDropdown( const select = (idx: number) => { setSelected(options[idx]); }; - if(!selected) { select(0); return false; } + if(!selected) { + select(0); return false; +} - const idx = options.findIndex((c) => key(c) === key(selected)); + const idx = options.findIndex(c => key(c) === key(selected)); if ( idx === -1 || (options.length - 1 <= idx && !backward) @@ -55,6 +57,6 @@ export function useDropdown( back, search, selected, - options, + options }; } diff --git a/pkg/interface/src/logic/lib/useHashLink.ts b/pkg/interface/src/logic/lib/useHashLink.ts index 77c91be2e..7d93ce5d4 100644 --- a/pkg/interface/src/logic/lib/useHashLink.ts +++ b/pkg/interface/src/logic/lib/useHashLink.ts @@ -1,6 +1,5 @@ import { useEffect } from 'react'; -import {useLocation} from "react-router-dom"; - +import { useLocation } from 'react-router-dom'; export function useHashLink() { const location = useLocation(); @@ -10,8 +9,5 @@ export function useHashLink() { return; } document.querySelector(location.hash)?.scrollIntoView({ behavior: 'smooth', block: 'start' }); - }, [location.hash]); - - } diff --git a/pkg/interface/src/logic/lib/useLazyScroll.ts b/pkg/interface/src/logic/lib/useLazyScroll.ts index bd1c31a32..03a3ce432 100644 --- a/pkg/interface/src/logic/lib/useLazyScroll.ts +++ b/pkg/interface/src/logic/lib/useLazyScroll.ts @@ -1,6 +1,6 @@ -import { useEffect, RefObject, useRef, useState } from "react"; -import _ from "lodash"; -import usePreviousValue from "./usePreviousValue"; +import { useEffect, RefObject, useRef, useState } from 'react'; +import _ from 'lodash'; +import usePreviousValue from './usePreviousValue'; export function distanceToBottom(el: HTMLElement) { const { scrollTop, scrollHeight, clientHeight } = el; @@ -40,7 +40,6 @@ export function useLazyScroll( } }, [count]); - useEffect(() => { if (!ref.current) { return; @@ -54,13 +53,12 @@ export function useLazyScroll( loadUntil(el); }; - ref.current.addEventListener("scroll", onScroll, { passive: true }); + ref.current.addEventListener('scroll', onScroll, { passive: true }); return () => { - ref.current?.removeEventListener("scroll", onScroll); + ref.current?.removeEventListener('scroll', onScroll); }; }, [ref?.current, count]); - return { isDone, isLoading }; } diff --git a/pkg/interface/src/logic/lib/useLocalStorageState.ts b/pkg/interface/src/logic/lib/useLocalStorageState.ts index b81214a67..13de71afe 100644 --- a/pkg/interface/src/logic/lib/useLocalStorageState.ts +++ b/pkg/interface/src/logic/lib/useLocalStorageState.ts @@ -1,4 +1,4 @@ -import { useState, useCallback, useEffect } from "react"; +import { useState, useCallback, useEffect } from 'react'; function retrieve(key: string, initial: T): T { const s = localStorage.getItem(key); @@ -25,7 +25,7 @@ export function useLocalStorageState(key: string, initial: T) { const setState = useCallback( (s: SetState) => { - const updated = typeof s === "function" ? s(state) : s; + const updated = typeof s === 'function' ? s(state) : s; _setState(updated); localStorage.setItem(key, JSON.stringify(updated)); }, diff --git a/pkg/interface/src/logic/lib/useModal.tsx b/pkg/interface/src/logic/lib/useModal.tsx index c7cb9be7d..3d19343cd 100644 --- a/pkg/interface/src/logic/lib/useModal.tsx +++ b/pkg/interface/src/logic/lib/useModal.tsx @@ -5,15 +5,15 @@ import React, { SyntheticEvent, useMemo, useEffect, - useRef, -} from "react"; + useRef +} from 'react'; -import { Box } from "@tlon/indigo-react"; -import { useOutsideClick } from "./useOutsideClick"; -import { ModalOverlay } from "~/views/components/ModalOverlay"; -import {Portal} from "~/views/components/Portal"; -import {ModalPortal} from "~/views/components/ModalPortal"; -import {PropFunc} from "~/types"; +import { Box } from '@tlon/indigo-react'; +import { useOutsideClick } from './useOutsideClick'; +import { ModalOverlay } from '~/views/components/ModalOverlay'; +import { Portal } from '~/views/components/Portal'; +import { ModalPortal } from '~/views/components/ModalPortal'; +import { PropFunc } from '@urbit/api'; type ModalFunc = (dismiss: () => void) => JSX.Element; interface UseModalProps { @@ -42,7 +42,7 @@ export function useModal(props: UseModalProps & PropFunc): UseModalR () => !modalShown ? null - : typeof modal === "function" + : typeof modal === 'function' ? modal(dismiss) : modal, [modalShown, modal, dismiss] @@ -59,7 +59,7 @@ export function useModal(props: UseModalProps & PropFunc): UseModalR bg="white" borderRadius={2} border={[0, 1]} - borderColor={["washedGray", "washedGray"]} + borderColor={['washedGray', 'washedGray']} display="flex" alignItems="stretch" flexDirection="column" @@ -76,6 +76,6 @@ export function useModal(props: UseModalProps & PropFunc): UseModalR return { showModal, - modal: modalComponent, + modal: modalComponent }; } diff --git a/pkg/interface/src/logic/lib/useOutsideClick.ts b/pkg/interface/src/logic/lib/useOutsideClick.ts index 4d4f95ca6..481fa2a90 100644 --- a/pkg/interface/src/logic/lib/useOutsideClick.ts +++ b/pkg/interface/src/logic/lib/useOutsideClick.ts @@ -1,8 +1,8 @@ -import { useEffect, RefObject } from "react"; +import { useEffect, RefObject } from 'react'; export function useOutsideClick( ref: RefObject, - onClick: () => void, + onClick: () => void ) { useEffect(() => { function handleClick(event: MouseEvent) { @@ -16,17 +16,16 @@ export function useOutsideClick( } function handleKeyDown(ev) { - if(ev.key === "Escape") { + if(ev.key === 'Escape') { onClick(); - } } - document.addEventListener("mousedown", handleClick); - document.addEventListener("keydown", handleKeyDown); + document.addEventListener('mousedown', handleClick); + document.addEventListener('keydown', handleKeyDown); return () => { - document.removeEventListener("mousedown", handleClick); - document.removeEventListener("keydown", handleKeyDown); + document.removeEventListener('mousedown', handleClick); + document.removeEventListener('keydown', handleKeyDown); }; }, [ref.current, onClick]); } diff --git a/pkg/interface/src/logic/lib/usePreviousValue.ts b/pkg/interface/src/logic/lib/usePreviousValue.ts index e24097810..109f9ffa8 100644 --- a/pkg/interface/src/logic/lib/usePreviousValue.ts +++ b/pkg/interface/src/logic/lib/usePreviousValue.ts @@ -1,7 +1,5 @@ -import { useRef } from "react"; -import { Primitive } from "~/types"; - - +import { useRef } from 'react'; +import { Primitive } from '@urbit/api'; export default function usePreviousValue(value: T): T { const prev = useRef(null); diff --git a/pkg/interface/src/logic/lib/useQuery.ts b/pkg/interface/src/logic/lib/useQuery.ts index 0957ca098..735060a2d 100644 --- a/pkg/interface/src/logic/lib/useQuery.ts +++ b/pkg/interface/src/logic/lib/useQuery.ts @@ -1,5 +1,5 @@ -import { useMemo, useCallback } from "react"; -import { useLocation } from "react-router-dom"; +import { useMemo, useCallback } from 'react'; +import { useLocation } from 'react-router-dom'; import _ from 'lodash'; export function useQuery() { @@ -25,6 +25,6 @@ export function useQuery() { return { query, - appendQuery, + appendQuery }; } diff --git a/pkg/interface/src/logic/lib/useS3.ts b/pkg/interface/src/logic/lib/useS3.ts index 0717d52f7..418e4594f 100644 --- a/pkg/interface/src/logic/lib/useS3.ts +++ b/pkg/interface/src/logic/lib/useS3.ts @@ -1,7 +1,7 @@ -import { useCallback, useMemo, useEffect, useRef, useState } from "react"; -import { S3State } from "../../types/s3-update"; -import S3 from "aws-sdk/clients/s3"; -import { dateToDa, deSig } from "./util"; +import { useCallback, useMemo, useEffect, useRef, useState } from 'react'; +import { S3State } from '../../types/s3-update'; +import S3 from 'aws-sdk/clients/s3'; +import { dateToDa, deSig } from './util'; export interface IuseS3 { canUpload: boolean; @@ -28,14 +28,14 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => { const canUpload = useMemo( () => - (client && s3.credentials && s3.configuration.currentBucket !== "") || false, + (client && s3.credentials && s3.configuration.currentBucket !== '') || false, [s3.credentials, s3.configuration.currentBucket, client] ); const upload = useCallback( async (file: File, bucket: string) => { if (!client.current) { - throw new Error("S3 not ready"); + throw new Error('S3 not ready'); } const fileParts = file.name.split('.'); @@ -47,8 +47,8 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => { Bucket: bucket, Key: `${window.ship}/${timestamp}-${fileName}.${fileExtension}`, Body: file, - ACL: "public-read", - ContentType: file.type, + ACL: 'public-read', + ContentType: file.type }; setUploading(true); @@ -63,8 +63,8 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => { ); const uploadDefault = useCallback(async (file: File) => { - if (s3.configuration.currentBucket === "") { - throw new Error("current bucket not set"); + if (s3.configuration.currentBucket === '') { + throw new Error('current bucket not set'); } return upload(file, s3.configuration.currentBucket); }, [s3]); @@ -84,11 +84,10 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => { } uploadDefault(files[0]).then(resolve); document.body.removeChild(fileSelector); - }) + }); document.body.appendChild(fileSelector); fileSelector.click(); - }) - + }); }, [uploadDefault] ); @@ -96,4 +95,4 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => { return { canUpload, upload, uploadDefault, uploading, promptUpload }; }; -export default useS3; \ No newline at end of file +export default useS3; diff --git a/pkg/interface/src/logic/lib/useStatelessAsyncClickable.ts b/pkg/interface/src/logic/lib/useStatelessAsyncClickable.ts index b92fdaa03..154f903ba 100644 --- a/pkg/interface/src/logic/lib/useStatelessAsyncClickable.ts +++ b/pkg/interface/src/logic/lib/useStatelessAsyncClickable.ts @@ -1,23 +1,23 @@ -import { MouseEvent, useCallback, useState, useEffect } from "react"; -export type AsyncClickableState = "waiting" | "error" | "loading" | "success"; +import { MouseEvent, useCallback, useState, useEffect } from 'react'; +export type AsyncClickableState = 'waiting' | 'error' | 'loading' | 'success'; export function useStatelessAsyncClickable( onClick: (e: MouseEvent) => Promise, name: string ) { - const [state, setState] = useState("waiting"); + const [state, setState] = useState('waiting'); const handleClick = useCallback( async (e: MouseEvent) => { try { - setState("loading"); + setState('loading'); await onClick(e); - setState("success"); + setState('success'); } catch (e) { console.error(e); - setState("error"); + setState('error'); } finally { setTimeout(() => { - setState("waiting"); + setState('waiting'); }, 3000); } }, @@ -26,7 +26,7 @@ export function useStatelessAsyncClickable( // When name changes, reset button useEffect(() => { - setState("waiting"); + setState('waiting'); }, [name]); return { buttonState: state, onClick: handleClick }; diff --git a/pkg/interface/src/logic/lib/useWaitForProps.ts b/pkg/interface/src/logic/lib/useWaitForProps.ts index 80e92e92a..8a81b0b0a 100644 --- a/pkg/interface/src/logic/lib/useWaitForProps.ts +++ b/pkg/interface/src/logic/lib/useWaitForProps.ts @@ -1,12 +1,11 @@ import { useState, useEffect, useCallback } from 'react'; - -export function useWaitForProps

(props: P, timeout: number = 0) { +export function useWaitForProps

(props: P, timeout = 0) { const [resolve, setResolve] = useState<() => void>(() => () => {}); const [ready, setReady] = useState<(p: P) => boolean | undefined>(); useEffect(() => { - if (typeof ready === "function" && ready(props)) { + if (typeof ready === 'function' && ready(props)) { resolve(); } }, [props, ready, resolve]); @@ -26,7 +25,7 @@ export function useWaitForProps

(props: P, timeout: number = 0) { setResolve(() => resolve); if(timeout > 0) { setTimeout(() => { - reject(new Error("Timed out")); + reject(new Error('Timed out')); }, timeout); } }); diff --git a/pkg/interface/src/logic/lib/util.ts b/pkg/interface/src/logic/lib/util.ts index 670c83d43..8cc6969d9 100644 --- a/pkg/interface/src/logic/lib/util.ts +++ b/pkg/interface/src/logic/lib/util.ts @@ -1,27 +1,27 @@ import { useEffect, useState } from 'react'; -import _ from "lodash"; -import f, { memoize } from "lodash/fp"; -import bigInt, { BigInteger } from "big-integer"; -import { Contact } from '~/types'; +import _ from 'lodash'; +import f, { memoize } from 'lodash/fp'; +import bigInt, { BigInteger } from 'big-integer'; +import { Contact } from '@urbit/api'; import useLocalState from '../state/local'; export const MOBILE_BROWSER_REGEX = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i; export const MOMENT_CALENDAR_DATE = { - sameDay: "[Today]", - nextDay: "[Tomorrow]", - nextWeek: "dddd", - lastDay: "[Yesterday]", - lastWeek: "[Last] dddd", - sameElse: "~YYYY.M.D", + sameDay: '[Today]', + nextDay: '[Tomorrow]', + nextWeek: 'dddd', + lastDay: '[Yesterday]', + lastWeek: '[Last] dddd', + sameElse: '~YYYY.M.D' }; export const getModuleIcon = (mod: string) => { - if (mod === "link") { - return "Collection"; + if (mod === 'link') { + return 'Collection'; } return _.capitalize(mod); -} +}; export function wait(ms: number) { return new Promise((resolve, reject) => { @@ -37,8 +37,8 @@ export function parentPath(path: string) { return _.dropRight(path.split('/'), 1).join('/'); } -const DA_UNIX_EPOCH = bigInt("170141184475152167957503069145530368000"); // `@ud` ~1970.1.1 -const DA_SECOND = bigInt("18446744073709551616"); // `@ud` ~s1 +const DA_UNIX_EPOCH = bigInt('170141184475152167957503069145530368000'); // `@ud` ~1970.1.1 +const DA_SECOND = bigInt('18446744073709551616'); // `@ud` ~s1 export function daToUnix(da: BigInteger) { // ported from +time:enjs:format in hoon.hoon const offset = DA_SECOND.divide(bigInt(2000)); @@ -59,20 +59,20 @@ export function makePatDa(patda: string) { } export function udToDec(ud: string): string { - return ud.replace(/\./g, ""); + return ud.replace(/\./g, ''); } export function decToUd(str: string): string { return _.trimStart( f.flow( - f.split(""), + f.split(''), f.reverse, f.chunk(3), - f.map(f.flow(f.reverse, f.join(""))), + f.map(f.flow(f.reverse, f.join(''))), f.reverse, - f.join(".") + f.join('.') )(str), - "0." + '0.' ); } @@ -86,12 +86,12 @@ export function clamp(x: number, min: number, max: number) { // color is a #000000 color export function adjustHex(color: string, amount: number): string { return f.flow( - f.split(""), + f.split(''), f.chunk(2), // get RGB channels - f.map((c) => parseInt(c.join(""), 16)), // as hex - f.map((c) => clamp(c + amount, 0, 255).toString(16)), // adjust - f.join(""), - (res) => `#${res}` //format + f.map(c => parseInt(c.join(''), 16)), // as hex + f.map(c => clamp(c + amount, 0, 255).toString(16)), // adjust + f.join(''), + res => `#${res}` // format )(color.slice(1)); } @@ -101,12 +101,12 @@ export function resourceAsPath(resource: any) { } export function uuid() { - let str = "0v"; - str += Math.ceil(Math.random() * 8) + "."; + let str = '0v'; + str += Math.ceil(Math.random() * 8) + '.'; for (let i = 0; i < 5; i++) { let _str = Math.ceil(Math.random() * 10000000).toString(32); - _str = ("00000" + _str).substr(-5, 5); - str += _str + "."; + _str = ('00000' + _str).substr(-5, 5); + str += _str + '.'; } return str.slice(0, -1); @@ -120,11 +120,11 @@ export function uuid() { */ export function daToDate(st: string) { const dub = function (n: string) { - return parseInt(n) < 10 ? "0" + parseInt(n) : n.toString(); + return parseInt(n) < 10 ? '0' + parseInt(n) : n.toString(); }; - const da = st.split(".."); - const bigEnd = da[0].split("."); - const lilEnd = da[1].split("."); + const da = st.split('..'); + const bigEnd = da[0].split('.'); + const lilEnd = da[1].split('.'); const ds = `${bigEnd[0].slice(1)}-${dub(bigEnd[1])}-${dub(bigEnd[2])}T${dub( lilEnd[0] )}:${dub(lilEnd[1])}:${dub(lilEnd[2])}Z`; @@ -138,9 +138,9 @@ export function daToDate(st: string) { ~2018.7.17..23.15.09..5be5 // urbit @da */ -export function dateToDa(d: Date, mil: boolean = false) { +export function dateToDa(d: Date, mil = false) { const fil = function (n: number) { - return n >= 10 ? n : "0" + n; + return n >= 10 ? n : '0' + n; }; return ( `~${d.getUTCFullYear()}.` + @@ -149,7 +149,7 @@ export function dateToDa(d: Date, mil: boolean = false) { `${fil(d.getUTCHours())}.` + `${fil(d.getUTCMinutes())}.` + `${fil(d.getUTCSeconds())}` + - `${mil ? "..0000" : ""}` + `${mil ? '..0000' : ''}` ); } @@ -157,16 +157,16 @@ export function deSig(ship: string) { if (!ship) { return null; } - return ship.replace("~", ""); + return ship.replace('~', ''); } export function uxToHex(ux: string) { - if (ux.length > 2 && ux.substr(0, 2) === "0x") { - const value = ux.substr(2).replace(".", "").padStart(6, "0"); + if (ux.length > 2 && ux.substr(0, 2) === '0x') { + const value = ux.substr(2).replace('.', '').padStart(6, '0'); return value; } - const value = ux.replace(".", "").padStart(6, "0"); + const value = ux.replace('.', '').padStart(6, '0'); return value; } @@ -187,13 +187,13 @@ export function writeText(str: string) { let success = false; function listener(e) { - e.clipboardData.setData("text/plain", str); + e.clipboardData.setData('text/plain', str); e.preventDefault(); success = true; } - document.addEventListener("copy", listener); - document.execCommand("copy"); - document.removeEventListener("copy", listener); + document.addEventListener('copy', listener); + document.execCommand('copy'); + document.removeEventListener('copy', listener); document?.getSelection()?.removeAllRanges(); @@ -206,21 +206,21 @@ export function writeText(str: string) { // trim patps to match dojo, chat-cli export function cite(ship: string) { let patp = ship, - shortened = ""; - if (patp === null || patp === "") { + shortened = ''; + if (patp === null || patp === '') { return null; } - if (patp.startsWith("~")) { + if (patp.startsWith('~')) { patp = patp.substr(1); } // comet if (patp.length === 56) { - shortened = "~" + patp.slice(0, 6) + "_" + patp.slice(50, 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); + shortened = '~' + patp.slice(14, 20) + '^' + patp.slice(21, 27); return shortened; } return `~${patp}`; @@ -232,7 +232,6 @@ export function alphabeticalOrder(a: string, b: string) { export function lengthOrder(a: string, b: string) { return b.length - a.length; - } // TODO: deprecated @@ -244,13 +243,13 @@ export function alphabetiseAssociations(associations: any) { let bName = b.substr(1); if (associations[a].metadata && associations[a].metadata.title) { aName = - associations[a].metadata.title !== "" + associations[a].metadata.title !== '' ? associations[a].metadata.title : a.substr(1); } if (associations[b].metadata && associations[b].metadata.title) { bName = - associations[b].metadata.title !== "" + associations[b].metadata.title !== '' ? associations[b].metadata.title : b.substr(1); } @@ -266,41 +265,42 @@ export function alphabetiseAssociations(associations: any) { // for example, 'some Chars!' becomes '~.some.~43.hars~21.' // export function stringToTa(str: string) { - let out = ""; + let out = ''; for (let i = 0; i < str.length; i++) { const char = str[i]; - let add = ""; + let add = ''; switch (char) { - case " ": - add = "."; + case ' ': + add = '.'; break; - case ".": - add = "~."; + case '.': + add = '~.'; break; - case "~": - add = "~~"; + case '~': + add = '~~'; break; default: const charCode = str.charCodeAt(i); if ( (charCode >= 97 && charCode <= 122) || // a-z (charCode >= 48 && charCode <= 57) || // 0-9 - char === "-" + char === '-' ) { add = char; } else { // TODO behavior for unicode doesn't match +wood's, // but we can probably get away with that for now. - add = "~" + charCode.toString(16) + "."; + add = '~' + charCode.toString(16) + '.'; } } out = out + add; } - return "~." + out; + return '~.' + out; } export function amOwnerOfGroup(groupPath: string) { - if (!groupPath) return false; + if (!groupPath) +return false; const groupOwner = /(\/~)?\/~([a-z-]{3,})\/.*/.exec(groupPath)?.[2]; return window.ship === groupOwner; } @@ -308,18 +308,18 @@ export function amOwnerOfGroup(groupPath: string) { export function getContactDetails(contact: any) { const member = !contact; contact = contact || { - nickname: "", + nickname: '', avatar: null, - color: "0x0", + color: '0x0' }; - const nickname = contact.nickname || ""; - const color = uxToHex(contact.color || "0x0"); + const nickname = contact.nickname || ''; + const color = uxToHex(contact.color || '0x0'); const avatar = contact.avatar || null; return { nickname, color, member, avatar }; } export function stringToSymbol(str: string) { - let result = ""; + let result = ''; for (let i = 0; i < str.length; i++) { const n = str.charCodeAt(i); if ((n >= 97 && n <= 122) || (n >= 48 && n <= 57)) { @@ -327,19 +327,17 @@ export function stringToSymbol(str: string) { } else if (n >= 65 && n <= 90) { result += String.fromCharCode(n + 32); } else { - result += "-"; + result += '-'; } } - result = result.replace(/^[\-\d]+|\-+/g, "-"); - result = result.replace(/^\-+|\-+$/g, ""); - if (result === "") { + result = result.replace(/^[\-\d]+|\-+/g, '-'); + result = result.replace(/^\-+|\-+$/g, ''); + if (result === '') { return dateToDa(new Date()); } return result; } - - /** * Formats a numbers as a `@ud` inserting dot where needed */ @@ -351,23 +349,24 @@ export function numToUd(num: number) { f.reverse, f.map(s => s.join('')), f.join('.') - )(num.toString()) + )(num.toString()); } -export function usePreventWindowUnload(shouldPreventDefault: boolean, message = "You have unsaved changes. Are you sure you want to exit?") { +export function usePreventWindowUnload(shouldPreventDefault: boolean, message = 'You have unsaved changes. Are you sure you want to exit?') { useEffect(() => { - if (!shouldPreventDefault) return; - const handleBeforeUnload = event => { + if (!shouldPreventDefault) +return; + const handleBeforeUnload = (event) => { event.preventDefault(); return message; - } - window.addEventListener("beforeunload", handleBeforeUnload); + }; + window.addEventListener('beforeunload', handleBeforeUnload); window.onbeforeunload = handleBeforeUnload; return () => { - window.removeEventListener("beforeunload", handleBeforeUnload); + window.removeEventListener('beforeunload', handleBeforeUnload); // @ts-ignore window.onbeforeunload = undefined; - } + }; }, [shouldPreventDefault]); } @@ -378,7 +377,7 @@ export function pluralize(text: string, isPlural = false, vowel = false) { // Hide is an optional second parameter for when this function is used in class components export function useShowNickname(contact: Contact | null, hide?: boolean): boolean { const hideNicknames = typeof hide !== 'undefined' ? hide : useLocalState(state => state.hideNicknames); - return !!(contact && contact.nickname && !hideNicknames); + return Boolean(contact && contact.nickname && !hideNicknames); } interface useHoveringInterface { @@ -406,7 +405,6 @@ export function getItemTitle(association: Association) { return cite(`~${name.slice(4)}`); } return cite(ship); - } - return association.metadata.title || association.resource -}; + return association.metadata.title || association.resource; +} diff --git a/pkg/interface/src/logic/lib/workspace.ts b/pkg/interface/src/logic/lib/workspace.ts index e17b81e7b..f13df6195 100644 --- a/pkg/interface/src/logic/lib/workspace.ts +++ b/pkg/interface/src/logic/lib/workspace.ts @@ -1,24 +1,24 @@ -import { Associations, Workspace } from "~/types"; +import { Associations, Workspace } from '@urbit/api'; export function getTitleFromWorkspace( associations: Associations, workspace: Workspace ) { switch (workspace.type) { - case "home": - return "My Channels"; - case "messages": - return "Messages"; - case "group": + case 'home': + return 'My Channels'; + case 'messages': + return 'Messages'; + case 'group': const association = associations.groups[workspace.group]; - return association?.metadata?.title || ""; + return association?.metadata?.title || ''; } } export function getGroupFromWorkspace( workspace: Workspace ): string | undefined { - if (workspace.type === "group") { + if (workspace.type === 'group') { return workspace.group; } diff --git a/pkg/interface/src/logic/reducers/contact-update.ts b/pkg/interface/src/logic/reducers/contact-update.ts index 932852ece..21b83a004 100644 --- a/pkg/interface/src/logic/reducers/contact-update.ts +++ b/pkg/interface/src/logic/reducers/contact-update.ts @@ -1,8 +1,8 @@ import _ from 'lodash'; import { StoreState } from '../../store/type'; import { Cage } from '~/types/cage'; -import { ContactUpdate } from '~/types/contact-update'; -import {resourceAsPath} from '../lib/util'; +import { ContactUpdate } from '@urbit/api/contacts'; +import { resourceAsPath } from '../lib/util'; type ContactState = Pick; @@ -78,4 +78,3 @@ const setPublic = (json: ContactUpdate, state: S) => { state.isContactPublic = data; }; - diff --git a/pkg/interface/src/logic/reducers/group-update.ts b/pkg/interface/src/logic/reducers/group-update.ts index 169b30d56..c4aeddb0a 100644 --- a/pkg/interface/src/logic/reducers/group-update.ts +++ b/pkg/interface/src/logic/reducers/group-update.ts @@ -10,9 +10,9 @@ import { OpenPolicyDiff, OpenPolicy, InvitePolicyDiff, - InvitePolicy, -} from '~/types/group-update'; -import { Enc, PatpNoSig } from '~/types/noun'; + InvitePolicy +} from '@urbit/api/groups'; +import { Enc, PatpNoSig } from '@urbit/api'; import { resourceAsPath } from '../lib/util'; type GroupState = Pick; @@ -23,7 +23,7 @@ function decodeGroup(group: Enc): Group { ...group, members, tags: decodeTags(group.tags), - policy: decodePolicy(group.policy), + policy: decodePolicy(group.policy) }; return res; } @@ -35,7 +35,7 @@ function decodePolicy(policy: Enc): GroupPolicy { } else { const { open } = policy; return { - open: { banned: new Set(open.banned), banRanks: new Set(open.banRanks) }, + open: { banned: new Set(open.banned), banRanks: new Set(open.banRanks) } }; } } @@ -98,7 +98,7 @@ export default class GroupReducer { members: new Set(), tags: { role: { admin: new Set([window.ship]) } }, policy: decodePolicy(policy), - hidden, + hidden }; } } @@ -189,7 +189,6 @@ export default class GroupReducer { } } - private inviteChangePolicy(diff: InvitePolicyDiff, policy: InvitePolicy) { if ('addInvites' in diff) { const { addInvites } = diff; diff --git a/pkg/interface/src/logic/reducers/group-view.ts b/pkg/interface/src/logic/reducers/group-view.ts index 92be7c0f5..dd8763c6d 100644 --- a/pkg/interface/src/logic/reducers/group-view.ts +++ b/pkg/interface/src/logic/reducers/group-view.ts @@ -1,25 +1,24 @@ -import { resourceAsPath } from "~/logic/lib/util"; - +import { resourceAsPath } from '~/logic/lib/util'; const initial = (json: any, state: any) => { const data = json.initial; if(data) { state.pendingJoin = data; } -} +}; const progress = (json: any, state: any) => { const data = json.progress; if(data) { const { progress, resource } = data; - state.pendingJoin = {...state.pendingJoin, [resource]: progress }; + state.pendingJoin = { ...state.pendingJoin, [resource]: progress }; if(progress === 'done') { setTimeout(() => { delete state.pendingJoin[resource]; }, 10000); } } -} +}; export const GroupViewReducer = (json: any, state: any) => { const data = json['group-view-update']; @@ -27,4 +26,4 @@ export const GroupViewReducer = (json: any, state: any) => { progress(data, state); initial(data, state); } -} +}; diff --git a/pkg/interface/src/logic/reducers/hark-update.ts b/pkg/interface/src/logic/reducers/hark-update.ts index 6a415aaf1..e610db563 100644 --- a/pkg/interface/src/logic/reducers/hark-update.ts +++ b/pkg/interface/src/logic/reducers/hark-update.ts @@ -3,22 +3,21 @@ import { NotifIndex, NotificationGraphConfig, GroupNotificationsConfig, - UnreadStats, -} from "~/types"; -import { makePatDa } from "~/logic/lib/util"; -import _ from "lodash"; -import {StoreState} from "../store/type"; + UnreadStats +} from '@urbit/api'; +import { makePatDa } from '~/logic/lib/util'; +import _ from 'lodash'; +import { StoreState } from '../store/type'; import { BigIntOrderedMap } from '../lib/BigIntOrderedMap'; -type HarkState = Pick; - +type HarkState = Pick; export const HarkReducer = (json: any, state: HarkState) => { - const data = _.get(json, "harkUpdate", false); + const data = _.get(json, 'harkUpdate', false); if (data) { reduce(data, state); } - const graphHookData = _.get(json, "hark-graph-hook-update", false); + const graphHookData = _.get(json, 'hark-graph-hook-update', false); if (graphHookData) { graphInitial(graphHookData, state); graphIgnore(graphHookData, state); @@ -26,7 +25,7 @@ export const HarkReducer = (json: any, state: HarkState) => { graphWatchSelf(graphHookData, state); graphMentions(graphHookData, state); } - const groupHookData = _.get(json, "hark-group-hook-update", false); + const groupHookData = _.get(json, 'hark-group-hook-update', false); if (groupHookData) { groupInitial(groupHookData, state); groupListen(groupHookData, state); @@ -35,31 +34,31 @@ export const HarkReducer = (json: any, state: HarkState) => { }; function groupInitial(json: any, state: HarkState) { - const data = _.get(json, "initial", false); + const data = _.get(json, 'initial', false); if (data) { state.notificationsGroupConfig = data; } } function graphInitial(json: any, state: HarkState) { - const data = _.get(json, "initial", false); + const data = _.get(json, 'initial', false); if (data) { state.notificationsGraphConfig = data; } } function graphListen(json: any, state: HarkState) { - const data = _.get(json, "listen", false); + const data = _.get(json, 'listen', false); if (data) { state.notificationsGraphConfig.watching = [ ...state.notificationsGraphConfig.watching, - data, + data ]; } } function graphIgnore(json: any, state: HarkState) { - const data = _.get(json, "ignore", false); + const data = _.get(json, 'ignore', false); if (data) { state.notificationsGraphConfig.watching = state.notificationsGraphConfig.watching.filter( ({ graph, index }) => !(graph === data.graph && index === data.index) @@ -68,30 +67,30 @@ function graphIgnore(json: any, state: HarkState) { } function groupListen(json: any, state: HarkState) { - const data = _.get(json, "listen", false); + const data = _.get(json, 'listen', false); if (data) { state.notificationsGroupConfig = [...state.notificationsGroupConfig, data]; } } function groupIgnore(json: any, state: HarkState) { - const data = _.get(json, "ignore", false); + const data = _.get(json, 'ignore', false); if (data) { state.notificationsGroupConfig = state.notificationsGroupConfig.filter( - (n) => n !== data + n => n !== data ); } } function graphMentions(json: any, state: HarkState) { - const data = _.get(json, "set-mentions", undefined); + const data = _.get(json, 'set-mentions', undefined); if (!_.isUndefined(data)) { state.notificationsGraphConfig.mentions = data; } } function graphWatchSelf(json: any, state: HarkState) { - const data = _.get(json, "set-watch-on-self", undefined); + const data = _.get(json, 'set-watch-on-self', undefined); if (!_.isUndefined(data)) { state.notificationsGraphConfig.watchOnSelf = data; } @@ -139,14 +138,14 @@ function seenIndex(json: any, state: HarkState) { function readEach(json: any, state: HarkState) { const data = _.get(json, 'read-each'); if(data) { - updateUnreads(state, data.index, u => u.delete(data.target)) + updateUnreads(state, data.index, u => u.delete(data.target)); } } function readSince(json: any, state: HarkState) { const data = _.get(json, 'read-count'); if(data) { - updateUnreadCount(state, data, () => 0) + updateUnreadCount(state, data, () => 0); } } @@ -160,7 +159,7 @@ function unreadSince(json: any, state: HarkState) { function unreadEach(json: any, state: HarkState) { const data = _.get(json, 'unread-each'); if(data) { - updateUnreads(state, data.index, us => us.add(data.target)) + updateUnreads(state, data.index, us => us.add(data.target)); } } @@ -183,15 +182,15 @@ function unreads(json: any, state: HarkState) { } } -function clearState(state){ - let initialState = { +function clearState(state) { + const initialState = { notifications: new BigIntOrderedMap(), archivedNotifications: new BigIntOrderedMap(), notificationsGroupConfig: [], notificationsGraphConfig: { watchOnSelf: false, mentions: false, - watching: [], + watching: [] }, unreads: { graph: {}, @@ -200,7 +199,7 @@ function clearState(state){ notificationsCount: 0 }; - Object.keys(initialState).forEach(key => { + Object.keys(initialState).forEach((key) => { state[key] = initialState[key]; }); } @@ -211,7 +210,7 @@ function updateUnreadCount(state: HarkState, index: NotifIndex, count: (c: numbe } const property = [index.graph.graph, index.graph.index, 'unreads']; const curr = _.get(state.unreads.graph, property, 0); - const newCount = count(curr) + const newCount = count(curr); _.set(state.unreads.graph, property, newCount); } @@ -226,7 +225,6 @@ function updateUnreads(state: HarkState, index: NotifIndex, f: (us: Set) _.set(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], unreads); } - function updateNotificationStats(state: HarkState, index: NotifIndex, statField: 'notifications' | 'unreads' | 'last', f: (x: number) => number) { if(statField === 'notifications') { state.notificationsCount = f(state.notificationsCount); @@ -241,13 +239,13 @@ function updateNotificationStats(state: HarkState, index: NotifIndex, statField: } function added(json: any, state: HarkState) { - const data = _.get(json, "added", false); + const data = _.get(json, 'added', false); if (data) { const { index, notification } = data; const time = makePatDa(data.time); const timebox = state.notifications.get(time) || []; - const arrIdx = timebox.findIndex((idxNotif) => + const arrIdx = timebox.findIndex(idxNotif => notifIdxEqual(index, idxNotif.index) ); if (arrIdx !== -1) { @@ -264,14 +262,14 @@ function added(json: any, state: HarkState) { } const dnd = (json: any, state: HarkState) => { - const data = _.get(json, "set-dnd", undefined); + const data = _.get(json, 'set-dnd', undefined); if (!_.isUndefined(data)) { state.doNotDisturb = data; } }; const timebox = (json: any, state: HarkState) => { - const data = _.get(json, "timebox", false); + const data = _.get(json, 'timebox', false); if (data) { const time = makePatDa(data.time); if (!data.archive) { @@ -281,21 +279,21 @@ const timebox = (json: any, state: HarkState) => { }; function more(json: any, state: HarkState) { - const data = _.get(json, "more", false); + const data = _.get(json, 'more', false); if (data) { - _.forEach(data, (d) => reduce(d, state)); + _.forEach(data, d => reduce(d, state)); } } function notifIdxEqual(a: NotifIndex, b: NotifIndex) { - if ("graph" in a && "graph" in b) { + if ('graph' in a && 'graph' in b) { return ( a.graph.graph === b.graph.graph && a.graph.group === b.graph.group && a.graph.module === b.graph.module && a.graph.description === b.graph.description ); - } else if ("group" in a && "group" in b) { + } else if ('group' in a && 'group' in b) { return ( a.group.group === b.group.group && a.group.description === b.group.description @@ -313,14 +311,14 @@ function setRead( const patDa = makePatDa(time); const timebox = state.notifications.get(patDa); if (_.isNull(timebox)) { - console.warn("Modifying nonexistent timebox"); + console.warn('Modifying nonexistent timebox'); return; } - const arrIdx = timebox.findIndex((idxNotif) => + const arrIdx = timebox.findIndex(idxNotif => notifIdxEqual(index, idxNotif.index) ); if (arrIdx === -1) { - console.warn("Modifying nonexistent index"); + console.warn('Modifying nonexistent index'); return; } timebox[arrIdx].notification.read = read; @@ -328,7 +326,7 @@ function setRead( } function read(json: any, state: HarkState) { - const data = _.get(json, "read-note", false); + const data = _.get(json, 'read-note', false); if (data) { const { time, index } = data; updateNotificationStats(state, index, 'notifications', x => x-1); @@ -337,7 +335,7 @@ function read(json: any, state: HarkState) { } function unread(json: any, state: HarkState) { - const data = _.get(json, "unread-note", false); + const data = _.get(json, 'unread-note', false); if (data) { const { time, index } = data; updateNotificationStats(state, index, 'notifications', x => x+1); @@ -346,16 +344,16 @@ function unread(json: any, state: HarkState) { } function archive(json: any, state: HarkState) { - const data = _.get(json, "archive", false); + const data = _.get(json, 'archive', false); if (data) { const { index } = data; const time = makePatDa(data.time); const timebox = state.notifications.get(time); if (!timebox) { - console.warn("Modifying nonexistent timebox"); + console.warn('Modifying nonexistent timebox'); return; } - const [archived, unarchived] = _.partition(timebox, (idxNotif) => + const [archived, unarchived] = _.partition(timebox, idxNotif => notifIdxEqual(index, idxNotif.index) ); if(unarchived.length === 0) { @@ -365,6 +363,6 @@ function archive(json: any, state: HarkState) { state.notifications.set(time, unarchived); } const newlyRead = archived.filter(x => !x.notification.read).length; - updateNotificationStats(state, index, 'notifications', (x) => x - newlyRead); + updateNotificationStats(state, index, 'notifications', x => x - newlyRead); } } diff --git a/pkg/interface/src/logic/reducers/invite-update.ts b/pkg/interface/src/logic/reducers/invite-update.ts index 30fb61042..c4f472be0 100644 --- a/pkg/interface/src/logic/reducers/invite-update.ts +++ b/pkg/interface/src/logic/reducers/invite-update.ts @@ -1,10 +1,9 @@ import _ from 'lodash'; import { StoreState } from '../../store/type'; import { Cage } from '~/types/cage'; -import { InviteUpdate } from '~/types/invite-update'; - -type InviteState = Pick; +import { InviteUpdate } from '@urbit/api/invite'; +type InviteState = Pick; export default class InviteReducer { reduce(json: Cage, state: S) { diff --git a/pkg/interface/src/logic/reducers/launch-update.ts b/pkg/interface/src/logic/reducers/launch-update.ts index d05145d78..56a00efce 100644 --- a/pkg/interface/src/logic/reducers/launch-update.ts +++ b/pkg/interface/src/logic/reducers/launch-update.ts @@ -51,12 +51,11 @@ export default class LaunchReducer { changeIsShown(json: LaunchUpdate, state: S) { const data = _.get(json, 'changeIsShown', false); if (data) { - let tile = state.launch.tiles[data.name]; + const tile = state.launch.tiles[data.name]; console.log(tile); if (tile) { tile.isShown = data.isShown; } } } - } diff --git a/pkg/interface/src/logic/reducers/metadata-update.ts b/pkg/interface/src/logic/reducers/metadata-update.ts index 00070b87e..41e6d7985 100644 --- a/pkg/interface/src/logic/reducers/metadata-update.ts +++ b/pkg/interface/src/logic/reducers/metadata-update.ts @@ -2,14 +2,14 @@ import _ from 'lodash'; import { StoreState } from '../../store/type'; -import { MetadataUpdate } from '~/types/metadata-update'; +import { MetadataUpdate } from '@urbit/api/metadata'; import { Cage } from '~/types/cage'; type MetadataState = Pick; export default class MetadataReducer { reduce(json: Cage, state: S) { - let data = json['metadata-update'] + const data = json['metadata-update']; if (data) { console.log(data); this.associations(data, state); @@ -29,13 +29,13 @@ export default class MetadataReducer { } associations(json: MetadataUpdate, state: S) { - let data = _.get(json, 'associations', false); + const data = _.get(json, 'associations', false); if (data) { - let metadata = state.associations; + const metadata = state.associations; Object.keys(data).forEach((key) => { - let val = data[key]; - let appName = val['app-name']; - let rid = val.resource; + const val = data[key]; + const appName = val['app-name']; + const rid = val.resource; if (!(appName in metadata)) { metadata[appName] = {}; } @@ -50,11 +50,11 @@ export default class MetadataReducer { } add(json: MetadataUpdate, state: S) { - let data = _.get(json, 'add', false); + const data = _.get(json, 'add', false); if (data) { - let metadata = state.associations; - let appName = data['app-name']; - let appPath = data.resource; + const metadata = state.associations; + const appName = data['app-name']; + const appPath = data.resource; if (!(appName in metadata)) { metadata[appName] = {}; @@ -69,11 +69,11 @@ export default class MetadataReducer { } update(json: MetadataUpdate, state: S) { - let data = _.get(json, 'update-metadata', false); + const data = _.get(json, 'update-metadata', false); if (data) { - let metadata = state.associations; - let appName = data['app-name']; - let rid = data.resource; + const metadata = state.associations; + const appName = data['app-name']; + const rid = data.resource; if (!(appName in metadata)) { metadata[appName] = {}; @@ -88,11 +88,11 @@ export default class MetadataReducer { } remove(json: MetadataUpdate, state: S) { - let data = _.get(json, 'remove', false); + const data = _.get(json, 'remove', false); if (data) { - let metadata = state.associations; - let appName = data['app-name']; - let rid = data.resource; + const metadata = state.associations; + const appName = data['app-name']; + const rid = data.resource; if (appName in metadata && rid in metadata[appName]) { delete metadata[appName][rid]; diff --git a/pkg/interface/src/logic/reducers/settings-update.ts b/pkg/interface/src/logic/reducers/settings-update.ts index 9716cbb85..7afef84ec 100644 --- a/pkg/interface/src/logic/reducers/settings-update.ts +++ b/pkg/interface/src/logic/reducers/settings-update.ts @@ -1,21 +1,21 @@ import _ from 'lodash'; import { StoreState } from '../../store/type'; import { - SettingsUpdate, -} from '~/types/settings'; + SettingsUpdate +} from '@urbit/api/settings'; type SettingsState = Pick; -export default class SettingsReducer{ +export default class SettingsReducer { reduce(json: Cage, state: S) { - let data = json["settings-event"]; + let data = json['settings-event']; if (data) { this.putBucket(data, state); this.delBucket(data, state); this.putEntry(data, state); this.delEntry(data, state); } - data = json["settings-data"]; + data = json['settings-data']; if (data) { this.getAll(data, state); this.getBucket(data, state); @@ -26,31 +26,31 @@ export default class SettingsReducer{ putBucket(json: SettingsUpdate, state: S) { const data = _.get(json, 'put-bucket', false); if (data) { - state.settings[data["bucket-key"]] = data.bucket; + state.settings[data['bucket-key']] = data.bucket; } } delBucket(json: SettingsUpdate, state: S) { const data = _.get(json, 'del-bucket', false); if (data) { - delete state.settings[data["bucket-key"]]; + delete state.settings[data['bucket-key']]; } } putEntry(json: SettingsUpdate, state: S) { const data = _.get(json, 'put-entry', false); if (data) { - if (!state.settings[data["bucket-key"]]) { - state.settings[data["bucket-key"]] = {}; + if (!state.settings[data['bucket-key']]) { + state.settings[data['bucket-key']] = {}; } - state.settings[data["bucket-key"]][data["entry-key"]] = data.value; + state.settings[data['bucket-key']][data['entry-key']] = data.value; } } delEntry(json: SettingsUpdate, state: S) { const data = _.get(json, 'del-entry', false); if (data) { - delete state.settings[data["bucket-key"]][data["entry-key"]]; + delete state.settings[data['bucket-key']][data['entry-key']]; } } diff --git a/pkg/interface/src/logic/state/local.tsx b/pkg/interface/src/logic/state/local.tsx index 64e68a0ca..70e76c468 100644 --- a/pkg/interface/src/logic/state/local.tsx +++ b/pkg/interface/src/logic/state/local.tsx @@ -1,10 +1,9 @@ -import React, { ReactNode } from "react"; +import React, { ReactNode } from 'react'; import f from 'lodash/fp'; import create, { State } from 'zustand'; import { persist } from 'zustand/middleware'; import produce from 'immer'; -import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgress } from "~/types/local-update"; - +import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgress } from '~/types/local-update'; export interface LocalState extends State { hideAvatars: boolean; @@ -22,8 +21,8 @@ export interface LocalState extends State { suspendedFocus?: HTMLElement; toggleOmnibox: () => void; set: (fn: (state: LocalState) => void) => void -}; -export const selectLocalState = +} +export const selectLocalState = (keys: K[]) => f.pick(keys); const useLocalState = create(persist((set, get) => ({ @@ -33,21 +32,21 @@ const useLocalState = create(persist((set, get) => ({ hideNicknames: false, tutorialProgress: 'hidden', tutorialRef: null, - setTutorialRef: (el: HTMLElement | null) => set(produce(state => { + setTutorialRef: (el: HTMLElement | null) => set(produce((state) => { state.tutorialRef = el; })), - hideTutorial: () => set(produce(state => { + hideTutorial: () => set(produce((state) => { state.tutorialProgress = 'hidden'; state.tutorialRef = null; })), - nextTutStep: () => set(produce(state => { - const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress) + nextTutStep: () => set(produce((state) => { + const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress); if(currIdx < tutorialProgress.length) { state.tutorialProgress = tutorialProgress[currIdx + 1]; } })), - prevTutStep: () => set(produce(state => { - const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress) + prevTutStep: () => set(produce((state) => { + const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress); if(currIdx > 0) { state.tutorialProgress = tutorialProgress[currIdx - 1]; } @@ -56,11 +55,11 @@ const useLocalState = create(persist((set, get) => ({ imageShown: true, audioShown: true, videoShown: true, - oembedShown: true, + oembedShown: true }, omniboxShown: false, suspendedFocus: undefined, - toggleOmnibox: () => set(produce(state => { + toggleOmnibox: () => set(produce((state) => { state.omniboxShown = !state.omniboxShown; if (typeof state.suspendedFocus?.focus === 'function') { state.suspendedFocus.focus(); @@ -86,7 +85,7 @@ function withLocalState(Component: any, stateMemb (object, key) => ({ ...object, [key]: state[key] }), {} ) ): useLocalState(); - return + return ; }); } diff --git a/pkg/interface/src/logic/store/base.ts b/pkg/interface/src/logic/store/base.ts index 9a48ebd80..faeacca1e 100644 --- a/pkg/interface/src/logic/store/base.ts +++ b/pkg/interface/src/logic/store/base.ts @@ -19,7 +19,7 @@ export default class BaseStore { clear() { this.handleEvent({ - data: { clear: true }, + data: { clear: true } }); } @@ -30,7 +30,7 @@ export default class BaseStore { return; } - if ("clear" in json && json.clear) { + if ('clear' in json && json.clear) { this.setState(this.initialState()); return; } diff --git a/pkg/interface/src/logic/store/store.ts b/pkg/interface/src/logic/store/store.ts index b2ab60c6c..e56723ace 100644 --- a/pkg/interface/src/logic/store/store.ts +++ b/pkg/interface/src/logic/store/store.ts @@ -6,7 +6,7 @@ import MetadataReducer from '../reducers/metadata-update'; import LocalReducer from '../reducers/local'; import { StoreState } from './type'; -import { Timebox } from '~/types'; +import { Timebox } from '@urbit/api'; import { Cage } from '~/types/cage'; import S3Reducer from '../reducers/s3-update'; import { GraphReducer } from '../reducers/graph-update'; @@ -16,10 +16,9 @@ import GroupReducer from '../reducers/group-update'; import LaunchReducer from '../reducers/launch-update'; import ConnectionReducer from '../reducers/connection'; import SettingsReducer from '../reducers/settings-update'; -import {OrderedMap} from '../lib/OrderedMap'; +import { OrderedMap } from '../lib/OrderedMap'; import { BigIntOrderedMap } from '../lib/BigIntOrderedMap'; -import {GroupViewReducer} from '../reducers/group-view'; - +import { GroupViewReducer } from '../reducers/group-view'; export default class GlobalStore extends BaseStore { inviteReducer = new InviteReducer(); @@ -58,7 +57,7 @@ export default class GlobalStore extends BaseStore { invites: {}, associations: { groups: {}, - graph: {}, + graph: {} }, groups: {}, groupKeys: new Set(), @@ -67,7 +66,7 @@ export default class GlobalStore extends BaseStore { launch: { firstTime: false, tileOrdering: [], - tiles: {}, + tiles: {} }, weather: {}, userLocation: null, @@ -87,7 +86,7 @@ export default class GlobalStore extends BaseStore { notificationsGraphConfig: { watchOnSelf: false, mentions: false, - watching: [], + watching: [] }, unreads: { graph: {}, @@ -95,7 +94,7 @@ export default class GlobalStore extends BaseStore { }, notificationsCount: 0, settings: {}, - pendingJoin: {}, + pendingJoin: {} }; } diff --git a/pkg/interface/src/logic/store/type.ts b/pkg/interface/src/logic/store/type.ts index 96b68f8a2..2348aedf9 100644 --- a/pkg/interface/src/logic/store/type.ts +++ b/pkg/interface/src/logic/store/type.ts @@ -1,20 +1,20 @@ -import { Path } from '~/types/noun'; -import { Invites } from '~/types/invite-update'; -import { Associations } from '~/types/metadata-update'; -import { Rolodex } from '~/types/contact-update'; -import { Groups } from '~/types/group-update'; +import { Path } from '@urbit/api'; +import { Invites } from '@urbit/api/invite'; +import { Associations } from '@urbit/api/metadata'; +import { Rolodex } from '@urbit/api/contacts'; +import { Groups } from '@urbit/api/groups'; import { S3State } from '~/types/s3-update'; import { LaunchState, WeatherState } from '~/types/launch-update'; import { ConnectionStatus } from '~/types/connection'; -import {Graphs} from '~/types/graph-update'; +import { Graphs } from '@urbit/api/graph'; import { Notifications, - NotificationGraphConfig, + NotificationGraphConfig, GroupNotificationsConfig, Unreads, JoinRequests, Patp -} from "~/types"; +} from '@urbit/api'; export interface StoreState { // local state @@ -35,7 +35,6 @@ export interface StoreState { graphs: Graphs; graphKeys: Set; - // App specific states // launch state launch: LaunchState; diff --git a/pkg/interface/src/logic/subscription/base.ts b/pkg/interface/src/logic/subscription/base.ts index eb4b3b3af..debf66541 100644 --- a/pkg/interface/src/logic/subscription/base.ts +++ b/pkg/interface/src/logic/subscription/base.ts @@ -1,6 +1,6 @@ -import BaseStore from "../store/base"; -import BaseApi from "../api/base"; -import { Path } from "~/types/noun"; +import BaseStore from '../store/base'; +import BaseApi from '../api/base'; +import { Path } from '@urbit/api'; export default class BaseSubscription { private errorCount = 0; @@ -19,24 +19,24 @@ export default class BaseSubscription { // Exists to allow subclasses to hook restart() { - this.handleEvent({ data: { connection: 'reconnecting' }}); + this.handleEvent({ data: { connection: 'reconnecting' } }); this.start(); } onChannelOpen(e: any) { this.errorCount = 0; - this.handleEvent({ data: { connection: 'connected' }}); + this.handleEvent({ data: { connection: 'connected' } }); } onChannelError(err) { console.error('event source error: ', err); this.errorCount++; if(this.errorCount >= 5) { - console.error("bailing out, too many retries"); - this.handleEvent({ data: { connection: 'disconnected' }}); + console.error('bailing out, too many retries'); + this.handleEvent({ data: { connection: 'disconnected' } }); return; } - this.handleEvent({ data: { connection: 'reconnecting' }}); + this.handleEvent({ data: { connection: 'reconnecting' } }); setTimeout(() => { this.restart(); }, Math.pow(2,this.errorCount - 1) * 750); diff --git a/pkg/interface/src/logic/subscription/global.ts b/pkg/interface/src/logic/subscription/global.ts index a434c41e0..5b444f06d 100644 --- a/pkg/interface/src/logic/subscription/global.ts +++ b/pkg/interface/src/logic/subscription/global.ts @@ -1,9 +1,8 @@ import BaseSubscription from './base'; import { StoreState } from '../store/type'; -import { Path } from '~/types/noun'; +import { Path } from '@urbit/api'; import _ from 'lodash'; - /** * Path to subscribe on and app to subscribe to */ @@ -68,7 +67,7 @@ export default class GlobalSubscription extends BaseSubscription { } stopApp(app: AppName) { - this.openSubscriptions[app].map(id => this.unsubscribe(id)) + this.openSubscriptions[app].map(id => this.unsubscribe(id)); this.openSubscriptions[app] = []; } } diff --git a/pkg/interface/src/types/cage.ts b/pkg/interface/src/types/cage.ts index 266188543..044fb4621 100644 --- a/pkg/interface/src/types/cage.ts +++ b/pkg/interface/src/types/cage.ts @@ -1,21 +1,17 @@ -import { ContactUpdate } from "./contact-update"; -import { InviteUpdate } from "./invite-update"; -import { LocalUpdate } from "./local-update"; -import { MetadataUpdate } from "./metadata-update"; -import { GroupUpdate } from "./group-update"; -import { LaunchUpdate, WeatherState } from "./launch-update"; -import { ConnectionStatus } from "./connection"; -import { SettingsUpdate } from "./settings"; +import { LocalUpdate } from './local-update'; +import { LaunchUpdate, WeatherState } from './launch-update'; +import { ConnectionStatus } from './connection'; +import { ContactUpdate, GroupUpdate, InviteUpdate, MetadataUpdate } from '@urbit/api'; +import { SettingsUpdate } from '@urbit/api/settings'; interface MarksToTypes { readonly json: any; - readonly "contact-update": ContactUpdate; - readonly "invite-update": InviteUpdate; - readonly "metadata-update": MetadataUpdate; + readonly 'contact-update': ContactUpdate; + readonly 'invite-update': InviteUpdate; + readonly 'metadata-update': MetadataUpdate; readonly groupUpdate: GroupUpdate; - readonly "launch-update": LaunchUpdate; - readonly "link-listen-update": LinkListenUpdate; - readonly "settings-event": SettingsUpdate; + readonly 'launch-update': LaunchUpdate; + readonly 'settings-event': SettingsUpdate; // not really marks but w/e readonly 'local': LocalUpdate; readonly 'weather': WeatherState | {}; diff --git a/pkg/interface/src/types/connection.ts b/pkg/interface/src/types/connection.ts index 6f624238e..38b06b09a 100644 --- a/pkg/interface/src/types/connection.ts +++ b/pkg/interface/src/types/connection.ts @@ -1,2 +1 @@ - export type ConnectionStatus = 'reconnecting' | 'disconnected' | 'connected'; diff --git a/pkg/interface/src/types/contact-update.ts b/pkg/interface/src/types/contact-update.ts deleted file mode 100644 index e48afc739..000000000 --- a/pkg/interface/src/types/contact-update.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Path, Patp } from "./noun"; - -export type ContactUpdate = - | ContactUpdateCreate - | ContactUpdateDelete - | ContactUpdateAdd - | ContactUpdateRemove - | ContactUpdateEdit - | ContactUpdateInitial - | ContactUpdateContacts; - -interface ContactUpdateCreate { - create: Path; -} - -interface ContactUpdateDelete { - delete: Path; -} - -interface ContactUpdateAdd { - add: { - path: Path; - ship: Patp; - contact: Contact; - }; -} - -interface ContactUpdateRemove { - remove: { - path: Path; - ship: Patp; - }; -} - -interface ContactUpdateEdit { - edit: { - path: Path; - ship: Patp; - "edit-field": ContactEdit; - }; -} - -interface ContactUpdateInitial { - initial: Rolodex; -} - -interface ContactUpdateContacts { - contacts: { - path: Path; - contacts: Contacts; - }; -} - -// - -type ContactAvatar = ContactAvatarUrl | ContactAvatarOcts; - -export type Rolodex = { - [p in Path]: Contacts; -}; - -export type Contacts = { - [p in Patp]: Contact; -}; - -interface ContactAvatarUrl { - url: string; -} - -interface ContactAvatarOcts { - octs: string; -} -export interface Contact { - nickname: string; - email: string; - phone: string; - website: string; - notes: string; - color: string; - avatar: string | null; -} - -export type ContactEdit = { - [k in keyof Contact]: Contact[k]; -}; diff --git a/pkg/interface/src/types/global.ts b/pkg/interface/src/types/global.ts index d43a77b1b..4294f574f 100644 --- a/pkg/interface/src/types/global.ts +++ b/pkg/interface/src/types/global.ts @@ -1,4 +1,4 @@ -import { PatpNoSig } from "./noun"; +import { PatpNoSig } from '@urbit/api'; declare global { interface Window { diff --git a/pkg/interface/src/types/index.ts b/pkg/interface/src/types/index.ts index 5c1d81b0b..c4abcba64 100644 --- a/pkg/interface/src/types/index.ts +++ b/pkg/interface/src/types/index.ts @@ -1,16 +1,8 @@ export * from './cage'; export * from './connection'; -export * from './contact-update'; export * from './global'; -export * from './group-update'; -export * from './group-view'; -export * from './graph-update'; -export * from './hark-update'; -export * from './invite-update'; export * from './launch-update'; export * from './local-update'; -export * from './metadata-update'; -export * from './noun'; export * from './s3-update'; export * from './workspace'; export * from './util'; diff --git a/pkg/interface/src/types/launch-update.ts b/pkg/interface/src/types/launch-update.ts index 099987845..879c3620b 100644 --- a/pkg/interface/src/types/launch-update.ts +++ b/pkg/interface/src/types/launch-update.ts @@ -1,11 +1,9 @@ - export type LaunchUpdate = LaunchUpdateInitial | LaunchUpdateFirstTime | LaunchUpdateOrder | LaunchUpdateIsShown; - interface LaunchUpdateInitial { initial: LaunchState; } diff --git a/pkg/interface/src/types/s3-update.ts b/pkg/interface/src/types/s3-update.ts index 3dfa51622..88763b7d6 100644 --- a/pkg/interface/src/types/s3-update.ts +++ b/pkg/interface/src/types/s3-update.ts @@ -1,6 +1,4 @@ - - - export interface S3Credentials { +export interface S3Credentials { endpoint: string; accessKeyId: string; secretAccessKey: string; @@ -51,7 +49,6 @@ interface S3UpdateSecretAccessKey { setSecretAccessKey: string; } - export type S3Update = S3UpdateCredentials | S3UpdateConfiguration diff --git a/pkg/interface/src/types/util.ts b/pkg/interface/src/types/util.ts index 2f3d390e5..f247978ca 100644 --- a/pkg/interface/src/types/util.ts +++ b/pkg/interface/src/types/util.ts @@ -1,4 +1,4 @@ -import { Icon } from "@tlon/indigo-react"; +import { Icon } from '@tlon/indigo-react'; export type PropFunc any> = Parameters[0]; export type Primitive = string | number | undefined | symbol | null | boolean; diff --git a/pkg/interface/src/types/workspace.ts b/pkg/interface/src/types/workspace.ts index 69da82ed3..e8238e725 100644 --- a/pkg/interface/src/types/workspace.ts +++ b/pkg/interface/src/types/workspace.ts @@ -1,5 +1,3 @@ - - interface GroupWorkspace { type: 'group'; group: string; diff --git a/pkg/interface/src/views/apps/chat/ChatResource.tsx b/pkg/interface/src/views/apps/chat/ChatResource.tsx index 6b47a4409..5b2950a92 100644 --- a/pkg/interface/src/views/apps/chat/ChatResource.tsx +++ b/pkg/interface/src/views/apps/chat/ChatResource.tsx @@ -3,7 +3,7 @@ import { RouteComponentProps } from 'react-router-dom'; import { Col } from '@tlon/indigo-react'; import _ from 'lodash'; -import { Association } from '~/types/metadata-update'; +import { Association } from '@urbit/api/metadata'; import { StoreState } from '~/logic/store/type'; import { useFileDrag } from '~/logic/lib/useDrag'; import ChatWindow from './components/ChatWindow'; @@ -127,14 +127,13 @@ export function ChatResource(props: ChatResourceProps) { setHasLoadedAllowed(true); })(); - }, [groupPath]); if(!graph) { return ; } - var modifiedContacts = { ...contacts }; + const modifiedContacts = { ...contacts }; delete modifiedContacts[`~${window.ship}`]; return ( @@ -148,7 +147,7 @@ export function ChatResource(props: ChatResourceProps) { setShowBanner={setShowBanner} group={group} groupPath={groupPath} - /> + /> {dragging && } { inCodeMode: false }, async () => { const output = await props.api.graph.eval(text); - const contents: Content[] = [{ code: { output, expression: text }}]; + const contents: Content[] = [{ code: { output, expression: text } }]; const post = createPost(contents); props.api.graph.addPost(ship, name, post); }); return; } - const post = createPost(tokenizeMessage((text))) + const post = createPost(tokenizeMessage((text))); props.deleteMessage(); @@ -110,7 +110,7 @@ class ChatInput extends Component { if (!this.props.canUpload) { return; } - Array.from(files).forEach(file => { + Array.from(files).forEach((file) => { this.props.uploadDefault(file) .then(this.uploadSuccess) .catch(this.uploadError); @@ -178,7 +178,7 @@ class ChatInput extends Component { width="16" height="16" onClick={() => this.props.promptUpload().then(this.uploadSuccess)} - /> + /> : null } @@ -200,4 +200,4 @@ class ChatInput extends Component { } } -export default withLocalState(withS3(ChatInput, {accept: 'image/*'}), ['hideAvatars']); +export default withLocalState(withS3(ChatInput, { accept: 'image/*' }), ['hideAvatars']); diff --git a/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx b/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx index 6252383dd..7f7cca0a3 100644 --- a/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx +++ b/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx @@ -17,7 +17,7 @@ import { useShowNickname, useHovering } from '~/logic/lib/util'; -import { Group, Association, Contacts, Post, Groups, Associations } from '~/types'; +import { Group, Association, Contacts, Post, Groups, Associations } from '@urbit/api'; import TextContent from './content/text'; import CodeContent from './content/code'; import RemoteContent from '~/views/components/RemoteContent'; @@ -66,12 +66,12 @@ interface ChatMessageProps { contacts: Contacts; className?: string; isPending: boolean; - style?: any; + style?: unknown; scrollWindow: HTMLDivElement; isLastMessage?: boolean; unreadMarkerRef: React.RefObject; - history: any; - api: any; + history: unknown; + api: GlobalApi; highlighted?: boolean; } @@ -149,7 +149,7 @@ export default class ChatMessage extends Component { highlighted, fontSize, associations, - groups, + groups }; const unreadContainerStyle = { @@ -230,7 +230,7 @@ export const MessageWithSigil = (props) => { fontSize } = props; - const dark = useLocalState((state) => state.dark); + const dark = useLocalState(state => state.dark); const datestamp = moment .unix(msg['time-sent'] / 1000) @@ -255,7 +255,7 @@ export const MessageWithSigil = (props) => { const [showOverlay, setShowOverlay] = useState(false); const toggleOverlay = () => { - setShowOverlay((value) => !value); + setShowOverlay(value => !value); }; const showCopyNotice = () => { diff --git a/pkg/interface/src/views/apps/chat/components/ChatWindow.tsx b/pkg/interface/src/views/apps/chat/components/ChatWindow.tsx index 8db2935a5..54d10b9d1 100644 --- a/pkg/interface/src/views/apps/chat/components/ChatWindow.tsx +++ b/pkg/interface/src/views/apps/chat/components/ChatWindow.tsx @@ -1,22 +1,17 @@ -import React, { Component } from "react"; -import { RouteComponentProps } from "react-router-dom"; -import _ from "lodash"; +import React, { Component } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; +import _ from 'lodash'; import bigInt, { BigInteger } from 'big-integer'; import { Col } from '@tlon/indigo-react'; +import { Patp, Contacts, Association, Associations, Group, Groups, Graph } from '@urbit/api'; -import GlobalApi from "~/logic/api/global"; -import { Patp, Path } from "~/types/noun"; -import { Contacts } from "~/types/contact-update"; -import { Association, Associations } from "~/types/metadata-update"; -import { Group, Groups } from "~/types/group-update"; -import { Envelope, IMessage } from "~/types/chat-update"; -import { Graph } from "~/types"; +import GlobalApi from '~/logic/api/global'; -import VirtualScroller from "~/views/components/VirtualScroller"; +import VirtualScroller from '~/views/components/VirtualScroller'; import ChatMessage, { MessagePlaceholder } from './ChatMessage'; -import { UnreadNotice } from "./unread-notice"; +import { UnreadNotice } from './unread-notice'; const INITIAL_LOAD = 20; const DEFAULT_BACKLOG_SIZE = 100; @@ -66,8 +61,6 @@ export default class ChatWindow extends Component scrollTop) @@ -246,7 +242,6 @@ export default class ChatWindow extends Component {this.virtualList = list}} + ref={(list) => { + this.virtualList = list; +}} origin="bottom" style={{ height: '100%' }} onStartReached={() => { @@ -274,7 +271,8 @@ export default class ChatWindow extends Component { const msg = graph.get(index)?.post; - if (!msg) return null; + if (!msg) +return null; if (!this.state.initialized) { return ; } @@ -285,7 +283,6 @@ export default class ChatWindow extends Component alphabeticalOrder(a.metadata.title, b.metadata.title); - const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path: string) => f.flow( f.pickBy((a: Association) => a.group === path), @@ -34,12 +33,11 @@ const getGraphNotifications = (associations: Associations, unreads: Unreads) => f.reduce(f.add, 0) )(associations.graph); - export default function Groups(props: GroupsProps & Parameters[0]) { const { associations, unreads, inbox, ...boxProps } = props; const groups = Object.values(associations?.groups || {}) - .filter((e) => e?.group in props.groups) + .filter(e => e?.group in props.groups) .sort(sortGroupsAlph); const graphUnreads = getGraphUnreads(associations || {}, unreads); const graphNotifications = getGraphNotifications(associations || {}, unreads); @@ -48,7 +46,7 @@ export default function Groups(props: GroupsProps & Parameters[0]) { <> {groups.map((group, index) => { const path = group?.group; - const unreadCount = graphUnreads(path) + const unreadCount = graphUnreads(path); const notCount = graphNotifications(path); return ( diff --git a/pkg/interface/src/views/apps/launch/components/ModalButton.tsx b/pkg/interface/src/views/apps/launch/components/ModalButton.tsx index eea2d3b6e..15b223e17 100644 --- a/pkg/interface/src/views/apps/launch/components/ModalButton.tsx +++ b/pkg/interface/src/views/apps/launch/components/ModalButton.tsx @@ -1,6 +1,6 @@ -import React from "react" -import { Box, Button, Icon, Text } from "@tlon/indigo-react" -import {useModal} from "~/logic/lib/useModal"; +import React from 'react'; +import { Box, Button, Icon, Text } from '@tlon/indigo-react'; +import { useModal } from '~/logic/lib/useModal'; const ModalButton = (props) => { const { @@ -13,7 +13,6 @@ const ModalButton = (props) => { } = props; const { modal, showModal } = useModal({ modal: props.children }); - return ( <> {modal} @@ -33,6 +32,6 @@ const ModalButton = (props) => { ); -} +}; export default ModalButton; diff --git a/pkg/interface/src/views/apps/links/LinkResource.tsx b/pkg/interface/src/views/apps/links/LinkResource.tsx index d85a2d770..0f61a7bf8 100644 --- a/pkg/interface/src/views/apps/links/LinkResource.tsx +++ b/pkg/interface/src/views/apps/links/LinkResource.tsx @@ -1,19 +1,18 @@ -import React, { useEffect, useCallback } from "react"; -import { Box, Row, Col, Center, LoadingSpinner, Text } from "@tlon/indigo-react"; -import { Switch, Route, Link } from "react-router-dom"; +import React, { useEffect } from 'react'; +import { Box, Col, Center, LoadingSpinner, Text } from '@tlon/indigo-react'; +import { Switch, Route, Link } from 'react-router-dom'; import bigInt from 'big-integer'; -import GlobalApi from "~/logic/api/global"; -import { StoreState } from "~/logic/store/type"; -import { uxToHex } from '~/logic/lib/util'; -import { RouteComponentProps } from "react-router-dom"; +import GlobalApi from '~/logic/api/global'; +import { StoreState } from '~/logic/store/type'; +import { RouteComponentProps } from 'react-router-dom'; -import { LinkItem } from "./components/LinkItem"; -import { LinkPreview } from "./components/link-preview"; -import { LinkWindow } from "./LinkWindow"; -import { Comments } from "~/views/components/Comments"; +import { LinkItem } from './components/LinkItem'; +import { LinkWindow } from './LinkWindow'; +import { Comments } from '~/views/components/Comments'; -import "./css/custom.css"; +import './css/custom.css'; +import { Association } from '@urbit/api/metadata'; const emptyMeasure = () => {}; @@ -38,11 +37,11 @@ export function LinkResource(props: LinkResourceProps) { history } = props; - const rid = association.resource; + const rid = association.resource; const relativePath = (p: string) => `${baseUrl}/resource/link${rid}${p}`; - const [, , ship, name] = rid.split("/"); + const [, , ship, name] = rid.split('/'); const resourcePath = `${ship.slice(1)}/${name}`; const resource = associations.graph[rid] ? associations.graph[rid] @@ -58,7 +57,7 @@ export function LinkResource(props: LinkResourceProps) { const resourceUrl = `${baseUrl}/resource/link${rid}`; if (!graph) { - return

; + return
; } return ( @@ -66,7 +65,7 @@ export function LinkResource(props: LinkResourceProps) { { return ( { const index = bigInt(props.match.params.index); const editCommentId = props.match.params.commentId || null; @@ -95,7 +94,7 @@ export function LinkResource(props: LinkResourceProps) { return
Malformed URL
; } - const node = !!graph ? graph.get(index) : null; + const node = graph ? graph.get(index) : null; if (!node) { return Not found; @@ -106,7 +105,7 @@ export function LinkResource(props: LinkResourceProps) { return ( - {"<- Back"} + {'<- Back'} (); const fetchLinks = useCallback( async (newer: boolean) => { @@ -44,18 +42,19 @@ export function LinkWindow(props: LinkWindowProps) { useEffect(() => { const list = virtualList?.current; - if(!list) return; + if(!list) +return; list.calculateVisibleItems(); }, [graph.size]); const first = graph.peekLargest()?.[0]; const [,,ship, name] = association.resource.split('/'); - const canWrite = isWriter(props.group, association.resource) + const canWrite = isWriter(props.group, association.resource); const style = useMemo(() => ({ - height: "100%", - width: "100%", + height: '100%', + width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' @@ -76,7 +75,7 @@ export function LinkWindow(props: LinkWindowProps) { return ( (virtualList.current = l ?? undefined)} + ref={l => (virtualList.current = l ?? undefined)} origin="top" style={style} onStartReached={() => {}} @@ -86,11 +85,12 @@ export function LinkWindow(props: LinkWindowProps) { renderer={({ index, measure, scrollWindow }) => { const node = graph.get(index); const post = node?.post; - if (!node || !post) return null; + if (!node || !post) +return null; const linkProps = { ...props, node, - measure, + measure }; if(canWrite && index.eq(first ?? bigInt.zero)) { return ( @@ -100,7 +100,7 @@ export function LinkWindow(props: LinkWindowProps) { - ) + ); } return ; }} diff --git a/pkg/interface/src/views/apps/links/components/LinkItem.tsx b/pkg/interface/src/views/apps/links/components/LinkItem.tsx index 9a29614f5..8934416b6 100644 --- a/pkg/interface/src/views/apps/links/components/LinkItem.tsx +++ b/pkg/interface/src/views/apps/links/components/LinkItem.tsx @@ -1,12 +1,12 @@ -import React, { useState, useEffect, useRef, useCallback } from 'react'; +import React, { useState, useEffect, useRef, useCallback, ReactElement } from 'react'; import { Link } from 'react-router-dom'; + import { Row, Col, Anchor, Box, Text, Icon, Action } from '@tlon/indigo-react'; +import { GraphNode, Group, Rolodex, Unreads } from '@urbit/api'; import { writeText } from '~/logic/lib/util'; import Author from '~/views/components/Author'; - import { roleForShip } from '~/logic/lib/group'; -import { Contacts, GraphNode, Group, Rolodex, Unreads } from '~/types'; import GlobalApi from '~/logic/api/global'; import { Dropdown } from '~/views/components/Dropdown'; import RemoteContent from '~/views/components/RemoteContent'; @@ -22,7 +22,7 @@ interface LinkItemProps { measure: (el: any) => void; } -export const LinkItem = (props: LinkItemProps) => { +export const LinkItem = (props: LinkItemProps): ReactElement => { const { node, resource, @@ -46,7 +46,7 @@ export const LinkItem = (props: LinkItemProps) => { // FF will only update on next tick setTimeout(() => { console.log(remoteRef.current); - if(document.activeElement instanceof HTMLIFrameElement + if(document.activeElement instanceof HTMLIFrameElement && remoteRef?.current?.containerRef?.contains(document.activeElement)) { markRead(); } @@ -55,8 +55,7 @@ export const LinkItem = (props: LinkItemProps) => { window.addEventListener('blur', onBlur); return () => { window.removeEventListener('blur', onBlur); - } - + }; }, [markRead]); const URLparser = new RegExp( @@ -94,11 +93,9 @@ export const LinkItem = (props: LinkItemProps) => { const commColor = (props.unreads.graph?.[appPath]?.[`/${index}`]?.unreads ?? 0) > 0 ? 'blue' : 'gray'; const isUnread = props.unreads.graph?.[appPath]?.['/']?.unreads?.has(node.post.index); - - const onMeasure = useCallback(() => { ref.current && measure(ref.current); - }, [ref.current, measure]) + }, [ref.current, measure]); useEffect(() => { onMeasure(); @@ -145,7 +142,8 @@ export const LinkItem = (props: LinkItemProps) => { alignSelf: 'center', style: { textOverflow: 'ellipsis', whiteSpace: 'pre', width: '100%' }, p: 2 - }} /> + }} + /> @@ -191,7 +189,7 @@ export const LinkItem = (props: LinkItemProps) => { } } - > + > diff --git a/pkg/interface/src/views/apps/links/components/LinkSubmit.tsx b/pkg/interface/src/views/apps/links/components/LinkSubmit.tsx index 2eaba4555..c9679062a 100644 --- a/pkg/interface/src/views/apps/links/components/LinkSubmit.tsx +++ b/pkg/interface/src/views/apps/links/components/LinkSubmit.tsx @@ -1,22 +1,22 @@ -import { BaseInput, Box, Button, LoadingSpinner, Text } from "@tlon/indigo-react"; -import React, { useCallback, useState } from "react"; -import GlobalApi from "~/logic/api/global"; -import { useFileDrag } from "~/logic/lib/useDrag"; -import useS3 from "~/logic/lib/useS3"; -import { S3State } from "~/types"; -import SubmitDragger from "~/views/components/SubmitDragger"; -import { createPost } from "~/logic/api/graph"; -import { hasProvider } from "oembed-parser"; +import { BaseInput, Box, Button, LoadingSpinner, Text } from '@tlon/indigo-react'; +import React, { useCallback, useState } from 'react'; +import GlobalApi from '~/logic/api/global'; +import { useFileDrag } from '~/logic/lib/useDrag'; +import useS3 from '~/logic/lib/useS3'; +import { S3State } from '@urbit/api'; +import SubmitDragger from '~/views/components/SubmitDragger'; +import { createPost } from '~/logic/api/graph'; +import { hasProvider } from 'oembed-parser'; interface LinkSubmitProps { api: GlobalApi; s3: S3State; name: string; ship: string; -}; +} const LinkSubmit = (props: LinkSubmitProps) => { - let { canUpload, uploadDefault, uploading, promptUpload } = useS3(props.s3); + const { canUpload, uploadDefault, uploading, promptUpload } = useS3(props.s3); const [submitFocused, setSubmitFocused] = useState(false); const [urlFocused, setUrlFocused] = useState(false); @@ -100,7 +100,7 @@ const LinkSubmit = (props: LinkSubmitProps) => { const onLinkChange = (linkValue: string) => { setLinkValueHook(linkValue); - const link = validateLink(linkValue) + const link = validateLink(linkValue); setLinkValid(link); }; @@ -133,7 +133,7 @@ const LinkSubmit = (props: LinkSubmitProps) => { px={2} pt={2} style={{ pointerEvents: 'none' }} - >{canUpload + >{canUpload ? <> Drop or{' '} { zIndex={9} alignItems="center" justifyContent="center" - > + > } {dragging && } @@ -223,4 +223,4 @@ const LinkSubmit = (props: LinkSubmitProps) => { ); }; -export default LinkSubmit; \ No newline at end of file +export default LinkSubmit; diff --git a/pkg/interface/src/views/apps/notifications/graph.tsx b/pkg/interface/src/views/apps/notifications/graph.tsx index 7517b0b50..f289f366c 100644 --- a/pkg/interface/src/views/apps/notifications/graph.tsx +++ b/pkg/interface/src/views/apps/notifications/graph.tsx @@ -1,31 +1,29 @@ -import React, { ReactNode, useCallback } from "react"; -import moment from "moment"; -import { Row, Box, Col, Text, Anchor, Icon, Action } from "@tlon/indigo-react"; -import { Link, useHistory } from "react-router-dom"; -import _ from "lodash"; +import React, { ReactElement, useCallback } from 'react'; +import moment from 'moment'; +import _ from 'lodash'; +import { useHistory } from 'react-router-dom'; +import styled from 'styled-components'; + +import { Row, Box, Col, Text, Anchor, Icon, Action } from '@tlon/indigo-react'; import { - Post, GraphNotifIndex, GraphNotificationContents, Associations, - Content, Rolodex, - Groups, -} from "~/types"; -import { Header } from "./header"; -import { cite, deSig, pluralize } from "~/logic/lib/util"; -import { Sigil } from "~/logic/lib/sigil"; -import RichText from "~/views/components/RichText"; -import GlobalApi from "~/logic/api/global"; -import ReactMarkdown from "react-markdown"; -import { getSnippet } from "~/logic/lib/publish"; -import styled from "styled-components"; -import {MentionText} from "~/views/components/MentionText"; -import ChatMessage, {MessageWithoutSigil} from "../chat/components/ChatMessage"; + Groups +} from '@urbit/api'; -function getGraphModuleIcon(module: string) { - if (module === "link") { - return "Collection"; +import { Header } from './header'; +import { cite, deSig, pluralize } from '~/logic/lib/util'; +import { Sigil } from '~/logic/lib/sigil'; +import GlobalApi from '~/logic/api/global'; +import { getSnippet } from '~/logic/lib/publish'; +import { MentionText } from '~/views/components/MentionText'; +import { MessageWithoutSigil } from '../chat/components/ChatMessage'; + +function getGraphModuleIcon(module: string): string { + if (module === 'link') { + return 'Collection'; } return _.capitalize(module); } @@ -34,32 +32,32 @@ const FilterBox = styled(Box)` background: linear-gradient( to bottom, transparent, - ${(p) => p.theme.colors.white} + ${p => p.theme.colors.white} ); `; -function describeNotification(description: string, plural: boolean) { +function describeNotification(description: string, plural: boolean): string { switch (description) { - case "link": - return `added ${pluralize("new link", plural)} to`; - case "comment": - return `left ${pluralize("comment", plural)} on`; - case "edit-comment": - return `updated ${pluralize("comment", plural)} on`; - case "note": - return `posted ${pluralize("note", plural)} to`; - case "edit-note": - return `updated ${pluralize("note", plural)} in`; - case "mention": - return "mentioned you on"; - case "message": - return `sent ${pluralize("message", plural)} to`; + case 'link': + return `added ${pluralize('new link', plural)} to`; + case 'comment': + return `left ${pluralize('comment', plural)} on`; + case 'edit-comment': + return `updated ${pluralize('comment', plural)} on`; + case 'note': + return `posted ${pluralize('note', plural)} to`; + case 'edit-note': + return `updated ${pluralize('note', plural)} in`; + case 'mention': + return 'mentioned you on'; + case 'message': + return `sent ${pluralize('message', plural)} to`; default: return description; } } -const GraphUrl = ({ url, title }) => ( +const GraphUrl = ({ url, title }): ReactElement => ( @@ -68,10 +66,10 @@ const GraphUrl = ({ url, title }) => ( ); -const GraphNodeContent = ({ group, post, contacts, mod, description, index, remoteContentPolicy }) => { +const GraphNodeContent = ({ group, post, contacts, mod, index }): ReactElement => { const { contents } = post; - const idx = index.slice(1).split("/"); - if (mod === "link") { + const idx = index.slice(1).split('/'); + if (mod === 'link') { if (idx.length === 1) { const [{ text }, { url }] = contents; return ; @@ -80,20 +78,20 @@ const GraphNodeContent = ({ group, post, contacts, mod, description, index, remo content={contents} contacts={contacts} group={group} - /> + />; } return null; } - if (mod === "publish") { - if (idx[1] === "2") { + if (mod === 'publish') { + if (idx[1] === '2') { return - } else if (idx[1] === "1") { + />; + } else if (idx[1] === '1') { const [{ text: header }, { text: body }] = contents; const snippet = getSnippet(body); return ( @@ -123,42 +121,41 @@ const GraphNodeContent = ({ group, post, contacts, mod, description, index, remo flexShrink={0} flexGrow={1} flexWrap="wrap" - > + > {}} group={group} contacts={contacts} groups={{}} - associations={{ graph: {}, groups: {}}} + associations={{ graph: {}, groups: {} }} msg={post} fontSize='0' pt='2' /> ); - } return null; }; -function getNodeUrl(mod: string, hidden: boolean, groupPath: string, graph: string, index: string) { +function getNodeUrl(mod: string, hidden: boolean, groupPath: string, graph: string, index: string): string { if (hidden && mod === 'chat') { groupPath = '/messages'; } else if (hidden) { groupPath = '/home'; } const graphUrl = `/~landscape${groupPath}/resource/${mod}${graph}`; - const idx = index.slice(1).split("/"); - if (mod === "publish") { + const idx = index.slice(1).split('/'); + if (mod === 'publish') { const [noteId] = idx; return `${graphUrl}/note/${noteId}`; - } else if (mod === "link") { + } else if (mod === 'link') { const [linkId] = idx; return `${graphUrl}/${linkId}`; } else if (mod === 'chat') { return graphUrl; } - return ""; + return ''; } const GraphNode = ({ post, @@ -174,9 +171,7 @@ const GraphNode = ({ read, onRead, showContact = false, - remoteContentPolicy -}) => { - const { contents } = post; +}): ReactElement => { author = deSig(author); const history = useHistory(); @@ -185,7 +180,7 @@ const GraphNode = ({ ship={`~${author}`} size={16} icon - color={`#000000`} + color={'#000000'} classes="mix-blend-diff" padding={2} /> @@ -212,12 +207,12 @@ const GraphNode = ({ alignItems="center" p="1" backgroundColor="white" - > + > {cite(author)} - {moment(time).format("HH:mm")} + {moment(time).format('HH:mm')} } @@ -249,7 +244,7 @@ export function GraphNotification(props: { }) { const { contents, index, read, time, api, timebox, groups } = props; - const authors = _.map(contents, "author"); + const authors = _.map(contents, 'author'); const { graph, group } = index; const icon = getGraphModuleIcon(index.module); const desc = describeNotification(index.description, contents.length !== 1); @@ -259,7 +254,7 @@ export function GraphNotification(props: { return; } - return api.hark["read"](timebox, { graph: index }); + return api.hark['read'](timebox, { graph: index }); }, [api, timebox, index, read]); return ( @@ -284,7 +279,7 @@ return ( author={content.author} contacts={props.contacts} mod={index.module} - time={content?.["time-sent"]} + time={content?.['time-sent']} description={index.description} index={content.index} graph={graph} diff --git a/pkg/interface/src/views/apps/notifications/group.tsx b/pkg/interface/src/views/apps/notifications/group.tsx index 2b5a66f8d..782069508 100644 --- a/pkg/interface/src/views/apps/notifications/group.tsx +++ b/pkg/interface/src/views/apps/notifications/group.tsx @@ -1,44 +1,34 @@ -import React, { ReactNode, useCallback } from "react"; -import moment from "moment"; -import { Row, Box, Col, Text, Anchor, Icon, Action } from "@tlon/indigo-react"; -import _ from "lodash"; -import { NotificationProps } from "./types"; +import React, { ReactElement, useCallback } from 'react'; +import _ from 'lodash'; + +import { Col } from '@tlon/indigo-react'; import { - Post, - GraphNotifIndex, - GraphNotificationContents, Associations, - Content, - IndexedNotification, GroupNotificationContents, GroupNotifIndex, GroupUpdate, - Rolodex, -} from "~/types"; -import { Header } from "./header"; -import { cite, deSig } from "~/logic/lib/util"; -import { Sigil } from "~/logic/lib/sigil"; -import RichText from "~/views/components/RichText"; -import GlobalApi from "~/logic/api/global"; -import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction"; + Rolodex +} from '@urbit/api'; +import { Header } from './header'; +import GlobalApi from '~/logic/api/global'; function describeNotification(description: string, plural: boolean) { switch (description) { - case "add-members": - return "joined"; - case "remove-members": - return "left"; + case 'add-members': + return 'joined'; + case 'remove-members': + return 'left'; default: return description; } } -function getGroupUpdateParticipants(update: GroupUpdate) { - if ("addMembers" in update) { +function getGroupUpdateParticipants(update: GroupUpdate): string[] { + if ('addMembers' in update) { return update.addMembers.ships; } - if ("removeMembers" in update) { + if ('removeMembers' in update) { return update.removeMembers.ships; } return []; @@ -56,7 +46,7 @@ interface GroupNotificationProps { api: GlobalApi; } -export function GroupNotification(props: GroupNotificationProps) { +export function GroupNotification(props: GroupNotificationProps): ReactElement { const { contents, index, read, time, api, timebox, associations } = props; const authors = _.flatten(_.map(contents, getGroupUpdateParticipants)); @@ -68,7 +58,7 @@ export function GroupNotification(props: GroupNotificationProps) { if (props.archived) { return; } - const func = read ? "unread" : "read"; + const func = read ? 'unread' : 'read'; return api.hark[func](timebox, { group: index }); }, [api, timebox, index, read]); diff --git a/pkg/interface/src/views/apps/notifications/header.tsx b/pkg/interface/src/views/apps/notifications/header.tsx index 7d7644b1a..528460f13 100644 --- a/pkg/interface/src/views/apps/notifications/header.tsx +++ b/pkg/interface/src/views/apps/notifications/header.tsx @@ -1,17 +1,19 @@ -import React from "react"; -import { Text as NormalText, Row, Icon, Rule, Box } from "@tlon/indigo-react"; -import f from "lodash/fp"; -import _ from "lodash"; -import moment from "moment"; -import { PropFunc } from "~/types/util"; -import { getContactDetails, useShowNickname } from "~/logic/lib/util"; -import { Associations, Contact, Contacts, Rolodex } from "~/types"; +import React, { ReactElement } from 'react'; +import f from 'lodash/fp'; +import _ from 'lodash'; +import moment from 'moment'; + +import { Text as NormalText, Row, Icon, Rule } from '@tlon/indigo-react'; +import { Associations, Contact, Contacts, Rolodex } from '@urbit/api'; + +import { PropFunc } from '~/types/util'; +import { useShowNickname } from '~/logic/lib/util'; const Text = (props: PropFunc) => ( ); -function Author(props: { patp: string; contacts: Contacts; last?: boolean }) { +function Author(props: { patp: string; contacts: Contacts; last?: boolean }): ReactElement { const contact: Contact | undefined = props.contacts?.[props.patp]; const showNickname = useShowNickname(contact); @@ -20,7 +22,7 @@ function Author(props: { patp: string; contacts: Contacts; last?: boolean }) { return ( {name} - {!props.last && ", "} + {!props.last && ', '} ); } @@ -36,7 +38,7 @@ export function Header(props: { time: number; read: boolean; associations: Associations; -} & PropFunc ) { +} & PropFunc ): ReactElement { const { description, channel, group, moduleIcon, read } = props; const contacts = props.contacts[group] || {}; @@ -50,17 +52,17 @@ export function Header(props: { const last = lent - 1 === parseInt(idx, 10); return ; }), - (auths) => ( + auths => ( {auths} {authors.length > 3 && - ` and ${authors.length - 3} other${authors.length === 4 ? "" : "s"}`} + ` and ${authors.length - 3} other${authors.length === 4 ? '' : 's'}`} ) )(authors); - const time = moment(props.time).format("HH:mm"); + const time = moment(props.time).format('HH:mm'); const groupTitle = props.associations.groups?.[props.group]?.metadata?.title; @@ -84,8 +86,8 @@ export function Header(props: { {authorDesc} {description} - {!!moduleIcon && } - {!!channel && {channelTitle}} + {Boolean(moduleIcon) && } + {Boolean(channel) && {channelTitle}} {groupTitle && <> diff --git a/pkg/interface/src/views/apps/notifications/inbox.tsx b/pkg/interface/src/views/apps/notifications/inbox.tsx index a5d5cf382..2f05b736e 100644 --- a/pkg/interface/src/views/apps/notifications/inbox.tsx +++ b/pkg/interface/src/views/apps/notifications/inbox.tsx @@ -1,22 +1,28 @@ -import React, { useEffect, useCallback, useRef, useState } from "react"; -import f from "lodash/fp"; -import _ from "lodash"; -import { Icon, Col, Center, Row, Box, Text, Anchor, Rule, LoadingSpinner } from "@tlon/indigo-react"; -import moment from "moment"; -import { Notifications, Rolodex, Timebox, IndexedNotification, Groups, joinProgress, JoinRequests, GroupNotificationsConfig, NotificationGraphConfig } from "~/types"; -import { MOMENT_CALENDAR_DATE, daToUnix, resourceAsPath } from "~/logic/lib/util"; -import { BigInteger } from "big-integer"; -import GlobalApi from "~/logic/api/global"; -import { Notification } from "./notification"; -import { Associations } from "~/types"; -import { InviteItem } from '~/views/components/Invite'; -import { useWaitForProps } from "~/logic/lib/useWaitForProps"; -import { useHistory } from "react-router-dom"; -import {useModal} from "~/logic/lib/useModal"; -import {JoinGroup} from "~/views/landscape/components/JoinGroup"; -import {JoiningStatus} from "./joining"; -import {Invites} from "./invites"; -import {useLazyScroll} from "~/logic/lib/useLazyScroll"; +import React, { useEffect, useCallback, useRef } from 'react'; +import f from 'lodash/fp'; +import _ from 'lodash'; +import moment from 'moment'; +import { BigInteger } from 'big-integer'; + +import { Col, Center, Box, Text, LoadingSpinner } from '@tlon/indigo-react'; +import { + Associations, + Notifications, + Rolodex, + Timebox, + IndexedNotification, + Groups, + JoinRequests, + GroupNotificationsConfig, + NotificationGraphConfig, + Invites as InviteType +} from '@urbit/api'; + +import { MOMENT_CALENDAR_DATE, daToUnix } from '~/logic/lib/util'; +import GlobalApi from '~/logic/api/global'; +import { Notification } from './notification'; +import { Invites } from './invites'; +import { useLazyScroll } from '~/logic/lib/useLazyScroll'; type DatedTimebox = [BigInteger, Timebox]; @@ -25,12 +31,12 @@ function filterNotification(associations: Associations, groups: string[]) { return () => true; } return (n: IndexedNotification) => { - if ("graph" in n.index) { + if ('graph' in n.index) { const { group } = n.index.graph; - return groups.findIndex((g) => group === g) !== -1; - } else if ("group" in n.index) { + return groups.findIndex(g => group === g) !== -1; + } else if ('group' in n.index) { const { group } = n.index.group; - return groups.findIndex((g) => group === g) !== -1; + return groups.findIndex(g => group === g) !== -1; } return true; }; @@ -46,7 +52,7 @@ export default function Inbox(props: { associations: Associations; contacts: Rolodex; filter: string[]; - invites: any; + invites: InviteType; pendingJoin: JoinRequests; notificationsGroupConfig: GroupNotificationsConfig; notificationsGraphConfig: NotificationGraphConfig; @@ -70,30 +76,30 @@ export default function Inbox(props: { const calendar = { ...MOMENT_CALENDAR_DATE, sameDay: function (now) { if (this.subtract(6, 'hours').isBefore(now)) { - return "[Earlier Today]"; + return '[Earlier Today]'; } else { return MOMENT_CALENDAR_DATE.sameDay; } } }; - let notificationsByDay = f.flow( + const notificationsByDay = f.flow( f.map(([date, nots]) => [ date, - nots.filter(filterNotification(associations, props.filter)), + nots.filter(filterNotification(associations, props.filter)) ]), f.groupBy(([d]) => { const date = moment(daToUnix(d)); if (moment().subtract(6, 'hours').isBefore(date)) { return 'latest'; } else { - return date.format("YYYYMMDD"); + return date.format('YYYYMMDD'); } - }), + }) )(notifications); const notificationsByDayMap = new Map( - Object.keys(notificationsByDay).map(timebox => { + Object.keys(notificationsByDay).map((timebox) => { return [timebox, notificationsByDay[timebox]]; }) ); @@ -105,13 +111,12 @@ export default function Inbox(props: { }, [api]); const { isDone, isLoading } = useLazyScroll( - scrollRef, + scrollRef, 0.2, _.flatten(notifications).length, loadMore ); - return ( @@ -123,7 +128,7 @@ export default function Inbox(props: { label={day === 'latest' ? 'Today' : moment(day).calendar(null, calendar)} timeboxes={timeboxes} contacts={props.contacts} - archive={!!props.showArchive} + archive={Boolean(props.showArchive)} associations={props.associations} api={api} groups={props.groups} @@ -142,7 +147,7 @@ export default function Inbox(props: { )} - + ); } @@ -167,9 +172,8 @@ function DaySection({ associations, api, groupConfig, - graphConfig, + graphConfig }) { - const lent = timeboxes.map(([,nots]) => nots.length).reduce(f.add, 0); if (lent === 0 || timeboxes.length === 0) { return null; diff --git a/pkg/interface/src/views/apps/notifications/invites.tsx b/pkg/interface/src/views/apps/notifications/invites.tsx index a856af68d..829a383bc 100644 --- a/pkg/interface/src/views/apps/notifications/invites.tsx +++ b/pkg/interface/src/views/apps/notifications/invites.tsx @@ -1,15 +1,12 @@ -import React, { useCallback, useState } from "react"; +import React, { ReactElement } from 'react'; import _ from 'lodash'; -import { Box, Row, Col } from "@tlon/indigo-react"; -import GlobalApi from "~/logic/api/global"; -import { Invites as IInvites, Associations, Invite, JoinRequests, Groups, Contacts, AppInvites, JoinProgress } from "~/types"; -import { resourceAsPath, alphabeticalOrder } from "~/logic/lib/util"; -import { useHistory } from "react-router-dom"; -import { useWaitForProps } from "~/logic/lib/useWaitForProps"; -import InviteItem from "~/views/components/Invite"; -import {JoiningStatus} from "./joining"; -import {useModal} from "~/logic/lib/useModal"; -import {JoinGroup} from "~/views/landscape/components/JoinGroup"; + +import { Col } from '@tlon/indigo-react'; +import { Invites as IInvites, Associations, Invite, JoinRequests, Groups, Contacts, AppInvites, JoinProgress } from '@urbit/api'; + +import GlobalApi from '~/logic/api/global'; +import { resourceAsPath, alphabeticalOrder } from '~/logic/lib/util'; +import InviteItem from '~/views/components/Invite'; interface InvitesProps { api: GlobalApi; @@ -26,50 +23,18 @@ interface InviteRef { invite: Invite; } -export function Invites(props: InvitesProps) { +export function Invites(props: InvitesProps): ReactElement { const { api, invites, pendingJoin } = props; - const [selected, setSelected] = useState<[string, string, Invite] | undefined>() - - const acceptInvite = ( - app: string, - uid: string, - invite: Invite - ) => async () => { - setSelected([app, uid, invite]); - showModal(); - }; - - const declineInvite = useCallback( - (app: string, uid: string) => () => api.invite.decline(app, uid), - [api] - ); - - const { modal, showModal } = useModal({ modal: () => { - const [app, uid, invite] = selected!; - const autojoin = `~${invite.resource.ship}/${invite.resource.name}`; - return ( - - )}}); const inviteArr: InviteRef[] = _.reduce(invites, (acc: InviteRef[], val: AppInvites, app: string) => { const appInvites = _.reduce(val, (invs: InviteRef[], invite: Invite, uid: string) => { return [...invs, { invite, uid, app }]; - }, []); return [...acc, ...appInvites]; }, []); - const invitesAndStatus: { [rid: string]: JoinProgress | InviteRef } = - {..._.keyBy(inviteArr, ({ invite }) => resourceAsPath(invite.resource)), ...props.pendingJoin }; - - + const invitesAndStatus: { [rid: string]: JoinProgress | InviteRef } = + { ..._.keyBy(inviteArr, ({ invite }) => resourceAsPath(invite.resource)), ...props.pendingJoin }; return ( { + .map((resource) => { const inviteOrStatus = invitesAndStatus[resource]; if(typeof inviteOrStatus === 'string') { return ( @@ -93,10 +58,10 @@ export function Invites(props: InvitesProps) { groups={props.groups} associations={props.associations} resource={resource} - pendingJoin={pendingJoin} - api={api} /> - ) - + pendingJoin={pendingJoin} + api={api} + /> + ); } else { const { app, uid, invite } = inviteOrStatus; console.log(inviteOrStatus); @@ -107,13 +72,13 @@ export function Invites(props: InvitesProps) { invite={invite} app={app} uid={uid} - pendingJoin={pendingJoin} + pendingJoin={pendingJoin} resource={resource} contacts={props.contacts} groups={props.groups} associations={props.associations} /> - ) + ); } })} diff --git a/pkg/interface/src/views/apps/notifications/joining.tsx b/pkg/interface/src/views/apps/notifications/joining.tsx index 7b2295fb2..576371205 100644 --- a/pkg/interface/src/views/apps/notifications/joining.tsx +++ b/pkg/interface/src/views/apps/notifications/joining.tsx @@ -1,35 +1,32 @@ -import React, { useState, useEffect } from "react"; -import { Col, Row, Text, SegmentedProgressBar, Box } from "@tlon/indigo-react"; -import GlobalApi from "~/logic/api/global"; +import React from 'react'; +import { Row, Text, SegmentedProgressBar, Box } from '@tlon/indigo-react'; import { JoinProgress, joinProgress, - MetadataUpdatePreview, - joinError, -} from "~/types"; -import { clamp } from "~/logic/lib/util"; + joinError +} from '@urbit/api'; interface JoiningStatusProps { status: JoinProgress; } const description: string[] = [ - "Attempting to contact host", - "Retrieving data", - "Finished join", - "Unable to join, you do not have the correct permissions", - "Internal error, please file an issue", + 'Attempting to contact host', + 'Retrieving data', + 'Finished join', + 'Unable to join, you do not have the correct permissions', + 'Internal error, please file an issue' ]; export function JoiningStatus(props: JoiningStatusProps) { const { status } = props; const current = joinProgress.indexOf(status); - const desc = description?.[current] || ""; + const desc = description?.[current] || ''; const isError = joinError.indexOf(status as any) !== -1; return ( - + {desc} diff --git a/pkg/interface/src/views/apps/notifications/metadata.tsx b/pkg/interface/src/views/apps/notifications/metadata.tsx index b5643f359..20d5ac0dc 100644 --- a/pkg/interface/src/views/apps/notifications/metadata.tsx +++ b/pkg/interface/src/views/apps/notifications/metadata.tsx @@ -1,8 +1,8 @@ -import React from "react"; -import { Box } from "@tlon/indigo-react"; +import React from 'react'; +import { Box } from '@tlon/indigo-react'; -import { MetadataBody, NotificationProps } from "./types"; -import { Header } from "./header"; +import { MetadataBody, NotificationProps } from './types'; +import { Header } from './header'; function getInvolvedUsers(body: MetadataBody) { return []; @@ -10,22 +10,22 @@ function getInvolvedUsers(body: MetadataBody) { function getDescription(body: MetadataBody) { const b = body.metadata; - if ("new" in b) { - return "created"; - } else if ("changedTitle" in b) { - return "changed the title to"; - } else if ("changedDescription" in b) { - return "changed the description to"; - } else if ("changedColor" in b) { - return "changed the color to"; - } else if ("deleted" in b) { - return "deleted"; + if ('new' in b) { + return 'created'; + } else if ('changedTitle' in b) { + return 'changed the title to'; + } else if ('changedDescription' in b) { + return 'changed the description to'; + } else if ('changedColor' in b) { + return 'changed the color to'; + } else if ('deleted' in b) { + return 'deleted'; } else { - throw new Error("bad metadata frond"); + throw new Error('bad metadata frond'); } } -export function MetadataNotification(props: NotificationProps<"metadata">) { +export function MetadataNotification(props: NotificationProps<'metadata'>) { const { unread } = props; const description = getDescription(unread.unreads[0].body); diff --git a/pkg/interface/src/views/apps/notifications/notification.tsx b/pkg/interface/src/views/apps/notifications/notification.tsx index cc55d3814..8c550257d 100644 --- a/pkg/interface/src/views/apps/notifications/notification.tsx +++ b/pkg/interface/src/views/apps/notifications/notification.tsx @@ -1,6 +1,6 @@ -import React, { ReactNode, useCallback, useMemo, useState } from "react"; -import { Row, Box } from "@tlon/indigo-react"; -import _ from "lodash"; +import React, { ReactNode, useCallback, useMemo, useState } from 'react'; +import { Row, Box } from '@tlon/indigo-react'; +import _ from 'lodash'; import { GraphNotificationContents, IndexedNotification, @@ -9,15 +9,15 @@ import { GroupNotificationsConfig, Groups, Associations, - Contacts, -} from "~/types"; -import GlobalApi from "~/logic/api/global"; -import { getParentIndex } from "~/logic/lib/notification"; -import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction"; -import { GroupNotification } from "./group"; -import { GraphNotification } from "./graph"; -import { BigInteger } from "big-integer"; -import { useHovering } from "~/logic/lib/util"; + Contacts +} from '@urbit/api'; +import GlobalApi from '~/logic/api/global'; +import { getParentIndex } from '~/logic/lib/notification'; +import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction'; +import { GroupNotification } from './group'; +import { GraphNotification } from './graph'; +import { BigInteger } from 'big-integer'; +import { useHovering } from '~/logic/lib/util'; interface NotificationProps { notification: IndexedNotification; @@ -37,7 +37,7 @@ function getMuted( graphs: NotificationGraphConfig ) { const { index, notification } = idxNotif; - if ("graph" in idxNotif.index) { + if ('graph' in idxNotif.index) { const { graph } = idxNotif.index.graph; if(!('graph' in notification.contents)) { throw new Error(); @@ -46,11 +46,11 @@ function getMuted( return _.findIndex( graphs?.watching || [], - (g) => g.graph === graph && g.index === parent + g => g.graph === graph && g.index === parent ) === -1; } - if ("group" in index) { - return _.findIndex(groups || [], (g) => g === index.group.group) === -1; + if ('group' in index) { + return _.findIndex(groups || [], g => g === index.group.group) === -1; } return false; } @@ -77,13 +77,13 @@ function NotificationWrapper(props: { ); const onChangeMute = useCallback(async () => { - const func = isMuted ? "unmute" : "mute"; + const func = isMuted ? 'unmute' : 'mute'; return api.hark[func](notif); }, [notif, api, isMuted]); const { hovering, bind } = useHovering(); - const changeMuteDesc = isMuted ? "Unmute" : "Mute"; + const changeMuteDesc = isMuted ? 'Unmute' : 'Mute'; return ( ); - if ("graph" in notification.index) { + if ('graph' in notification.index) { const index = notification.index.graph; const c: GraphNotificationContents = (contents as any).graph; @@ -147,7 +147,7 @@ export function Notification(props: NotificationProps) { ); } - if ("group" in notification.index) { + if ('group' in notification.index) { const index = notification.index.group; const c: GroupNotificationContents = (contents as any).group; return ( diff --git a/pkg/interface/src/views/apps/notifications/notifications.tsx b/pkg/interface/src/views/apps/notifications/notifications.tsx index 87e5764c2..3f9bd7555 100644 --- a/pkg/interface/src/views/apps/notifications/notifications.tsx +++ b/pkg/interface/src/views/apps/notifications/notifications.tsx @@ -1,25 +1,25 @@ -import React, { useCallback, useState, useRef } from "react"; +import React, { useCallback, useState, useRef, ReactElement } from 'react'; import _ from 'lodash'; -import { Box, Col, Text, Row } from "@tlon/indigo-react"; -import { Link, Switch, Route } from "react-router-dom"; -import Helmet from "react-helmet"; +import { Link, Switch, Route } from 'react-router-dom'; +import Helmet from 'react-helmet'; -import { Body } from "~/views/components/Body"; -import { PropFunc } from "~/types/util"; -import Inbox from "./inbox"; -import NotificationPreferences from "./preferences"; -import { Dropdown } from "~/views/components/Dropdown"; -import { Formik } from "formik"; -import { FormikOnBlur } from "~/views/components/FormikOnBlur"; -import GroupSearch from "~/views/components/GroupSearch"; -import {useTutorialModal} from "~/views/components/useTutorialModal"; +import { Box, Col, Text, Row } from '@tlon/indigo-react'; -const baseUrl = "/~notifications"; +import { Body } from '~/views/components/Body'; +import { PropFunc } from '~/types/util'; +import Inbox from './inbox'; +import NotificationPreferences from './preferences'; +import { Dropdown } from '~/views/components/Dropdown'; +import { FormikOnBlur } from '~/views/components/FormikOnBlur'; +import GroupSearch from '~/views/components/GroupSearch'; +import { useTutorialModal } from '~/views/components/useTutorialModal'; + +const baseUrl = '/~notifications'; const HeaderLink = React.forwardRef(( props: PropFunc & { view?: string; current: string }, ref -) => { +): ReactElement => { const { current, view, ...textProps } = props; const to = view ? `${baseUrl}/${view}` : baseUrl; const active = view ? current === view : !current; @@ -35,7 +35,7 @@ interface NotificationFilter { groups: string[]; } -export default function NotificationsScreen(props: any) { +export default function NotificationsScreen(props: any): ReactElement { const relativePath = (p: string) => baseUrl + p; const [filter, setFilter] = useState({ groups: [] }); @@ -43,20 +43,20 @@ export default function NotificationsScreen(props: any) { setFilter({ groups }); }; const onReadAll = useCallback(() => { - props.api.hark.readAll() + props.api.hark.readAll(); }, []); const groupFilterDesc = filter.groups.length === 0 - ? "All" + ? 'All' : filter.groups - .map((g) => props.associations?.groups?.[g]?.metadata?.title) - .join(", "); + .map(g => props.associations?.groups?.[g]?.metadata?.title) + .join(', '); const anchorRef = useRef(null); useTutorialModal('notifications', true, anchorRef.current); return ( { const { view } = routeProps.match.params; return ( @@ -89,7 +89,8 @@ export default function NotificationsScreen(props: any) { + justifyContent="space-between" + > - {view === "preferences" && ( + {view === 'preferences' && ( ) => { console.log(values); try { - let promises: Promise[] = []; + const promises: Promise[] = []; if (values.mentions !== graphConfig.mentions) { promises.push(api.hark.setMentions(values.mentions)); } @@ -46,7 +45,7 @@ export default function NotificationPreferences( promises.push(api.hark.setWatchOnSelf(values.watchOnSelf)); } if (values.dnd !== dnd && !_.isUndefined(values.dnd)) { - promises.push(api.hark.setDoNotDisturb(values.dnd)) + promises.push(api.hark.setDoNotDisturb(values.dnd)); } await Promise.all(promises); diff --git a/pkg/interface/src/views/apps/profile/components/EditProfile.tsx b/pkg/interface/src/views/apps/profile/components/EditProfile.tsx index d43cfbaad..e22f01b29 100644 --- a/pkg/interface/src/views/apps/profile/components/EditProfile.tsx +++ b/pkg/interface/src/views/apps/profile/components/EditProfile.tsx @@ -1,30 +1,25 @@ -import React from "react"; -import * as Yup from "yup"; +import React, { ReactElement } from 'react'; +import * as Yup from 'yup'; import _ from 'lodash'; +import { Formik } from 'formik'; +import { useHistory } from 'react-router-dom'; import { ManagedForm as Form, ManagedTextInputField as Input, ManagedCheckboxField as Checkbox, - Center, Col, - Box, Text, Row, - Button, -} from "@tlon/indigo-react"; -import { Formik, FormikHelpers } from "formik"; -import { useHistory } from "react-router-dom"; - -import { uxToHex } from "~/logic/lib/util"; -import { Sigil } from "~/logic/lib/sigil"; -import { AsyncButton } from "~/views/components/AsyncButton"; -import { ColorInput } from "~/views/components/ColorInput"; -import { ImageInput } from "~/views/components/ImageInput"; -import { MarkdownField } from "~/views/apps/publish/components/MarkdownField"; -import { resourceFromPath } from "~/logic/lib/group"; -import GroupSearch from "~/views/components/GroupSearch"; +} from '@tlon/indigo-react'; +import { uxToHex } from '~/logic/lib/util'; +import { AsyncButton } from '~/views/components/AsyncButton'; +import { ColorInput } from '~/views/components/ColorInput'; +import { ImageInput } from '~/views/components/ImageInput'; +import { MarkdownField } from '~/views/apps/publish/components/MarkdownField'; +import { resourceFromPath } from '~/logic/lib/group'; +import GroupSearch from '~/views/components/GroupSearch'; const formSchema = Yup.object({ nickname: Yup.string(), @@ -45,8 +40,7 @@ const emptyContact = { isPublic: false }; - -export function EditProfile(props: any) { +export function EditProfile(props: any): ReactElement { const { contact, ship, api, isPublic } = props; const history = useHistory(); if (contact) { @@ -58,10 +52,10 @@ export function EditProfile(props: any) { try { await Object.keys(values).reduce((acc, key) => { console.log(key); - const newValue = key !== "color" ? values[key] : uxToHex(values[key]); + const newValue = key !== 'color' ? values[key] : uxToHex(values[key]); if (newValue !== contact[key]) { - if (key === "isPublic") { + if (key === 'isPublic') { return acc.then(() => api.contacts.setPublic(newValue) ); @@ -70,23 +64,22 @@ export function EditProfile(props: any) { console.log(toRemove); const toAdd: string[] = _.difference(newValue, contact?.groups || []); console.log(toAdd); - let promises: Promise[] = []; + const promises: Promise[] = []; promises.concat( toRemove.map(e => - api.contacts.edit(ship, {'remove-group': resourceFromPath(e) }) + api.contacts.edit(ship, { 'remove-group': resourceFromPath(e) }) ) ); promises.concat( toAdd.map(e => - api.contacts.edit(ship, {'add-group': resourceFromPath(e) }) + api.contacts.edit(ship, { 'add-group': resourceFromPath(e) }) ) ); return acc.then(() => Promise.all(promises)); - } else if ( - key !== "last-updated" && - key !== "isPublic" + key !== 'last-updated' && + key !== 'isPublic' ) { return acc.then(() => api.contacts.edit(ship, { [key]: newValue }) @@ -95,7 +88,7 @@ export function EditProfile(props: any) { } return acc; }, Promise.resolve()); - //actions.setStatus({ success: null }); + // actions.setStatus({ success: null }); history.push(`/~profile/${ship}`); } catch (e) { console.error(e); diff --git a/pkg/interface/src/views/apps/profile/components/Profile.tsx b/pkg/interface/src/views/apps/profile/components/Profile.tsx index 0072b591b..c7ed938e0 100644 --- a/pkg/interface/src/views/apps/profile/components/Profile.tsx +++ b/pkg/interface/src/views/apps/profile/components/Profile.tsx @@ -1,26 +1,24 @@ -import React, { useEffect, useRef, useState } from "react"; -import { Sigil } from "~/logic/lib/sigil"; -import { ViewProfile } from './ViewProfile'; -import { EditProfile } from './EditProfile'; -import { SetStatusBarModal } from '~/views/components/SetStatusBarModal'; +import React, { ReactElement, useEffect, useRef, useState } from 'react'; +import { useHistory } from 'react-router-dom'; -import { uxToHex } from "~/logic/lib/util"; import { Center, Box, Row, BaseImage, - StatelessTextInput as Input, - Button, Text } from "@tlon/indigo-react"; + import RichText from '~/views/components/RichText' import useLocalState from "~/logic/state/local"; -import { useHistory } from "react-router-dom"; -import {useTutorialModal} from "~/views/components/useTutorialModal"; +import { Sigil } from '~/logic/lib/sigil'; +import { ViewProfile } from './ViewProfile'; +import { EditProfile } from './EditProfile'; +import { SetStatusBarModal } from '~/views/components/SetStatusBarModal'; +import { uxToHex } from '~/logic/lib/util'; +import { useTutorialModal } from '~/views/components/useTutorialModal'; - -export function Profile(props: any) { +export function Profile(props: any): ReactElement { const { hideAvatars } = useLocalState(({ hideAvatars }) => ({ hideAvatars })); @@ -32,17 +30,13 @@ export function Profile(props: any) { const { contact, nackedContacts, hasLoaded, isPublic, isEdit, ship } = props; const nacked = nackedContacts.has(ship); - const [statusModal, showStatusModal] = useState(false); - - useEffect(() => { if(hasLoaded && !contact && !nacked) { props.api.contacts.retrieve(ship); } - }, [hasLoaded, contact]) + }, [hasLoaded, contact]); - - const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : "#000000"; + const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : '#000000'; const cover = (contact?.cover) ? : ; @@ -59,12 +53,14 @@ export function Profile(props: any) {
+ width="100%" + > + width="100%" + > {ship === `~${window.ship}` ? ( @@ -72,7 +68,10 @@ export function Profile(props: any) { { history.push(`/~profile/${ship}/edit`) }}> + onClick={() => { + history.push(`/~profile/${ship}/edit`); +}} + > Edit Profile + isPublic={isPublic} + /> ) : ( { - setStatus(!!contact ? contact.status : ''); + setStatus(contact ? contact.status : ''); }, [contact]); const editStatus = () => { - api.contacts.edit(ship, {status: _status}); + api.contacts.edit(ship, { status: _status }); if (callback) { callback(); @@ -53,7 +52,8 @@ export function SetStatus(props: any) { color="white" ml={2} width="25%" - onClick={editStatus}> + onClick={editStatus} + > Set Status diff --git a/pkg/interface/src/views/apps/profile/components/ViewProfile.tsx b/pkg/interface/src/views/apps/profile/components/ViewProfile.tsx index cd639b135..939d8aab7 100644 --- a/pkg/interface/src/views/apps/profile/components/ViewProfile.tsx +++ b/pkg/interface/src/views/apps/profile/components/ViewProfile.tsx @@ -1,28 +1,21 @@ -import React, {useEffect, useState} from "react"; +import React from 'react'; import _ from 'lodash'; -import { Sigil } from "~/logic/lib/sigil"; +import { useHistory } from 'react-router-dom'; import { Center, Box, Text, Row, - Button, Col, - LoadingSpinner -} from "@tlon/indigo-react"; -import { AsyncButton } from "~/views/components/AsyncButton"; -import RichText from "~/views/components/RichText"; -import { useHistory } from "react-router-dom"; -import {GroupSummary} from "~/views/landscape/components/GroupSummary"; -import {MetadataUpdatePreview} from "~/types"; -import {GroupLink} from "~/views/components/GroupLink"; -import {lengthOrder} from "~/logic/lib/util"; -import useLocalState from "~/logic/state/local"; +} from '@tlon/indigo-react'; +import RichText from '~/views/components/RichText'; +import { GroupLink } from '~/views/components/GroupLink'; +import { lengthOrder } from '~/logic/lib/util'; +import useLocalState from '~/logic/state/local'; -export function ViewProfile(props: any) { - const history = useHistory(); +export function ViewProfile(props: any): ReactElement { const { hideNicknames } = useLocalState(({ hideNicknames }) => ({ hideNicknames })); @@ -33,17 +26,19 @@ export function ViewProfile(props: any) { + width="100%" + >
- {((!hideNicknames && contact?.nickname) ? contact.nickname : "")} + {((!hideNicknames && contact?.nickname) ? contact.nickname : '')}
+ width="100%" + >
{ship}
@@ -52,10 +47,11 @@ export function ViewProfile(props: any) { pb={2} alignItems="center" justifyContent="center" - width="100%"> + width="100%" + >
- {(contact?.bio ? contact.bio : "")} + {(contact?.bio ? contact.bio : '')}
@@ -82,7 +78,8 @@ export function ViewProfile(props: any) { borderRadius={1} bg="white" border={1} - borderColor="washedGray"> + borderColor="washedGray" + >
{ship} remains private diff --git a/pkg/interface/src/views/apps/profile/profile.tsx b/pkg/interface/src/views/apps/profile/profile.tsx index 977a0975e..ea99e7ece 100644 --- a/pkg/interface/src/views/apps/profile/profile.tsx +++ b/pkg/interface/src/views/apps/profile/profile.tsx @@ -1,34 +1,24 @@ -import React from "react"; -import { Route, Link } from "react-router-dom"; +import React from 'react'; +import { Route, Link } from 'react-router-dom'; import Helmet from 'react-helmet'; -import { Box, Text, Row, Col, Icon, BaseImage } from "@tlon/indigo-react"; +import { Box } from '@tlon/indigo-react'; -import { uxToHex } from "~/logic/lib/util"; - -import { Profile } from "./components/Profile"; -import useLocalState from "~/logic/state/local"; +import { Profile } from './components/Profile'; export default function ProfileScreen(props: any) { - const { dark } = props; - const hideAvatars = useLocalState(state => state.hideAvatars); return ( <> { props.notificationsCount ? `(${String(props.notificationsCount) }) `: '' }Landscape - Profile { + path={'/~profile/:ship/:edit?'} + render={({ match }) => { const ship = match.params.ship; const isEdit = match.url.includes('edit'); const isPublic = props.isContactPublic; const contact = props.contacts?.[ship]; - const sigilColor = contact?.color - ? `#${uxToHex(contact.color)}` - : dark - ? "#FFFFFF" - : "#000000"; return ( diff --git a/pkg/interface/src/views/apps/publish/PublishResource.tsx b/pkg/interface/src/views/apps/publish/PublishResource.tsx index cd9f077c1..38aa2a07d 100644 --- a/pkg/interface/src/views/apps/publish/PublishResource.tsx +++ b/pkg/interface/src/views/apps/publish/PublishResource.tsx @@ -1,11 +1,11 @@ -import React from "react"; -import { Box } from "@tlon/indigo-react"; +import React from 'react'; +import { Box } from '@tlon/indigo-react'; -import GlobalApi from "~/logic/api/global"; -import { StoreState } from "~/logic/store/type"; -import { Association } from "~/types"; -import { RouteComponentProps } from "react-router-dom"; -import { NotebookRoutes } from "./components/NotebookRoutes"; +import GlobalApi from '~/logic/api/global'; +import { StoreState } from '~/logic/store/type'; +import { Association } from '@urbit/api'; +import { RouteComponentProps } from 'react-router-dom'; +import { NotebookRoutes } from './components/NotebookRoutes'; type PublishResourceProps = StoreState & { association: Association; @@ -16,7 +16,7 @@ type PublishResourceProps = StoreState & { export function PublishResource(props: PublishResourceProps) { const { association, api, baseUrl, notebooks } = props; const rid = association.resource; - const [, , ship, book] = rid.split("/"); + const [, , ship, book] = rid.split('/'); const notebookContacts = props.contacts[association.group]; return ( diff --git a/pkg/interface/src/views/apps/publish/components/EditPost.tsx b/pkg/interface/src/views/apps/publish/components/EditPost.tsx index 0f6ed4dcc..cb7ab51cf 100644 --- a/pkg/interface/src/views/apps/publish/components/EditPost.tsx +++ b/pkg/interface/src/views/apps/publish/components/EditPost.tsx @@ -1,12 +1,16 @@ -import React from "react"; +import React, { ReactElement } from 'react'; import _ from 'lodash'; -import { PostFormSchema, PostForm } from "./NoteForm"; -import { FormikHelpers } from "formik"; -import GlobalApi from "~/logic/api/global"; -import { RouteComponentProps, useLocation } from "react-router-dom"; -import { GraphNode, TextContent, Association, S3State } from "~/types"; -import { getLatestRevision, editPost } from "~/logic/lib/publish"; -import {useWaitForProps} from "~/logic/lib/useWaitForProps"; +import { FormikHelpers } from 'formik'; +import { RouteComponentProps, useLocation } from 'react-router-dom'; + +import { GraphNode } from '@urbit/api'; + +import { PostFormSchema, PostForm } from './NoteForm'; +import GlobalApi from '~/logic/api/global'; +import { getLatestRevision, editPost } from '~/logic/lib/publish'; +import { useWaitForProps } from '~/logic/lib/useWaitForProps'; +import { S3State } from '~/types'; + interface EditPostProps { ship: string; noteId: number; @@ -16,7 +20,7 @@ interface EditPostProps { s3: S3State; } -export function EditPost(props: EditPostProps & RouteComponentProps) { +export function EditPost(props: EditPostProps & RouteComponentProps): ReactElement { const { note, book, noteId, api, ship, history, s3 } = props; const [revNum, title, body] = getLatestRevision(note); const location = useLocation(); @@ -24,19 +28,19 @@ export function EditPost(props: EditPostProps & RouteComponentProps) { const waiter = useWaitForProps(props); const initial: PostFormSchema = { title, - body, + body }; const onSubmit = async ( values: PostFormSchema, actions: FormikHelpers - ) => { + ): Promise => { const { title, body } = values; try { const newRev = revNum + 1; const nodes = editPost(newRev, noteId, title, body); await api.graph.addNodes(ship, book, nodes); - await waiter(p => { + await waiter((p) => { const [rev] = getLatestRevision(p.note); return rev === newRev; }); @@ -44,7 +48,7 @@ export function EditPost(props: EditPostProps & RouteComponentProps) { history.push(noteUrl); } catch (e) { console.error(e); - actions.setStatus({ error: "Failed to edit notebook" }); + actions.setStatus({ error: 'Failed to edit notebook' }); } }; diff --git a/pkg/interface/src/views/apps/publish/components/MarkdownEditor.tsx b/pkg/interface/src/views/apps/publish/components/MarkdownEditor.tsx index 32f372f5d..4f917084f 100644 --- a/pkg/interface/src/views/apps/publish/components/MarkdownEditor.tsx +++ b/pkg/interface/src/views/apps/publish/components/MarkdownEditor.tsx @@ -1,26 +1,26 @@ -import React, { createRef, useCallback, useRef } from "react"; -import { IUnControlledCodeMirror, UnControlled as CodeEditor } from "react-codemirror2"; +import React, { createRef, useCallback, useRef } from 'react'; +import { IUnControlledCodeMirror, UnControlled as CodeEditor } from 'react-codemirror2'; import { useFormikContext } from 'formik'; import { Prompt } from 'react-router-dom'; import { Editor } from 'codemirror'; -import { MOBILE_BROWSER_REGEX, usePreventWindowUnload } from "~/logic/lib/util"; -import { PropFunc } from "~/types/util"; -import CodeMirror from "codemirror"; +import { MOBILE_BROWSER_REGEX, usePreventWindowUnload } from '~/logic/lib/util'; +import { PropFunc } from '~/types/util'; +import CodeMirror from 'codemirror'; -import "codemirror/mode/markdown/markdown"; -import "codemirror/addon/display/placeholder"; -import "codemirror/addon/edit/continuelist"; +import 'codemirror/mode/markdown/markdown'; +import 'codemirror/addon/display/placeholder'; +import 'codemirror/addon/edit/continuelist'; -import "codemirror/lib/codemirror.css"; -import { Box } from "@tlon/indigo-react"; -import { useFileDrag } from "~/logic/lib/useDrag"; -import SubmitDragger from "~/views/components/SubmitDragger"; -import useS3 from "~/logic/lib/useS3"; -import { S3State } from "~/types"; +import 'codemirror/lib/codemirror.css'; +import { Box } from '@tlon/indigo-react'; +import { useFileDrag } from '~/logic/lib/useDrag'; +import SubmitDragger from '~/views/components/SubmitDragger'; +import useS3 from '~/logic/lib/useS3'; +import { S3State } from '@urbit/api'; const MARKDOWN_CONFIG = { - name: "markdown", + name: 'markdown' }; interface MarkdownEditorProps { @@ -49,12 +49,12 @@ export function MarkdownEditor( const options = { mode: MARKDOWN_CONFIG, - theme: "tlon", + theme: 'tlon', lineNumbers: false, lineWrapping: true, - scrollbarStyle: "native", + scrollbarStyle: 'native', // cursorHeight: 0.85, - placeholder: placeholder || "", + placeholder: placeholder || '', extraKeys: { 'Enter': 'newlineAndIndentContinueMarkdownList' } }; @@ -84,7 +84,7 @@ export function MarkdownEditor( const codeMirror: Editor = editor.current.editor; const doc = codeMirror.getDoc(); - Array.from(files).forEach(async file => { + Array.from(files).forEach(async (file) => { const placeholder = `![Uploading ${file.name}](...)`; doc.setValue(doc.getValue() + placeholder); const url = await uploadDefault(file); diff --git a/pkg/interface/src/views/apps/publish/components/MarkdownField.tsx b/pkg/interface/src/views/apps/publish/components/MarkdownField.tsx index 750a64978..e96aec733 100644 --- a/pkg/interface/src/views/apps/publish/components/MarkdownField.tsx +++ b/pkg/interface/src/views/apps/publish/components/MarkdownField.tsx @@ -1,8 +1,8 @@ -import React, { useCallback } from "react"; -import _ from "lodash"; -import { Box, ErrorLabel } from "@tlon/indigo-react"; -import { useField } from "formik"; -import { MarkdownEditor } from "./MarkdownEditor"; +import React, { useCallback } from 'react'; +import _ from 'lodash'; +import { Box, ErrorLabel } from '@tlon/indigo-react'; +import { useField } from 'formik'; +import { MarkdownEditor } from './MarkdownEditor'; export const MarkdownField = ({ id, @@ -13,13 +13,13 @@ export const MarkdownField = ({ const handleBlur = useCallback( (e: any) => { - _.set(e, "target.id", id); + _.set(e, 'target.id', id); onBlur && onBlur(e); }, [onBlur, id] ); - const hasError = !!(error && touched); + const hasError = Boolean(error && touched); return ( - + {error} diff --git a/pkg/interface/src/views/apps/publish/components/MetadataForm.tsx b/pkg/interface/src/views/apps/publish/components/MetadataForm.tsx index c586321a1..7a3bb39ae 100644 --- a/pkg/interface/src/views/apps/publish/components/MetadataForm.tsx +++ b/pkg/interface/src/views/apps/publish/components/MetadataForm.tsx @@ -12,10 +12,10 @@ import { import { Formik, Form, useFormikContext, FormikHelpers } from "formik"; import GlobalApi from "~/logic/api/global"; import { Notebook } from "~/types/publish-update"; -import { Contacts } from "~/types/contact-update"; +import { Contacts } from "@urbit/api/contacts"; import { FormError } from "~/views/components/FormError"; import { RouteComponentProps, useHistory } from "react-router-dom"; -import {Association} from "~/types"; +import {Association} from "@urbit/api"; import { uxToHex } from "~/logic/lib/util"; interface MetadataFormProps { diff --git a/pkg/interface/src/views/apps/publish/components/Note.tsx b/pkg/interface/src/views/apps/publish/components/Note.tsx index d336650fa..2bb8e77e0 100644 --- a/pkg/interface/src/views/apps/publish/components/Note.tsx +++ b/pkg/interface/src/views/apps/publish/components/Note.tsx @@ -1,16 +1,16 @@ -import React, { useState, useEffect } from "react"; -import { Box, Text, Col } from "@tlon/indigo-react"; -import ReactMarkdown from "react-markdown"; +import React, { useState, useEffect } from 'react'; +import { Box, Text, Col } from '@tlon/indigo-react'; +import ReactMarkdown from 'react-markdown'; import bigInt from 'big-integer'; -import { Link, RouteComponentProps } from "react-router-dom"; -import { Spinner } from "~/views/components/Spinner"; -import { Comments } from "~/views/components/Comments"; -import { NoteNavigation } from "./NoteNavigation"; -import GlobalApi from "~/logic/api/global"; +import { Link, RouteComponentProps } from 'react-router-dom'; +import { Spinner } from '~/views/components/Spinner'; +import { Comments } from '~/views/components/Comments'; +import { NoteNavigation } from './NoteNavigation'; +import GlobalApi from '~/logic/api/global'; import { getLatestRevision, getComments } from '~/logic/lib/publish'; -import Author from "~/views/components/Author"; -import { Contacts, GraphNode, Graph, Association, Unreads, Group } from "~/types"; +import Author from '~/views/components/Author'; +import { Contacts, GraphNode, Graph, Association, Unreads, Group } from '@urbit/api'; interface NoteProps { ship: string; @@ -34,12 +34,11 @@ export function Note(props: NoteProps & RouteComponentProps) { const deletePost = async () => { setDeleting(true); - const indices = [note.post.index] + const indices = [note.post.index]; await api.graph.removeNodes(ship, book, indices); props.history.push(rootUrl); }; - const comments = getComments(note); const [revNum, title, body, post] = getLatestRevision(note); const index = note.post.index.split('/'); @@ -49,8 +48,6 @@ export function Note(props: NoteProps & RouteComponentProps) { api.hark.markEachAsRead(props.association, '/',`/${index[1]}/1/1`, 'note', 'publish'); }, [props.association, props.note]); - - let adminLinks: JSX.Element | null = null; if (window.ship === note?.post?.author) { adminLinks = ( @@ -67,7 +64,7 @@ export function Note(props: NoteProps & RouteComponentProps) { color="red" ml={2} onClick={deletePost} - style={{ cursor: "pointer" }} + style={{ cursor: 'pointer' }} > Delete @@ -96,21 +93,21 @@ export function Note(props: NoteProps & RouteComponentProps) { ref={windowRef} > - {"<- Notebook Index"} + {'<- Notebook Index'} - {title || ""} + {title || ''} {adminLinks} - - + + { history.goBack(); }} - type="button">Cancel} + type="button" + >Cancel} diff --git a/pkg/interface/src/views/apps/publish/components/NoteNavigation.tsx b/pkg/interface/src/views/apps/publish/components/NoteNavigation.tsx index b3633df3f..0a0d345fb 100644 --- a/pkg/interface/src/views/apps/publish/components/NoteNavigation.tsx +++ b/pkg/interface/src/views/apps/publish/components/NoteNavigation.tsx @@ -1,29 +1,31 @@ -import React, { Component } from "react"; -import moment from "moment"; -import { Box } from "@tlon/indigo-react"; -import { Link } from "react-router-dom"; -import { Graph, GraphNode } from "~/types"; -import { getLatestRevision } from "~/logic/lib/publish"; -import { BigInteger } from "big-integer"; +import React, { ReactElement } from 'react'; +import moment from 'moment'; +import { Link } from 'react-router-dom'; +import { BigInteger } from 'big-integer'; + +import { Box } from '@tlon/indigo-react'; +import { Graph } from '@urbit/api'; + +import { getLatestRevision } from '~/logic/lib/publish'; function NavigationItem(props: { url: string; title: string; date: number; prev?: boolean; -}) { +}): ReactElement { const date = moment(props.date).fromNow(); return ( - {props.prev ? "Previous" : "Next"} + {props.prev ? 'Previous' : 'Next'} {props.title} {date} @@ -53,7 +55,7 @@ interface NoteNavigationProps { baseUrl: string; } -export function NoteNavigation(props: NoteNavigationProps) { +export function NoteNavigation(props: NoteNavigationProps): ReactElement { let nextComponent = ; let prevComponent = ; const { noteId, notebook } = props; @@ -72,13 +74,13 @@ export function NoteNavigation(props: NoteNavigationProps) { if (next && nextId) { const nextUrl = makeNoteUrl(nextId); const [, title, , post] = getLatestRevision(next); - const date = post["time-sent"]; + const date = post['time-sent']; nextComponent = ; } if (prev && prevId) { const prevUrl = makeNoteUrl(prevId); const [, title, , post] = getLatestRevision(prev); - const date = post["time-sent"]; + const date = post['time-sent']; prevComponent = ( ); diff --git a/pkg/interface/src/views/apps/publish/components/NotePreview.tsx b/pkg/interface/src/views/apps/publish/components/NotePreview.tsx index 1820188c4..becefc7e3 100644 --- a/pkg/interface/src/views/apps/publish/components/NotePreview.tsx +++ b/pkg/interface/src/views/apps/publish/components/NotePreview.tsx @@ -4,14 +4,14 @@ import styled from 'styled-components'; import { Col, Row, Box, Text, Icon, Image } from '@tlon/indigo-react'; import Author from '~/views/components/Author'; -import { GraphNode } from '~/types/graph-update'; -import { Contacts, Group } from '~/types'; +import { GraphNode } from '@urbit/api/graph'; +import { Contacts, Group } from '@urbit/api'; import { getComments, getLatestRevision, - getSnippet, -} from "~/logic/lib/publish"; -import {Unreads} from "~/types"; + getSnippet +} from '~/logic/lib/publish'; +import { Unreads } from '@urbit/api'; import GlobalApi from '~/logic/api/global'; import ReactMarkdown from 'react-markdown'; diff --git a/pkg/interface/src/views/apps/publish/components/NoteRoutes.tsx b/pkg/interface/src/views/apps/publish/components/NoteRoutes.tsx index 916347038..9ac272b83 100644 --- a/pkg/interface/src/views/apps/publish/components/NoteRoutes.tsx +++ b/pkg/interface/src/views/apps/publish/components/NoteRoutes.tsx @@ -1,12 +1,12 @@ -import React from "react"; -import { Route, Switch } from "react-router-dom"; +import React from 'react'; +import { Route, Switch } from 'react-router-dom'; -import GlobalApi from "~/logic/api/global"; -import { RouteComponentProps } from "react-router-dom"; -import Note from "./Note"; -import { EditPost } from "./EditPost"; +import GlobalApi from '~/logic/api/global'; +import { RouteComponentProps } from 'react-router-dom'; +import Note from './Note'; +import { EditPost } from './EditPost'; -import { GraphNode, Graph, Contacts, Association, S3State, Group } from "~/types"; +import { GraphNode, Graph, Contacts, Association, S3State, Group } from '@urbit/api'; interface NoteRoutesProps { ship: string; @@ -32,17 +32,18 @@ export function NoteRoutes(props: NoteRoutesProps & RouteComponentProps) { } + path={relativePath('/edit')} + render={routeProps => } /> + path={relativePath('/:commentId?')} + render={routeProps => + rootUrl={rootUrl} + /> } /> diff --git a/pkg/interface/src/views/apps/publish/components/Notebook.tsx b/pkg/interface/src/views/apps/publish/components/Notebook.tsx index 206349723..f689297c7 100644 --- a/pkg/interface/src/views/apps/publish/components/Notebook.tsx +++ b/pkg/interface/src/views/apps/publish/components/Notebook.tsx @@ -1,10 +1,12 @@ -import React from "react"; -import { RouteComponentProps, Link } from "react-router-dom"; -import { NotebookPosts } from "./NotebookPosts"; -import { Col, Box, Text, Button, Row } from "@tlon/indigo-react"; -import GlobalApi from "~/logic/api/global"; -import { Contacts, Rolodex, Groups, Associations, Graph, Association, Unreads } from "~/types"; -import { useShowNickname } from "~/logic/lib/util"; +import React, { ReactElement } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; + +import { Col, Box, Text, Row } from '@tlon/indigo-react'; +import { Contacts, Rolodex, Groups, Associations, Graph, Association, Unreads } from '@urbit/api'; + +import { NotebookPosts } from './NotebookPosts'; +import GlobalApi from '~/logic/api/global'; +import { useShowNickname } from '~/logic/lib/util'; interface NotebookProps { api: GlobalApi; @@ -21,7 +23,7 @@ interface NotebookProps { unreads: Unreads; } -export function Notebook(props: NotebookProps & RouteComponentProps) { +export function Notebook(props: NotebookProps & RouteComponentProps): ReactElement { const { ship, book, @@ -36,7 +38,6 @@ export function Notebook(props: NotebookProps & RouteComponentProps) { return null; // Waiting on groups to populate } - const relativePath = (p: string) => props.baseUrl + p; const contact = notebookContacts?.[ship]; diff --git a/pkg/interface/src/views/apps/publish/components/NotebookPosts.tsx b/pkg/interface/src/views/apps/publish/components/NotebookPosts.tsx index 639cf5330..8cd94f68b 100644 --- a/pkg/interface/src/views/apps/publish/components/NotebookPosts.tsx +++ b/pkg/interface/src/views/apps/publish/components/NotebookPosts.tsx @@ -1,7 +1,7 @@ -import React, { Component } from "react"; -import { Col } from "@tlon/indigo-react"; -import { NotePreview } from "./NotePreview"; -import { Contacts, Graph, Unreads, Group } from "~/types"; +import React, { Component } from 'react'; +import { Col } from '@tlon/indigo-react'; +import { NotePreview } from './NotePreview'; +import { Contacts, Graph, Unreads, Group } from '@urbit/api'; interface NotebookPostsProps { contacts: Contacts; diff --git a/pkg/interface/src/views/apps/publish/components/NotebookRoutes.tsx b/pkg/interface/src/views/apps/publish/components/NotebookRoutes.tsx index a3d1f5474..a4c8e1e7e 100644 --- a/pkg/interface/src/views/apps/publish/components/NotebookRoutes.tsx +++ b/pkg/interface/src/views/apps/publish/components/NotebookRoutes.tsx @@ -1,6 +1,6 @@ -import React, { useEffect } from "react"; -import { RouteComponentProps, Route, Switch } from "react-router-dom"; -import GlobalApi from "~/logic/api/global"; +import React, { useEffect } from 'react'; +import { RouteComponentProps, Route, Switch } from 'react-router-dom'; +import GlobalApi from '~/logic/api/global'; import { Association, Associations, @@ -10,16 +10,14 @@ import { Rolodex, Unreads, S3State -} from "~/types"; -import { Center, LoadingSpinner } from "@tlon/indigo-react"; +} from '@urbit/api'; +import { Center, LoadingSpinner } from '@tlon/indigo-react'; import bigInt from 'big-integer'; -import Notebook from "./Notebook"; -import NewPost from "./new-post"; +import Notebook from './Notebook'; +import NewPost from './new-post'; import { NoteRoutes } from './NoteRoutes'; - - interface NotebookRoutesProps { api: GlobalApi; ship: string; @@ -49,7 +47,6 @@ export function NotebookRoutes( const group = groups?.[props.association?.group]; - const relativePath = (path: string) => `${baseUrl}${path}`; return ( @@ -66,12 +63,13 @@ export function NotebookRoutes( contacts={notebookContacts} association={props.association} rootUrl={rootUrl} - baseUrl={baseUrl} />; + baseUrl={baseUrl} + />; }} /> ( + path={relativePath('/new')} + render={routeProps => ( { const { noteId } = routeProps.match.params; const noteIdNum = bigInt(noteId); diff --git a/pkg/interface/src/views/apps/publish/components/Writers.js b/pkg/interface/src/views/apps/publish/components/Writers.js index b6be6c159..e8ff547af 100644 --- a/pkg/interface/src/views/apps/publish/components/Writers.js +++ b/pkg/interface/src/views/apps/publish/components/Writers.js @@ -1,16 +1,14 @@ import React, { Component } from 'react'; import { Box, Text } from '@tlon/indigo-react'; import { ShipSearch } from '~/views/components/ShipSearch'; -import { Formik, Form, FormikHelpers } from 'formik'; +import { Formik, Form } from 'formik'; import { resourceFromPath } from '~/logic/lib/group'; import { AsyncButton } from '~/views/components/AsyncButton'; -import { cite } from '~/logic/lib/util'; export class Writers extends Component { render() { const { association, groups, contacts, api } = this.props; - const [,,,name] = association?.resource.split('/'); const resource = resourceFromPath(association?.group); const onSubmit = async (values, actions) => { diff --git a/pkg/interface/src/views/apps/publish/components/new-post.tsx b/pkg/interface/src/views/apps/publish/components/new-post.tsx index 2b0dd366a..3dea28bfb 100644 --- a/pkg/interface/src/views/apps/publish/components/new-post.tsx +++ b/pkg/interface/src/views/apps/publish/components/new-post.tsx @@ -1,13 +1,13 @@ -import React from "react"; -import { FormikHelpers } from "formik"; -import GlobalApi from "~/logic/api/global"; -import { useWaitForProps } from "~/logic/lib/useWaitForProps"; -import { RouteComponentProps } from "react-router-dom"; -import { PostForm, PostFormSchema } from "./NoteForm"; -import {createPost} from "~/logic/api/graph"; -import {Graph} from "~/types/graph-update"; -import {Association, S3State} from "~/types"; -import {newPost} from "~/logic/lib/publish"; +import React from 'react'; +import { FormikHelpers } from 'formik'; +import GlobalApi from '~/logic/api/global'; +import { useWaitForProps } from '~/logic/lib/useWaitForProps'; +import { RouteComponentProps } from 'react-router-dom'; +import { PostForm, PostFormSchema } from './NoteForm'; +import { createPost } from '~/logic/api/graph'; +import { Graph } from '@urbit/api/graph'; +import { Association, S3State } from '@urbit/api'; +import { newPost } from '~/logic/lib/publish'; interface NewPostProps { api: GlobalApi; @@ -30,21 +30,21 @@ export default function NewPost(props: NewPostProps & RouteComponentProps) { ) => { const { title, body } = values; try { - const [noteId, nodes] = newPost(title, body) - await api.graph.addNodes(ship, book, nodes) - await waiter(p => + const [noteId, nodes] = newPost(title, body); + await api.graph.addNodes(ship, book, nodes); + await waiter(p => p.graph.has(noteId) && !p.graph.get(noteId)?.post?.pending ); history.push(`${props.baseUrl}/note/${noteId}`); } catch (e) { console.error(e); - actions.setStatus({ error: "Posting note failed" }); + actions.setStatus({ error: 'Posting note failed' }); } }; const initialValues: PostFormSchema = { - title: "", - body: "", + title: '', + body: '' }; return ( diff --git a/pkg/interface/src/views/apps/settings/components/lib/BackgroundPicker.tsx b/pkg/interface/src/views/apps/settings/components/lib/BackgroundPicker.tsx index 53aed1b3e..deae6d478 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/BackgroundPicker.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/BackgroundPicker.tsx @@ -1,32 +1,30 @@ -import React from "react"; +import React, { ReactElement } from 'react'; + import { - Box, Row, Label, Col, ManagedRadioButtonField as Radio, - ManagedTextInputField as Input, -} from "@tlon/indigo-react"; +} from '@tlon/indigo-react'; -import GlobalApi from "~/logic/api/global"; -import { S3State } from "~/types"; -import { ImageInput } from "~/views/components/ImageInput"; -import {ColorInput} from "~/views/components/ColorInput"; +import GlobalApi from '~/logic/api/global'; +import { ImageInput } from '~/views/components/ImageInput'; +import { ColorInput } from '~/views/components/ColorInput'; +import { S3State } from '~/types/s3-update'; -export type BgType = "none" | "url" | "color"; +export type BgType = 'none' | 'url' | 'color'; export function BackgroundPicker({ bgType, bgUrl, api, - s3, + s3 }: { bgType: BgType; bgUrl?: string; api: GlobalApi; s3: S3State; -}) { - +}): ReactElement { const rowSpace = { my: 0, alignItems: 'center' }; const radioProps = { my: 4, mr: 4, name: 'bgType' }; return ( @@ -34,7 +32,7 @@ export function BackgroundPicker({ - {bgType === "url" && ( + {bgType === 'url' && ( )} - {bgType === "color" && ( - + {bgType === 'color' && ( + )} diff --git a/pkg/interface/src/views/apps/settings/components/lib/BucketList.tsx b/pkg/interface/src/views/apps/settings/components/lib/BucketList.tsx index 5ffdc270a..931f230b0 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/BucketList.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/BucketList.tsx @@ -1,30 +1,29 @@ -import React, { useCallback } from "react"; +import React, { ReactElement, useCallback } from 'react'; +import { Formik } from 'formik'; import { ManagedTextInputField as Input, ManagedForm as Form, Box, Button, - Col, Text, Menu, MenuButton, MenuList, - MenuItem, -} from "@tlon/indigo-react"; -import { Formik } from "formik"; + MenuItem +} from '@tlon/indigo-react'; -import GlobalApi from "~/logic/api/global"; +import GlobalApi from '~/logic/api/global'; export function BucketList({ buckets, selected, - api, + api }: { buckets: Set; selected: string; api: GlobalApi; -}) { +}): ReactElement { const _buckets = Array.from(buckets); const onSubmit = useCallback( @@ -53,14 +52,14 @@ export function BucketList({ ); return ( - +
- {_buckets.map((bucket) => ( + {_buckets.map(bucket => ( { - setRemoteContentPolicy(state => { + setRemoteContentPolicy((state) => { Object.assign(state.remoteContentPolicy, values); }); actions.setSubmitting(false); }} > - {(props) => ( + {props => ( state.hideAvatars); +export default function SettingsScreen(props: any): ReactElement { return ( <> Landscape - Settings { + path={['/~settings']} + render={() => { return ( + borderRadius={1} + > ({ children, @@ -15,11 +15,11 @@ export function AsyncButton({ useEffect(() => { const s = status || {}; let done = false; - if ("success" in s) { + if ('success' in s) { setSuccess(true); onSuccess(); done = true; - } else if ("error" in s) { + } else if ('error' in s) { setSuccess(false); done = true; } @@ -40,13 +40,13 @@ export function AsyncButton({ > {isSubmitting ? ( ) : success === true ? ( - "Done" + 'Done' ) : success === false ? ( - "Errored" + 'Errored' ) : ( children )} diff --git a/pkg/interface/src/views/components/Author.tsx b/pkg/interface/src/views/components/Author.tsx index 8428121e4..1a3a8b56c 100644 --- a/pkg/interface/src/views/components/Author.tsx +++ b/pkg/interface/src/views/components/Author.tsx @@ -1,13 +1,15 @@ -import React, { ReactNode, useState, useRef } from 'react'; +import React, { ReactElement, ReactNode, useState } from 'react'; import moment from 'moment'; +import { useHistory } from 'react-router-dom'; + import { Row, Box, BaseImage } from '@tlon/indigo-react'; +import { Contacts } from '@urbit/api/contacts'; +import { Group } from '@urbit/api'; + import { uxToHex, cite, useShowNickname } from '~/logic/lib/util'; -import { Contacts } from '~/types/contact-update'; import OverlaySigil from './OverlaySigil'; import { Sigil } from '~/logic/lib/sigil'; -import { Group } from '~/types'; import GlobalApi from '~/logic/api/global'; -import { useHistory } from 'react-router-dom'; interface AuthorProps { contacts: Contacts; @@ -21,7 +23,7 @@ interface AuthorProps { } // eslint-disable-next-line max-lines-per-function -export default function Author(props: AuthorProps) { +export default function Author(props: AuthorProps): ReactElement { const { contacts, ship = '', date, showImage, group } = props; const history = useHistory(); let contact; @@ -36,7 +38,7 @@ export default function Author(props: AuthorProps) { const [showOverlay, setShowOverlay] = useState(false); const toggleOverlay = () => { - setShowOverlay((value) => !value); + setShowOverlay(value => !value); }; const img = diff --git a/pkg/interface/src/views/components/Body.tsx b/pkg/interface/src/views/components/Body.tsx index 7449689fd..4e2e64203 100644 --- a/pkg/interface/src/views/components/Body.tsx +++ b/pkg/interface/src/views/components/Body.tsx @@ -1,6 +1,6 @@ -import React, { ReactNode } from "react"; +import React, { ReactNode } from 'react'; -import { Box } from "@tlon/indigo-react"; +import { Box } from '@tlon/indigo-react'; export function Body( props: { children: ReactNode } & Parameters[0] @@ -14,7 +14,7 @@ export function Body( width="100%" borderRadius={2} border={[0, 1]} - borderColor={["washedGray", "washedGray"]} + borderColor={['washedGray', 'washedGray']} {...boxProps} > {props.children} diff --git a/pkg/interface/src/views/components/ChipInput.tsx b/pkg/interface/src/views/components/ChipInput.tsx index e75934fff..02471d817 100644 --- a/pkg/interface/src/views/components/ChipInput.tsx +++ b/pkg/interface/src/views/components/ChipInput.tsx @@ -2,23 +2,23 @@ import React, { useCallback, useState, ReactNode, - SyntheticEvent, useEffect, useRef, -} from "react"; + ReactElement +} from 'react'; +import { useField } from 'formik'; +import Mousetrap from 'mousetrap'; + import { - Box, Label, Row, Col, StatelessTextInput as Input, ErrorLabel -} from "@tlon/indigo-react"; -import { useField } from "formik"; -import Mousetrap from "mousetrap"; -import * as Yup from "yup"; +} from '@tlon/indigo-react'; -function Chip(props: { children: ReactNode }) { + +function Chip(props: { children: ReactNode }): ReactElement { return ( ( + const [{ onBlur, value }, meta, { setValue }] = useField( id ); - const [newChip, setNextChip] = useState(""); + const [newChip, setNextChip] = useState(''); const onChange = useCallback( (e: any) => { setNextChip(e.target.value); @@ -57,7 +57,7 @@ export function ChipInput(props: ChipInputProps) { const addNewChip = useCallback(() => { setValue([...value, newChip]); - setNextChip(""); + setNextChip(''); }, [setValue, value, newChip, setNextChip]); const removeLastChip = useCallback(() => { @@ -70,18 +70,18 @@ export function ChipInput(props: ChipInputProps) { return () => {}; } const mousetrap = Mousetrap(inputRef.current); - mousetrap.bind("backspace", (e) => { + mousetrap.bind('backspace', (e) => { if (newChip.length === 0) { removeLastChip(); return false; } return true; }); - mousetrap.bind("tab", (e) => { + mousetrap.bind('tab', (e) => { addNewChip(); return false; }); - mousetrap.bind("space", (e) => { + mousetrap.bind('space', (e) => { if (props.breakOnSpace) { addNewChip(); return false; @@ -89,9 +89,9 @@ export function ChipInput(props: ChipInputProps) { return true; }); return () => { - mousetrap.unbind("tab"); - mousetrap.unbind("backspace"); - mousetrap.unbind("space"); + mousetrap.unbind('tab'); + mousetrap.unbind('backspace'); + mousetrap.unbind('space'); }; }, [inputRef.current, addNewChip, newChip]); @@ -128,7 +128,7 @@ export function ChipInput(props: ChipInputProps) { py="1" /> - + {meta.error} diff --git a/pkg/interface/src/views/components/ColorInput.tsx b/pkg/interface/src/views/components/ColorInput.tsx index 0ee279db1..8ef2b9d00 100644 --- a/pkg/interface/src/views/components/ColorInput.tsx +++ b/pkg/interface/src/views/components/ColorInput.tsx @@ -1,15 +1,16 @@ -import React from "react"; -import { useField } from "formik"; +import React, { FormEvent, ReactElement } from 'react'; +import { useField } from 'formik'; + import { Col, Label, Row, Box, ErrorLabel, - StatelessTextInput as Input, -} from "@tlon/indigo-react"; + StatelessTextInput as Input +} from '@tlon/indigo-react'; -import { uxToHex, hexToUx } from "~/logic/lib/util"; +import { hexToUx } from '~/logic/lib/util'; type ColorInputProps = Parameters[0] & { id: string; @@ -17,14 +18,14 @@ type ColorInputProps = Parameters[0] & { disabled?: boolean; }; -export function ColorInput(props: ColorInputProps) { +export function ColorInput(props: ColorInputProps): ReactElement { const { id, label, caption, disabled, ...rest } = props; const [{ value, onBlur }, meta, { setValue }] = useField(id); - const hex = value.replace('#', '').replace("0x","").replace(".", ""); - const padded = hex.padStart(6, "0"); + const hex = value.replace('#', '').replace('0x','').replace('.', ''); + const padded = hex.padStart(6, '0'); - const onChange = (e: any) => { + const onChange = (e: FormEvent) => { let { value: newValue } = e.target as HTMLInputElement; newValue = newValue.replace('#', ''); const valid = newValue.match(/^(\d|[a-f]|[A-F]){0,6}$/); @@ -36,7 +37,6 @@ export function ColorInput(props: ColorInputProps) { setValue(result); }; - return ( @@ -78,7 +78,7 @@ export function ColorInput(props: ColorInputProps) { /> - + {meta.error} diff --git a/pkg/interface/src/views/components/CommentInput.tsx b/pkg/interface/src/views/components/CommentInput.tsx index 99189efb7..9c669bbd5 100644 --- a/pkg/interface/src/views/components/CommentInput.tsx +++ b/pkg/interface/src/views/components/CommentInput.tsx @@ -1,15 +1,15 @@ -import React from "react"; -import * as Yup from "yup"; -import { Formik, FormikHelpers, Form, useFormikContext } from "formik"; -import { AsyncButton } from "./AsyncButton"; -import { ManagedTextAreaField as TextArea } from "@tlon/indigo-react"; +import React from 'react'; +import * as Yup from 'yup'; +import { Formik, FormikHelpers, Form, useFormikContext } from 'formik'; +import { AsyncButton } from './AsyncButton'; +import { ManagedTextAreaField as TextArea } from '@tlon/indigo-react'; interface FormSchema { comment: string; } const formSchema = Yup.object({ - comment: Yup.string().required("Comment can't be empty"), + comment: Yup.string().required('Comment can\'t be empty') }); interface CommentInputProps { @@ -25,7 +25,7 @@ interface CommentInputProps { const SubmitTextArea = (props) => { const { submitForm } = useFormikContext(); const onKeyDown = (e: KeyboardEvent) => { - if ((e.getModifierState("Control") || e.metaKey) && e.key === "Enter") { + if ((e.getModifierState('Control') || e.metaKey) && e.key === 'Enter') { submitForm(); } }; @@ -33,9 +33,9 @@ const SubmitTextArea = (props) => { }; export default function CommentInput(props: CommentInputProps) { - const initialValues: FormSchema = { comment: props.initial || "" }; - const label = props.label || "Add Comment"; - const loading = props.loadingText || "Commenting..."; + const initialValues: FormSchema = { comment: props.initial || '' }; + const label = props.label || 'Add Comment'; + const loading = props.loadingText || 'Commenting...'; return ( {label} diff --git a/pkg/interface/src/views/components/CommentItem.tsx b/pkg/interface/src/views/components/CommentItem.tsx index 4bd76a8eb..a18905c7a 100644 --- a/pkg/interface/src/views/components/CommentItem.tsx +++ b/pkg/interface/src/views/components/CommentItem.tsx @@ -1,13 +1,14 @@ -import React, { useState } from 'react'; -import { Link } from "react-router-dom"; -import { Contacts } from '~/types/contact-update'; -import GlobalApi from '~/logic/api/global'; -import { Box, Row, Text } from '@tlon/indigo-react'; +import React from 'react'; +import { Link } from 'react-router-dom'; import styled from 'styled-components'; + +import { Box, Row, Text } from '@tlon/indigo-react'; +import { Contacts } from '@urbit/api/contacts'; +import { GraphNode } from '@urbit/api/graph'; +import { Group } from '@urbit/api'; + +import GlobalApi from '~/logic/api/global'; import Author from '~/views/components/Author'; -import { GraphNode, TextContent } from '~/types/graph-update'; -import tokenizeMessage from '~/logic/lib/tokenizeMessage'; -import { Group } from '~/types'; import { MentionText } from '~/views/components/MentionText'; import { getLatestCommentRevision } from '~/logic/lib/publish'; @@ -28,9 +29,9 @@ interface CommentItemProps { group: Group; } -export function CommentItem(props: CommentItemProps) { +export function CommentItem(props: CommentItemProps): ReactElement { const { ship, contacts, name, api, comment, group } = props; - const [revNum, post] = getLatestCommentRevision(comment); + const [, post] = getLatestCommentRevision(comment); const disabled = props.pending || window.ship !== post?.author; const onDelete = async () => { @@ -39,7 +40,7 @@ export function CommentItem(props: CommentItemProps) { const commentIndexArray = (comment.post?.index || '/').split('/'); const commentIndex = commentIndexArray[commentIndexArray.length - 1]; - const updateUrl = `${props.baseUrl}/${commentIndex}` + const updateUrl = `${props.baseUrl}/${commentIndex}`; return ( diff --git a/pkg/interface/src/views/components/Comments.tsx b/pkg/interface/src/views/components/Comments.tsx index 51297020b..6c6d13513 100644 --- a/pkg/interface/src/views/components/Comments.tsx +++ b/pkg/interface/src/views/components/Comments.tsx @@ -3,10 +3,10 @@ import bigInt from 'big-integer'; import { Col } from '@tlon/indigo-react'; import { CommentItem } from './CommentItem'; import CommentInput from './CommentInput'; -import { Contacts } from '~/types/contact-update'; +import { Contacts } from '@urbit/api/contacts'; import GlobalApi from '~/logic/api/global'; import { FormikHelpers } from 'formik'; -import { Group, GraphNode, Association } from '~/types'; +import { Group, GraphNode, Association } from '@urbit/api'; import { createPost, createBlankNodeWithChildPost } from '~/logic/api/graph'; import { getLatestCommentRevision } from '~/logic/lib/publish'; import tokenizeMessage from '~/logic/lib/tokenizeMessage'; diff --git a/pkg/interface/src/views/components/Dropdown.tsx b/pkg/interface/src/views/components/Dropdown.tsx index deb3ac96d..48c19076a 100644 --- a/pkg/interface/src/views/components/Dropdown.tsx +++ b/pkg/interface/src/views/components/Dropdown.tsx @@ -4,14 +4,17 @@ import React, { useRef, useEffect, useCallback, -} from "react"; -import styled from "styled-components"; -import _ from "lodash"; -import { Box, Col } from "@tlon/indigo-react"; -import { useOutsideClick } from "~/logic/lib/useOutsideClick"; -import { useLocation } from "react-router-dom"; -import { Portal } from "./Portal"; -import { getRelativePosition, AlignY, AlignX } from "~/logic/lib/relativePosition"; + ReactElement +} from 'react'; +import styled from 'styled-components'; +import _ from 'lodash'; +import { useLocation } from 'react-router-dom'; + +import { Box } from '@tlon/indigo-react'; + +import { useOutsideClick } from '~/logic/lib/useOutsideClick'; +import { Portal } from './Portal'; +import { getRelativePosition, AlignY, AlignX } from '~/logic/lib/relativePosition'; interface DropdownProps { children: ReactNode; @@ -33,7 +36,7 @@ const DropdownOptions = styled(Box)` transition-timing-function: ease; `; -export function Dropdown(props: DropdownProps) { +export function Dropdown(props: DropdownProps): ReactElement { const { children, options } = props; const dropdownRef = useRef(null); const anchorRef = useRef(null); @@ -81,14 +84,14 @@ export function Dropdown(props: DropdownProps) { }, []); return ( - + {children} {open && ( { // check if entry is exact match @@ -36,14 +37,14 @@ interface DropdownSearchExtraProps { disabled?: boolean; placeholder?: string; onChange?: (e: ChangeEvent) => void; - onBlur?: (e: any) => void; + onBlur?: (e: FocusEvent) => void; onFocus?: (e: FocusEvent) => void; } type DropdownSearchProps = PropFunc & DropdownSearchExtraProps; -export function DropdownSearch(props: DropdownSearchProps) { +export function DropdownSearch(props: DropdownSearchProps): ReactElement { const textarea = useRef(); const { candidates, @@ -54,13 +55,13 @@ export function DropdownSearch(props: DropdownSearchProps) { renderCandidate, disabled, placeholder, - onFocus = () => {}, - onChange = () => {}, - onBlur = () => {}, + onFocus = (): void => {}, + onChange = (): void => {}, + onBlur = (): void => {}, ...rest } = props; - const [query, setQuery] = useState(""); + const [query, setQuery] = useState(''); const exact = useCallback( (s: string) => { return isExact ? isExact(s) : undefined; @@ -77,7 +78,7 @@ export function DropdownSearch(props: DropdownSearchProps) { const handleSelect = useCallback( (c: C) => { - setQuery(""); + setQuery(''); onSelect(c); }, [setQuery, onSelect] @@ -96,14 +97,14 @@ export function DropdownSearch(props: DropdownSearchProps) { } const mousetrap = Mousetrap(textarea.current); - mousetrap.bind(["down", "tab"], next); - mousetrap.bind(["up", "shift+tab"], back); - mousetrap.bind("enter", onEnter); + mousetrap.bind(['down', 'tab'], next); + mousetrap.bind(['up', 'shift+tab'], back); + mousetrap.bind('enter', onEnter); return () => { - mousetrap.unbind(["down", "tab"]); - mousetrap.unbind(["up", "shift+tab"]); - mousetrap.unbind("enter"); + mousetrap.unbind(['down', 'tab']); + mousetrap.unbind(['up', 'shift+tab']); + mousetrap.unbind('enter'); }; }, [textarea.current, next, back, onEnter]); diff --git a/pkg/interface/src/views/components/ErrorBoundary.tsx b/pkg/interface/src/views/components/ErrorBoundary.tsx index 5d36f7ab9..c166dfe6b 100644 --- a/pkg/interface/src/views/components/ErrorBoundary.tsx +++ b/pkg/interface/src/views/components/ErrorBoundary.tsx @@ -13,7 +13,7 @@ class ErrorBoundary extends Component< history.listen((location, action) => { if (this.state.error) { this.setState({ - error: undefined, + error: undefined }); } }); @@ -26,7 +26,7 @@ class ErrorBoundary extends Component< render() { if (this.state.error) { - return () + return (); } return this.props.children; } diff --git a/pkg/interface/src/views/components/FormError.tsx b/pkg/interface/src/views/components/FormError.tsx index 6aa5cca1c..ed10100a8 100644 --- a/pkg/interface/src/views/components/FormError.tsx +++ b/pkg/interface/src/views/components/FormError.tsx @@ -1,16 +1,16 @@ -import React from "react"; -import { useFormikContext } from "formik"; -import { ErrorLabel } from "@tlon/indigo-react"; -import {PropFunc} from "~/types/util"; +import React from 'react'; +import { useFormikContext } from 'formik'; +import { ErrorLabel } from '@tlon/indigo-react'; +import { PropFunc } from '~/types/util'; export function FormError(props: { message?: string } & PropFunc) { const { status } = useFormikContext(); const { message, ...rest } = props; - let s = status || {}; + const s = status || {}; const contents = message || s?.error; return ( - {contents} + {contents} ); } diff --git a/pkg/interface/src/views/components/FormSubmit.tsx b/pkg/interface/src/views/components/FormSubmit.tsx index ae481de85..6851c8618 100644 --- a/pkg/interface/src/views/components/FormSubmit.tsx +++ b/pkg/interface/src/views/components/FormSubmit.tsx @@ -1,13 +1,13 @@ -import React, { useCallback, ReactNode } from "react"; -import { useFormikContext } from "formik"; -import { Row, Button } from "@tlon/indigo-react"; -import { AsyncButton } from "./AsyncButton"; +import React, { useCallback, ReactNode, ReactElement } from 'react'; +import { useFormikContext } from 'formik'; +import { Row, Button } from '@tlon/indigo-react'; +import { AsyncButton } from './AsyncButton'; interface FormSubmitProps { children?: ReactNode; } -export function FormSubmit(props: FormSubmitProps) { +export function FormSubmit(props: FormSubmitProps): ReactElement { const { children } = props; const { initialValues, values, dirty, resetForm, isSubmitting } = useFormikContext(); @@ -19,7 +19,6 @@ export function FormSubmit(props: FormSubmitProps) { resetForm({ errors: {}, touched: {}, values: initialValues, status: {} }); }, [resetForm, initialValues]); - return ( void; detailed?: boolean; } & PropFunc -) { +): ReactElement { const { resource, api, associations, groups, measure, ...rest } = props; const name = resource.slice(6); const [preview, setPreview] = useState(null); @@ -31,7 +33,7 @@ export function GroupLink( ) : ( @@ -41,7 +43,7 @@ export function GroupLink( api={api} autojoin={name} /> - ), + ) }); useEffect(() => { diff --git a/pkg/interface/src/views/components/GroupSearch.tsx b/pkg/interface/src/views/components/GroupSearch.tsx index 4cc75b781..d9bc2f6e8 100644 --- a/pkg/interface/src/views/components/GroupSearch.tsx +++ b/pkg/interface/src/views/components/GroupSearch.tsx @@ -1,4 +1,8 @@ -import React, { useMemo, useState } from 'react'; +import React, { ReactElement, useMemo, useState } from 'react'; +import { useFormikContext, FieldArray } from 'formik'; +import _ from 'lodash'; +import styled from 'styled-components'; + import { Box, Text, @@ -8,15 +12,12 @@ import { Icon, ErrorLabel } from '@tlon/indigo-react'; -import _ from 'lodash'; -import { useField, useFormikContext, FieldArray } from 'formik'; -import styled from 'styled-components'; +import { Groups } from '@urbit/api'; +import { Associations, Association } from '@urbit/api/metadata'; + import { roleForShip } from '~/logic/lib/group'; - import { DropdownSearch } from './DropdownSearch'; -import { Groups } from '~/types'; -import { Associations, Association } from '~/types/metadata-update'; interface GroupSearchProps { disabled?: boolean; @@ -36,7 +37,7 @@ const CandidateBox = styled(Box)<{ selected: boolean }>` } `; -const Candidate = ({ title, selected, onClick }) => ( +const Candidate = ({ title, selected, onClick }): ReactElement => ( void -) { +): ReactElement { const { title } = a.metadata; const onClick = () => { @@ -68,7 +69,7 @@ type FormValues = { [id in I]: string[]; }; -export function GroupSearch>(props: GroupSearchProps) { +export function GroupSearch>(props: GroupSearchProps): ReactElement { const { id, caption, label } = props; const { values, @@ -76,7 +77,7 @@ export function GroupSearch>(props: Gr errors, initialValues, setFieldValue, - setFieldTouched, + setFieldTouched } = useFormikContext(); const [inputIdx, setInputIdx] = useState(initialValues[id].length); const name = `${id}[${inputIdx}]`; @@ -177,7 +178,8 @@ export function GroupSearch>(props: Gr ); - }} /> + }} + /> ); } diff --git a/pkg/interface/src/views/components/HoverBox.tsx b/pkg/interface/src/views/components/HoverBox.tsx index d678c1e4d..8159a68e3 100644 --- a/pkg/interface/src/views/components/HoverBox.tsx +++ b/pkg/interface/src/views/components/HoverBox.tsx @@ -1,19 +1,19 @@ -import React from "react"; -import { Link } from "react-router-dom"; -import styled from "styled-components"; -import { Box } from "@tlon/indigo-react"; -import { PropFunc } from "~/types/util"; +import React from 'react'; +import { Link } from 'react-router-dom'; +import styled from 'styled-components'; +import { Box } from '@tlon/indigo-react'; +import { PropFunc } from '~/types/util'; interface HoverBoxProps { selected: boolean; bg: string; bgActive: string; } export const HoverBox = styled(Box)` - background-color: ${(p) => + background-color: ${p => p.selected ? p.theme.colors[p.bgActive] : p.theme.colors[p.bg]}; pointer: cursor; &:hover { - background-color: ${(p) => p.theme.colors[p.bgActive]}; + background-color: ${p => p.theme.colors[p.bgActive]}; } `; diff --git a/pkg/interface/src/views/components/IconRadio.tsx b/pkg/interface/src/views/components/IconRadio.tsx index 912329e8f..e58e76a80 100644 --- a/pkg/interface/src/views/components/IconRadio.tsx +++ b/pkg/interface/src/views/components/IconRadio.tsx @@ -1,15 +1,15 @@ -import React, { useCallback, useMemo } from "react"; -import styled from "styled-components"; +import React, { useCallback, useMemo } from 'react'; +import styled from 'styled-components'; +import { useField } from 'formik'; + import { Icon, Box, Row, BaseLabel, - Indicator, Col, - Label, -} from "@tlon/indigo-react"; -import { useField } from "formik"; + Label +} from '@tlon/indigo-react'; type IconRadioProps = Parameters[0] & { id: string; @@ -40,45 +40,50 @@ type IconIndicatorProps = Parameters & { const indicator = { state: { on: { - //"*": { fill: "white" }, - backgroundColor: "blue", - borderColor: "blue", + // "*": { fill: "white" }, + backgroundColor: 'blue', + borderColor: 'blue' }, off: { - //"*": { fill: "transparent" }, - backgroundColor: "white", - borderColor: "lightGray", + // "*": { fill: "transparent" }, + backgroundColor: 'white', + borderColor: 'lightGray' }, onError: { - //"*": { fill: "white" }, - backgroundColor: "red", - borderColor: "red", + // "*": { fill: "white" }, + backgroundColor: 'red', + borderColor: 'red' }, offError: { // "*": { fill: "transparent" }, - backgroundColor: "washedRed", - borderColor: "red", + backgroundColor: 'washedRed', + borderColor: 'red' }, offDisabled: { - //"*": { fill: "transparent" }, - backgroundColor: "washedGray", - borderColor: "lightGray", + // "*": { fill: "transparent" }, + backgroundColor: 'washedGray', + borderColor: 'lightGray' }, onDisabled: { - //"*": { fill: "lightGray" }, - backgroundColor: "washedGray", - borderColor: "lightGray", - }, - }, + // "*": { fill: "lightGray" }, + backgroundColor: 'washedGray', + borderColor: 'lightGray' + } + } }; const IconIndicator = ({ disabled, selected, hasError, children, ...rest }) => { const style = useMemo(() => { - if (selected && disabled) return indicator.state.onDisabled; - if (selected && hasError) return indicator.state.onError; - if (selected) return indicator.state.on; - if (disabled) return indicator.state.offDisabled; - if (hasError) return indicator.state.offError; + if (selected && disabled) +return indicator.state.onDisabled; + if (selected && hasError) +return indicator.state.onError; + if (selected) +return indicator.state.on; + if (disabled) +return indicator.state.offDisabled; + if (hasError) +return indicator.state.offError; return indicator.state.off; }, [selected, disabled, hasError]); @@ -95,7 +100,7 @@ export function IconRadio(props: IconRadioProps) { name, id, value: id, - type: "radio", + type: 'radio' }); const onChange = useCallback( @@ -122,12 +127,12 @@ export function IconRadio(props: IconRadioProps) { > - + {caption ? ( diff --git a/pkg/interface/src/views/components/Invite/Group.tsx b/pkg/interface/src/views/components/Invite/Group.tsx index ea5c37b98..9345d1c78 100644 --- a/pkg/interface/src/views/components/Invite/Group.tsx +++ b/pkg/interface/src/views/components/Invite/Group.tsx @@ -1,11 +1,11 @@ -import React, { ReactNode } from "react"; -import { Text, Box, Button, Icon, Row, Rule, Col } from "@tlon/indigo-react"; +import React, { ReactElement, ReactNode } from 'react'; +import { Text, Box, Icon, Row } from '@tlon/indigo-react'; -import { cite } from "~/logic/lib/util"; -import { MetadataUpdatePreview, JoinProgress, Invite } from "~/types"; -import { GroupSummary } from "~/views/landscape/components/GroupSummary"; -import { InviteSkeleton } from "./InviteSkeleton"; -import { JoinSkeleton } from "./JoinSkeleton"; +import { cite } from '~/logic/lib/util'; +import { MetadataUpdatePreview, JoinProgress, Invite } from '@urbit/api'; +import { GroupSummary } from '~/views/landscape/components/GroupSummary'; +import { InviteSkeleton } from './InviteSkeleton'; +import { JoinSkeleton } from './JoinSkeleton'; interface GroupInviteProps { preview: MetadataUpdatePreview; @@ -15,12 +15,12 @@ interface GroupInviteProps { onDecline: () => Promise; } -export function GroupInvite(props: GroupInviteProps) { +export function GroupInvite(props: GroupInviteProps): ReactElement { const { preview, invite, status, onAccept, onDecline } = props; const { metadata, members } = props.preview; let inner: ReactNode = null; - let Outer: (p: { children: ReactNode }) => JSX.Element = (p) => ( + let Outer: (p: { children: ReactNode }) => JSX.Element = p => ( <>{p.children} ); @@ -68,7 +68,7 @@ export function GroupInvite(props: GroupInviteProps) { gray metadata={metadata} memberCount={members} - channelCount={preview?.["channel-count"]} + channelCount={preview?.['channel-count']} /> diff --git a/pkg/interface/src/views/components/Invite/InviteSkeleton.tsx b/pkg/interface/src/views/components/Invite/InviteSkeleton.tsx index c1b4d3899..bcf773f68 100644 --- a/pkg/interface/src/views/components/Invite/InviteSkeleton.tsx +++ b/pkg/interface/src/views/components/Invite/InviteSkeleton.tsx @@ -1,8 +1,8 @@ -import React, { ReactNode } from "react"; -import { Text, Box, Button, Icon, Row, Rule, Col } from "@tlon/indigo-react"; +import React, { ReactElement, ReactNode } from 'react'; +import { Row, Rule, Col } from '@tlon/indigo-react'; -import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction"; -import { PropFunc } from "~/types"; +import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction'; +import { PropFunc } from '~/types'; export interface InviteSkeletonProps { onAccept: () => Promise; @@ -14,7 +14,7 @@ export interface InviteSkeletonProps { export function InviteSkeleton( props: InviteSkeletonProps & PropFunc -) { +): ReactElement { const { children, acceptDesc, diff --git a/pkg/interface/src/views/components/Invite/JoinSkeleton.tsx b/pkg/interface/src/views/components/Invite/JoinSkeleton.tsx index c3e39bd63..c3e6874f3 100644 --- a/pkg/interface/src/views/components/Invite/JoinSkeleton.tsx +++ b/pkg/interface/src/views/components/Invite/JoinSkeleton.tsx @@ -1,14 +1,15 @@ -import React, { ReactNode } from "react"; -import { Col, Row, SegmentedProgressBar, Text, Rule } from "@tlon/indigo-react"; -import { JoiningStatus } from "~/views/apps/notifications/joining"; -import { JoinProgress, PropFunc } from "~/types"; +import React, { ReactElement, ReactNode } from 'react'; +import { Col, Rule } from '@tlon/indigo-react'; +import { JoiningStatus } from '~/views/apps/notifications/joining'; +import { JoinProgress } from '@urbit/api'; +import { PropFunc } from '~/types/util'; type JoinSkeletonProps = { children: ReactNode; status: JoinProgress; } & PropFunc; -export function JoinSkeleton(props: JoinSkeletonProps) { +export function JoinSkeleton(props: JoinSkeletonProps): ReactElement { const { children, status, ...rest } = props; return ( <> diff --git a/pkg/interface/src/views/components/Invite/index.tsx b/pkg/interface/src/views/components/Invite/index.tsx index 4bdab74b5..1ada18e65 100644 --- a/pkg/interface/src/views/components/Invite/index.tsx +++ b/pkg/interface/src/views/components/Invite/index.tsx @@ -1,25 +1,23 @@ -import React, { Component, useState, useEffect, useCallback, useMemo } from "react"; -import { Invite } from "~/types/invite-update"; -import { Text, Box, Button, Icon, Row, Rule, Col } from "@tlon/indigo-react"; -import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction"; -import { cite } from "~/logic/lib/util"; +import React, { useState, useEffect, useCallback } from 'react'; +import { useHistory } from 'react-router-dom'; + import { MetadataUpdatePreview, Contacts, JoinRequests, - JoinProgress, Groups, - Associations, -} from "~/types"; -import GlobalApi from "~/logic/api/global"; -import { GroupSummary } from "~/views/landscape/components/GroupSummary"; -import { JoiningStatus } from "~/views/apps/notifications/joining"; -import { resourceFromPath } from "~/logic/lib/group"; -import { GroupInvite } from "./Group"; -import { InviteSkeleton } from "./InviteSkeleton"; -import { JoinSkeleton } from "./JoinSkeleton"; -import { useWaitForProps } from "~/logic/lib/useWaitForProps"; -import { useHistory } from "react-router-dom"; + Associations +} from '@urbit/api'; +import { Invite } from '@urbit/api/invite'; +import { Text, Icon, Row } from '@tlon/indigo-react'; + +import { cite } from '~/logic/lib/util'; +import GlobalApi from '~/logic/api/global'; +import { resourceFromPath } from '~/logic/lib/group'; +import { GroupInvite } from './Group'; +import { InviteSkeleton } from './InviteSkeleton'; +import { JoinSkeleton } from './JoinSkeleton'; +import { useWaitForProps } from '~/logic/lib/useWaitForProps'; interface InviteItemProps { invite?: Invite; @@ -78,10 +76,10 @@ export function InviteItem(props: InviteItemProps) { await api.invite.decline(app, uid); }, [app, uid]); - const handlers = { onAccept: inviteAccept, onDecline: inviteDecline } + const handlers = { onAccept: inviteAccept, onDecline: inviteDecline }; useEffect(() => { - if (!app || app === "groups") { + if (!app || app === 'groups') { (async () => { setPreview(await api.metadata.preview(resource)); })(); @@ -102,7 +100,7 @@ export function InviteItem(props: InviteItemProps) { {...handlers} /> ); - } else if (invite && name.startsWith("dm--")) { + } else if (invite && name.startsWith('dm--')) { return ( ); - } else if (status && name.startsWith("dm--")) { + } else if (status && name.startsWith('dm--')) { return ( @@ -148,7 +146,7 @@ export function InviteItem(props: InviteItemProps) { ); } else if (status) { - const [, , ship, name] = resource.split("/"); + const [, , ship, name] = resource.split('/'); return ( diff --git a/pkg/interface/src/views/components/Loading.tsx b/pkg/interface/src/views/components/Loading.tsx index a11f5e09c..78822673e 100644 --- a/pkg/interface/src/views/components/Loading.tsx +++ b/pkg/interface/src/views/components/Loading.tsx @@ -1,7 +1,7 @@ -import React from "react"; -import { Text, Center, LoadingSpinner } from "@tlon/indigo-react"; +import React from 'react'; +import { Text, Center, LoadingSpinner } from '@tlon/indigo-react'; -import { Body } from "./Body"; +import { Body } from './Body'; interface LoadingProps { text?: string; @@ -11,7 +11,7 @@ export function Loading({ text }: LoadingProps) {
- {!!text && {text}} + {Boolean(text) && {text}}
); diff --git a/pkg/interface/src/views/components/MentionText.tsx b/pkg/interface/src/views/components/MentionText.tsx index 02bdf0c22..96187d94c 100644 --- a/pkg/interface/src/views/components/MentionText.tsx +++ b/pkg/interface/src/views/components/MentionText.tsx @@ -1,7 +1,7 @@ import React, { useState, useCallback } from 'react'; import _ from 'lodash'; import { Text, Box } from '@tlon/indigo-react'; -import { Contact, Contacts, Content, Group } from '~/types'; +import { Contact, Contacts, Content, Group } from '@urbit/api'; import RichText from '~/views/components/RichText'; import { cite, useShowNickname, uxToHex } from '~/logic/lib/util'; import OverlaySigil from '~/views/components/OverlaySigil'; diff --git a/pkg/interface/src/views/components/ModalOverlay.tsx b/pkg/interface/src/views/components/ModalOverlay.tsx index fee8c578e..9838fe1ad 100644 --- a/pkg/interface/src/views/components/ModalOverlay.tsx +++ b/pkg/interface/src/views/components/ModalOverlay.tsx @@ -1,9 +1,9 @@ -import React, { useCallback, UIEvent, MouseEvent, useRef } from "react"; -import { Box } from "@tlon/indigo-react"; -import { PropFunc } from "~/types/util"; +import React, { useCallback, UIEvent, MouseEvent, useRef } from 'react'; +import { Box } from '@tlon/indigo-react'; +import { PropFunc } from '~/types/util'; interface ModalOverlayProps { - spacing: PropFunc["m"]; + spacing: PropFunc['m']; dismiss: () => void; } type Props = ModalOverlayProps & PropFunc; @@ -22,7 +22,7 @@ export const ModalOverlay = (props: Props) => { const onKeyDown = useCallback( (e: any) => { - if (e.key === "Escape") { + if (e.key === 'Escape') { props.dismiss(); e.stopPropagation(); } @@ -49,4 +49,4 @@ export const ModalOverlay = (props: Props) => { ); -} +}; diff --git a/pkg/interface/src/views/components/OverlaySigil.tsx b/pkg/interface/src/views/components/OverlaySigil.tsx index f2d031feb..066e1de7d 100644 --- a/pkg/interface/src/views/components/OverlaySigil.tsx +++ b/pkg/interface/src/views/components/OverlaySigil.tsx @@ -1,5 +1,5 @@ -import React, { useState, useRef, useEffect, PureComponent } from 'react'; -import { Contact, Group } from '~/types'; +import React, { useState, useRef, useEffect } from 'react'; +import { Contact, Group } from '@urbit/api'; import ProfileOverlay, { OVERLAY_HEIGHT } from './ProfileOverlay'; import { Box, ColProps } from '@tlon/indigo-react'; diff --git a/pkg/interface/src/views/components/Portal.tsx b/pkg/interface/src/views/components/Portal.tsx index 7bc5d51b6..297168e90 100644 --- a/pkg/interface/src/views/components/Portal.tsx +++ b/pkg/interface/src/views/components/Portal.tsx @@ -1,10 +1,10 @@ -import { useEffect, ReactNode, useMemo } from "react"; -import { createPortal } from "react-dom"; +import { useEffect, ReactNode, useMemo } from 'react'; +import { createPortal } from 'react-dom'; export function Portal(props: { children: ReactNode }) { - const root = document.getElementById("portal-root"); + const root = document.getElementById('portal-root'); - const el = useMemo(() => document.createElement("div"), []); + const el = useMemo(() => document.createElement('div'), []); useEffect(() => { root?.appendChild(el); diff --git a/pkg/interface/src/views/components/ProfileOverlay.tsx b/pkg/interface/src/views/components/ProfileOverlay.tsx index a77f3737d..0b3167b8e 100644 --- a/pkg/interface/src/views/components/ProfileOverlay.tsx +++ b/pkg/interface/src/views/components/ProfileOverlay.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; -import { Contact, Group } from '~/types'; +import { Contact, Group } from '@urbit/api'; import { cite, useShowNickname } from '~/logic/lib/util'; import { Sigil } from '~/logic/lib/sigil'; @@ -34,7 +34,7 @@ type ProfileOverlayProps = ColProps & { api: any; }; -class ProfileOverlay extends PureComponent { +class ProfileOverlay extends PureComponent> { public popoverRef: React.Ref; constructor(props) { @@ -124,13 +124,14 @@ class ProfileOverlay extends PureComponent { > {(!isOwn) && ( - history.push(`/~landscape/dm/${ship}`)}/> + history.push(`/~landscape/dm/${ship}`)} /> )} history.push(`/~profile/~${ship}`)}> + onClick={() => history.push(`/~profile/~${ship}`)} + > {img} diff --git a/pkg/interface/src/views/components/RemoteContent.tsx b/pkg/interface/src/views/components/RemoteContent.tsx index c6f16c658..b490a3d2d 100644 --- a/pkg/interface/src/views/components/RemoteContent.tsx +++ b/pkg/interface/src/views/components/RemoteContent.tsx @@ -66,7 +66,8 @@ class RemoteContent extends PureComponent { this.setState({ embed: result }); }).catch((error) => { - if (error.name === 'AbortError') return; + if (error.name === 'AbortError') +return; this.setState({ embed: 'error' }); }); } @@ -81,7 +82,7 @@ class RemoteContent extends PureComponent + > {contents} ); } @@ -171,7 +172,7 @@ class RemoteContent extends PureComponent + > {this.state.unfold ? 'collapse' : 'expand'} : null} {this.state.embed && this.state.embed.html && this.state.unfold ? -
{ this.containerRef = el; }} - dangerouslySetInnerHTML={{__html: this.state.embed.html}}>
+
{ + this.containerRef = el; +}} + dangerouslySetInnerHTML={{ __html: this.state.embed.html }} + >
: null}
diff --git a/pkg/interface/src/views/components/ShipSearch.tsx b/pkg/interface/src/views/components/ShipSearch.tsx index e4e6c4566..5a0433495 100644 --- a/pkg/interface/src/views/components/ShipSearch.tsx +++ b/pkg/interface/src/views/components/ShipSearch.tsx @@ -2,33 +2,28 @@ import React, { useMemo, useCallback, ChangeEvent, - useState, - SyntheticEvent, - useEffect, useRef, -} from "react"; + ReactElement +} from 'react'; +import _ from 'lodash'; +import ob from 'urbit-ob'; +import * as Yup from 'yup'; +import { FieldArray, useFormikContext } from 'formik'; + import { - Box, Label, Icon, Text, Row, Col, - ErrorLabel, -} from "@tlon/indigo-react"; -import _ from "lodash"; -import ob from "urbit-ob"; -import * as Yup from "yup"; -import { useField, FieldArray, useFormikContext } from "formik"; -import styled from "styled-components"; + ErrorLabel +} from '@tlon/indigo-react'; +import { Rolodex, Groups } from '@urbit/api'; -import { DropdownSearch } from "./DropdownSearch"; -import { Associations, Association } from "~/types/metadata-update"; -import { cite, deSig } from "~/logic/lib/util"; -import { Rolodex, Groups } from "~/types"; -import { HoverBox } from "./HoverBox"; -const INVALID_SHIP_ERR = "Invalid ship"; +import { DropdownSearch } from './DropdownSearch'; +import { cite, deSig } from '~/logic/lib/util'; +import { HoverBox } from './HoverBox'; interface InviteSearchProps { autoFocus?: boolean; @@ -42,7 +37,7 @@ interface InviteSearchProps { maxLength?: number; } -const getNicknameForShips = (groups: Groups, contacts: Rolodex) => { +const getNicknameForShips = (groups: Groups, contacts: Rolodex): readonly [string[], Map] => { const peerSet = new Set(); const nicknames = new Map(); _.forEach(groups, (group, path) => { @@ -71,7 +66,7 @@ const getNicknameForShips = (groups: Groups, contacts: Rolodex) => { return [Array.from(peerSet), nicknames] as const; }; -const Candidate = ({ title, detail, selected, onClick }) => ( +const Candidate = ({ title, detail, selected, onClick }): ReactElement => ( = { }; const shipItemSchema = Yup.string().test( - "is-patp", - "${value} is not a valid @p", + 'is-patp', + '${value} is not a valid @p', x => ob.isValidPatp(`~${x}`) ); export const shipSearchSchema = Yup.array(shipItemSchema).compact(); export const shipSearchSchemaInGroup = (members: string[]) => - Yup.array(shipItemSchema.oneOf(members, "${value} not a member of this group")).compact(); + Yup.array(shipItemSchema.oneOf(members, '${value} not a member of this group')).compact(); export function ShipSearch>( props: InviteSearchProps -) { +): ReactElement { const { id, label, caption } = props; const { values, touched, errors, initialValues, - setFieldValue, + setFieldValue } = useFormikContext(); const inputIdx = useRef(initialValues[id].length); @@ -133,7 +128,7 @@ export function ShipSearch>( const renderCandidate = useCallback( (s: string, selected: boolean, onSelect: (s: string) => void) => { - const detail = _.uniq(nicknames.get(s)).join(", "); + const detail = _.uniq(nicknames.get(s)).join(', '); const onClick = () => { onSelect(s); }; @@ -152,7 +147,7 @@ export function ShipSearch>( const onChange = (e: ChangeEvent) => { const newValue = - e.target.value?.length > 0 ? `~${deSig(e.target.value)}` : ""; + e.target.value?.length > 0 ? `~${deSig(e.target.value)}` : ''; setFieldValue(name(), newValue); }; @@ -165,7 +160,7 @@ export function ShipSearch>( const onAdd = (ship: string) => { setFieldValue(name(), ship); inputIdx.current += 1; - arrayHelpers.push(""); + arrayHelpers.push(''); }; const onRemove = (idx: number) => { @@ -196,7 +191,7 @@ export function ShipSearch>( props.maxLength ? selected.length >= props.maxLength : false } search={(s: string, t: string) => - (t || "").toLowerCase().startsWith(s.toLowerCase()) + (t || '').toLowerCase().startsWith(s.toLowerCase()) } getKey={(s: string) => s} onChange={onChange} @@ -227,7 +222,7 @@ export function ShipSearch>( ))}
0}> - {error.join(", ")} + {error.join(', ')} ); diff --git a/pkg/interface/src/views/components/StatelessAsyncAction.tsx b/pkg/interface/src/views/components/StatelessAsyncAction.tsx index be46d4de1..68ab25f63 100644 --- a/pkg/interface/src/views/components/StatelessAsyncAction.tsx +++ b/pkg/interface/src/views/components/StatelessAsyncAction.tsx @@ -1,7 +1,7 @@ -import React, { ReactNode } from "react"; +import React, { ReactNode } from 'react'; import { useStatelessAsyncClickable } from '~/logic/lib/useStatelessAsyncClickable'; -import { LoadingSpinner, Action } from "@tlon/indigo-react"; +import { LoadingSpinner, Action } from '@tlon/indigo-react'; interface AsyncActionProps { children: ReactNode; @@ -19,7 +19,7 @@ export function StatelessAsyncAction({ }: AsyncActionProps & Parameters[0]) { const { onClick: handleClick, - buttonState: state, + buttonState: state } = useStatelessAsyncClickable(onClick, name); return ( @@ -27,16 +27,17 @@ export function StatelessAsyncAction({ height="18px" hideDisabled={!disabled} disabled={disabled || state === 'loading'} - onClick={handleClick} {...rest}> - {state === "error" ? ( - "Error" - ) : state === "loading" ? ( + onClick={handleClick} {...rest} + > + {state === 'error' ? ( + 'Error' + ) : state === 'loading' ? ( - ) : state === "success" ? ( - "Done" + ) : state === 'success' ? ( + 'Done' ) : ( children )} diff --git a/pkg/interface/src/views/components/StatelessAsyncButton.tsx b/pkg/interface/src/views/components/StatelessAsyncButton.tsx index faf933ed3..68c27d226 100644 --- a/pkg/interface/src/views/components/StatelessAsyncButton.tsx +++ b/pkg/interface/src/views/components/StatelessAsyncButton.tsx @@ -1,9 +1,8 @@ -import React, { ReactNode, useState, useEffect, useCallback } from "react"; +import React, { ReactElement, ReactNode } from 'react'; -import { Button, LoadingSpinner } from "@tlon/indigo-react"; -import { useFormikContext } from "formik"; +import { Button, LoadingSpinner } from '@tlon/indigo-react'; -import { useStatelessAsyncClickable } from "~/logic/lib/useStatelessAsyncClickable"; +import { useStatelessAsyncClickable } from '~/logic/lib/useStatelessAsyncClickable'; interface AsyncButtonProps { children: ReactNode; @@ -14,33 +13,33 @@ interface AsyncButtonProps { export function StatelessAsyncButton({ children, onClick, - name = "", + name = '', disabled = false, ...rest -}: AsyncButtonProps & Parameters[0]) { +}: AsyncButtonProps & Parameters[0]): ReactElement { const { onClick: handleClick, - buttonState: state, + buttonState: state } = useStatelessAsyncClickable(onClick, name); return ( - + { isAdmin && ( diff --git a/pkg/interface/src/views/landscape/components/GroupSettings/Personal.tsx b/pkg/interface/src/views/landscape/components/GroupSettings/Personal.tsx index 5d262d6b2..8f14a4ba6 100644 --- a/pkg/interface/src/views/landscape/components/GroupSettings/Personal.tsx +++ b/pkg/interface/src/views/landscape/components/GroupSettings/Personal.tsx @@ -1,41 +1,22 @@ -import React, { useCallback } from "react"; +import React from 'react'; -import { AsyncButton } from "~/views/components/AsyncButton"; -import * as Yup from "yup"; import { - Box, - ManagedTextInputField as Input, - ManagedToggleSwitchField as Toggle, Col, Label, - Button, - LoadingSpinner, BaseLabel, - Anchor, BaseAnchor -} from "@tlon/indigo-react"; -import { Group, GroupPolicy } from "~/types/group-update"; -import { Enc } from "~/types/noun"; -import { Association } from "~/types/metadata-update"; -import GlobalApi from "~/logic/api/global"; -import { resourceFromPath, roleForShip } from "~/logic/lib/group"; -import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton"; -import { ColorInput } from "~/views/components/ColorInput"; -import { useHistory } from "react-router-dom"; - -import { uxToHex } from "~/logic/lib/util"; -import { FormikOnBlur } from "~/views/components/FormikOnBlur"; -import {GroupNotificationsConfig} from "~/types"; -import {StatelessAsyncToggle} from "~/views/components/StatelessAsyncToggle"; - +} from '@tlon/indigo-react'; +import { GroupNotificationsConfig } from '@urbit/api'; +import { Association } from '@urbit/api/metadata'; +import GlobalApi from '~/logic/api/global'; +import { StatelessAsyncToggle } from '~/views/components/StatelessAsyncToggle'; export function GroupPersonalSettings(props: { api: GlobalApi; association: Association; notificationsGroupConfig: GroupNotificationsConfig; }) { - const groupPath = props.association.group; const watching = props.notificationsGroupConfig.findIndex(g => g === groupPath) !== -1; @@ -48,7 +29,7 @@ export function GroupPersonalSettings(props: { return ( Group Notifications - ) { +export function GroupSummary(props: GroupSummaryProps & PropFunc): ReactElement { const { channelCount, memberCount, metadata, resource, children, ...rest } = props; const anchorRef = useRef(null); useTutorialModal( - "group-desc", + 'group-desc', resource === `/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`, anchorRef.current ); @@ -39,7 +39,8 @@ export function GroupSummary(props: GroupSummaryProps & PropFunc) { fontSize="1" textOverflow="ellipsis" whiteSpace="nowrap" - overflow="hidden">{metadata.title} + overflow="hidden" + >{metadata.title} {memberCount} participants @@ -57,7 +58,8 @@ export function GroupSummary(props: GroupSummaryProps & PropFunc) { width="100%" fontSize="1" textOverflow="ellipsis" - overflow="hidden"> + overflow="hidden" + > {metadata.description} } diff --git a/pkg/interface/src/views/landscape/components/GroupSwitcher.tsx b/pkg/interface/src/views/landscape/components/GroupSwitcher.tsx index 769963f98..91926e00f 100644 --- a/pkg/interface/src/views/landscape/components/GroupSwitcher.tsx +++ b/pkg/interface/src/views/landscape/components/GroupSwitcher.tsx @@ -9,11 +9,11 @@ import { import { uxToHex } from '~/logic/lib/util'; import { Link } from 'react-router-dom'; -import { Associations } from '~/types/metadata-update'; +import { Associations } from '@urbit/api/metadata'; import { Dropdown } from '~/views/components/Dropdown'; -import { Workspace } from '~/types'; import { getTitleFromWorkspace } from '~/logic/lib/workspace'; -import {MetadataIcon} from './MetadataIcon'; +import { MetadataIcon } from './MetadataIcon'; +import { Workspace } from '~/types/workspace'; const GroupSwitcherItem = ({ to, children, bottom = false, ...rest }) => ( @@ -93,7 +93,8 @@ export function GroupSwitcher(props: { top="0px" pl='3' borderBottom='1px solid' - borderColor='washedGray'> + borderColor='washedGray' + > ( - "recent-groups", + 'recent-groups', [] ); useEffect(() => { - if (workspace.type !== "group") { + if (workspace.type !== 'group') { return; } - setRecentGroups((gs) => _.uniq([workspace.group, ...gs])); + setRecentGroups(gs => _.uniq([workspace.group, ...gs])); }, [workspace]); if (!(associations && (groupPath ? groupPath in groups : true))) { @@ -77,7 +76,7 @@ export function GroupsPane(props: GroupsPaneProps) { {...routeProps} baseUrl={baseUrl} - />)} + />)} - ) + ); return ( { const { app, host, name } = routeProps.match.params as Record< string, @@ -102,7 +101,7 @@ export function GroupsPane(props: GroupsPaneProps) { const appName = app as AppName; const resource = `/ship/${host}/${name}`; - const association = associations.graph[resource] + const association = associations.graph[resource]; const resourceUrl = `${baseUrl}/resource/${app}${resource}`; if (!association) { @@ -130,7 +129,7 @@ export function GroupsPane(props: GroupsPaneProps) { }} /> { const { app, host, name } = routeProps.match.params; const appPath = `/ship/${host}/${name}`; @@ -170,7 +169,7 @@ export function GroupsPane(props: GroupsPaneProps) { }} /> { const newUrl = `${baseUrl}/new`; return ( @@ -191,10 +190,10 @@ export function GroupsPane(props: GroupsPaneProps) { }} /> { const hasDescription = groupAssociation?.metadata?.description; - const channelCount = Object.keys(props?.associations?.graph ?? {}).filter(e => { + const channelCount = Object.keys(props?.associations?.graph ?? {}).filter((e) => { return props?.associations?.graph?.[e]?.['group'] === groupPath; }).length; let summary: ReactNode; @@ -205,13 +204,11 @@ export function GroupsPane(props: GroupsPaneProps) { channelCount={channelCount} metadata={groupAssociation.metadata} resource={groupAssociation.group} - /> + />; } else { summary = ( Create or select a channel to get started ); - - } const title = groupAssociation?.metadata?.title ?? 'Landscape'; return ( @@ -223,7 +220,7 @@ export function GroupsPane(props: GroupsPaneProps) { {summary} diff --git a/pkg/interface/src/views/landscape/components/InvitePopover.tsx b/pkg/interface/src/views/landscape/components/InvitePopover.tsx index 72dc13d93..de02b7588 100644 --- a/pkg/interface/src/views/landscape/components/InvitePopover.tsx +++ b/pkg/interface/src/views/landscape/components/InvitePopover.tsx @@ -1,26 +1,26 @@ -import React, { useCallback, useRef, useMemo } from "react"; +import React, { useCallback, useRef } from 'react'; import _ from 'lodash'; -import { Switch, Route, useHistory } from "react-router-dom"; -import { Formik, Form } from "formik"; +import { Switch, Route, useHistory } from 'react-router-dom'; +import { Formik, Form } from 'formik'; import * as Yup from 'yup'; import { ManagedTextInputField as Input, Box, Text, Col, - Button, Row -} from "@tlon/indigo-react"; +} from '@tlon/indigo-react'; -import { ShipSearch } from "~/views/components/ShipSearch"; -import { Association } from "~/types/metadata-update"; -import { AsyncButton } from "~/views/components/AsyncButton"; -import { useOutsideClick } from "~/logic/lib/useOutsideClick"; -import { FormError } from "~/views/components/FormError"; -import { resourceFromPath } from "~/logic/lib/group"; -import GlobalApi from "~/logic/api/global"; -import { Groups, Rolodex, Workspace } from "~/types"; -import { deSig } from "~/logic/lib/util"; +import { ShipSearch } from '~/views/components/ShipSearch'; +import { Association } from '@urbit/api/metadata'; +import { AsyncButton } from '~/views/components/AsyncButton'; +import { useOutsideClick } from '~/logic/lib/useOutsideClick'; +import { FormError } from '~/views/components/FormError'; +import { resourceFromPath } from '~/logic/lib/group'; +import GlobalApi from '~/logic/api/global'; +import { Groups, Rolodex } from '@urbit/api'; +import { deSig } from '~/logic/lib/util'; +import { Workspace } from '~/types/workspace'; interface InvitePopoverProps { baseUrl: string; @@ -38,15 +38,15 @@ interface FormSchema { } const formSchema = Yup.object({ - emails: Yup.array(Yup.string().email("Invalid email")), - ships: Yup.array(Yup.string()).min(1, "Must invite at least one ship") + emails: Yup.array(Yup.string().email('Invalid email')), + ships: Yup.array(Yup.string()).min(1, 'Must invite at least one ship') }); export function InvitePopover(props: InvitePopoverProps) { const { baseUrl, api, association } = props; const relativePath = (p: string) => baseUrl + p; - const { title } = association?.metadata || ""; + const { title } = association?.metadata || ''; const innerRef = useRef(null); const history = useHistory(); @@ -75,10 +75,9 @@ export function InvitePopover(props: InvitePopoverProps) { const initialValues: FormSchema = { ships: [], emails: [], description: '' }; - return ( - + { + .required('Must provide group to join') + .test('is-valid', 'Invalid group', (group: string | null | undefined) => { if (!group) { return false; } - const [patp, name] = group.split("/"); + const [patp, name] = group.split('/'); return urbitOb.isValidPatp(patp) && name.length > 0; - }), + }) }); interface FormSchema { @@ -60,17 +58,16 @@ function Autojoin(props: { autojoin: string | null }) { return null; } -export function JoinGroup(props: JoinGroupProps) { +export function JoinGroup(props: JoinGroupProps): ReactElement { const { api, autojoin, associations, groups } = props; const history = useHistory(); const initialValues: FormSchema = { - group: autojoin || "", + group: autojoin || '' }; const [preview, setPreview] = useState< MetadataUpdatePreview | string | null >(null); - const waiter = useWaitForProps(props, _.isString(preview) ? 1 : 5000); const onConfirm = useCallback(async (group: string) => { @@ -78,9 +75,9 @@ export function JoinGroup(props: JoinGroupProps) { await api.groups.join(ship, name); try { await waiter((p: JoinGroupProps) => { - return group in p.groups && + return group in p.groups && (group in (p.associations?.graph ?? {}) - || group in (p.associations?.groups ?? {})) + || group in (p.associations?.groups ?? {})); }); if(props.groups?.[group]?.hidden) { @@ -98,7 +95,7 @@ export function JoinGroup(props: JoinGroupProps) { const onSubmit = useCallback( async (values: FormSchema, actions: FormikHelpers) => { - const [ship, name] = values.group.split("/"); + const [ship, name] = values.group.split('/'); const path = `/ship/${ship}/${name}`; // skip if it's unmanaged try { @@ -107,13 +104,13 @@ export function JoinGroup(props: JoinGroupProps) { setPreview(prev); } catch (e) { if (!(e instanceof Error)) { - actions.setStatus({ error: "Unknown error" }); - } else if (e.message === "no-permissions") { + actions.setStatus({ error: 'Unknown error' }); + } else if (e.message === 'no-permissions') { actions.setStatus({ error: - "Unable to join group, you do not have the correct permissions", + 'Unable to join group, you do not have the correct permissions' }); - } else if (e.message === "offline") { + } else if (e.message === 'offline') { setPreview(path); } } @@ -131,8 +128,8 @@ export function JoinGroup(props: JoinGroupProps) { {_.isString(preview) ? ( The host appears to be offline. Join anyway? - onConfirm(preview)} > @@ -173,7 +170,7 @@ export function JoinGroup(props: JoinGroupProps) { )} - onConfirm(preview.group)} @@ -188,7 +185,7 @@ export function JoinGroup(props: JoinGroupProps) { initialValues={initialValues} onSubmit={onSubmit} > - + & { metadata: Metadata; diff --git a/pkg/interface/src/views/landscape/components/NewChannel.tsx b/pkg/interface/src/views/landscape/components/NewChannel.tsx index 900210ac9..e58e606f8 100644 --- a/pkg/interface/src/views/landscape/components/NewChannel.tsx +++ b/pkg/interface/src/views/landscape/components/NewChannel.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { ReactElement } from 'react'; import { Box, ManagedTextInputField as Input, @@ -14,13 +14,14 @@ import { FormError } from '~/views/components/FormError'; import { RouteComponentProps } from 'react-router-dom'; import { stringToSymbol, parentPath, deSig } from '~/logic/lib/util'; import { resourceFromPath } from '~/logic/lib/group'; -import { Associations } from '~/types/metadata-update'; +import { Associations } from '@urbit/api/metadata'; import { useWaitForProps } from '~/logic/lib/useWaitForProps'; -import { Groups } from '~/types/group-update'; +import { Groups } from '@urbit/api/groups'; import { ShipSearch, shipSearchSchemaInGroup, shipSearchSchema } from '~/views/components/ShipSearch'; -import { Rolodex, Workspace } from '~/types'; +import { Rolodex } from '@urbit/api'; import { IconRadio } from '~/views/components/IconRadio'; import { ChannelWriteFieldSchema, ChannelWritePerms } from './ChannelWritePerms'; +import { Workspace } from '~/types/workspace'; type FormSchema = { name: string; @@ -47,7 +48,7 @@ interface NewChannelProps { workspace: Workspace; } -export function NewChannel(props: NewChannelProps & RouteComponentProps) { +export function NewChannel(props: NewChannelProps & RouteComponentProps): ReactElement { const { history, api, group, workspace, groups } = props; const waiter = useWaitForProps(props, 5000); @@ -59,7 +60,7 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) { : ''); try { let { description, moduleType, ships, writers } = values; - ships = ships.filter(e => e !== ""); + ships = ships.filter(e => e !== ''); if (workspace?.type === 'messages' && ships.length === 1) { return history.push(`/~landscape/dm/${deSig(ships[0])}`); } @@ -138,7 +139,7 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) { maxWidth="348px" gapY="4" > - + Channel Type = isPrivate ? { invite: { - pending: [], - }, + pending: [] + } } : { open: { banRanks: [], - banned: [], - }, + banned: [] + } }; await api.groups.create(name, policy, title, description); const path = `/ship/~${window.ship}/${name}`; diff --git a/pkg/interface/src/views/landscape/components/Participants.tsx b/pkg/interface/src/views/landscape/components/Participants.tsx index 6ae9275fb..16f8e1655 100644 --- a/pkg/interface/src/views/landscape/components/Participants.tsx +++ b/pkg/interface/src/views/landscape/components/Participants.tsx @@ -2,7 +2,6 @@ import React, { useState, useMemo, useCallback, - SyntheticEvent, ChangeEvent } from 'react'; import { @@ -11,26 +10,25 @@ import { Row, Text, Icon, - Center, - Button, Action, StatelessTextInput as Input } from '@tlon/indigo-react'; import _ from 'lodash'; import f from 'lodash/fp'; import VisibilitySensor from 'react-visibility-sensor'; +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; + +import { Contact, Contacts } from '@urbit/api/contacts'; +import { Group, RoleTags } from '@urbit/api/groups'; +import { Association } from '@urbit/api/metadata'; -import { Contact, Contacts } from '~/types/contact-update'; import { Sigil } from '~/logic/lib/sigil'; import { cite, uxToHex } from '~/logic/lib/util'; -import { Group, RoleTags } from '~/types/group-update'; import { roleForShip, resourceFromPath } from '~/logic/lib/group'; -import { Association } from '~/types/metadata-update'; -import { useHistory, Link } from 'react-router-dom'; import { Dropdown } from '~/views/components/Dropdown'; import GlobalApi from '~/logic/api/global'; import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction'; -import styled from 'styled-components'; import useLocalState from '~/logic/state/local'; const TruncText = styled(Text)` @@ -109,7 +107,7 @@ export function Participants(props: { group: Group; association: Association; api: GlobalApi; -}) { +}): ReactElement { const { api } = props; const tabFilters: Record< ParticipantsTabId, diff --git a/pkg/interface/src/views/landscape/components/PopoverRoutes.tsx b/pkg/interface/src/views/landscape/components/PopoverRoutes.tsx index 4c18d93c0..f457428bd 100644 --- a/pkg/interface/src/views/landscape/components/PopoverRoutes.tsx +++ b/pkg/interface/src/views/landscape/components/PopoverRoutes.tsx @@ -1,20 +1,21 @@ -import React, { useRef, useCallback } from "react"; -import { Route, Switch, RouteComponentProps, Link } from "react-router-dom"; -import { Box, Row, Col, Icon, Text } from "@tlon/indigo-react"; -import { HoverBoxLink } from "~/views/components/HoverBox"; -import { Contacts, Contact } from "~/types/contact-update"; -import { Group } from "~/types/group-update"; -import { Association } from "~/types/metadata-update"; -import GlobalApi from "~/logic/api/global"; -import { GroupNotificationsConfig, S3State, Associations } from "~/types"; +import React, { useRef, useCallback, ReactElement } from 'react'; +import { Route, Switch, RouteComponentProps, Link } from 'react-router-dom'; +import { Box, Col, Text } from '@tlon/indigo-react'; -import { GroupSettings } from "./GroupSettings/GroupSettings"; -import { Participants } from "./Participants"; -import {useHashLink} from "~/logic/lib/useHashLink"; -import {DeleteGroup} from "./DeleteGroup"; -import {resourceFromPath} from "~/logic/lib/group"; -import {ModalOverlay} from "~/views/components/ModalOverlay"; -import { SidebarItem } from "~/views/landscape/components/SidebarItem"; +import { GroupNotificationsConfig, Associations } from '@urbit/api'; +import { Contacts, Contact } from '@urbit/api/contacts'; +import { Group } from '@urbit/api/groups'; +import { Association } from '@urbit/api/metadata'; + +import GlobalApi from '~/logic/api/global'; +import { GroupSettings } from './GroupSettings/GroupSettings'; +import { Participants } from './Participants'; +import { useHashLink } from '~/logic/lib/useHashLink'; +import { DeleteGroup } from './DeleteGroup'; +import { resourceFromPath } from '~/logic/lib/group'; +import { ModalOverlay } from '~/views/components/ModalOverlay'; +import { SidebarItem } from '~/views/landscape/components/SidebarItem'; +import { S3State } from '~/types'; export function PopoverRoutes( props: { @@ -28,7 +29,7 @@ export function PopoverRoutes( notificationsGroupConfig: GroupNotificationsConfig; rootIdentity: Contact; } & RouteComponentProps -) { +): ReactElement { const relativeUrl = (url: string) => `${props.baseUrl}/popover${url}`; const innerRef = useRef(null); @@ -47,7 +48,7 @@ export function PopoverRoutes( return ( { const { view } = routeProps.match.params; return ( @@ -64,13 +65,13 @@ export function PopoverRoutes( > @@ -79,14 +80,14 @@ export function PopoverRoutes( Group {groupSize} { admin && ( @@ -96,12 +97,12 @@ export function PopoverRoutes( @@ -110,16 +111,16 @@ export function PopoverRoutes( - - {"<- Back"} + + {'<- Back'} - {view === "settings" && ( + {view === 'settings' && ( )} - {view === "participants" && ( + {view === 'participants' && ( @@ -48,11 +39,11 @@ export function Resource(props: ResourceProps) { - {app === "chat" ? ( + {app === 'chat' ? ( - ) : app === "publish" ? ( + ) : app === 'publish' ? ( ) : ( @@ -60,7 +51,7 @@ export function Resource(props: ResourceProps) { { return ( diff --git a/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx b/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx index 63bc35f86..43750dd5d 100644 --- a/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx +++ b/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx @@ -1,21 +1,15 @@ -import React, { ReactNode } from "react"; -import { Row, Icon, Box, Col, Text } from "@tlon/indigo-react"; -import styled from "styled-components"; -import { Link } from "react-router-dom"; - -import { ChatResource } from "~/views/apps/chat/ChatResource"; -import { PublishResource } from "~/views/apps/publish/PublishResource"; - -import RichText from "~/views/components/RichText"; - -import { Association } from "~/types/metadata-update"; -import GlobalApi from "~/logic/api/global"; -import { RouteComponentProps, Route, Switch } from "react-router-dom"; -import { ChannelSettings } from "./ChannelSettings"; -import { ChannelMenu } from "./ChannelMenu"; -import { NotificationGraphConfig, Groups } from "~/types"; -import {isWriter} from "~/logic/lib/group"; +import React, { ReactElement, ReactNode } from 'react'; +import { Icon, Box, Col, Text } from '@tlon/indigo-react'; +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; import urbitOb from 'urbit-ob'; + +import { Association } from '@urbit/api/metadata'; +import { Groups, Rolodex } from '@urbit/api'; + +import RichText from '~/views/components/RichText'; +import GlobalApi from '~/logic/api/global'; +import { isWriter } from '~/logic/lib/group'; import { getItemTitle } from '~/logic/lib/util'; const TruncatedBox = styled(Box)` @@ -26,7 +20,7 @@ const TruncatedBox = styled(Box)` type ResourceSkeletonProps = { groups: Groups; - contacts: any; + contacts: Rolodex; association: Association; api: GlobalApi; baseUrl: string; @@ -35,20 +29,20 @@ type ResourceSkeletonProps = { groupTags?: any; }; -export function ResourceSkeleton(props: ResourceSkeletonProps) { - const { association, api, baseUrl, children, atRoot, groups } = props; - const app = association?.metadata?.module || association["app-name"]; +export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement { + const { association, baseUrl, children, groups } = props; + const app = association?.metadata?.module || association['app-name']; const rid = association.resource; const group = groups[association.group]; let workspace = association.group; - if (group?.hidden && app === "chat") { - workspace = "/messages"; + if (group?.hidden && app === 'chat') { + workspace = '/messages'; } else if (group?.hidden) { - workspace = "/home"; + workspace = '/home'; } - let title = (workspace === "/messages") + let title = (workspace === '/messages') ? getItemTitle(association) : association?.metadata?.title; @@ -59,7 +53,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) { title = (props.contacts?.[title]?.nickname) ? props.contacts[title].nickname : title; } - const [, , ship, resource] = rid.split("/"); + const [, , ship, resource] = rid.split('/'); const resourcePath = (p: string) => baseUrl + p; @@ -89,10 +83,10 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) { fontSize='1' mr={3} my="1" - display={["block", "none"]} + display={['block', 'none']} flexShrink={0} > - {"<- Back"} + {'<- Back'} + minWidth={0} + > {title} - {(workspace === "/messages") ? recipient : association?.metadata?.description} + {(workspace === '/messages') ? recipient : association?.metadata?.description} diff --git a/pkg/interface/src/views/landscape/components/Sidebar/Apps.tsx b/pkg/interface/src/views/landscape/components/Sidebar/Apps.tsx index d56236ad0..fd32f998a 100644 --- a/pkg/interface/src/views/landscape/components/Sidebar/Apps.tsx +++ b/pkg/interface/src/views/landscape/components/Sidebar/Apps.tsx @@ -1,8 +1,8 @@ -import { useEffect, useCallback } from "react"; -import { Graphs, UnreadStats } from "~/types"; -import { SidebarItemStatus, SidebarAppConfig } from "./types"; +import { useCallback } from 'react'; +import { Graphs, UnreadStats } from '@urbit/api'; +import { SidebarAppConfig } from './types'; export function useGraphModule( graphKeys: Set, @@ -11,10 +11,10 @@ export function useGraphModule( ): SidebarAppConfig { const getStatus = useCallback( (s: string) => { - const [, , host, name] = s.split("/"); + const [, , host, name] = s.split('/'); const graphKey = `${host.slice(1)}/${name}`; if (!graphKeys.has(graphKey)) { - return "unsubscribed"; + return 'unsubscribed'; } const unreads = graphUnreads?.[s]?.['/']?.unreads; @@ -38,7 +38,6 @@ export function useGraphModule( return 0; } return 1; - }, [getStatus, graphUnreads]); return { getStatus, lastUpdated }; diff --git a/pkg/interface/src/views/landscape/components/Sidebar/Sidebar.tsx b/pkg/interface/src/views/landscape/components/Sidebar/Sidebar.tsx index 7cd0e2dcb..1ec0a3234 100644 --- a/pkg/interface/src/views/landscape/components/Sidebar/Sidebar.tsx +++ b/pkg/interface/src/views/landscape/components/Sidebar/Sidebar.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useRef } from 'react'; +import React, { ReactElement, ReactNode, useRef } from 'react'; import styled from 'styled-components'; import { Col @@ -12,14 +12,14 @@ import { Groups, Invites, Rolodex -} from '~/types'; +} from '@urbit/api'; import { SidebarListHeader } from './SidebarListHeader'; import { useLocalStorageState } from '~/logic/lib/useLocalStorageState'; import { getGroupFromWorkspace } from '~/logic/lib/workspace'; import { SidebarAppConfigs } from './types'; import { SidebarList } from './SidebarList'; import { roleForShip } from '~/logic/lib/group'; -import {useTutorialModal} from '~/views/components/useTutorialModal'; +import { useTutorialModal } from '~/views/components/useTutorialModal'; const ScrollbarLessCol = styled(Col)` scrollbar-width: none !important; @@ -46,7 +46,7 @@ interface SidebarProps { workspace: Workspace; } -export function Sidebar(props: SidebarProps) { +export function Sidebar(props: SidebarProps): ReactElement { const { associations, selected, workspace } = props; const groupPath = getGroupFromWorkspace(workspace); const display = props.mobileHide ? ['none', 'flex'] : 'flex'; diff --git a/pkg/interface/src/views/landscape/components/Sidebar/SidebarItem.tsx b/pkg/interface/src/views/landscape/components/Sidebar/SidebarItem.tsx index b42b345bb..6557edd75 100644 --- a/pkg/interface/src/views/landscape/components/Sidebar/SidebarItem.tsx +++ b/pkg/interface/src/views/landscape/components/Sidebar/SidebarItem.tsx @@ -1,26 +1,26 @@ -import React, {useRef} from "react"; -import _ from 'lodash'; - -import { Icon, Row, Box, Text, BaseImage } from "@tlon/indigo-react"; - -import { SidebarAppConfigs, SidebarItemStatus } from "./Sidebar"; -import { HoverBoxLink } from "~/views/components/HoverBox"; -import { Groups, Association } from "~/types"; -import { Sigil } from '~/logic/lib/sigil'; +import React, { ReactElement, useRef } from 'react'; import urbitOb from 'urbit-ob'; -import { getModuleIcon, getItemTitle, uxToHex } from "~/logic/lib/util"; -import {useTutorialModal} from "~/views/components/useTutorialModal"; -import {TUTORIAL_HOST, TUTORIAL_GROUP} from "~/logic/lib/tutorialModal"; + +import { Icon, Row, Box, Text, BaseImage } from '@tlon/indigo-react'; +import { Groups, Association, Rolodex } from '@urbit/api'; + +import { HoverBoxLink } from '~/views/components/HoverBox'; +import { Sigil } from '~/logic/lib/sigil'; +import { getModuleIcon, getItemTitle, uxToHex } from '~/logic/lib/util'; +import { useTutorialModal } from '~/views/components/useTutorialModal'; +import { TUTORIAL_HOST, TUTORIAL_GROUP } from '~/logic/lib/tutorialModal'; +import { SidebarAppConfigs, SidebarItemStatus } from './types'; +import { Workspace } from '~/types/workspace'; function SidebarItemIndicator(props: { status?: SidebarItemStatus }) { switch (props.status) { - case "disconnected": + case 'disconnected': return ; - case "unsubscribed": + case 'unsubscribed': return ; - case "mention": + case 'mention': return ; - case "loading": + case 'loading': return ; default: return null; @@ -30,20 +30,20 @@ function SidebarItemIndicator(props: { status?: SidebarItemStatus }) { export function SidebarItem(props: { hideUnjoined: boolean; association: Association; - contacts: any; + contacts: Rolodex; groups: Groups; path: string; selected: boolean; apps: SidebarAppConfigs; workspace: Workspace; -}) { +}): ReactElement { const { association, path, selected, apps, groups } = props; let title = getItemTitle(association); - const appName = association?.["app-name"]; + const appName = association?.['app-name']; const mod = association?.metadata?.module || appName; - const rid = association?.resource + const rid = association?.resource; const groupPath = association?.group; - const anchorRef = useRef(null) + const anchorRef = useRef(null); useTutorialModal( mod as any, groupPath === `/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`, @@ -54,11 +54,11 @@ export function SidebarItem(props: { if (!app) { return null; } - const DM = (isUnmanaged && props.workspace?.type === "messages"); + const DM = (isUnmanaged && props.workspace?.type === 'messages'); const itemStatus = app.getStatus(path); - const hasUnread = itemStatus === "unread" || itemStatus === "mention"; + const hasUnread = itemStatus === 'unread' || itemStatus === 'mention'; - const isSynced = itemStatus !== "unsubscribed"; + const isSynced = itemStatus !== 'unsubscribed'; let baseUrl = `/~landscape${groupPath}`; @@ -72,7 +72,7 @@ export function SidebarItem(props: { ? `${baseUrl}/resource/${mod}${rid}` : `${baseUrl}/join/${mod}${rid}`; - const color = selected ? "black" : isSynced ? "gray" : "lightGray"; + const color = selected ? 'black' : isSynced ? 'gray' : 'lightGray'; if (props.hideUnjoined && !isSynced) { return null; @@ -82,15 +82,15 @@ export function SidebarItem(props: { if (urbitOb.isValidPatp(title)) { if (props.contacts?.[title] && props.contacts[title].avatar) { - img = ; + img = ; } else { - img = + img = ; } if (props.contacts?.[title] && props.contacts[title].nickname) { title = props.contacts[title].nickname; } } else { - img = + img = ; } return ( @@ -125,9 +125,9 @@ export function SidebarItem(props: { overflow='hidden' width='100%' mono={urbitOb.isValidPatp(title)} - fontWeight={hasUnread ? "bold" : "regular"} - color={selected || isSynced ? "black" : "lightGray"} - style={{ textOverflow: 'ellipsis', whiteSpace: 'pre'}} + fontWeight={hasUnread ? 'bold' : 'regular'} + color={selected || isSynced ? 'black' : 'lightGray'} + style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }} > {title} diff --git a/pkg/interface/src/views/landscape/components/Sidebar/SidebarList.tsx b/pkg/interface/src/views/landscape/components/Sidebar/SidebarList.tsx index 25af63bff..8264b5d30 100644 --- a/pkg/interface/src/views/landscape/components/Sidebar/SidebarList.tsx +++ b/pkg/interface/src/views/landscape/components/Sidebar/SidebarList.tsx @@ -1,8 +1,10 @@ -import React, { useMemo } from "react"; -import { alphabeticalOrder } from "~/logic/lib/util"; -import { Associations, AppAssociations, Workspace, Groups } from "~/types"; -import { SidebarAppConfigs, SidebarListConfig, SidebarSort } from "./types"; -import { SidebarItem } from "./SidebarItem"; +import React, { ReactElement } from 'react'; +import { Associations, AppAssociations, Groups, Rolodex } from '@urbit/api'; + +import { alphabeticalOrder } from '~/logic/lib/util'; +import { SidebarAppConfigs, SidebarListConfig, SidebarSort } from './types'; +import { SidebarItem } from './SidebarItem'; +import { Workspace } from '~/types/workspace'; function sidebarSort( associations: AppAssociations, @@ -20,8 +22,8 @@ function sidebarSort( const lastUpdated = (a: string, b: string) => { const aAssoc = associations[a]; const bAssoc = associations[b]; - const aAppName = aAssoc?.["app-name"]; - const bAppName = bAssoc?.["app-name"]; + const aAppName = aAssoc?.['app-name']; + const bAppName = bAssoc?.['app-name']; const aUpdated = apps[aAppName]?.lastUpdated(a) || 0; const bUpdated = apps[bAppName]?.lastUpdated(b) || 0; @@ -37,7 +39,7 @@ function sidebarSort( export function SidebarList(props: { apps: SidebarAppConfigs; - contacts: any; + contacts: Rolodex; config: SidebarListConfig; associations: Associations; groups: Groups; @@ -45,7 +47,7 @@ export function SidebarList(props: { group?: string; selected?: string; workspace: Workspace; -}) { +}): ReactElement { const { selected, group, config, workspace } = props; const associations = { ...props.associations.graph }; @@ -53,11 +55,11 @@ export function SidebarList(props: { .filter((a) => { const assoc = associations[a]; if (workspace?.type === 'messages') { - return (!(assoc.group in props.associations.groups) && assoc.metadata.module === "chat"); + return (!(assoc.group in props.associations.groups) && assoc.metadata.module === 'chat'); } else { return group ? assoc.group === group - : (!(assoc.group in props.associations.groups) && assoc.metadata.module !== "chat"); + : (!(assoc.group in props.associations.groups) && assoc.metadata.module !== 'chat'); } }) .sort(sidebarSort(associations, props.apps)[config.sortBy]); diff --git a/pkg/interface/src/views/landscape/components/Sidebar/SidebarListHeader.tsx b/pkg/interface/src/views/landscape/components/Sidebar/SidebarListHeader.tsx index 7d713d66e..829e21e9c 100644 --- a/pkg/interface/src/views/landscape/components/Sidebar/SidebarListHeader.tsx +++ b/pkg/interface/src/views/landscape/components/Sidebar/SidebarListHeader.tsx @@ -1,5 +1,7 @@ -import React, { useCallback } from "react"; -import * as Yup from "yup"; +import React, { ReactElement, useCallback } from 'react'; +import { FormikHelpers } from 'formik'; +import { Link } from 'react-router-dom'; + import { Row, Box, @@ -7,18 +9,18 @@ import { ManagedRadioButtonField as Radio, ManagedCheckboxField as Checkbox, Col, - Text, -} from "@tlon/indigo-react"; -import { FormikOnBlur } from "~/views/components/FormikOnBlur"; -import { Dropdown } from "~/views/components/Dropdown"; -import { FormikHelpers } from "formik"; -import { SidebarListConfig, Workspace } from "./types"; -import { Link, useHistory } from 'react-router-dom'; -import { getGroupFromWorkspace } from "~/logic/lib/workspace"; -import { roleForShip } from "~/logic/lib/group"; -import {Groups, Rolodex, Associations} from "~/types"; -import { NewChannel } from "~/views/landscape/components/NewChannel"; -import GlobalApi from "~/logic/api/global"; + Text +} from '@tlon/indigo-react'; +import { Groups, Rolodex, Associations } from '@urbit/api'; + +import { FormikOnBlur } from '~/views/components/FormikOnBlur'; +import { Dropdown } from '~/views/components/Dropdown'; +import { SidebarListConfig } from './types'; +import { getGroupFromWorkspace } from '~/logic/lib/workspace'; +import { roleForShip } from '~/logic/lib/group'; +import { NewChannel } from '~/views/landscape/components/NewChannel'; +import GlobalApi from '~/logic/api/global'; +import { Workspace } from '~/types/workspace'; export function SidebarListHeader(props: { api: GlobalApi; @@ -30,9 +32,7 @@ export function SidebarListHeader(props: { selected: string; workspace: Workspace; handleSubmit: (c: SidebarListConfig) => void; -}) { - - const history = useHistory(); +}): ReactElement { const onSubmit = useCallback( (values: SidebarListConfig, actions: FormikHelpers) => { props.handleSubmit(values); @@ -46,9 +46,9 @@ export function SidebarListHeader(props: { const memberMetadata = groupPath ? props.associations.groups?.[groupPath].metadata.vip === 'member-metadata' : false; - const isAdmin = memberMetadata || (role === "admin") || (props.workspace?.type === 'home') || (props.workspace?.type === "messages"); + const isAdmin = memberMetadata || (role === 'admin') || (props.workspace?.type === 'home') || (props.workspace?.type === 'messages'); - const noun = (props.workspace?.type === "messages") ? "Messages" : "Channels"; + const noun = (props.workspace?.type === 'messages') ? 'Messages' : 'Channels'; return ( - {props.workspace?.type === "messages" + {props.workspace?.type === 'messages' ? ( } > - + ) : ( - + : `/~landscape/${props.workspace?.type}/new`} + > + ) } @@ -111,7 +112,7 @@ export function SidebarListHeader(props: { flexShrink='0' width="auto" alignY="top" - alignX={["right", "left"]} + alignX={['right', 'left']} options={ diff --git a/pkg/interface/src/views/landscape/components/Sidebar/types.ts b/pkg/interface/src/views/landscape/components/Sidebar/types.ts index 7cdc970e0..56ecda551 100644 --- a/pkg/interface/src/views/landscape/components/Sidebar/types.ts +++ b/pkg/interface/src/views/landscape/components/Sidebar/types.ts @@ -1,11 +1,11 @@ export type SidebarItemStatus = - | "unread" - | "mention" - | "unsubscribed" - | "disconnected" - | "loading"; + | 'unread' + | 'mention' + | 'unsubscribed' + | 'disconnected' + | 'loading'; -export type SidebarSort = "asc" | "lastUpdated"; +export type SidebarSort = 'asc' | 'lastUpdated'; export interface SidebarListConfig { sortBy: SidebarSort; @@ -18,5 +18,5 @@ export interface SidebarAppConfig { } export type SidebarAppConfigs = { - [a in "chat" | "link" | "publish"]: SidebarAppConfig; + [a in 'chat' | 'link' | 'publish']: SidebarAppConfig; }; diff --git a/pkg/interface/src/views/landscape/components/SidebarItem.tsx b/pkg/interface/src/views/landscape/components/SidebarItem.tsx index 798d31e3e..71aea2a02 100644 --- a/pkg/interface/src/views/landscape/components/SidebarItem.tsx +++ b/pkg/interface/src/views/landscape/components/SidebarItem.tsx @@ -1,8 +1,8 @@ -import React from "react"; -import { Row, Icon, Text } from "@tlon/indigo-react"; +import React from 'react'; +import { Row, Icon, Text } from '@tlon/indigo-react'; -import { IconRef, PropFunc } from "~/types/util"; -import { HoverBoxLink } from "~/views/components/HoverBox"; +import { IconRef, PropFunc } from '~/types/util'; +import { HoverBoxLink } from '~/views/components/HoverBox'; interface SidebarItemProps { selected?: boolean; @@ -11,17 +11,17 @@ interface SidebarItemProps { to: string; color?: string; children?: JSX.Element; -} +} export const SidebarItem = ({ icon, text, to, selected = false, - color = "black", + color = 'black', children, ...rest -}: SidebarItemProps & PropFunc) => { +}: SidebarItemProps & PropFunc): ReactElement => { return ( ; graphs: Graphs; linkListening: Set; - links: LinkCollections; - notebooks: Notebooks; invites: Invites; selected?: string; selectedApp?: AppName; @@ -33,10 +28,10 @@ interface SkeletonProps { subscription: GlobalSubscription; includeUnmanaged: boolean; workspace: Workspace; - unreads: any; + unreads: unknown; } -export function Skeleton(props: SkeletonProps) { +export function Skeleton(props: SkeletonProps): ReactElement { const graphConfig = useGraphModule(props.graphKeys, props.graphs, props.unreads.graph); const config = useMemo( () => ({ diff --git a/pkg/interface/src/views/landscape/components/TutorialModal.tsx b/pkg/interface/src/views/landscape/components/TutorialModal.tsx index 350287fc8..aa1c67618 100644 --- a/pkg/interface/src/views/landscape/components/TutorialModal.tsx +++ b/pkg/interface/src/views/landscape/components/TutorialModal.tsx @@ -1,11 +1,11 @@ -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback } from 'react'; import _ from 'lodash'; -import { Box, Col, Row, Button, Text, Icon, Action } from "@tlon/indigo-react"; -import { useHistory } from "react-router-dom"; -import { TutorialProgress, tutorialProgress as progress } from "~/types"; +import { Box, Col, Row, Button, Text, Icon } from '@tlon/indigo-react'; +import { useHistory } from 'react-router-dom'; +import { TutorialProgress, tutorialProgress as progress } from '~/types'; -import { Portal } from "~/views/components/Portal"; -import useLocalState, { selectLocalState } from "~/logic/state/local"; +import { Portal } from '~/views/components/Portal'; +import useLocalState, { selectLocalState } from '~/logic/state/local'; import { progressDetails, MODAL_HEIGHT_PX, @@ -14,21 +14,21 @@ import { MODAL_HEIGHT, TUTORIAL_HOST, TUTORIAL_GROUP, - getTrianglePosition, -} from "~/logic/lib/tutorialModal"; -import { getRelativePosition } from "~/logic/lib/relativePosition"; -import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton"; -import GlobalApi from "~/logic/api/global"; -import {Triangle} from "~/views/components/Triangle"; -import {ModalOverlay} from "~/views/components/ModalOverlay"; + getTrianglePosition +} from '~/logic/lib/tutorialModal'; +import { getRelativePosition } from '~/logic/lib/relativePosition'; +import { StatelessAsyncButton } from '~/views/components/StatelessAsyncButton'; +import GlobalApi from '~/logic/api/global'; +import { Triangle } from '~/views/components/Triangle'; +import { ModalOverlay } from '~/views/components/ModalOverlay'; const localSelector = selectLocalState([ - "tutorialProgress", - "nextTutStep", - "prevTutStep", - "tutorialRef", - "hideTutorial", - "set" + 'tutorialProgress', + 'nextTutStep', + 'prevTutStep', + 'tutorialRef', + 'hideTutorial', + 'set' ]); export function TutorialModal(props: { api: GlobalApi }) { @@ -47,7 +47,7 @@ export function TutorialModal(props: { api: GlobalApi }) { alignX, alignY, offsetX, - offsetY, + offsetY } = progressDetails[tutorialProgress]; const [coords, setCoords] = useState({}); @@ -56,7 +56,7 @@ export function TutorialModal(props: { api: GlobalApi }) { const history = useHistory(); const next = useCallback( () => { - const idx = progress.findIndex((p) => p === tutorialProgress); + const idx = progress.findIndex(p => p === tutorialProgress); const { url } = progressDetails[progress[idx + 1]]; nextTutStep(); history.push(url); @@ -64,7 +64,7 @@ export function TutorialModal(props: { api: GlobalApi }) { [nextTutStep, history, tutorialProgress, setCoords] ); const prev = useCallback(() => { - const idx = progress.findIndex((p) => p === tutorialProgress); + const idx = progress.findIndex(p => p === tutorialProgress); prevTutStep(); history.push(progressDetails[progress[idx - 1]].url); }, [prevTutStep, history, tutorialProgress]); @@ -94,7 +94,6 @@ export function TutorialModal(props: { api: GlobalApi }) { setCoords(withMobile); } else { setCoords({}); - } }, [tutorialRef]); @@ -115,12 +114,12 @@ export function TutorialModal(props: { api: GlobalApi }) { await props.api.groups.leaveGroup(TUTORIAL_HOST, TUTORIAL_GROUP); }, [props.api]); - const progressIdx = progress.findIndex((p) => p === tutorialProgress); + const progressIdx = progress.findIndex(p => p === tutorialProgress); useEffect(() => { if ( - tutorialProgress !== "hidden" && - tutorialProgress !== "done" && + tutorialProgress !== 'hidden' && + tutorialProgress !== 'done' && tutorialRef ) { const interval = setInterval(updatePos, 100); @@ -164,7 +163,7 @@ export function TutorialModal(props: { api: GlobalApi }) { ); } - if (tutorialProgress === "hidden") { + if (tutorialProgress === 'hidden') { return null; } @@ -191,11 +190,9 @@ export function TutorialModal(props: { api: GlobalApi }) { - ) - + ); } - if(Object.keys(coords).length === 0) { return null; } @@ -208,7 +205,7 @@ export function TutorialModal(props: { api: GlobalApi }) { bg="white" zIndex={50} height={MODAL_HEIGHT_PX} - width={["100%", MODAL_WIDTH_PX]} + width={['100%', MODAL_WIDTH_PX]} borderRadius="2" > - - + - + {description} { progressIdx > 1 && ( diff --git a/pkg/interface/src/views/landscape/index.tsx b/pkg/interface/src/views/landscape/index.tsx index f09baaa6e..d64511cd8 100644 --- a/pkg/interface/src/views/landscape/index.tsx +++ b/pkg/interface/src/views/landscape/index.tsx @@ -1,14 +1,13 @@ -import React, { Component, useEffect, useCallback } from 'react'; +import React, { Component, useEffect, useCallback, ReactElement } from 'react'; import { Route, Switch, RouteComponentProps } from 'react-router-dom'; import Helmet from 'react-helmet'; import './css/custom.css'; -import { PatpNoSig } from '~/types/noun'; +import { PatpNoSig } from '@urbit/api'; import GlobalApi from '~/logic/api/global'; import { StoreState } from '~/logic/store/type'; import { GroupsPane } from './components/GroupsPane'; -import { Workspace } from '~/types'; import { NewGroup } from './components/NewGroup'; import { JoinGroup } from './components/JoinGroup'; @@ -16,7 +15,8 @@ import { cite } from '~/logic/lib/util'; import { Body } from '../components/Body'; import { Box } from '@tlon/indigo-react'; import { Loading } from '../components/Loading'; - +import { Workspace } from '~/types/workspace'; +import GlobalSubscription from '~/logic/subscription/global'; type LandscapeProps = StoreState & { ship: PatpNoSig; @@ -24,7 +24,7 @@ type LandscapeProps = StoreState & { subscription: GlobalSubscription; } -export function DMRedirect(props: LandscapeProps & RouteComponentProps & { ship: string; }) { +export function DMRedirect(props: LandscapeProps & RouteComponentProps & { ship: string; }): ReactElement { const { ship, api, history, graphKeys } = props; const goToGraph = useCallback((graph: string) => { history.push(`/~landscape/messages/resource/chat/ship/~${graph}`); @@ -47,7 +47,6 @@ export function DMRedirect(props: LandscapeProps & RouteComponentProps & { ship: const aud = ship !== window.ship ? [`~${ship}`] : []; const title = `${cite(window.ship)} <-> ${cite(ship)}`; - api.graph.createUnmanagedGraph( `dm--${ship}`, title, @@ -57,22 +56,20 @@ export function DMRedirect(props: LandscapeProps & RouteComponentProps & { ship: ).then(() => { goToGraph(station); }); - }, []); return ( ); - } -export default class Landscape extends Component { - componentDidMount() { +export default class Landscape extends Component> { + componentDidMount(): void { this.props.subscription.startApp('groups'); this.props.subscription.startApp('graph'); } - render() { + render(): ReactElement { const { props } = this; return ( @@ -82,7 +79,7 @@ export default class Landscape extends Component { { + render={(routeProps) => { const { host, name @@ -93,10 +90,11 @@ export default class Landscape extends Component { return ( - ) - }}/> + ); + }} + /> { + render={() => { const ws: Workspace = { type: 'home' }; return ( @@ -104,7 +102,7 @@ export default class Landscape extends Component { }} /> { + render={() => { const ws: Workspace = { type: 'messages' }; return ( @@ -112,7 +110,7 @@ export default class Landscape extends Component { }} /> { + render={(routeProps) => { return ( @@ -129,13 +127,13 @@ export default class Landscape extends Component { }} /> { + render={(routeProps) => { const { ship } = routeProps.match.params; - return + return ; }} /> { + render={(routeProps) => { const { ship, name } = routeProps.match.params; const autojoin = ship && name ? `${ship}/${name}` : null; return ( diff --git a/pkg/npm/.gitignore b/pkg/npm/.gitignore new file mode 100644 index 000000000..d8b83df9c --- /dev/null +++ b/pkg/npm/.gitignore @@ -0,0 +1 @@ +package-lock.json diff --git a/pkg/npm/README.md b/pkg/npm/README.md new file mode 100644 index 000000000..ba1f6d01a --- /dev/null +++ b/pkg/npm/README.md @@ -0,0 +1 @@ +Each one of the folders in this directory is published at `@urbit/{folder name}` \ No newline at end of file diff --git a/pkg/npm/api/.eslintrc.js b/pkg/npm/api/.eslintrc.js new file mode 100644 index 000000000..fba1ccabc --- /dev/null +++ b/pkg/npm/api/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: "@urbit" +}; \ No newline at end of file diff --git a/pkg/npm/api/contacts/index.d.ts b/pkg/npm/api/contacts/index.d.ts new file mode 100644 index 000000000..bbb037c89 --- /dev/null +++ b/pkg/npm/api/contacts/index.d.ts @@ -0,0 +1,78 @@ +import { Path, Patp } from ".."; +import {Resource} from "../groups/update.d"; + +export type ContactUpdate = + | ContactUpdateAdd + | ContactUpdateRemove + | ContactUpdateEdit + | ContactUpdateInitial + +interface ContactUpdateAdd { + add: { + ship: Patp; + contact: Contact; + }; +} + +interface ContactUpdateRemove { + remove: { + ship: Patp; + }; +} + +interface ContactUpdateEdit { + edit: { + path: Path; + ship: Patp; + "edit-field": ContactEditField; + timestamp: number; + }; +} + +interface ContactUpdateAllowShips { + allow: { + ships: Patp[]; + } +} + +interface ContactUpdateAllowGroup { + allow: { + group: Path; + } +} + +interface ContactUpdateSetPublic { + 'set-public': boolean; +} + +export interface ContactShare { + share: Patp; +} + +interface ContactUpdateInitial { + initial: Rolodex; +} + +export type Rolodex = { + [p in Patp]: Contact; +}; + +export interface Contact { + nickname: string; + bio: string; + status: string; + color: string; + avatar: string | null; + cover: string | null; + groups: Path[]; + 'last-updated': number; +} + +type ContactKeys = keyof Contact; + +export type ContactEditFieldPrim = Exclude; + +export type ContactEditField = Partial> & { + 'add-group'?: Resource; + 'remove-group'?: Resource; +}; diff --git a/pkg/npm/api/contacts/index.ts b/pkg/npm/api/contacts/index.ts new file mode 100644 index 000000000..ca5ba9d01 --- /dev/null +++ b/pkg/npm/api/contacts/index.ts @@ -0,0 +1,50 @@ +import { Enc, Path, Patp, Poke } from ".."; +import { + Contact, + ContactUpdateAdd, + ContactUpdateEdit, + ContactUpdateRemove, + ContactEditField, + ContactShare, + ContactUpdate, +} from "./index.d"; + +export const storeAction = (data: T): Poke => ({ + app: "contact-store", + mark: "contact-action", + json: data, +}); + +export const add = (ship: Patp, contact: Contact): Poke => { + contact["last-updated"] = Date.now(); + + return storeAction({ + add: { ship, contact }, + }); +}; + +export const remove = (ship: Patp): Poke => + storeAction({ + remove: { ship }, + }); + +export const share = (recipient: Patp): Poke => ({ + app: "contact-push-hook", + mark: "contact-action", + json: { share: recipient }, +}); + +export const edit = ( + path: Path, + ship: Patp, + editField: ContactEditField +): Poke => + storeAction({ + edit: { + path, + ship, + "edit-field": editField, + timestamp: Date.now(), + }, + }); + diff --git a/pkg/interface/src/types/graph-update.ts b/pkg/npm/api/graph/index.d.ts similarity index 75% rename from pkg/interface/src/types/graph-update.ts rename to pkg/npm/api/graph/index.d.ts index ad071b97d..b98872e81 100644 --- a/pkg/interface/src/types/graph-update.ts +++ b/pkg/npm/api/graph/index.d.ts @@ -1,5 +1,5 @@ -import { Patp } from "./noun"; -import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap"; +import { Patp } from ".."; +import BigIntOrderedMap from "../lib/BigIntOrderedMap"; export interface TextContent { text: string; @@ -37,6 +37,15 @@ export interface Post { "time-sent": number; } +export interface GraphNodePoke { + post: Post; + children: GraphChildrenPoke | null; +} + +export interface GraphChildrenPoke { + [k: string]: GraphNodePoke; +} + export interface GraphNode { children: Graph; post: Post; diff --git a/pkg/npm/api/graph/index.ts b/pkg/npm/api/graph/index.ts new file mode 100644 index 000000000..82d4cc986 --- /dev/null +++ b/pkg/npm/api/graph/index.ts @@ -0,0 +1,370 @@ +import _ from 'lodash'; +import { PatpNoSig, Patp, Poke, Thread, Path, Enc } from '..'; +import { Content, GraphNode, Post, GraphNodePoke, GraphChildrenPoke } from './index.d'; +import { deSig, unixToDa } from '../lib/util'; +import { makeResource, resourceFromPath } from '../groups/index'; +import { GroupPolicy } from '../groups/update.d'; + +export const createBlankNodeWithChildPost = ( + ship: PatpNoSig, + parentIndex: string = '', + childIndex: string = '', + contents: Content[] +): GraphNodePoke => { + const date = unixToDa(Date.now()).toString(); + const nodeIndex = parentIndex + '/' + date; + + const childGraph: GraphChildrenPoke = {}; + childGraph[childIndex] = { + post: { + author: `~${ship}`, + index: nodeIndex + '/' + childIndex, + 'time-sent': Date.now(), + contents, + hash: null, + signatures: [] + }, + children: null + }; + + return { + post: { + author: `~${ship}`, + index: nodeIndex, + 'time-sent': Date.now(), + contents: [], + hash: null, + signatures: [] + }, + children: childGraph + }; +}; + +function markPending(nodes: any) { + _.forEach(nodes, node => { + node.post.author = deSig(node.post.author); + node.post.pending = true; + markPending(node.children || {}); + }); +} + +export const createPost = ( + ship: PatpNoSig, + contents: Content[], + parentIndex: string = '', + childIndex:string = 'DATE_PLACEHOLDER' +): Post => { + if (childIndex === 'DATE_PLACEHOLDER') { + childIndex = unixToDa(Date.now()).toString(); + } + return { + author: `~${ship}`, + index: parentIndex + '/' + childIndex, + 'time-sent': Date.now(), + contents, + hash: null, + signatures: [] + }; +}; + +function moduleToMark(mod: string): string | undefined { + if(mod === 'link') { + return 'graph-validator-link'; + } + if(mod === 'publish') { + return 'graph-validator-publish'; + } + if(mod === 'chat') { + return 'graph-validator-chat'; + } + return undefined; +} + +const storeAction = (data: T): Poke => ({ + app: 'graph-store', + mark: 'graph-update', + json: data +}); + +export { storeAction as graphStoreAction }; + +const viewAction = (threadName: string, action: T): Thread => ({ + inputMark: 'graph-view-action', + outputMark: 'json', + threadName, + body: action +}); + +export { viewAction as graphViewAction }; + +const hookAction = (data: T): Poke => ({ + app: 'graph-push-hook', + mark: 'graph-update', + json: data +}); + +export { hookAction as graphHookAction }; + + +export const createManagedGraph = ( + ship: PatpNoSig, + name: string, + title: string, + description: string, + group: Path, + mod: string +): Thread => { + const associated = { group: resourceFromPath(group) }; + const resource = makeResource(`~${ship}`, name); + + return viewAction('graph-create', { + create: { + resource, + title, + description, + associated, + module: mod, + mark: moduleToMark(mod) + } + }); +} + +export const createUnmanagedGraph = ( + ship: PatpNoSig, + name: string, + title: string, + description: string, + policy: Enc, + mod: string +): Thread => { + const resource = makeResource(`~${ship}`, name); + + return viewAction('graph-create', { + create: { + resource, + title, + description, + associated: { policy }, + module: mod, + mark: moduleToMark(mod) + } + }); +} + +export const joinGraph = ( + ship: Patp, + name: string +): Thread => { + const resource = makeResource(ship, name); + return viewAction('graph-join', { + join: { + resource, + ship, + } + }); +} + +export const deleteGraph = ( + ship: PatpNoSig, + name: string +): Thread => { + const resource = makeResource(`~${ship}`, name); + return viewAction('graph-delete', { + "delete": { + resource + } + }); +} + +export const leaveGraph = ( + ship: Patp, + name: string +): Thread => { + const resource = makeResource(ship, name); + return viewAction('graph-leave', { + "leave": { + resource + } + }); +} + +export const groupifyGraph = ( + ship: Patp, + name: string, + toPath?: string +): Thread => { + const resource = makeResource(ship, name); + const to = toPath && resourceFromPath(toPath); + + return viewAction('graph-groupify', { + groupify: { + resource, + to + } + }); +} + +export const evalCord = ( + cord: string +): Thread => { + return ({ + inputMark: 'graph-view-action', + outputMark: 'tang', + threadName: 'graph-eval', + body: { + eval: cord + } + }); +} + +export const addGraph = ( + ship: Patp, + name: string, + graph: any, + mark: any +): Poke => { + return storeAction({ + 'add-graph': { + resource: { ship, name }, + graph, + mark + } + }); +} + +export const addPost = ( + ship: Patp, + name: string, + post: Post +) => { + let nodes = {}; + nodes[post.index] = { + post, + children: null + }; + return addNodes(ship, name, nodes); +} + +export const addNode = ( + ship: Patp, + name: string, + node: GraphNode +) => { + let nodes = {}; + nodes[node.post.index] = node; + + return addNodes(ship, name, nodes); +} + +export const addNodes = ( + ship: Patp, + name: string, + nodes: Object +): Poke => { + const action = { + 'add-nodes': { + resource: { ship, name }, + nodes + } + }; + + markPending(action['add-nodes'].nodes); + action['add-nodes'].resource.ship = action['add-nodes'].resource.ship.slice(1); + // this.store.handleEvent({ data: { 'graph-update': action } });// TODO address this.store + return hookAction(action); +} + +export const removeNodes = ( + ship: Patp, + name: string, + indices: string[] +): Poke => { + return hookAction({ + 'remove-nodes': { + resource: { ship, name }, + indices + } + }); +} + +// TODO these abominations +// getKeys() { +// return this.scry('graph-store', '/keys') +// .then((keys) => { +// this.store.handleEvent({ +// data: keys +// }); +// }); +// } + +// getTags() { +// return this.scry('graph-store', '/tags') +// .then((tags) => { +// this.store.handleEvent({ +// data: tags +// }); +// }); +// } + +// getTagQueries() { +// return this.scry('graph-store', '/tag-queries') +// .then((tagQueries) => { +// this.store.handleEvent({ +// data: tagQueries +// }); +// }); +// } + +// getGraph(ship: string, resource: string) { +// return this.scry('graph-store', `/graph/${ship}/${resource}`) +// .then((graph) => { +// this.store.handleEvent({ +// data: graph +// }); +// }); +// } + +// async getNewest(ship: string, resource: string, count: number, index = '') { +// const data = await this.scry('graph-store', `/newest/${ship}/${resource}/${count}${index}`); +// this.store.handleEvent({ data }); +// } + +// async getOlderSiblings(ship: string, resource: string, count: number, index = '') { +// const idx = index.split('/').map(decToUd).join('/'); +// const data = await this.scry('graph-store', +// `/node-siblings/older/${ship}/${resource}/${count}${idx}` +// ); +// this.store.handleEvent({ data }); +// } + +// async getYoungerSiblings(ship: string, resource: string, count: number, index = '') { +// const idx = index.split('/').map(decToUd).join('/'); +// const data = await this.scry('graph-store', +// `/node-siblings/younger/${ship}/${resource}/${count}${idx}` +// ); +// this.store.handleEvent({ data }); +// } + + +// getGraphSubset(ship: string, resource: string, start: string, end: string) { +// return this.scry( +// 'graph-store', +// `/graph-subset/${ship}/${resource}/${end}/${start}` +// ).then((subset) => { +// this.store.handleEvent({ +// data: subset +// }); +// }); +// } + +// getNode(ship: string, resource: string, index: string) { +// const idx = index.split('/').map(numToUd).join('/'); +// return this.scry( +// 'graph-store', +// `/node/${ship}/${resource}${idx}` +// ).then((node) => { +// this.store.handleEvent({ +// data: node +// }); +// }); +// } diff --git a/pkg/npm/api/groups/index.d.ts b/pkg/npm/api/groups/index.d.ts new file mode 100644 index 000000000..76567715c --- /dev/null +++ b/pkg/npm/api/groups/index.d.ts @@ -0,0 +1,2 @@ +export * from './update.d'; +export * from './view.d'; \ No newline at end of file diff --git a/pkg/npm/api/groups/index.ts b/pkg/npm/api/groups/index.ts new file mode 100644 index 000000000..02122d3e5 --- /dev/null +++ b/pkg/npm/api/groups/index.ts @@ -0,0 +1,117 @@ +import { Enc, Path, Patp, PatpNoSig, Poke } from ".."; +import { + Group, + GroupAction, + GroupPolicyDiff, + GroupUpdateAddMembers, + GroupUpdateAddTag, + GroupUpdateChangePolicy, + GroupUpdateRemoveGroup, + GroupUpdateRemoveMembers, + GroupUpdateRemoveTag, + Resource, + Tag +} from "./index.d"; +import { GroupPolicy } from "./update"; + +export const proxyAction = (data: T): Poke => ({ + app: 'group-push-hook', + mark: 'group-update', + json: data +}); + +export const storeAction = (data: T): Poke => ({ + app: 'group-store', + mark: 'group-update', + json: data +}); + +export const remove = ( + resource: Resource, + ships: PatpNoSig[] +): Poke => proxyAction({ + removeMembers: { + resource, + ships + } +}); + +export const addTag = ( + resource: Resource, + tag: Tag, + ships: Patp[] +): Poke => proxyAction({ + addTag: { + resource, + tag, + ships + } +}); + +export const removeTag = ( + tag: Tag, + resource: Resource, + ships: PatpNoSig[] +): Poke => proxyAction({ + removeTag: { + tag, + resource, + ships + } +}); + +export const add = ( + resource: Resource, + ships: PatpNoSig[] +): Poke => proxyAction({ + addMembers: { + resource, + ships + } +}); + +export const removeGroup = ( + resource: Resource +): Poke => storeAction({ + removeGroup: { + resource + } +}); + +export const changePolicy = ( + resource: Resource, + diff: GroupPolicyDiff +): Poke => proxyAction({ + changePolicy: { + resource, + diff + } +}); + +export const roleTags = ['janitor', 'moderator', 'admin']; +// TODO make this type better? + +export function roleForShip(group: Group, ship: PatpNoSig): string | undefined { + return roleTags.reduce((currRole, role) => { + const roleShips = group?.tags?.role?.[role]; + return roleShips && roleShips.has(ship) ? role : currRole; + }, undefined as string | undefined); +} + +export function resourceFromPath(path: Path): Resource { + const [, , ship, name] = path.split('/'); + return { ship, name } +} + +export function makeResource(ship: string, name:string) { + return { ship, name }; +} + +export const groupBunts = { + group: (): Group => ({ members: new Set(), tags: { role: {} }, hidden: false, policy: groupBunts.policy() }), + policy: (): GroupPolicy => ({ open: { banned: new Set(), banRanks: new Set() } }) +}; + +export const joinError = ['no-perms', 'strange'] as const; +export const joinResult = ['done', ...joinError] as const; +export const joinProgress = ['start', 'added', ...joinResult] as const; diff --git a/pkg/interface/src/types/group-update.ts b/pkg/npm/api/groups/update.d.ts similarity index 88% rename from pkg/interface/src/types/group-update.ts rename to pkg/npm/api/groups/update.d.ts index ad7c35004..00d3ac340 100644 --- a/pkg/interface/src/types/group-update.ts +++ b/pkg/npm/api/groups/update.d.ts @@ -1,6 +1,6 @@ -import { PatpNoSig, Path, Jug, ShipRank, Enc } from './noun'; +import { PatpNoSig, Path, Jug, ShipRank, Enc } from '..'; +import { roleTags } from './index'; -export const roleTags = ['janitor', 'moderator', 'admin'] as const; export type RoleTags = typeof roleTags[number]; interface RoleTag { tag: 'admin' | 'moderator' | 'janitor'; @@ -133,7 +133,7 @@ interface GroupUpdateRemoveTag { removeTag: { tag: Tag; resource: Resource; - ships: PatpNoSig; + ships: PatpNoSig[]; }; } @@ -174,7 +174,4 @@ export type GroupUpdate = export type GroupAction = Omit; -export const groupBunts = { - group: (): Group => ({ members: new Set(), tags: { role: {} }, hidden: false, policy: groupBunts.policy() }), - policy: (): GroupPolicy => ({ open: { banned: new Set(), banRanks: new Set() } }) -}; + diff --git a/pkg/interface/src/types/group-view.ts b/pkg/npm/api/groups/view.d.ts similarity index 54% rename from pkg/interface/src/types/group-view.ts rename to pkg/npm/api/groups/view.d.ts index 635a2954a..e85e5adb5 100644 --- a/pkg/interface/src/types/group-view.ts +++ b/pkg/npm/api/groups/view.d.ts @@ -1,9 +1,10 @@ -export const joinError = ['no-perms', 'strange'] as const; +import { joinError, joinProgress, joinResult } from "."; + export type JoinError = typeof joinError[number]; -export const joinResult = ['done', ...joinError] as const; + export type JoinResult = typeof joinResult[number]; -export const joinProgress = ['start', 'added', ...joinResult] as const; + export type JoinProgress = typeof joinProgress[number]; export interface JoinRequests { diff --git a/pkg/interface/src/types/hark-update.ts b/pkg/npm/api/hark/index.d.ts similarity index 88% rename from pkg/interface/src/types/hark-update.ts rename to pkg/npm/api/hark/index.d.ts index 156f96b66..eaa92db40 100644 --- a/pkg/interface/src/types/hark-update.ts +++ b/pkg/npm/api/hark/index.d.ts @@ -1,7 +1,6 @@ -import _ from "lodash"; -import { Post } from "./graph-update"; -import { GroupUpdate } from "./group-update"; -import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap"; +import { Post } from "../graph/index.d"; +import { GroupUpdate } from "../groups/index.d"; +import BigIntOrderedMap from "../lib/BigIntOrderedMap"; export type GraphNotifDescription = "link" | "comment" | "note" | "mention"; diff --git a/pkg/npm/api/hark/index.ts b/pkg/npm/api/hark/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/npm/api/index.d.ts b/pkg/npm/api/index.d.ts new file mode 100644 index 000000000..eecb669f3 --- /dev/null +++ b/pkg/npm/api/index.d.ts @@ -0,0 +1,7 @@ +export * from './contacts/index.d' +export * from './graph/index.d'; +export * from './groups/index.d'; +export * from './hark/index.d'; +export * from './invite/index.d'; +export * from './lib/index.d'; +export * from './metadata/index.d'; \ No newline at end of file diff --git a/pkg/npm/api/index.js b/pkg/npm/api/index.js new file mode 100644 index 000000000..0c6904283 --- /dev/null +++ b/pkg/npm/api/index.js @@ -0,0 +1,14 @@ +import BigIntOrderedMap from './lib/BigIntOrderedMap'; + +export { + BigIntOrderedMap +}; + +export * from './contacts'; +export * from './graph'; +export * from './groups'; +export * from './hark'; +export * from './invite'; +export * from './metadata'; +export * from './settings'; +export * from './index.d'; \ No newline at end of file diff --git a/pkg/interface/src/types/invite-update.ts b/pkg/npm/api/invite/index.d.ts similarity index 73% rename from pkg/interface/src/types/invite-update.ts rename to pkg/npm/api/invite/index.d.ts index b897687a4..05b43ce8a 100644 --- a/pkg/interface/src/types/invite-update.ts +++ b/pkg/npm/api/invite/index.d.ts @@ -1,14 +1,21 @@ -import { Serial, PatpNoSig, Path } from './noun'; -import {Resource} from './group-update'; +import { Serial, PatpNoSig, Path } from '..'; +import { Resource } from "../groups/update.d"; export type InviteUpdate = InviteUpdateInitial | InviteUpdateCreate | InviteUpdateDelete | InviteUpdateInvite +| InviteUpdateAccept | InviteUpdateAccepted | InviteUpdateDecline; +interface InviteUpdateAccept { + accept: { + term: string; + uid: Serial; + } +} interface InviteUpdateInitial { initial: Invites; @@ -16,19 +23,19 @@ interface InviteUpdateInitial { interface InviteUpdateCreate { create: { - path: Path; + term: string; }; } interface InviteUpdateDelete { delete: { - path: Path; + term: string; }; } interface InviteUpdateInvite { invite: { - path: Path; + term: string; uid: Serial; invite: Invite; }; @@ -36,14 +43,14 @@ interface InviteUpdateInvite { interface InviteUpdateAccepted { accepted: { - path: Path; + term: string; uid: Serial; }; } interface InviteUpdateDecline { decline: { - path: Path; + term: string; uid: Serial; }; } diff --git a/pkg/npm/api/invite/index.ts b/pkg/npm/api/invite/index.ts new file mode 100644 index 000000000..51e6ab5fb --- /dev/null +++ b/pkg/npm/api/invite/index.ts @@ -0,0 +1,28 @@ +import { InviteUpdate, InviteUpdateAccept, InviteUpdateDecline } from "./index.d"; +import { Poke, Serial } from ".."; + +export const action = (data: T): Poke => ({ + app: 'invite-store', + mark: 'invite-action', + json: data +}); + +export const accept = ( + app: string, + uid: Serial +): Poke => action({ + accept: { + term: app, + uid + } +}); + +export const decline = ( + app: string, + uid: Serial +): Poke => action({ + decline: { + term: app, + uid + } +}); diff --git a/pkg/npm/api/lib/BigIntOrderedMap.ts b/pkg/npm/api/lib/BigIntOrderedMap.ts new file mode 100644 index 000000000..6f42da526 --- /dev/null +++ b/pkg/npm/api/lib/BigIntOrderedMap.ts @@ -0,0 +1,233 @@ +import { BigInteger } from "big-integer"; + +interface NonemptyNode { + n: [BigInteger, V]; + l: MapNode; + r: MapNode; +} + +type MapNode = NonemptyNode | null; + +/** + * An implementation of ordered maps for JS + * Plagiarised wholesale from sys/zuse + */ +export default class BigIntOrderedMap implements Iterable<[BigInteger, V]> { + private root: MapNode = null; + size: number = 0; + + constructor(initial: [BigInteger, V][] = []) { + initial.forEach(([key, val]) => { + this.set(key, val); + }); + } + + /** + * Retrieve an value for a key + */ + get(key: BigInteger): V | null { + const inner = (node: MapNode): V | null => { + if (!node) { + return null; + } + const [k, v] = node.n; + if (key.eq(k)) { + return v; + } + if (key.gt(k)) { + return inner(node.l); + } else { + return inner(node.r); + } + }; + + return inner(this.root); + } + + /** + * Put an item by a key + */ + set(key: BigInteger, value: V): void { + + const inner = (node: MapNode): MapNode => { + if (!node) { + return { + n: [key, value], + l: null, + r: null, + }; + } + const [k] = node.n; + if (key.eq(k)) { + this.size--; + return { + ...node, + n: [k, value], + }; + } + if (key.gt(k)) { + const l = inner(node.l); + if (!l) { + throw new Error("invariant violation"); + } + return { + ...node, + l, + }; + } + const r = inner(node.r); + if (!r) { + throw new Error("invariant violation"); + } + + return { ...node, r }; + }; + this.size++; + this.root = inner(this.root); + } + + /** + * Remove all entries + */ + clear() { + this.root = null; + } + + /** + * Predicate testing if map contains key + */ + has(key: BigInteger): boolean { + const inner = (node: MapNode): boolean => { + if (!node) { + return false; + } + const [k] = node.n; + + if (k.eq(key)) { + return true; + } + if (key.gt(k)) { + return inner(node.l); + } + return inner(node.r); + }; + return inner(this.root); + } + + /** + * Remove value associated with key, returning whether that key + * existed in the first place + */ + delete(key: BigInteger) { + const inner = (node: MapNode): [boolean, MapNode] => { + if (!node) { + return [false, null]; + } + const [k] = node.n; + if (k.eq(key)) { + return [true, this.nip(node)]; + } + if (key.gt(k)) { + const [bool, l] = inner(node.l); + return [ + bool, + { + ...node, + l, + }, + ]; + } + + const [bool, r] = inner(node.r); + return [ + bool, + { + ...node, + r, + }, + ]; + }; + const [ret, newRoot] = inner(this.root); + if(ret) { + this.size--; + } + this.root = newRoot; + return ret; + } + + private nip(nod: NonemptyNode): MapNode { + const inner = (node: NonemptyNode): MapNode => { + if (!node.l) { + return node.r; + } + if (!node.r) { + return node.l; + } + return { + ...node.l, + r: inner(node.r), + }; + }; + return inner(nod); + } + + peekLargest(): [BigInteger, V] | undefined { + const inner = (node: MapNode): [BigInteger, V] | undefined => { + if(!node) { + return undefined; + } + if(node.l) { + return inner(node.l); + } + return node.n; + } + return inner(this.root); + } + + peekSmallest(): [BigInteger, V] | undefined { + const inner = (node: MapNode): [BigInteger, V] | undefined => { + if(!node) { + return undefined; + } + if(node.r) { + return inner(node.r); + } + return node.n; + } + return inner(this.root); + } + + keys(): BigInteger[] { + const list = Array.from(this); + return list.map(([key]) => key); + } + + forEach(f: (value: V, key: BigInteger) => void) { + const list = Array.from(this); + return list.forEach(([k,v]) => f(v,k)); + } + + [Symbol.iterator](): IterableIterator<[BigInteger, V]> { + let result: [BigInteger, V][] = []; + const inner = (node: MapNode) => { + if (!node) { + return; + } + inner(node.l); + result.push(node.n); + inner(node.r); + }; + inner(this.root); + + let idx = 0; + return { + [Symbol.iterator]: this[Symbol.iterator], + next: (): IteratorResult<[BigInteger, V]> => { + if (idx < result.length) { + return { value: result[idx++], done: false }; + } + return { done: true, value: null }; + }, + }; + } +} diff --git a/pkg/interface/src/types/noun.ts b/pkg/npm/api/lib/index.d.ts similarity index 60% rename from pkg/interface/src/types/noun.ts rename to pkg/npm/api/lib/index.d.ts index f566f0240..f8f94ce66 100644 --- a/pkg/interface/src/types/noun.ts +++ b/pkg/npm/api/lib/index.d.ts @@ -2,6 +2,8 @@ * Martian embassy */ +import BigIntOrderedMap from "./BigIntOrderedMap"; + // an urbit style path rendered as string export type Path = string; @@ -18,19 +20,12 @@ export type Serial = string; export type Jug = Map>; // name of app -export type AppName = 'contacts' | 'groups' | 'graph'; - -export function getTagFromFrond(frond: O): keyof O { - const tags = Object.keys(frond) as Array; - const tag = tags[0]; - if(!tag) { - throw new Error("bad frond"); - } - return tag; -} +export type AppName = 'chat' | 'link' | 'contacts' | 'publish' | 'graph'; export type ShipRank = 'czar' | 'king' | 'duke' | 'earl' | 'pawn'; +export type Action = 'poke' | 'subscribe' | 'ack' | 'unsubscribe' | 'delete'; + export type SetElement = S extends Set<(infer T)> ? T : never; export type MapKey = M extends Map<(infer K), any> ? K : never; @@ -46,4 +41,22 @@ export type Enc = { [s: string]: Enc> } : S extends object ? { [K in keyof S]: Enc } : + S extends BigIntOrderedMap ? + { [index: string]: T } : S; + +export type Mark = string; + +export interface Poke { + ship?: string; // This should be handled by the http library, but is part of the spec + app: string; + mark: Mark; + json: Action; +} + +export interface Thread { + inputMark: string; + outputMark: string; + threadName: string; + body: Action; +} diff --git a/pkg/npm/api/lib/util.ts b/pkg/npm/api/lib/util.ts new file mode 100644 index 000000000..c646c3a8a --- /dev/null +++ b/pkg/npm/api/lib/util.ts @@ -0,0 +1,211 @@ +import _ from "lodash"; +import f from "lodash/fp"; +import bigInt, { BigInteger } from "big-integer"; +import { Resource } from "../groups/index.d"; + +const DA_UNIX_EPOCH = bigInt("170141184475152167957503069145530368000"); // `@ud` ~1970.1.1 + +const DA_SECOND = bigInt("18446744073709551616"); // `@ud` ~s1 + +/** + * Returns true if an app uses a graph backend + * + * @param {string} app The name of the app + * + * @return {boolean} Whether or not it uses a graph backend + */ +export function appIsGraph(app: string): boolean { + return app === 'publish' || app == 'link'; +} + +/** + * Given a bigint representing an urbit date, returns a unix timestamp. + * + * @param {BigInteger} da The urbit date + * + * @return {number} The unix timestamp + */ +export function daToUnix(da: BigInteger): number { + // ported from +time:enjs:format in hoon.hoon + const offset = DA_SECOND.divide(bigInt(2000)); + const epochAdjusted = offset.add(da.subtract(DA_UNIX_EPOCH)); + + return Math.round( + epochAdjusted.multiply(bigInt(1000)).divide(DA_SECOND).toJSNumber() + ); +} + +/** + * Given a unix timestamp, returns a bigint representing an urbit date + * + * @param {number} unix The unix timestamp + * + * @return {BigInteger} The urbit date + */ +export function unixToDa(unix: number): BigInteger { + const timeSinceEpoch = bigInt(unix).multiply(DA_SECOND).divide(bigInt(1000)); + return DA_UNIX_EPOCH.add(timeSinceEpoch); +} + + +export function makePatDa(patda: string): BigInteger { + return bigInt(udToDec(patda)); +} + +export function udToDec(ud: string): string { + return ud.replace(/\./g, ""); +} + +export function decToUd(str: string): string { + return _.trimStart( + f.flow( + f.split(""), + f.reverse, + f.chunk(3), + f.map(f.flow(f.reverse, f.join(""))), + f.reverse, + f.join(".") + )(str), + "0." + ); +} + +export function resourceAsPath(resource: Resource): string { + const { name, ship } = resource; + return `/ship/~${ship}/${name}`; +} + +export function uuid(): string { + let str = "0v"; + str += Math.ceil(Math.random() * 8) + "."; + for (let 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); +} + +/* + Goes from: + ~2018.7.17..23.15.09..5be5 // urbit @da + To: + (javascript Date object) +*/ +export function daToDate(st: string): Date { + const dub = function (n: string) { + return parseInt(n) < 10 ? "0" + parseInt(n) : n.toString(); + }; + const da = st.split(".."); + const bigEnd = da[0].split("."); + const lilEnd = da[1].split("."); + const 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: Date, mil: boolean = false): string { + const fil = function (n: number) { + 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: string): string | null { + if (!ship) { + return null; + } + return ship.replace("~", ""); +} + +// trim patps to match dojo, chat-cli +export function cite(ship: string): string { + let patp = ship, + shortened = ""; + if (patp === null || patp === "") { + return ""; + } + 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}`; +} + +// encode the string into @ta-safe format, using logic from +wood. +// for example, 'some Chars!' becomes '~.some.~43.hars~21.' +// +export function stringToTa(str: string): string { + let out = ""; + for (let i = 0; i < str.length; i++) { + const char = str[i]; + let add = ""; + switch (char) { + case " ": + add = "."; + break; + case ".": + add = "~."; + break; + case "~": + add = "~~"; + break; + default: + const charCode = str.charCodeAt(i); + if ( + (charCode >= 97 && charCode <= 122) || // a-z + (charCode >= 48 && charCode <= 57) || // 0-9 + char === "-" + ) { + add = char; + } else { + // TODO behavior for unicode doesn't match +wood's, + // but we can probably get away with that for now. + add = "~" + charCode.toString(16) + "."; + } + } + out = out + add; + } + return "~." + out; +} + + +/** + * Formats a numbers as a `@ud` inserting dot where needed + */ +export function numToUd(num: number): string { + return f.flow( + f.split(''), + f.reverse, + f.chunk(3), + f.reverse, + f.map(s => s.join('')), + f.join('.') + )(num.toString()) +} diff --git a/pkg/interface/src/types/metadata-update.ts b/pkg/npm/api/metadata/index.d.ts similarity index 78% rename from pkg/interface/src/types/metadata-update.ts rename to pkg/npm/api/metadata/index.d.ts index 640bd5a9b..c0a12075d 100644 --- a/pkg/interface/src/types/metadata-update.ts +++ b/pkg/npm/api/metadata/index.d.ts @@ -1,5 +1,4 @@ -import { AppName, Path, Patp } from './noun'; - +import { AppName, Path, Patp } from '..'; export type MetadataUpdate = MetadataUpdateInitial @@ -16,15 +15,15 @@ type ResourceAssociations = { } type MetadataUpdateAdd = { - add: Association; + add: AssociationPoke; } type MetadataUpdateUpdate = { - update: Association; + update: AssociationPoke; } type MetadataUpdateRemove = { - remove: Resource & { + remove: MdResource & { group: Path; } } @@ -43,16 +42,22 @@ export type AppAssociations = { [p in Path]: Association; } -interface Resource { +interface MdResource { resource: Path; 'app-name': AppName; } -export type Association = Resource & { +export type Association = MdResource & { group: Path; metadata: Metadata; }; +export interface AssociationPoke { + group: Path; + resource: MdResource; + metadata: Metadata; +} + export interface Metadata { color: string; creator: Patp; diff --git a/pkg/npm/api/metadata/index.ts b/pkg/npm/api/metadata/index.ts new file mode 100644 index 000000000..e880f1c9d --- /dev/null +++ b/pkg/npm/api/metadata/index.ts @@ -0,0 +1,43 @@ +import { AppName, Path, PatpNoSig, Poke } from ".."; +import { Association, Metadata, MetadataUpdateAdd, MetadataUpdateUpdate } from './index.d'; + +export const action = (data: T): Poke => ({ + app: 'metadata-hook', + mark: 'metadata-action', + json: data +}); + +export const add = ( + appName: AppName, + resource: string, + group: string, + metadata: Metadata, +): Poke => { + return action({ + add: { + group, + resource: { + resource, + 'app-name': appName + }, + metadata + } + }); +} + +export const update = ( + association: Association, + newMetadata: Partial +): Poke => { + const { resource, metadata, group } = association; + return action({ + add: { + group, + resource: { + resource, + 'app-name': association['app-name'], + }, + metadata: {...metadata, ...newMetadata } + } + }); +} diff --git a/pkg/npm/api/package.json b/pkg/npm/api/package.json new file mode 100644 index 000000000..55e8e273d --- /dev/null +++ b/pkg/npm/api/package.json @@ -0,0 +1,23 @@ +{ + "name": "@urbit/api", + "version": "1.0.0", + "description": "", + "repository": { + "type": "git", + "url": "ssh://git@github.com/urbit/urbit.git", + "directory": "pkg/npm/api" + }, + "main": "index.js", + "types": "index.d.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@urbit/eslint-config": "^1.0.0", + "big-integer": "^1.6.48", + "lodash": "^4.17.20" + } +} diff --git a/pkg/interface/src/types/settings.ts b/pkg/npm/api/settings/index.d.ts similarity index 100% rename from pkg/interface/src/types/settings.ts rename to pkg/npm/api/settings/index.d.ts diff --git a/pkg/npm/api/settings/index.ts b/pkg/npm/api/settings/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/npm/api/tsconfig.json b/pkg/npm/api/tsconfig.json new file mode 100644 index 000000000..1d5a91a97 --- /dev/null +++ b/pkg/npm/api/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "noFallthroughCasesInSwitch": true, + "noUnusedParameters": false, + "noImplicitReturns": true, + "moduleResolution": "node", + "esModuleInterop": true, + "noUnusedLocals": false, + "noImplicitAny": false, + "noEmit": true, + "target": "es2015", + "module": "es2015", + "strict": true, + "jsx": "react", + "baseUrl": ".", + "paths": { + "~/*": ["src/*"] + } + }, + "include": [ + "**/*" + ], + "exclude": [ "node_modules" ] +} diff --git a/pkg/npm/eslint-config/index.js b/pkg/npm/eslint-config/index.js new file mode 100644 index 000000000..b94e161db --- /dev/null +++ b/pkg/npm/eslint-config/index.js @@ -0,0 +1,188 @@ +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": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "vars": "all", + "args": "none", + "ignoreRestSiblings": false + } + ], + "no-use-before-define": "off", + "@typescript-eslint/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 + } + ] +}; diff --git a/pkg/npm/eslint-config/package.json b/pkg/npm/eslint-config/package.json new file mode 100644 index 000000000..1036e8ff1 --- /dev/null +++ b/pkg/npm/eslint-config/package.json @@ -0,0 +1,26 @@ +{ + "name": "@urbit/eslint-config", + "version": "1.0.0", + "description": "", + "repository": { + "type": "git", + "url": "ssh://git@github.com/urbit/urbit.git", + "directory": "pkg/npm/eslint-config" + }, + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "peerDependencies": { + "eslint": ">= 3" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^4.15.0", + "@typescript-eslint/parser": "^4.15.0", + "babel-eslint": "^10.1.0", + "eslint-plugin-react": "^7.22.0", + "typescript": "^4.1.5" + } +} diff --git a/pkg/npm/http-api/.gitignore b/pkg/npm/http-api/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/npm/http-api/README.md b/pkg/npm/http-api/README.md new file mode 100644 index 000000000..f82185bbc --- /dev/null +++ b/pkg/npm/http-api/README.md @@ -0,0 +1,21 @@ +# Urbit Connector + +This project allows you to connect to an [Urbit](https://urbit.org) ship via a JavaScript application. + +## Example + +Check out the `example` directory for examples of how to use this code. + +1. Open `example/index.html` in your browser and follow the instructions there, or +2. With a ship running in the same fashion as indicated in the file above, run `node example/index.js` + +The code for either of these can be found in `src/example/browser.js` or `src/example/node.js`, depending on your context. + +## Design + +This library is designed to be useful for node applications that communicate with an urbit running either on the local computer or on a remote one. + +The majority of its methods are asynchronous and return Promises. This is due to the non-blocking nature of JavaScript. If used in a React app, response handlers should be bound with `this` to `setState` after a message is received. + +## NOTE +You must enable CORS requests on your urbit for this library to work in browser context. Use `+cors-registry` to see domains which have made requests to your urbit, and then approve the needed one, e.g. `|cors-approve http://zod.arvo.network`. diff --git a/pkg/npm/http-api/example/browser.js b/pkg/npm/http-api/example/browser.js new file mode 100644 index 000000000..9ec2eea57 --- /dev/null +++ b/pkg/npm/http-api/example/browser.js @@ -0,0 +1,17 @@ +/* + * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). + * This devtool is not neither made for production nor for readable output files. + * It uses "eval()" calls to create a separate source file in the browser devtools. + * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) + * or disable the default devtool with "devtool: false". + * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). + */ +/******/ (() => { // webpackBootstrap +/*!********************************!*\ + !*** ./src/example/browser.js ***! + \********************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +eval("// import Urbit from '../../dist/browser';\n// window.Urbit = Urbit;\n\n//# sourceURL=webpack://@urbit/http-api/./src/example/browser.js?"); +/******/ })() +; \ No newline at end of file diff --git a/pkg/npm/http-api/example/index.html b/pkg/npm/http-api/example/index.html new file mode 100644 index 000000000..cbe00460f --- /dev/null +++ b/pkg/npm/http-api/example/index.html @@ -0,0 +1,98 @@ + + + + + + Demo + + + + +
+ Show instructions +

Assuming you are running a fakezod on port 8080, run

+ |cors-approve '{window.location.origin}' +

in its dojo.

+

Press the button to run the code below. Output will be logged. You should see < ~zod: opening airlock in your dojo. Create a chat and send a message to see the events logged.

+
window.airlock = await Urbit.authenticate({
+    ship: 'zod',
+    url: 'localhost:8080',
+    code: 'lidlut-tabwed-pillex-ridrup',
+    verbose: true
+});
+window.airlock.subscribe('chat-view', '/primary', { event: console.log });
+
+ + +
+
+    
+ + + \ No newline at end of file diff --git a/pkg/npm/http-api/example/node.js b/pkg/npm/http-api/example/node.js new file mode 100644 index 000000000..4dc1b5b03 --- /dev/null +++ b/pkg/npm/http-api/example/node.js @@ -0,0 +1,17 @@ +/* + * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). + * This devtool is not neither made for production nor for readable output files. + * It uses "eval()" calls to create a separate source file in the browser devtools. + * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) + * or disable the default devtool with "devtool: false". + * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). + */ +/******/ (() => { // webpackBootstrap +/*!*****************************!*\ + !*** ./src/example/node.js ***! + \*****************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +eval("// import Urbit from '../../dist/index';\n// async function blastOff() {\n// const airlock = await Urbit.authenticate({\n// ship: 'zod',\n// url: 'localhost:8080',\n// code: 'lidlut-tabwed-pillex-ridrup',\n// verbose: true\n// });\n// airlock.subscribe('chat-view', '/primary');\n// }\n// blastOff();\n\n//# sourceURL=webpack://@urbit/http-api/./src/example/node.js?"); +/******/ })() +; \ No newline at end of file diff --git a/pkg/npm/http-api/index.js b/pkg/npm/http-api/index.js new file mode 100644 index 000000000..351a32475 --- /dev/null +++ b/pkg/npm/http-api/index.js @@ -0,0 +1,2 @@ +import Urbit from './dist'; +export { Urbit as default, Urbit }; \ No newline at end of file diff --git a/pkg/npm/http-api/package.json b/pkg/npm/http-api/package.json new file mode 100644 index 000000000..25d701a28 --- /dev/null +++ b/pkg/npm/http-api/package.json @@ -0,0 +1,68 @@ +{ + "name": "@urbit/http-api", + "version": "1.1.0", + "license": "MIT", + "description": "Library to interact with an Urbit ship over HTTP", + "repository": { + "type": "git", + "url": "ssh://git@github.com/urbit/urbit.git", + "directory": "pkg/npm/http-api" + }, + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", + "browser": "dist/esm/index.js", + "types": "dist/esm/index.d.ts", + "files": [ + "dist", + "src" + ], + "engines": { + "node": ">=13" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "npm run clean && webpack --config webpack.prod.js && tsc -p tsconfig.json && tsc -p tsconfig-cjs.json", + "clean": "rm -rf dist/*" + }, + "peerDependencies": {}, + "prettier": { + "printWidth": 80, + "semi": true, + "singleQuote": true, + "trailingComma": "es5" + }, + "author": "", + "devDependencies": { + "@babel/core": "^7.12.3", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/plugin-proposal-object-rest-spread": "^7.12.1", + "@babel/plugin-proposal-optional-chaining": "^7.12.1", + "@babel/preset-typescript": "^7.12.1", + "@types/eventsource": "^1.1.5", + "@types/react": "^16.9.56", + "@typescript-eslint/eslint-plugin": "^4.7.0", + "@typescript-eslint/parser": "^4.7.0", + "@types/browser-or-node": "^1.2.0", + "babel-loader": "^8.2.1", + "clean-webpack-plugin": "^3.0.0", + "tslib": "^2.0.3", + "typescript": "^3.9.7", + "webpack": "^5.4.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" + }, + "dependencies": { + "@babel/runtime": "^7.12.5", + "browser-or-node": "^1.3.0", + "browserify-zlib": "^0.2.0", + "buffer": "^5.7.1", + "encoding": "^0.1.13", + "eventsource": "^1.0.7", + "node-fetch": "^2.6.1", + "stream-browserify": "^3.0.0", + "stream-http": "^3.1.1", + "util": "^0.12.3", + "xmlhttprequest": "^1.8.0", + "xmlhttprequest-ssl": "^1.6.0" + } +} diff --git a/pkg/npm/http-api/src/app/base.ts b/pkg/npm/http-api/src/app/base.ts new file mode 100644 index 000000000..715886b51 --- /dev/null +++ b/pkg/npm/http-api/src/app/base.ts @@ -0,0 +1,41 @@ +import Urbit from '..'; + +export interface UrbitAppInterface { + airlock: Urbit; + app: string; +} + +export default class UrbitApp implements UrbitAppInterface { + airlock: Urbit; + + get app(): string { + throw new Error('Access app property on base UrbitApp'); + } + + constructor(airlock: Urbit) { + this.airlock = airlock; + } + + /** + * Getter that barfs if no ship has been passed + */ + get ship(): string { + if (!this.airlock.ship) { + throw new Error('No ship specified'); + } + return this.airlock.ship; + } + + /** + * Helper to allow any app to handle subscriptions. + * + * @param path Path on app to subscribe to + */ + subscribe(path: string) { + const ship = this.ship; + const app = this.app; + // @ts-ignore + return this.airlock.subscribe(app, path); + } + // TODO handle methods that don't exist +} diff --git a/pkg/npm/http-api/src/example/browser.js b/pkg/npm/http-api/src/example/browser.js new file mode 100644 index 000000000..2dfc71410 --- /dev/null +++ b/pkg/npm/http-api/src/example/browser.js @@ -0,0 +1,3 @@ +// import Urbit from '../../dist/browser'; + +// window.Urbit = Urbit; \ No newline at end of file diff --git a/pkg/npm/http-api/src/example/node.js b/pkg/npm/http-api/src/example/node.js new file mode 100644 index 000000000..bbceb0c7b --- /dev/null +++ b/pkg/npm/http-api/src/example/node.js @@ -0,0 +1,14 @@ +// import Urbit from '../../dist/index'; + +// async function blastOff() { +// const airlock = await Urbit.authenticate({ +// ship: 'zod', +// url: 'localhost:8080', +// code: 'lidlut-tabwed-pillex-ridrup', +// verbose: true +// }); + +// airlock.subscribe('chat-view', '/primary'); +// } + +// blastOff(); diff --git a/pkg/npm/http-api/src/index.ts b/pkg/npm/http-api/src/index.ts new file mode 100644 index 000000000..b08f46e53 --- /dev/null +++ b/pkg/npm/http-api/src/index.ts @@ -0,0 +1,457 @@ +import { isBrowser, isNode } from 'browser-or-node'; +import { Action, Thread } from '../../api'; + +import { AuthenticationInterface, SubscriptionInterface, CustomEventHandler, PokeInterface, SubscriptionRequestInterface, headers, UrbitInterface, SSEOptions, PokeHandlers } from './types'; +import UrbitApp from './app/base'; +import { uncamelize, hexString } from './utils'; + +/** + * A class for interacting with an urbit ship, given its URL and code + */ +export class Urbit implements UrbitInterface { + /** + * UID will be used for the channel: The current unix time plus a random hex string + */ + uid: string = `${Math.floor(Date.now() / 1000)}-${hexString(6)}`; + + /** + * Last Event ID is an auto-updated index of which events have been sent over this channel + */ + lastEventId: number = 0; + + lastAcknowledgedEventId: number = 0; + + /** + * SSE Client is null for now; we don't want to start polling until it the channel exists + */ + sseClient: EventSource | null = null; + + /** + * Cookie gets set when we log in. + */ + cookie?: string | undefined; + + /** + * A registry of requestId to successFunc/failureFunc + * + * These functions are registered during a +poke and are executed + * in the onServerEvent()/onServerError() callbacks. Only one of + * the functions will be called, and the outstanding poke will be + * removed after calling the success or failure function. + */ + + outstandingPokes: Map = new Map(); + + /** + * A registry of requestId to subscription functions. + * + * These functions are registered during a +subscribe and are + * executed in the onServerEvent()/onServerError() callbacks. The + * event function will be called whenever a new piece of data on this + * subscription is available, which may be 0, 1, or many times. The + * disconnect function may be called exactly once. + */ + + outstandingSubscriptions: Map = new Map(); + + /** + * Ship can be set, in which case we can do some magic stuff like send chats + */ + ship?: string | null; + + /** + * If verbose, logs output eagerly. + */ + verbose?: boolean; + + /** + * All registered apps, keyed by name + */ + static apps: Map = new Map(); + + /** This is basic interpolation to get the channel URL of an instantiated Urbit connection. */ + get channelUrl(): string { + return `${this.url}/~/channel/${this.uid}`; + } + + get fetchOptions(): any { + const headers: headers = { + 'Content-Type': 'application/json', + }; + if (!isBrowser) { + headers.Cookie = this.cookie; + } + return { + credentials: 'include', + headers + }; + } + + /** + * Constructs a new Urbit connection. + * + * @param url The URL (with protocol and port) of the ship to be accessed + * @param code The access code for the ship at that address + */ + constructor( + public url: string, + public code: string + ) { + return this; + // We return a proxy so we can set dynamic properties like `Urbit.onChatHook` + // @ts-ignore + return new Proxy(this, { + get(target: Urbit, property: string) { + // First check if this is a regular property + if (property in target) { + return (target as any)[property]; + } + + // Then check if it's a registered app + const app = Urbit.apps.get(uncamelize(property)); + if (app) { + return new app(target); + } + + // Then check to see if we're trying to register an EventSource watcher + if (property.startsWith('on')) { + const on = uncamelize(property.replace('on', '')).toLowerCase(); + return ((action: CustomEventHandler) => { + target.eventSource().addEventListener('message', (event: MessageEvent) => { + if (target.verbose) { + console.log(`Received SSE from ${on}: `, event); + } + if (event.data && JSON.parse(event.data)) { + const data: any = JSON.parse(event.data); + if (data.json.hasOwnProperty(on)) { + action(data.json[on], data.json.response); + } + } + }); + }); + } + + return undefined; + } + }) + } + + /** + * All-in-one hook-me-up. + * + * Given a ship, url, and code, this returns an airlock connection + * that is ready to go. It `|hi`s itself to create the channel, + * then opens the channel via EventSource. + * + * @param AuthenticationInterface + */ + static async authenticate({ ship, url, code, verbose = false }: AuthenticationInterface) { + const airlock = new Urbit(`http://${url}`, code); + airlock.verbose = verbose; + airlock.ship = ship; + await airlock.connect(); + await airlock.poke({ app: 'hood', mark: 'helm-hi', json: 'opening airlock' }); + await airlock.eventSource(); + return airlock; + } + + /** + * Connects to the Urbit ship. Nothing can be done until this is called. + * That's why we roll it into this.authenticate + */ + async connect(): Promise { + if (this.verbose) { + console.log(`password=${this.code} `, isBrowser ? "Connecting in browser context at " + `${this.url}/~/login` : "Connecting from node context"); + } + return fetch(`${this.url}/~/login`, { + method: 'post', + body: `password=${this.code}`, + credentials: 'include', + }).then(response => { + if (this.verbose) { + console.log('Received authentication response', response); + } + const cookie = response.headers.get('set-cookie'); + if (!this.ship) { + this.ship = new RegExp(/urbauth-~([\w-]+)/).exec(cookie)[1]; + } + if (!isBrowser) { + this.cookie = cookie; + } + }).catch(error => { + console.log(XMLHttpRequest); + console.log('errored') + console.log(error); + }); + } + + + /** + * Returns (and initializes, if necessary) the SSE pipe for the appropriate channel. + */ + eventSource(): EventSource { + if (!this.sseClient || this.sseClient.readyState === this.sseClient.CLOSED) { + const sseOptions: SSEOptions = { + headers: {} + }; + if (isBrowser) { + sseOptions.withCredentials = true; + } else if (isNode) { + sseOptions.headers.Cookie = this.cookie; + } + this.sseClient = new EventSource(this.channelUrl, { + withCredentials: true + }); + this.sseClient!.addEventListener('message', (event: MessageEvent) => { + if (this.verbose) { + console.log('Received SSE: ', event); + } + this.ack(Number(event.lastEventId)); + if (event.data && JSON.parse(event.data)) { + const data: any = JSON.parse(event.data); + if (data.response === 'poke' && this.outstandingPokes.has(data.id)) { + const funcs = this.outstandingPokes.get(data.id); + if (data.hasOwnProperty('ok')) { + funcs.onSuccess(); + } else if (data.hasOwnProperty('err')) { + funcs.onError(data.err); + } else { + console.error('Invalid poke response', data); + } + this.outstandingPokes.delete(data.id); + } else if (data.response === 'subscribe' || + (data.response === 'poke' && this.outstandingSubscriptions.has(data.id))) { + const funcs = this.outstandingSubscriptions.get(data.id); + if (data.hasOwnProperty('err')) { + funcs.err(data.err); + this.outstandingSubscriptions.delete(data.id); + } + } else if (data.response === 'diff' && this.outstandingSubscriptions.has(data.id)) { + const funcs = this.outstandingSubscriptions.get(data.id); + funcs.event(data.json); + } else if (data.response === 'quit' && this.outstandingSubscriptions.has(data.id)) { + const funcs = this.outstandingSubscriptions.get(data.id); + funcs.quit(data); + this.outstandingSubscriptions.delete(data.id); + } else { + console.log('Unrecognized response', data); + } + // An incoming message, for example: + // { + // id: 10, + // json: { + // 'chat-update' : { // This is where we hook our "on" handlers like "onChatUpdate" + // message: { + // envelope: { + // author: 'zod', + // letter: { + // text: 'hi' + // }, + // number: 10, + // uid: 'saludhafhsdf', + // when: 124459 + // }, + // path: '/~zod/mailbox' + // } + // } + // } + // } + } + }); + this.sseClient!.addEventListener('error', function(event: Event) { + console.error('pipe error', event); + }); + + } + return this.sseClient; + } + + addEventListener(callback: (data: any) => void) { + return this.eventSource().addEventListener('message', (event: MessageEvent) => { + if (event.data && JSON.parse(event.data)) { + callback(JSON.parse(event.data)); + } + }); + } + + /** + * Autoincrements the next event ID for the appropriate channel. + */ + getEventId(): number { + this.lastEventId = Number(this.lastEventId) + 1; + return this.lastEventId; + } + + /** + * Acknowledges an event. + * + * @param eventId The event to acknowledge. + */ + ack(eventId: number): Promise { + return this.sendMessage('ack', { 'event-id': eventId }); + } + + /** + * This is a wrapper method that can be used to send any action with data. + * + * Every message sent has some common parameters, like method, headers, and data + * structure, so this method exists to prevent duplication. + * + * @param action The action to send + * @param data The data to send with the action + * + * @returns void | number If successful, returns the number of the message that was sent + */ + async sendMessage(action: Action, data?: object): Promise { + + const id = this.getEventId(); + if (this.verbose) { + console.log(`Sending message ${id}:`, action, data,); + } + let response: Response | undefined; + try { + response = await fetch(this.channelUrl, { + ...this.fetchOptions, + method: 'put', + body: JSON.stringify([{ + id, + action, + ...data, + }]), + }); + } catch (error) { + console.error('message error', error); + response = undefined; + } + if (this.verbose) { + console.log(`Received from message ${id}: `, response); + } + return id; + } + + /** + * Pokes a ship with data. + * + * @param app The app to poke + * @param mark The mark of the data being sent + * @param json The data to send + */ + poke(params: PokeInterface): Promise { + const { app, mark, json, onSuccess, onError } = {onSuccess: () => {}, onError: () => {}, ...params}; + return new Promise((resolve, reject) => { + this + .sendMessage('poke', { ship: this.ship, app, mark, json }) + .then(pokeId => { + if (!pokeId) { + return reject('Poke failed'); + } + if (!this.sseClient) resolve(pokeId); // A poke may occur before a listener has been opened + this.outstandingPokes.set(pokeId, { + onSuccess: () => { + onSuccess(); + resolve(pokeId); + }, + onError: (event) => { + onError(event); + reject(event.err); + } + }); + }).catch(error => { + console.error(error); + }); + }); + } + + /** + * Subscribes to a path on an app on a ship. + * + * @param app The app to subsribe to + * @param path The path to which to subscribe + * @param handlers Handlers to deal with various events of the subscription + */ + async subscribe(params: SubscriptionRequestInterface): Promise { + const { app, path, err, event, quit } = { err: () => {}, event: () => {}, quit: () => {}, ...params }; + + const subscriptionId = await this.sendMessage('subscribe', { ship: this.ship, app, path }); + console.log('subscribed', subscriptionId); + + if (!subscriptionId) return; + + this.outstandingSubscriptions.set(subscriptionId, { + err, event, quit + }); + + return subscriptionId; + } + + /** + * Unsubscribes to a given subscription. + * + * @param subscription + */ + unsubscribe(subscription: string): Promise { + return this.sendMessage('unsubscribe', { subscription }); + } + + /** + * Deletes the connection to a channel. + */ + delete(): Promise { + return this.sendMessage('delete'); + } + + /** + * + * @param app The app into which to scry + * @param path The path at which to scry + */ + async scry(app: string, path: string): Promise { + const response = await fetch(`/~/scry/${app}${path}.json`, this.fetchOptions); + return await response.json(); + } + + /** + * + * @param inputMark The mark of the data being sent + * @param outputMark The mark of the data being returned + * @param threadName The thread to run + * @param body The data to send to the thread + */ + async spider(params: Thread): Promise { + const { inputMark, outputMark, threadName, body } = params; + const res = await fetch(`/spider/${inputMark}/${threadName}/${outputMark}.json`, { + ...this.fetchOptions, + method: 'POST', + body: JSON.stringify(body) + }); + + return res.json(); + } + + app(appName: string): UrbitApp { + const appClass = Urbit.apps.get(appName); + if (!appClass) { + throw new Error(`App ${appName} not found`); + } + return new appClass(this); + } + + /** + * Utility function to connect to a ship that has its *.arvo.network domain configured. + * + * @param name Name of the ship e.g. zod + * @param code Code to log in + */ + static async onArvoNetwork(ship: string, code: string): Promise { + const url = `https://${ship}.arvo.network`; + return await Urbit.authenticate({ ship, url, code }); + } + + static extend(appClass: any): void { + Urbit.apps.set(appClass.app, appClass); + } +} + + + +export default Urbit; diff --git a/pkg/npm/http-api/src/types/index.d.ts b/pkg/npm/http-api/src/types/index.d.ts new file mode 100644 index 000000000..57d095f76 --- /dev/null +++ b/pkg/npm/http-api/src/types/index.d.ts @@ -0,0 +1,47 @@ +import { Action, Mark, Poke } from '../../../api/index'; + +export interface PokeHandlers { + onSuccess?: () => void; + onError?: (e: any) => void; +} + +export type PokeInterface = PokeHandlers & Poke; + + +export interface AuthenticationInterface { + ship: string; + url: string; + code: string; + verbose?: boolean; +} + +export interface SubscriptionInterface { + err?(error: any): void; + event?(data: any): void; + quit?(data: any): void; +} + +export type SubscriptionRequestInterface = SubscriptionInterface & { + app: string; + path: string; +} + +export interface headers { + 'Content-Type': string; + Cookie?: string; +} + +export interface UrbitInterface { + connect(): void; +} + +export interface CustomEventHandler { + (data: any, response: string): void; +} + +export interface SSEOptions { + headers?: { + Cookie?: string + }; + withCredentials?: boolean; +} diff --git a/pkg/npm/http-api/src/utils.ts b/pkg/npm/http-api/src/utils.ts new file mode 100644 index 000000000..94c0998cb --- /dev/null +++ b/pkg/npm/http-api/src/utils.ts @@ -0,0 +1,82 @@ +import * as http from 'http'; + +interface HttpResponse { + req: http.ClientRequest; + res: http.IncomingMessage; + data: string; +} + +export function request( + url: string, + options: http.ClientRequestArgs, + body?: string +): Promise { + return new Promise((resolve, reject) => { + const req = http.request(url, options, res => { + let data = ""; + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + resolve({ req, res, data }); + }); + res.on("error", e => { + reject(e); + }); + }); + if (body) { + req.write(body); + } + req.end(); + }); +} + +export function camelize(str: string) { + return str + .replace(/\s(.)/g, function($1: string) { return $1.toUpperCase(); }) + .replace(/\s/g, '') + .replace(/^(.)/, function($1: string) { return $1.toLowerCase(); }); +} + +export function uncamelize(str: string, separator = '-') { + // Replace all capital letters by separator followed by lowercase one + var str = str.replace(/[A-Z]/g, function (letter: string) { + return separator + letter.toLowerCase(); + }); + return str.replace(new RegExp('^' + separator), ''); +} + +/** + * Returns a hex string of given length. + * + * Poached from StackOverflow. + * + * @param len Length of hex string to return. + */ +export function hexString(len: number): string { + const maxlen = 8; + const min = Math.pow(16, Math.min(len, maxlen) - 1); + const max = Math.pow(16, Math.min(len, maxlen)) - 1; + const n = Math.floor(Math.random() * (max - min + 1)) + min; + let r = n.toString(16); + while (r.length < len) { + r = r + hexString(len - maxlen); + } + return r; +} + +/** + * Generates a random UID. + * + * Copied from https://github.com/urbit/urbit/blob/137e4428f617c13f28ed31e520eff98d251ed3e9/pkg/interface/src/lib/util.js#L3 + */ +export function uid(): string { + let str = '0v'; + str += Math.ceil(Math.random() * 8) + '.'; + for (let 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); +} \ No newline at end of file diff --git a/pkg/npm/http-api/test/default.test.ts b/pkg/npm/http-api/test/default.test.ts new file mode 100644 index 000000000..719f10efb --- /dev/null +++ b/pkg/npm/http-api/test/default.test.ts @@ -0,0 +1,8 @@ +import Urbit from '../src'; + +describe('blah', () => { + it('works', () => { + const connection = new Urbit('~sampel-palnet', '+code'); + expect(connection).toEqual(2); + }); +}); diff --git a/pkg/npm/http-api/tsconfig-cjs.json b/pkg/npm/http-api/tsconfig-cjs.json new file mode 100644 index 000000000..ec2ee6283 --- /dev/null +++ b/pkg/npm/http-api/tsconfig-cjs.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS", + "outDir": "./dist/cjs" + }, +} \ No newline at end of file diff --git a/pkg/npm/http-api/tsconfig.json b/pkg/npm/http-api/tsconfig.json new file mode 100644 index 000000000..b5703e6ed --- /dev/null +++ b/pkg/npm/http-api/tsconfig.json @@ -0,0 +1,18 @@ +{ + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist", "@types"], + "compilerOptions": { + "outDir": "./dist/esm", + "module": "ES2020", + "noImplicitAny": true, + "target": "ES2020", + "pretty": true, + "moduleResolution": "node", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "declaration": true, + "sourceMap": true, + "strict": false + // "lib": ["ES2020"], + } +} \ No newline at end of file diff --git a/pkg/npm/http-api/webpack.prod.js b/pkg/npm/http-api/webpack.prod.js new file mode 100644 index 000000000..aab72b662 --- /dev/null +++ b/pkg/npm/http-api/webpack.prod.js @@ -0,0 +1,109 @@ +const path = require('path'); +const webpack = require('webpack'); + +const shared = { + mode: 'production', + entry: { + app: './src/index.ts' + }, + module: { + rules: [ + { + test: /\.(j|t)s$/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/typescript'], + plugins: [ + '@babel/plugin-proposal-class-properties', + '@babel/plugin-proposal-object-rest-spread', + '@babel/plugin-proposal-optional-chaining', + ], + } + }, + exclude: /node_modules/ + } + ] + }, + resolve: { + extensions: ['.js', '.ts', '.ts'], + fallback: { + fs: false, + child_process: false, + util: require.resolve("util/"), + buffer: require.resolve('buffer/'), + assert: false, + http: require.resolve('stream-http'), + https: require.resolve('stream-http'), + stream: require.resolve('stream-browserify'), + zlib: require.resolve("browserify-zlib"), + } + }, + + optimization: { + minimize: false, + usedExports: true + } +}; + +const serverConfig = { + ...shared, + target: 'node', + output: { + filename: 'index.js', + path: path.resolve(__dirname, 'dist'), + library: 'Urbit', + libraryExport: 'default' + }, + plugins: [ + new webpack.ProvidePlugin({ + XMLHttpRequest: ['xmlhttprequest-ssl', 'XMLHttpRequest'], + EventSource: 'eventsource', + fetch: ['node-fetch', 'default'], + }), + ], +}; + +const browserConfig = { + ...shared, + target: 'web', + output: { + filename: 'browser.js', + path: path.resolve(__dirname, 'dist'), + library: 'Urbit', + libraryExport: 'default' + }, + plugins: [ + new webpack.ProvidePlugin({ + Buffer: 'buffer', + }), + ], +}; + + +const exampleBrowserConfig = { + ...shared, + mode: 'development', + entry: { + app: './src/example/browser.js' + }, + output: { + filename: 'browser.js', + path: path.resolve(__dirname, 'example'), + } +}; + +const exampleNodeConfig = { + ...shared, + mode: 'development', + target: 'node', + entry: { + app: './src/example/node.js' + }, + output: { + filename: 'node.js', + path: path.resolve(__dirname, 'example'), + } +}; + +module.exports = [ serverConfig, browserConfig, exampleBrowserConfig, exampleNodeConfig ]; \ No newline at end of file