├── .babelrc ├── .circleci ├── config.yml └── setup.sh ├── .editorconfig ├── .eslintrc ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── ROADMAP.md ├── jest-puppeteer.config.js ├── package-lock.json ├── package.json ├── src ├── components │ ├── BackgroundAnimation.js │ ├── DelayRender.js │ ├── Disclaimer.js │ ├── DropZone.js │ ├── EmojiPicker.js │ ├── FilePreview │ │ ├── PreviewTextFile.js │ │ └── index.js │ ├── FileUploadButton.js │ ├── Highlight.js │ ├── JoinChannel.js │ ├── LoginForm.js │ ├── MessageList.js │ ├── MessageParts │ │ ├── MessageContent.js │ │ ├── MessageTimestamp.js │ │ ├── MessageUser.js │ │ ├── MessageUserAvatar.js │ │ └── index.js │ ├── MessageRow.js │ ├── MessageTypes │ │ ├── FileMessage.js │ │ ├── FirstMessage.js │ │ ├── LoadMore.js │ │ ├── LoadingMessages.js │ │ ├── LoadingOrFirstMessage.js │ │ ├── TextMessage.js │ │ └── index.js │ ├── MessagesDateSeparator.js │ ├── Spinner │ │ ├── MoonLoader.js │ │ └── index.js │ ├── Suspense.js │ └── textProcessor │ │ ├── index.js │ │ ├── react-auto-link.js │ │ ├── react-emoji.js │ │ ├── react-highlight.js │ │ └── react-ipfs-link.js ├── config │ ├── countries.json │ ├── i18n.config.js │ ├── network.default.json │ ├── setting.options.json │ └── ui.default.json ├── containers │ ├── AlphaDisclaimer.js │ ├── Channel.js │ ├── ChannelControls.js │ ├── ChannelHeader.js │ ├── ChannelLink.js │ ├── ChannelMessages.js │ ├── ChannelStatus.js │ ├── ControlPanel.js │ ├── MessageUserProfilePanel.js │ └── SendMessage.js ├── context │ └── RootContext.js ├── fonts │ ├── README.md │ ├── flaticon │ │ ├── flaticon.eot │ │ ├── flaticon.html │ │ ├── flaticon.svg │ │ ├── flaticon.ttf │ │ ├── flaticon.woff │ │ └── license.pdf │ ├── lato │ │ ├── Lato-ExtraLight.ttf │ │ ├── Lato-Light.ttf │ │ ├── Lato-Light.woff2 │ │ ├── Lato-Thin.ttf │ │ ├── Lato-Thin.woff2 │ │ └── OFL.txt │ └── robotomono │ │ ├── LICENSE.txt │ │ ├── RobotoMono-Light.ttf │ │ └── RobotoMono-Light.woff2 ├── images │ ├── earth.png │ ├── orbit_logo_32x32.png │ ├── orbit_logo_400x400.png │ └── uport.png ├── index.html ├── index.js ├── locales │ ├── TODO.md │ ├── en │ │ ├── index.js │ │ └── translation.json │ ├── fi │ │ ├── index.js │ │ └── translation.json │ ├── index.js │ ├── pt │ │ ├── index.js │ │ └── translation.json │ └── zh │ │ ├── index.js │ │ └── translation.json ├── stores │ ├── ChannelStore.js │ ├── NetworkStore.js │ ├── RootStore.js │ ├── SessionStore.js │ ├── SettingsStore.js │ └── UiStore.js ├── styles │ ├── Animations.scss │ ├── App.scss │ ├── BackgroundAnimation.scss │ ├── Channel.scss │ ├── ChannelHeader.scss │ ├── ChannelLink.scss │ ├── ChannelView.scss │ ├── Colors.scss │ ├── ControlPanel.scss │ ├── DevTools.scss │ ├── Disclaimer.scss │ ├── DropZone.scss │ ├── EmojiPicker.scss │ ├── FileMessage.scss │ ├── Fonts.scss │ ├── InputField.scss │ ├── JoinChannel.scss │ ├── LoadingView.scss │ ├── LoginView.scss │ ├── Main.scss │ ├── MessageRow.scss │ ├── MessageUserProfilePanel.scss │ ├── Scrollbars.scss │ ├── SendMessage.scss │ ├── SettingsView.scss │ ├── Spinner.scss │ ├── SubmitButton.scss │ ├── TextMessage.scss │ ├── flaticon.css │ └── normalize.css ├── themes │ └── index.js ├── utils │ ├── animations.js │ ├── base-x.js │ ├── channels.js │ ├── cookies.js │ ├── create-color.js │ ├── debug.js │ ├── file-helpers.js │ ├── flatten.js │ ├── format-time.js │ ├── hooks.js │ ├── https.js │ ├── logger.js │ ├── mouse-position.js │ ├── notify.js │ ├── promise-queue.js │ ├── rect.js │ ├── throttle.js │ ├── uuid.js │ └── worker-proxy.js ├── views │ ├── App.js │ ├── ChannelView.js │ ├── IndexView.js │ ├── LoginView.js │ ├── LogoutView.js │ └── SettingsView.js └── workers │ └── network.worker.js ├── test ├── e2e.test.js └── sample.mp4 └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": [ 4 | "@babel/plugin-syntax-dynamic-import", 5 | "@babel/plugin-transform-async-to-generator", 6 | "@babel/plugin-transform-flow-strip-types", 7 | "@babel/plugin-syntax-flow", 8 | [ 9 | "@babel/plugin-proposal-object-rest-spread", 10 | { 11 | "loose": true 12 | } 13 | ], 14 | [ 15 | "@babel/plugin-transform-spread", 16 | { 17 | "loose": true 18 | } 19 | ], 20 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 21 | ["@babel/plugin-proposal-class-properties", { "loose": true }], 22 | "react-hot-loader/babel" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:10 6 | steps: 7 | - checkout 8 | - run: 9 | name: Update npm 10 | command: 'sudo npm install -g npm@latest' 11 | - run: 12 | name: Install puppeteer dependencies 13 | command: 'sh .circleci/setup.sh' 14 | - restore_cache: 15 | key: dependency-cache-{{ checksum "package.json" }} 16 | - run: 17 | name: Install project dependencies 18 | command: npm ci 19 | - save_cache: 20 | key: dependency-cache-{{ checksum "package.json" }} 21 | paths: 22 | - ./node_modules 23 | - run: 24 | name: Build 25 | command: npm run build 26 | - run: 27 | name: Run tests 28 | command: npm test 29 | - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ 30 | path: dist 31 | destination: dist 32 | -------------------------------------------------------------------------------- /.circleci/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo apt-get update 4 | sudo apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \ 5 | libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \ 6 | libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \ 7 | libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \ 8 | ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | }, 5 | "settings": { 6 | "react": { "version": "detect" } 7 | }, 8 | "extends": ["standard", "standard-jsx"], 9 | "parser": "babel-eslint", 10 | "rules": { 11 | "react/jsx-curly-newline": ["off", "consistent"], 12 | "react/jsx-handler-names": "warn", 13 | "no-unused-vars": "warn" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### SublimeText ### 2 | *.sublime-workspace 3 | 4 | ### VSCode ### 5 | .vscode 6 | 7 | ### OSX ### 8 | .DS_Store 9 | .AppleDouble 10 | .LSOverride 11 | Icon 12 | 13 | # Thumbnails 14 | ._* 15 | 16 | # Files that might appear on external disk 17 | .Spotlight-V100 18 | .Trashes 19 | 20 | ### Windows ### 21 | # Windows image file caches 22 | Thumbs.db 23 | ehthumbs.db 24 | 25 | # Folder config file 26 | Desktop.ini 27 | 28 | # Recycle Bin used on file shares 29 | $RECYCLE.BIN/ 30 | 31 | # App specific 32 | node_modules/ 33 | .tmp 34 | 35 | # Test bundle 36 | test/tests.bundle* 37 | 38 | # Sourcemaps 39 | dist/**/*.js.map 40 | 41 | dist 42 | 43 | .env 44 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [community@orbitdb.org](mailto:community@orbitdb.org), which goes to all members of the @OrbitDB community team, or to [richardlitt@orbitdb.org](mailto:richardlitt@orbitdb.org), which goes only to [@RichardLitt](https://github.com/RichardLitt) or to [haadcode@orbitdb.org](mailto:haadcode@orbitdb.org), which goes only to [@haadcode](https://github.com/haadcode). All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute 2 | 3 | Please contribute! Here are some things that would be great: 4 | 5 | - [Open an issue!](https://github.com/orbitdb/orbit-web/issues/new) 6 | - Open a pull request! 7 | - Say hi! :wave: 8 | 9 | Please note that we have a [Code of Conduct](CODE_OF_CONDUCT.md), and that all activity in the [@OrbitDB](https://github.com/orbitdb) organization falls under it. Read it when you get the chance, as being part of this community means that you agree to abide by it. Thanks. 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2018 Protocol Labs Inc. 4 | Copyright (c) 2018-2019 Haja Networks Oy 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Orbit Web 2 | 3 | [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/orbitdb/Lobby) [![Matrix](https://img.shields.io/badge/matrix-%23orbitdb%3Apermaweb.io-blue.svg)](https://riot.permaweb.io/#/room/#orbitdb:permaweb.io) [![CircleCI](https://circleci.com/gh/orbitdb/orbit-web/tree/master.svg?style=shield)](https://circleci.com/gh/orbitdb/orbit-web/tree/master) 4 | 5 | > A distributed, peer-to-peer chat application built on [IPFS](http://ipfs.io) 6 | 7 | Browser application for [Orbit](https://github.com/orbitdb/orbit). Try it at https://orbit.chat. 8 | 9 | Built with the following packages: 10 | 11 | - [orbit-core](https://github.com/orbitdb/orbit-core) - Core Orbit communication library. 12 | - [js-ipfs](https://github.com/ipfs/js-ipfs) - A new p2p hypermedia protocol for content-addressed storage. 13 | 14 | See also: 15 | 16 | - [orbit-db](https://github.com/orbitdb/orbit-db) - Serverless, p2p database that orbit-core uses to store its data. 17 | - [orbit-textui](https://github.com/orbitdb/orbit-textui) - Terminal client prototype for Orbit. 18 | - [orbit-electron](https://github.com/orbitdb/orbit-electron) - Stand-alone desktop application for Orbit Chat built with Electron. 19 | - [IPFS](https://ipfs.io) - IPFS 20 | 21 | ## Development 22 | 23 | This project uses [npm](http://npmjs.com/) and [nodejs](https://nodejs.org/). 24 | 25 | ### Run 26 | 27 | Get the source code and install dependencies: 28 | 29 | ```sh 30 | git clone https://github.com/orbitdb/orbit-web.git 31 | cd orbit-web/ 32 | npm install 33 | ``` 34 | 35 | Start the application: 36 | `npm run dev` 37 | 38 | _Run will start a development server, open the app in the browser and watch for changes in the source files. Upon change, it'll automatically compile and reload the app in the browser_ 39 | 40 | ### Build 41 | 42 | `npm run build` 43 | 44 | _This produces a fully stand-alone build in `dist/` which can be run from `dist/index.html` file or on a http-server._ 45 | 46 | ## Contribute 47 | 48 | We would be happy to accept PRs! If you want to work on something, it'd be good to talk beforehand to make sure nobody else is working on it. You can reach us on [Gitter](https://gitter.im/orbitdb/Lobby), or in the comments of the [issues section](https://github.com/orbitdb/orbit-web/issues). 49 | 50 | We also have **regular community calls**, which we announce in the issues in [the @orbitdb welcome repository](https://github.com/orbitdb/welcome/issues). Join us! 51 | 52 | If you want to code but don't know where to start, check out the issues labelled ["help wanted"](https://github.com/orbitdb/orbit-web/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+sort%3Areactions-%2B1-desc). 53 | 54 | For specific guidelines for contributing to this repository, check out the [Contributing guide](CONTIRBUTING.md). For more on contributing to OrbitDB in general, take a look at the [orbitdb welcome repository](https://github.com/orbitdb/welcome). Please note that all interactions in [@OrbitDB](https://github.com/orbitdb) fall under our [Code of Conduct](CODE_OF_CONDUCT.md). 55 | 56 | ## Deploying to a domain with IPFS 57 | 58 | 1. Point the domain to some public IPFS gateway 59 | 60 | - TYPE: **A** 61 | - Name: **@** (or a subdomain) 62 | - Value: **_< IP address of the gateway >_** 63 | 64 | 2. `npm run build` 65 | 3. Pin `dist`-folder to IPFS (upload the folder to some pinning service) 66 | 4. Add or update the _TXT_ entry of `_dnslink` subdomain in DNS records of the domain 67 | 68 | - TYPE: **TXT** 69 | - Name: **\_dnslink** 70 | - Value: **"dnslink=/ipfs/_< hash from pinning service >_"** 71 | 72 | ## License 73 | 74 | [MIT](LICENSE) © 2017-2018 Protocol Labs Inc., 2018-2019 Haja Networks Oy 75 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Orbit Web 2 | 3 | The current implementation status, immediate-term plans, and future goals of this repository. 4 | 5 | We welcome feature requests as edits to the "[Backlog](#backlog)" section below. Suggest something by opening a pull request editing this file. 6 | 7 | ## Timeline 8 | 9 | TBA 10 | 11 | ## Roadmap 12 | 13 | Our short-to-medium-term roadmap items, in order of descending priority. 14 | 15 | ### Backlog 16 | 17 | | Status | Feature | Owner | Details | 18 | | :------ | :-------------------- | :--------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | 19 | | ☑ | simplify startup | [@latenssi](https://github.com/latenssi) | start the network automatically | 20 | | ☑ | fix build | [@latenssi](https://github.com/latenssi) | currently build is not outputting a _standalone_ working web app, some paths are incorrect etc. | 21 | | ☑ | add alpha disclaimer | [@latenssi](https://github.com/latenssi) | add a disclaimer that this is alpha version, things will break and chats will disappear etc. | 22 | | ☑ | check documentation | [@latenssi](https://github.com/latenssi) | make sure documentation is up to date before merging the new [version](https://github.com/orbitdb/orbit-chat) in to orbit-web | 23 | | ☑ | merge | [@latenssi](https://github.com/latenssi) | merge the new [version](https://github.com/orbitdb/orbit-chat) in to orbit-web as master branch | 24 | | ☑ | high memory usage | [@latenssi](https://github.com/latenssi) | currently the memory usage of the app is very high, check what is causing it | 25 | | ☑ | high cpu usage | [@latenssi](https://github.com/latenssi) | currently the cpu usage of the app is very high, check what is causing it | 26 | | ☑ | performance profiling | [@latenssi](https://github.com/latenssi) | check if something can be optimized | 27 | | ☑ | 60 fps level | [@latenssi](https://github.com/latenssi) | check if we are meeting a "60 fps level" of smoothnes | 28 | | ☐ | file streaming | [@latenssi](https://github.com/latenssi) | allow streaming of files (videos) from web worker | 29 | | ☐ | deploying | [@latenssi](https://github.com/latenssi) | figure out an automated deployment strategy (deploying to IPFS is preferred) | 30 | | ☑ | domain | [@latenssi](https://github.com/latenssi) | point orbit.chat domain to this repos deployment (wherever it is) | 31 | | ☑ | basic automated tests | [@latenssi](https://github.com/latenssi) | start implementing automated tests, keep it simple and lean | 32 | | ☑ | load more messages | [@latenssi](https://github.com/latenssi) | automatically load more messages as user scrolls up in the channels history | 33 | | ☑ | notifications | [@latenssi](https://github.com/latenssi) | send notification to a user when somebody mentions her in the chat (e.g. "@somebody hi!") | 34 | | ☑ | drag and drop files | [@latenssi](https://github.com/latenssi) | support drag and drop of files | 35 | | ☑ | render spaces | [@latenssi](https://github.com/latenssi) | currently "test          test" renders as "test test" in chat | 36 | | ☑ | /me missing space | [@latenssi](https://github.com/latenssi) | currently "/me doing something" renders as "mynicknamedoing something" in chat | 37 | | ☑ | browser compatibility | [@latenssi](https://github.com/latenssi) | some users have reported that the app is not wokring on Firefox on linux | 38 | | ☑ | ProfilePanel position | [@latenssi](https://github.com/latenssi) | fix MessageUserProfilePanel vertical positioning, currently it can overflow from the top of the view | 39 | | ☑ | mobile version | [@latenssi](https://github.com/latenssi) | make the app more progressive (PWA) | 40 | | ☑ | run ipfs in a worker | [@latenssi](https://github.com/latenssi) | run ipfs in a service worker to mitigate the slowdowns caused by signature checks etc. | 41 | | ☐ | private channels | [@latenssi](https://github.com/latenssi) | write only for selected people, everyone can read (read can not be limited because of current techical limitations) | 42 | | ☐ | chat pinner | [@latenssi](https://github.com/latenssi) | a node which pins a chat so even if everybody leaves, it will keep the chat history in case somebody joins (e.g. IRC bot) | 43 | -------------------------------------------------------------------------------- /jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | launch: { 3 | dumpio: false, 4 | headless: true 5 | }, 6 | server: { 7 | command: 'npm run test-server', 8 | port: 8088 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orbit-web", 3 | "version": "1.0.0-alpha-15", 4 | "description": "Orbit Chat", 5 | "main": "src/components/index.js", 6 | "scripts": { 7 | "build": "webpack --mode=production", 8 | "dev": "npm run start", 9 | "lint": "eslint src/ test/", 10 | "lint:fix": "eslint --fix src/ test/", 11 | "start": "webpack-dev-server --mode=development", 12 | "test-server": "http-server ./dist -s -p 8088", 13 | "test": "npm run lint && jest --runInBand" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/orbitdb/orbit-web.git" 18 | }, 19 | "keywords": [ 20 | "orbitdb", 21 | "orbit", 22 | "orbit-web" 23 | ], 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/orbitdb/orbit-web/issues" 27 | }, 28 | "homepage": "https://github.com/orbitdb/orbit-web#readme", 29 | "browserslist": [ 30 | "defaults" 31 | ], 32 | "jest": { 33 | "preset": "jest-puppeteer" 34 | }, 35 | "dependencies": { 36 | "autolinker": "^3.11.0", 37 | "classnames": "^2.2.6", 38 | "date-fns": "^2.2.1", 39 | "domkit": "0.0.3", 40 | "emoji-mart": "^2.9.1", 41 | "highlight.js": "^10.4.1", 42 | "i18next": "^17.0.16", 43 | "ipfs": "^0.37.1", 44 | "lodash.debounce": "^4.0.8", 45 | "mobx": "^5.13.0", 46 | "mobx-react": "^6.1.3", 47 | "orbit_": "^0.2.0-rc1", 48 | "pleasejs": "^0.4.2", 49 | "prop-types": "^15.6.2", 50 | "react": "^16.9.0", 51 | "react-dom": "^16.9.0", 52 | "react-i18next": "^10.12.5", 53 | "react-router-dom": "^5.1.0", 54 | "react-transition-group": "^4.3.0" 55 | }, 56 | "devDependencies": { 57 | "@babel/core": "^7.6.0", 58 | "@babel/plugin-proposal-class-properties": "^7.5.5", 59 | "@babel/plugin-proposal-decorators": "^7.6.0", 60 | "@babel/plugin-proposal-object-rest-spread": "^7.5.5", 61 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 62 | "@babel/plugin-syntax-flow": "^7.0.0", 63 | "@babel/plugin-transform-async-to-generator": "^7.5.0", 64 | "@babel/plugin-transform-flow-strip-types": "^7.1.6", 65 | "@babel/plugin-transform-spread": "^7.2.2", 66 | "@babel/polyfill": "^7.6.0", 67 | "@babel/preset-env": "^7.6.0", 68 | "@babel/preset-react": "^7.0.0", 69 | "@babel/register": "^7.6.0", 70 | "babel-eslint": "^10.0.3", 71 | "babel-loader": "^8.0.0", 72 | "clean-webpack-plugin": "^3.0.0", 73 | "css-loader": "^3.2.0", 74 | "eslint": "6.4.0", 75 | "eslint-config-standard": "14.1.0", 76 | "eslint-config-standard-jsx": "8.1.0", 77 | "eslint-plugin-import": "~2.18.0", 78 | "eslint-plugin-node": "~10.0.0", 79 | "eslint-plugin-promise": "~4.2.1", 80 | "eslint-plugin-react": "~7.14.2", 81 | "eslint-plugin-standard": "~4.0.0", 82 | "file-loader": "^4.2.0", 83 | "html-webpack-plugin": "^3.2.0", 84 | "http-server": "^0.11.1", 85 | "ignore-styles": "^5.0.1", 86 | "jest": "^24.9.0", 87 | "jest-puppeteer": "^4.3.0", 88 | "node-sass": "^4.3.3", 89 | "postcss-loader": "^3.0.0", 90 | "postcss-preset-env": "^6.7.0", 91 | "puppeteer": "^1.20.0", 92 | "react-hot-loader": "^4.12.13", 93 | "sass-loader": "^8.0.0", 94 | "style-loader": "^1.0.0", 95 | "url-loader": "^2.1.0", 96 | "webpack": "^4.40.2", 97 | "webpack-babel-env-deps": "^1.5.0", 98 | "webpack-cli": "^3.3.9", 99 | "webpack-dev-server": "^3.8.1", 100 | "worker-loader": "^2.0.0" 101 | }, 102 | "localMaintainers": [ 103 | "haad ", 104 | "shamb0t ", 105 | "hajamark " 106 | ] 107 | } 108 | -------------------------------------------------------------------------------- /src/components/BackgroundAnimation.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React from 'react' 4 | import PropTypes from 'prop-types' 5 | 6 | import '../styles/BackgroundAnimation.scss' 7 | 8 | const defaultDelay = 0.2 9 | 10 | function BackgroundAnimation ({ 11 | circleSize = 2, 12 | size = Math.min(window.innerWidth, window.innerHeight) / 2, 13 | startX, 14 | startY, 15 | delay = defaultDelay, 16 | theme, 17 | style, 18 | ...rest 19 | }) { 20 | const maxSize = size / 2 21 | const minSize = 32 * (size / 256) 22 | const amount = 7 23 | const opacity = 1 24 | const colors = [ 25 | 'rgba(50, 32, 56, ' + opacity + ')', 26 | 'rgba(62, 32, 76, ' + opacity + ')', 27 | 'rgba(87, 32, 110, ' + opacity + ')', 28 | 'rgba(118, 32, 154, ' + opacity + ')', 29 | 'rgba(156, 56, 203, ' + opacity + ')', 30 | 'rgba(188, 84, 238, ' + opacity + ')', 31 | 'rgba(225, 170, 253, ' + opacity + ')' 32 | ].reverse() 33 | 34 | const inc = (maxSize - minSize) / (amount - 1) 35 | const rings = [0, 1, 2, 3, 4, 5, 6] 36 | 37 | const circles = rings.reverse().map(i => { 38 | const radius = minSize + i * inc 39 | const color = colors[i] 40 | return ( 41 | 49 | ) 50 | }) 51 | 52 | const dots = rings.map(i => { 53 | const speed = 0.5 54 | const velocity = ((i + 1) * 9.80665) / speed 55 | const c = Math.max(212 - i * 16, 0) 56 | const color = `rgba(${c}, ${c}, ${c}, ${0.5 - (i + 1) * 0.01})` // 0.025 57 | const mul = Math.random() < 0.5 ? -1 : 1 // randomize between negative and positive pos 58 | const pos = (minSize + i * inc) * mul // starting position for the dot 59 | const dotSize = circleSize * (Math.random() * 2) + 1 60 | const startRadians = Math.floor(Math.random() * 360) 61 | const keyframes = `@keyframes rot${i} { 62 | 0% { transform: rotate(${startRadians}deg) translate(${pos}px) rotate(-${startRadians}deg) } 63 | 100% { transform: rotate(${startRadians + 64 | 360}deg) translate(${pos}px) rotate(-${startRadians + 360}deg) } 65 | }` 66 | 67 | const styleSheet = document.styleSheets[0] 68 | styleSheet.insertRule(keyframes, styleSheet.cssRules.length) 69 | 70 | return ( 71 | 83 | ) 84 | }) 85 | 86 | // Minor adjustment to the positions and sizing of graphics so that the outermost dot 87 | // will not be cut off by the frame when at max or min x or y 88 | const outerDotSize = dots[0].props.r * 2 89 | const adjustedSize = size + outerDotSize 90 | const adjustedScale = size / adjustedSize 91 | const adjustedPosition = (outerDotSize / 2) * adjustedScale 92 | let transform = `scale(${adjustedScale},${adjustedScale}),` 93 | transform += `translate(${adjustedPosition},${adjustedPosition})` 94 | 95 | return ( 96 |
97 | 98 | 99 | {circles} 100 | 101 | 102 | 103 | {dots} 104 | 105 |
106 | ) 107 | } 108 | 109 | BackgroundAnimation.propTypes = { 110 | circleSize: PropTypes.number, 111 | size: PropTypes.number, 112 | startX: PropTypes.number, 113 | startY: PropTypes.number, 114 | delay: PropTypes.number, 115 | theme: PropTypes.object, 116 | style: PropTypes.object 117 | } 118 | 119 | export default BackgroundAnimation 120 | -------------------------------------------------------------------------------- /src/components/DelayRender.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { useEffect, useState } from 'react' 4 | import PropTypes from 'prop-types' 5 | 6 | function DelayRender ({ children, visible, startDelay = 200, stopDelay = 1000 }) { 7 | const [render1, setRender1] = useState(false) 8 | const [render2, setRender2] = useState(false) 9 | 10 | useEffect(() => { 11 | const id = setTimeout(() => setRender1(visible), visible ? startDelay : 0) 12 | return () => clearTimeout(id) 13 | }, [visible, startDelay]) 14 | 15 | useEffect(() => { 16 | const id = setTimeout(() => setRender2(render1), render1 ? 0 : stopDelay) 17 | return () => clearTimeout(id) 18 | }, [render1, stopDelay]) 19 | 20 | return render2 ? children : null 21 | } 22 | 23 | DelayRender.propTypes = { 24 | children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired, 25 | visible: PropTypes.bool, 26 | startDelay: PropTypes.number, 27 | stopDelay: PropTypes.number 28 | } 29 | 30 | export default DelayRender 31 | -------------------------------------------------------------------------------- /src/components/Disclaimer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React from 'react' 4 | import PropTypes from 'prop-types' 5 | 6 | import '../styles/Disclaimer.scss' 7 | 8 | function Disclaimer ({ text }) { 9 | return ( 10 |
11 |
{text}
12 |
13 | ) 14 | } 15 | 16 | Disclaimer.propTypes = { 17 | text: PropTypes.string.isRequired 18 | } 19 | 20 | export default Disclaimer 21 | -------------------------------------------------------------------------------- /src/components/DropZone.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import '../styles/DropZone.scss' 5 | 6 | const DropZone = props => { 7 | const { onDrop, onDragLeave, label } = props 8 | return ( 9 |
10 |
{label}
11 |
12 | ) 13 | } 14 | 15 | DropZone.propTypes = { 16 | onDrop: PropTypes.func, 17 | onDragLeave: PropTypes.func, 18 | label: PropTypes.string 19 | } 20 | 21 | export default DropZone 22 | -------------------------------------------------------------------------------- /src/components/EmojiPicker.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React from 'react' 4 | import { Emoji } from 'emoji-mart' 5 | 6 | import 'emoji-mart/css/emoji-mart.css' 7 | import '../styles/EmojiPicker.scss' 8 | 9 | function EmojiPicker ({ emojis, emojiSize, emojiSet, onChange, ...rest }, ref) { 10 | const [selectedIndex, setSelectedIndex] = React.useState(0) 11 | 12 | const listRef = React.useRef() 13 | 14 | const handleChange = React.useCallback( 15 | done => { 16 | try { 17 | const emoji = emojis[selectedIndex] 18 | return onChange(emoji, done) 19 | } catch (e) { 20 | return onChange(null, done) 21 | } 22 | }, 23 | [selectedIndex, emojis, onChange] 24 | ) 25 | 26 | const handleClick = React.useCallback( 27 | index => { 28 | setSelectedIndex(index) 29 | handleChange() 30 | }, 31 | [handleChange] 32 | ) 33 | 34 | const handleKeyDown = React.useCallback( 35 | e => { 36 | if (selectedIndex > emojis.length) setSelectedIndex(0) 37 | 38 | let handled = true 39 | 40 | switch (e.key) { 41 | case 'ArrowRight': 42 | setSelectedIndex(calculateLeftRightIndex(true)) 43 | break 44 | case 'ArrowLeft': 45 | setSelectedIndex(calculateLeftRightIndex(false)) 46 | break 47 | case 'ArrowDown': 48 | setSelectedIndex(calculateUpDownIndex(true)) 49 | break 50 | case 'ArrowUp': 51 | setSelectedIndex(calculateUpDownIndex(false)) 52 | break 53 | case 'Tab': 54 | handleChange(true) 55 | break 56 | case 'Enter': 57 | handleChange(true) 58 | break 59 | default: 60 | handled = false 61 | break 62 | } 63 | 64 | if (handled) e.preventDefault() 65 | }, 66 | [selectedIndex, emojis.length, handleChange] 67 | ) 68 | 69 | // Allow parent component to call 'handleKeyDown' 70 | React.useImperativeHandle(ref, () => ({ handleKeyDown }), [handleKeyDown]) 71 | 72 | const calculateLeftRightIndex = React.useCallback( 73 | right => 74 | right 75 | ? (selectedIndex + 1) % emojis.length 76 | : selectedIndex > 0 77 | ? selectedIndex - 1 78 | : emojis.length - 1, 79 | [selectedIndex, emojis.length] 80 | ) 81 | 82 | const calculateUpDownIndex = React.useCallback( 83 | down => { 84 | if (!listRef.current) return 85 | 86 | const actualEmojiSize = emojiSize + 2 * 1 // 1px padding 87 | const actualWidth = listRef.current.offsetWidth - 5 * 2 // 5px padding 88 | const itemsPerRow = actualWidth / actualEmojiSize 89 | 90 | if (down) { 91 | if (selectedIndex + itemsPerRow > emojis.length - 1) { 92 | // Going over the bottom, must flip 93 | return selectedIndex % itemsPerRow 94 | } else { 95 | // Normal case 96 | return selectedIndex + itemsPerRow 97 | } 98 | } else { 99 | if (selectedIndex - itemsPerRow < 0) { 100 | // Going over the top, must flip 101 | const rows = Math.floor(emojis.length / itemsPerRow) 102 | if (emojis.length % itemsPerRow > selectedIndex) { 103 | // There is an element at the same index on the last row 104 | return selectedIndex + itemsPerRow * rows 105 | } else { 106 | // There is NOT an element at the same index on the last row 107 | // Go to the second last row since it should have this index 108 | return selectedIndex + itemsPerRow * (rows - 1) 109 | } 110 | } else { 111 | // Normal case 112 | return selectedIndex - itemsPerRow 113 | } 114 | } 115 | }, 116 | [selectedIndex, emojis.length, emojiSize] 117 | ) 118 | 119 | return ( 120 |
    121 | {emojis.map((emoji, idx) => { 122 | return ( 123 |
  • handleClick(idx)} 127 | > 128 | 129 |
  • 130 | ) 131 | })} 132 |
133 | ) 134 | } 135 | 136 | export default React.forwardRef(EmojiPicker) 137 | -------------------------------------------------------------------------------- /src/components/FilePreview/PreviewTextFile.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { useEffect, useState } from 'react' 4 | import PropTypes from 'prop-types' 5 | 6 | import Highlight from '../Highlight' 7 | 8 | import { getFileExtension } from '../../utils/file-helpers' 9 | 10 | function PreviewTextFile ({ blob, filename, onLoad, ...rest }) { 11 | const [result, setResult] = useState(null) 12 | 13 | useEffect(() => { 14 | const fileReader = new FileReader() 15 | fileReader.onload = ({ target: { result } }) => { 16 | setResult(result) 17 | setTimeout(onLoad, 0) 18 | } 19 | fileReader.onerror = () => { 20 | fileReader.abort() 21 | throw new Error('Unable to read file') 22 | } 23 | fileReader.readAsText(blob, 'utf-8') 24 | }, [blob]) 25 | 26 | return result ? ( 27 | 28 | {result} 29 | 30 | ) : null 31 | } 32 | 33 | PreviewTextFile.propTypes = { 34 | blob: PropTypes.object.isRequired, 35 | filename: PropTypes.string.isRequired, 36 | onLoad: PropTypes.func.isRequired 37 | } 38 | 39 | export default PreviewTextFile 40 | -------------------------------------------------------------------------------- /src/components/FilePreview/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { useCallback, useState, useEffect, useRef } from 'react' 4 | import PropTypes from 'prop-types' 5 | import { useTranslation } from 'react-i18next' 6 | 7 | import PreviewTextFile from './PreviewTextFile' 8 | 9 | import CustomSuspense from '../Suspense' 10 | 11 | import Logger from '../../utils/logger' 12 | import { isAudio, isImage, isVideo } from '../../utils/file-helpers' 13 | 14 | const logger = new Logger() 15 | 16 | async function loadPreviewContent (loadFunc, hash, name, mimeType, onLoad) { 17 | const fileIsAudio = isAudio(name) 18 | const fileIsImage = isImage(name) 19 | const fileIsVideo = isVideo(name) 20 | 21 | const buffer = await loadFunc(hash) 22 | const blob = new Blob([buffer], { type: mimeType }) 23 | const url = window.URL.createObjectURL(blob) 24 | 25 | if (fileIsAudio) { 26 | return