├── tsconfig.json ├── ui-automation ├── cypress.json ├── cypress │ ├── fixtures │ │ └── example.json │ ├── integration │ │ └── example_spec.js │ ├── screenshots │ │ └── This section describes how to manage your Holochat profile -- Fill out the required fields marked with an and click Register.png │ ├── plugins │ │ └── index.js │ └── support │ │ ├── index.js │ │ └── commands.js └── package.json ├── test ├── backnforth │ ├── _config.json │ ├── person2.json │ └── person1.json ├── coustom_room │ ├── _config.json │ ├── person2.json │ └── person1.json ├── private_backnforth │ ├── _config.json │ ├── person2.json │ └── person1.json ├── rooms_public.json ├── rooms_private.json ├── rooms_private_membership.json ├── rooms_exceptions.json ├── profiles.json ├── messages_exceptions.json ├── coustom_room.json ├── messages.json └── rooms_private_messaging.json ├── ui-src ├── public │ ├── Flag.png │ ├── Info.png │ ├── Pin.png │ ├── Settings.png │ ├── favicon.ico │ ├── holo-logo.png │ ├── j-avatar.png │ ├── GitHub-Mark.png │ ├── art-brock-avatar.png │ ├── followers-mockup.png │ ├── holochain-circle.png │ ├── keith-swann-avatar.png │ ├── mark-finnern-avatar.png │ ├── adam-thompson-avatar.png │ ├── connor-turland-avatar.png │ ├── philip-beadle-avatar.png │ ├── manifest.json │ ├── manifest0.json │ ├── index.html │ └── hc.js ├── copy-to-ui.js ├── src │ ├── stories │ │ └── index.js │ ├── actions.js │ ├── reducers │ │ └── index.js │ ├── root.js │ ├── containers │ │ └── profileContainer.js │ ├── index.js │ ├── components │ │ ├── profile.story.js │ │ ├── lists │ │ │ ├── message-list.js │ │ │ ├── channel-list.story.js │ │ │ ├── channel-list.js │ │ │ └── message-list.story.js │ │ ├── idea-card.story.js │ │ ├── profile.js │ │ ├── message.story.js │ │ ├── message.js │ │ └── idea-card.js │ ├── withRoot.js │ └── layouts │ │ ├── profile.js │ │ ├── index.js │ │ └── messages.js ├── .storybook │ ├── addons.js │ └── config.js ├── copy-release.js └── package.json ├── dna ├── @types │ ├── holochat │ │ └── holochat.d.ts │ └── holochain │ │ ├── index.d.ts │ │ └── holochain.d.ts ├── anchors │ ├── anchor.json │ └── anchors.js ├── rooms │ ├── room.json │ └── rooms.js ├── coustom_room │ ├── coustom_room_details.json │ ├── cr_message.json │ ├── coustom_room.js │ └── coustom_room.ts ├── properties_schema.json ├── hashtag │ ├── tag_post.json │ └── hashtag.js ├── messages │ ├── message.json │ └── messages.js ├── profiles │ ├── profile.json │ └── profiles.js ├── identity │ └── identity.js ├── membership │ └── membership.js └── dna.json ├── .gitignore ├── ui ├── holo_chat.css ├── index.html └── holo_chat.js ├── .travis.yml ├── docker-compose.yml ├── README.md ├── docs └── post_functions.md └── hack.md /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true 3 | } 4 | -------------------------------------------------------------------------------- /ui-automation/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "chromeWebSecurity": false 3 | } 4 | -------------------------------------------------------------------------------- /test/backnforth/_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "GossipInterval":500, 3 | "Duration":3 4 | } 5 | -------------------------------------------------------------------------------- /test/coustom_room/_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "GossipInterval":500, 3 | "Duration":5 4 | } 5 | -------------------------------------------------------------------------------- /ui-src/public/Flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/Flag.png -------------------------------------------------------------------------------- /ui-src/public/Info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/Info.png -------------------------------------------------------------------------------- /ui-src/public/Pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/Pin.png -------------------------------------------------------------------------------- /test/private_backnforth/_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "GossipInterval":500, 3 | "Duration":5 4 | } 5 | -------------------------------------------------------------------------------- /ui-src/public/Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/Settings.png -------------------------------------------------------------------------------- /ui-src/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/favicon.ico -------------------------------------------------------------------------------- /ui-src/public/holo-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/holo-logo.png -------------------------------------------------------------------------------- /ui-src/public/j-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/j-avatar.png -------------------------------------------------------------------------------- /ui-src/public/GitHub-Mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/GitHub-Mark.png -------------------------------------------------------------------------------- /ui-src/public/art-brock-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/art-brock-avatar.png -------------------------------------------------------------------------------- /ui-src/public/followers-mockup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/followers-mockup.png -------------------------------------------------------------------------------- /ui-src/public/holochain-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/holochain-circle.png -------------------------------------------------------------------------------- /ui-src/public/keith-swann-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/keith-swann-avatar.png -------------------------------------------------------------------------------- /ui-src/public/mark-finnern-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/mark-finnern-avatar.png -------------------------------------------------------------------------------- /ui-src/public/adam-thompson-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/adam-thompson-avatar.png -------------------------------------------------------------------------------- /ui-src/public/connor-turland-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/connor-turland-avatar.png -------------------------------------------------------------------------------- /ui-src/public/philip-beadle-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-src/public/philip-beadle-avatar.png -------------------------------------------------------------------------------- /ui-src/copy-to-ui.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | var rimraf = require('rimraf') 3 | rimraf.sync('../ui') 4 | fs.renameSync('build', '../ui') 5 | -------------------------------------------------------------------------------- /ui-src/src/stories/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | -------------------------------------------------------------------------------- /ui-src/.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-actions/register' 2 | import '@storybook/addon-links/register' 3 | import 'storybook-addon-specifications/register' 4 | -------------------------------------------------------------------------------- /ui-automation/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /dna/@types/holochat/holochat.d.ts: -------------------------------------------------------------------------------- 1 | type UUID = string; 2 | 3 | interface message { 4 | uuid : UUID; 5 | message : any; 6 | } 7 | 8 | interface updateMessage { 9 | new_message: any; 10 | old_hash : Hash; 11 | } 12 | -------------------------------------------------------------------------------- /dna/anchors/anchor.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Anchor Schema", 3 | "type": "object", 4 | "properties": { 5 | "anchorType": { 6 | "type": "string" 7 | }, 8 | "anchorText": { 9 | "type": "string" 10 | } 11 | }, 12 | "required": ["anchorType"] 13 | } 14 | -------------------------------------------------------------------------------- /ui-src/copy-release.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | var rimraf = require('rimraf') 4 | rimraf.sync('../release') 5 | fs.mkdirSync(path.join(__dirname, '../release')) 6 | fs.renameSync('../ui', '../release/ui') 7 | fs.renameSync('../dna', '../release/dna') 8 | -------------------------------------------------------------------------------- /dna/rooms/room.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Room Schema", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string" 7 | }, 8 | "access": { 9 | "type": "string" 10 | }, 11 | "purpose": { 12 | "type": "string" 13 | } 14 | }, 15 | "required": ["name","access"] 16 | } 17 | -------------------------------------------------------------------------------- /dna/coustom_room/coustom_room_details.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Room Schema", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string" 7 | }, 8 | "access": { 9 | "type": "string" 10 | }, 11 | "purpose": { 12 | "type": "string" 13 | } 14 | }, 15 | "required": ["name"] 16 | } 17 | -------------------------------------------------------------------------------- /ui-src/src/actions.js: -------------------------------------------------------------------------------- 1 | // Holochain actions 2 | export const REGISTER = 'register' 3 | 4 | export function register (profile, then) { 5 | return { 6 | type: REGISTER, 7 | meta: { 8 | isHc: true, 9 | namespace: 'profiles', 10 | data: profile, 11 | then 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /dna/properties_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Properties Schema", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string" 7 | }, 8 | "purpose": { 9 | "type": "string" 10 | }, 11 | "language": { 12 | "type": "string" 13 | }, 14 | "initial_admin": { 15 | "type": "string" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ui-automation/cypress/integration/example_spec.js: -------------------------------------------------------------------------------- 1 | describe('This section describes how to manage your Holochat handle', function () { 2 | it('Let\'s check that all 3 instances of Clutter are running.)', function () { 3 | cy.visit('http://localhost:3141') 4 | cy.request('http://localhost:4141') 5 | cy.request('http://localhost:5141') 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /ui-automation/cypress/screenshots/This section describes how to manage your Holochat profile -- Fill out the required fields marked with an and click Register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/holochat/HEAD/ui-automation/cypress/screenshots/This section describes how to manage your Holochat profile -- Fill out the required fields marked with an and click Register.png -------------------------------------------------------------------------------- /ui-src/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /ui-src/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import * as A from '../actions' 2 | 3 | const initialState = { 4 | key: 10 5 | } 6 | 7 | export default function holochatApp (state = initialState, action) { 8 | const { type, meta, payload } = action 9 | switch (type) { 10 | case A.REGISTER: 11 | console.log(payload) 12 | return state 13 | default: 14 | return state 15 | } 16 | } -------------------------------------------------------------------------------- /dna/hashtag/tag_post.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Hash Tag posts Schema", 3 | "type": "object", 4 | "properties": { 5 | "content": { 6 | "type": "string" 7 | }, 8 | "timestamp": { 9 | "type": "string" 10 | }, 11 | "room": { 12 | "type": "string" 13 | }, 14 | "inReplyTo": { 15 | "type": "string" 16 | } 17 | }, 18 | "required": ["content", "timestamp", "room"] 19 | } 20 | -------------------------------------------------------------------------------- /ui-src/public/manifest0.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /ui-src/.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/react' 2 | const componentStories = require.context('../src/components', true, /story\.js$/) 3 | const listStories = require.context('../src/components/lists', true, /story\.js$/) 4 | function loadStories () { 5 | componentStories.keys().forEach(componentStories) 6 | listStories.keys().forEach(listStories) 7 | } 8 | 9 | configure(loadStories, module) 10 | -------------------------------------------------------------------------------- /ui-src/src/root.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Provider } from 'react-redux' 4 | import { BrowserRouter as Router, Route } from 'react-router-dom' 5 | import Index from './layouts/index' 6 | 7 | const Root = ({ store }) => ( 8 | 9 | 10 | 11 | 12 | 13 | ) 14 | 15 | export default Root 16 | -------------------------------------------------------------------------------- /dna/coustom_room/cr_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Message Schema", 3 | "type": "object", 4 | "properties": { 5 | "author":{ 6 | "type": "string" 7 | }, 8 | "timestamp": { 9 | "type": "string" 10 | }, 11 | "content": { 12 | "properties":{ 13 | "text":{ 14 | "type": "string" 15 | }, 16 | "mediaLink":{ 17 | "type": "string" 18 | } 19 | } 20 | } 21 | }, 22 | "required": ["author","timestamp","content"] 23 | } 24 | -------------------------------------------------------------------------------- /dna/messages/message.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Message Schema", 3 | "type": "object", 4 | "properties": { 5 | "author":{ 6 | "type": "string" 7 | }, 8 | "content": { 9 | "properties":{ 10 | "text":{ 11 | "type": "string" 12 | }, 13 | "mediaLink":{ 14 | "type": "string" 15 | } 16 | } 17 | }, 18 | "timestamp": { 19 | "type": "string" 20 | }, 21 | "room_name": { 22 | "type": "string" 23 | } 24 | }, 25 | "required": ["author","room_name","content", "timestamp"] 26 | } 27 | -------------------------------------------------------------------------------- /test/coustom_room/person2.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests": [{ 3 | "Time": 0, 4 | "Convey": "0. Gets Keys and initialize", 5 | "Zome": "coustom_room", 6 | "FnName": "getKey", 7 | "Input": "", 8 | "Output": "%key%" 9 | }, 10 | { 11 | "Time": 3000, 12 | "Convey": "1. Get the rooms|chats you are part of (UUID)", 13 | "Zome": "coustom_room", 14 | "FnName": "getMyRooms", 15 | "Input": "", 16 | "RegExp":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /ui-src/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Holo Chat 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /dna/profiles/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Profile Schema", 3 | "type": "object", 4 | "properties": { 5 | "agent_id": { 6 | "type": "string" 7 | }, 8 | "agent_hash": { 9 | "type": "string" 10 | }, 11 | "username": { 12 | "type": "string" 13 | }, 14 | "firstName": { 15 | "type": "string" 16 | }, 17 | "lastName": { 18 | "type": "string" 19 | }, 20 | "email": { 21 | "type": "string" 22 | }, 23 | "avatar": { 24 | "type": "string" 25 | } 26 | }, 27 | "required": ["agent_id", "username", "firstName", "lastName", "email"] 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .txt 2 | .key 3 | agent.txt 4 | restart.sh 5 | 6 | # See https://help.github.com/ignore-files/ for more about ignoring files. 7 | .DS_Store 8 | # dependencies 9 | ui-src/node_modules 10 | ui-automation/cypress/screenshots 11 | ui-automation/cypress/videos 12 | ui-automation/node_modules 13 | ui 14 | 15 | # testing 16 | /coverage 17 | 18 | # production 19 | /build 20 | 21 | # misc 22 | .DS_Store 23 | .env.local 24 | .env.development.local 25 | .env.test.local 26 | .env.production.local 27 | 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | yarn.lock 33 | package-lock.json* 34 | -------------------------------------------------------------------------------- /test/coustom_room/person1.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests": [ 3 | { 4 | "Time":0, 5 | "Convey": "0. Gets Keys and initialize", 6 | "Zome": "coustom_room", 7 | "FnName": "getKey", 8 | "Input": "", 9 | "Output": "%key%" 10 | }, 11 | { 12 | "Time":10, 13 | "Convey": "1. Create new Room", 14 | "Zome": "coustom_room", 15 | "FnName": "createCoustomRoom", 16 | "Input": ["QmcWesmzNkvv2jSKo1msv8xNo5h7NxoyPJL8KCtm7cfzzx"], 17 | "RegExp": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", 18 | "Err": "" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /ui/holo_chat.css: -------------------------------------------------------------------------------- 1 | .rooms { 2 | width: 300px; 3 | cursor: pointer; 4 | } 5 | #room-name-input, #room-name-button { 6 | width: 100%; 7 | } 8 | 9 | .rooms ul { 10 | 11 | } 12 | 13 | #messages { 14 | width: 100%; 15 | height: 300px; 16 | border: 1px solid grey; 17 | border-radius: 2px; 18 | overflow: scroll; 19 | padding-left: 10px; 20 | } 21 | 22 | #messages .username { 23 | display: block; 24 | font-size: 12px; 25 | font-weight: bold; 26 | } 27 | 28 | #messages .timestamp { 29 | float: right; 30 | font-size: 10px; 31 | color: light-grey; 32 | } 33 | 34 | #messages .message { 35 | padding-left: 5px; 36 | } 37 | 38 | .selected-room { 39 | border: 1px solid grey; 40 | border-radius: 4px; 41 | } 42 | -------------------------------------------------------------------------------- /ui-src/src/containers/profileContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import Profile from '../components/profile' 3 | import { 4 | register 5 | } from '../actions' 6 | 7 | const mapStateToProps = state => { 8 | return { 9 | ...state 10 | } 11 | } 12 | 13 | const mapDispatchToProps = (dispatch) => { 14 | return { 15 | register: (profile) => { 16 | console.log('registering!') 17 | const prof = { 18 | username: 'Hi', 19 | firstName: 'Jim', 20 | lastName: 'Roberts', 21 | email: '123@email.com' 22 | } 23 | dispatch(register(prof)) 24 | } 25 | } 26 | } 27 | 28 | export default connect( 29 | mapStateToProps, 30 | mapDispatchToProps 31 | )(Profile) -------------------------------------------------------------------------------- /ui-automation/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | module.exports = (on, config) => { 15 | // `on` is used to hook into various events Cypress emits 16 | // `config` is the resolved Cypress config 17 | } 18 | -------------------------------------------------------------------------------- /ui-automation/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /ui-src/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { compact } from 'lodash' 4 | import { applyMiddleware, compose, createStore } from 'redux' 5 | import promiseMiddleware from 'redux-promise' 6 | import { requestSendingMiddleware, hcMiddleware } from 'hc-redux-middleware' 7 | import holochatApp from './reducers' 8 | import Root from './root' 9 | 10 | const middleware = compact([ 11 | hcMiddleware, 12 | requestSendingMiddleware, 13 | promiseMiddleware 14 | ]) 15 | 16 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose 17 | let store = createStore(holochatApp, undefined, composeEnhancers(applyMiddleware(...middleware))) 18 | 19 | ReactDOM.render(, document.querySelector('#root')) 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: node_js 3 | node_js: 4 | - '8' 5 | env: 6 | matrix: 7 | - DOCKER_COMPOSE_VERSION=1.18.0 8 | 9 | services: 10 | - docker 11 | before_install: 12 | - cd ui-src 13 | - npm install 14 | - npm run build 15 | - npm run start & 16 | - cd .. 17 | - sudo rm /usr/local/bin/docker-compose 18 | - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname 19 | -s`-`uname -m` > docker-compose 20 | - chmod +x docker-compose 21 | - sudo mv docker-compose /usr/local/bin 22 | - TARGETDIR=$(pwd) docker-compose up -d 23 | script: 24 | - cd ui-automation 25 | - npm install 26 | - npm test 27 | - docker-compose kill 28 | - docker-compose down 29 | cache: 30 | directories: 31 | - ui-automation/node_modules 32 | -------------------------------------------------------------------------------- /ui-automation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui-automation", 3 | "version": "0.0.1", 4 | "description": "e2e tests for holochat", 5 | "main": "index.js", 6 | "repository": "https://github.com/Holochain/holochat", 7 | "author": "Philip Beadle", 8 | "license": "MIT", 9 | "scripts": { 10 | "test": "CYPRESS_baseUrl=http://localhost:3000 cypress run", 11 | "cypress:open": "CYPRESS_baseUrl=http://localhost:3000 cypress open", 12 | "cypress:docker": "CYPRESS_baseUrl=http://localhost:4141 cypress open" 13 | }, 14 | "dependencies": { 15 | "cypress": "^1.4.1" 16 | }, 17 | "standard": { 18 | "globals": [ 19 | "describe", 20 | "context", 21 | "before", 22 | "beforeEach", 23 | "after", 24 | "afterEach", 25 | "it", 26 | "expect", 27 | "cy" 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ui-automation/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /ui-src/src/components/profile.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { storiesOf } from '@storybook/react' 3 | import { action } from '@storybook/addon-actions' 4 | import { specs, describe, it } from 'storybook-addon-specifications' 5 | import { configure, mount } from 'enzyme' 6 | import Adapter from 'enzyme-adapter-react-16' 7 | import Profile from './profile' 8 | import expect from 'expect' 9 | 10 | configure({ adapter: new Adapter() }) 11 | 12 | storiesOf('Profile', module) 13 | .add('Registering a new person', () => { 14 | // const following = [ 15 | // {'handle': 'philt3r', 'userHash': 'wegwtrwrt'}, 16 | // {'handle': 'Test 2', 'userHash': 'dddd'} 17 | // ] 18 | specs(() => describe('The Follow Form with followed entities', function () { 19 | it('You can see the list of entities you are following, if you are following any.', () => { 20 | expect(true) 21 | }) 22 | })) 23 | 24 | return getProfile() 25 | }) 26 | 27 | function getProfile () { 28 | return ( 29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | 5 | bootstrap: 6 | image: holochain/holochain-proto:develop 7 | ports: 8 | - "3142:3142" 9 | command: bs 10 | 11 | holochat1: 12 | image: holochain/holochain-proto:develop 13 | ports: 14 | - "3141:3141" 15 | depends_on: 16 | - bootstrap 17 | volumes: 18 | - "${TARGETDIR}:/holochat" 19 | command: bash -c "cd /holochat; hcdev -port=6001 -agentID=lucy -bootstrapServer=bootstrap:3142 web 3141" 20 | 21 | holochat2: 22 | image: holochain/holochain-proto:develop 23 | ports: 24 | - "4141:4141" 25 | depends_on: 26 | - bootstrap 27 | volumes: 28 | - "${TARGETDIR}:/holochat" 29 | command: bash -c "cd /holochat; hcdev -port=6002 -agentID=phil -bootstrapServer=bootstrap:3142 web 4141" 30 | 31 | holochat3: 32 | image: holochain/holochain-proto:develop 33 | ports: 34 | - "5141:5141" 35 | depends_on: 36 | - bootstrap 37 | volumes: 38 | - "${TARGETDIR}:/holochat" 39 | command: bash -c "cd /holochat; hcdev -port=6003 -agentID=clarence -bootstrapServer=bootstrap:3142 web 5141" 40 | -------------------------------------------------------------------------------- /test/rooms_public.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests":[ 3 | { 4 | "Convey":"0. Register User ", 5 | "Zome":"profiles", 6 | "FnName":"register", 7 | "Input":{"username":"cnorris", "firstName":"Chuck", "lastName":"Norris", "email":"chuck@norris.com"}, 8 | "Output":"%h6%", 9 | "Err":"" 10 | },{ 11 | "Convey":"1. Check if hes registered", 12 | "Zome":"profiles", 13 | "FnName":"isRegistered", 14 | "Input":"", 15 | "Output":true, 16 | "Err":"" 17 | }, 18 | { 19 | "Convey":"2. newRoom should return the room's hash", 20 | "Zome":"rooms", 21 | "FnName":"newRoom", 22 | "Input":{"name":"general", "access":"public"}, 23 | "Output":"%h5%", 24 | "Err":"" 25 | }, 26 | { 27 | "Convey":"3. getPublicRooms should return a list of rooms", 28 | "Zome":"rooms", 29 | "FnName":"getPublicRooms", 30 | "Input":"", 31 | "Output":[{"id":"%r1%","name":"general","access":"public"}], 32 | "Err":"" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /ui-src/src/withRoot.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { MuiThemeProvider, createMuiTheme } from 'material-ui/styles' 3 | import CssBaseline from 'material-ui/CssBaseline' 4 | 5 | // A theme with custom primary and secondary color. 6 | // It's optional. 7 | const theme = createMuiTheme({ 8 | palette: { 9 | primary: { 10 | light: '#d05ce3', 11 | main: '#9c27b0', 12 | dark: '#6a0080' 13 | }, 14 | secondary: { 15 | light: '#6ff9ff', 16 | main: '#26cd6a', 17 | dark: '#0095a8' 18 | } 19 | } 20 | }) 21 | 22 | // Expose the theme as a global variable so people can play with it. 23 | if (process.browser) { 24 | window.theme = theme 25 | } 26 | 27 | function withRoot (Component) { 28 | function WithRoot (props) { 29 | // MuiThemeProvider makes the theme available down the React tree 30 | // thanks to React context. 31 | return ( 32 | 33 | {/* Reboot kickstart an elegant, consistent, and simple baseline to build upon. >> Reboot renamed CssBaseline. */} 34 | 35 | 36 | 37 | ) 38 | } 39 | 40 | return WithRoot 41 | } 42 | 43 | export default withRoot 44 | -------------------------------------------------------------------------------- /ui-src/public/hc.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013-2017, The MetaCurrency Project (Eric Harris-Braun & Arthur Brock) 2 | // Use of this source code is governed by GPLv3 found in the LICENSE file 3 | //--------------------------------------------------------------------------------------- 4 | 5 | // Holochain UI library 6 | 7 | // use send to make an ajax call to your exposed functions 8 | function send(fn,data,resultFn) { 9 | if (Clutter.debug) console.log("calling: " + fn+" with "+JSON.stringify(data)); 10 | 11 | eval(Clutter.getHook(fn, "preSendHook")); 12 | $.post( 13 | "/fn/clutter/"+fn, 14 | data, 15 | function(response) { 16 | if (Clutter.debug) console.log("response: " + response); 17 | eval(Clutter.getHook(fn, "successPreResult")); 18 | resultFn(response); 19 | eval(Clutter.getHook(fn, "successPostResult")); 20 | } 21 | ).error(function(response) { 22 | console.log("response failed: " + response.responseText); 23 | $(".error").html("" + response.responseText + "").toggleClass("show", true) 24 | setTimeout 25 | ( () => 26 | { $(".error") .toggleClass("show", false); 27 | eval(Clutter.getHook(fn, "errorTimeoutComplete")); 28 | }, 29 | 2000 30 | ); 31 | }) 32 | ; 33 | }; 34 | -------------------------------------------------------------------------------- /dna/identity/identity.js: -------------------------------------------------------------------------------- 1 | function genesis(){return true} 2 | 3 | function bridgeGenesis(side,dna,appData){ 4 | appDNAHash=getDpkiDNA() 5 | data=bridge(appDNAHash, "dpkiLib", "registerDpkiTo", App.Agent.Hash) 6 | debug("data = "+data) 7 | return true 8 | } 9 | 10 | function registerDpkiKeyTo(){ 11 | appDNAHash=getDpkiDNA(); 12 | debug("registerDpkiKeyTo = "+appDNAHash) 13 | data = bridge(appDNAHash, "dpkiLib", "registerDpkiKeyTo", App.Agent.Hash) 14 | debug("data = "+JSON.stringify(data)) 15 | return data 16 | } 17 | 18 | function hasRegisteredKey(app_agent_id){ 19 | debug("AGENT : "+app_agent_id) 20 | appDNAHash=getDpkiDNA(); 21 | debug("hasRegisteredKey = "+appDNAHash) 22 | data = bridge(appDNAHash, "dpkiLib", "hasRegisteredKey",app_agent_id) 23 | debug("data = "+JSON.stringify(data)) 24 | return data 25 | } 26 | 27 | function getUserDetails(app_agent_id){ 28 | appDNAHash=getDpkiDNA(); 29 | debug("getUserDetails = "+appDNAHash) 30 | data = bridge(appDNAHash, "dpkiLib", "getUserDetails", app_agent_id) 31 | debug("data = "+JSON.stringify(data)) 32 | return data 33 | } 34 | 35 | 36 | ///////////////// 37 | // DPKI DNA Hash 38 | //////////////// 39 | function getDpkiDNA(){ 40 | bridges = getBridges(); 41 | if (bridges[0] != undefined) { 42 | return bridges[0].ToApp 43 | } else { 44 | debug("identityZome: No bridge found!") 45 | } 46 | return "" 47 | } 48 | -------------------------------------------------------------------------------- /dna/@types/holochain/index.d.ts: -------------------------------------------------------------------------------- 1 | // holochain ambient type defs for API 2 | 3 | /// 4 | 5 | declare function property(name: string): string; 6 | declare function makeHash (entryType: string, entryData: any): Hash; 7 | declare function debug(value: any): void; 8 | declare function call(zomeName: string, functionName: string, arguments: string | object): any; 9 | declare function bridge(appDNAHash: Hash, zomeName: string, functionName: string, arguments: string | object): any; 10 | declare function getBridges(): BridgeStatus[]; 11 | declare function sign(doc: string): string; 12 | declare function verifySignature(signature: string, data: string, pubKey: string): boolean; 13 | declare function commit(entryType: string, entryData: string | object): Hash; 14 | declare function get(hash: Hash, options?: object): GetResponse | any; 15 | declare function getLinks(base: Hash, tag: string, options?: object): GetLinksResponse[]; 16 | declare function update(entryType: string, entryData: string | object, replaces: Hash) : Hash; 17 | declare function updateAgent(options: object): Hash; 18 | declare function remove(entryHash: Hash, message: string): Hash; 19 | declare function query(options?: object): QueryResponse[] | any[]; 20 | declare function send(to: Hash, message: object, options?: object): any; 21 | declare function bundleStart(timeout: number, userParam: any): void; 22 | declare function bundleClose(commit: boolean): void; 23 | 24 | declare var HC: HolochainSystemGlobals; 25 | declare var App: HolochainAppGlobals; -------------------------------------------------------------------------------- /ui-src/src/components/lists/message-list.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classNames from 'classnames' 3 | import PropTypes from 'prop-types' 4 | import withRoot from '../../withRoot' 5 | import { withStyles } from 'material-ui/styles' 6 | import List, { ListItem, ListItemText } from 'material-ui/List' 7 | import Message from '../message' 8 | 9 | const styles = theme => ({ 10 | listItemMessage: { 11 | position: 'relative' 12 | } 13 | }) 14 | 15 | class MessageList extends React.Component { 16 | render () { 17 | const { classes, messages } = this.props 18 | return ( 19 | 20 | {messages.map((messageByDate, messageDateIndex) => ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | {messageByDate.messages.map((message, index) => ( 28 | 29 | 30 | 31 | ))} 32 | 33 | 34 | 35 | ))} 36 | 37 | ) 38 | } 39 | } 40 | 41 | MessageList.propTypes = { 42 | classes: PropTypes.object.isRequired, 43 | messages: PropTypes.object.isRequired 44 | 45 | } 46 | 47 | export default withRoot(withStyles(styles)(MessageList)) 48 | -------------------------------------------------------------------------------- /dna/@types/holochain/holochain.d.ts: -------------------------------------------------------------------------------- 1 | // holochain type definitions 2 | 3 | 4 | type Hash = string; 5 | type Signature = string; 6 | type HolochainError = object; 7 | type PackageRequest = object; 8 | 9 | /*============================================ 10 | = Holochain Data Types = 11 | ============================================*/ 12 | 13 | interface Header { 14 | Type: string; 15 | Time: string; 16 | HeaderLink: Hash; 17 | EntryLink: Hash; 18 | TypeLink: Hash; 19 | Sig: Signature; 20 | Change: Hash; 21 | } 22 | 23 | interface GetResponse { 24 | Entry?: any; 25 | EntryType?: string; 26 | Sources?: Hash[]; 27 | } 28 | 29 | interface GetLinksResponse { 30 | Hash: Hash; 31 | Entry?: any; 32 | EntryType?: string; 33 | Tag?: string; 34 | Source?: Hash; 35 | } 36 | 37 | interface QueryResponse { 38 | Hash?: string 39 | Entry?: any 40 | Header?: Header 41 | } 42 | 43 | interface BridgeStatus { 44 | Side: number; 45 | CalleeName?: string; 46 | CalleeApp?: Hash; 47 | Token?: string; 48 | } 49 | 50 | 51 | /*===== End of Holochain Data Types ======*/ 52 | 53 | 54 | interface HolochainSystemGlobals { 55 | Version: string; 56 | HashNotFound: any; 57 | Status: any; 58 | GetMask: any; 59 | LinkAction: any; 60 | PkgReq: any; 61 | Bridge: any; 62 | SysEntryType: any; 63 | BundleCancel: any; 64 | } 65 | 66 | interface HolochainAppGlobals { 67 | Name: string; 68 | DNA: { 69 | Hash: Hash; 70 | }; 71 | Key: { 72 | Hash: Hash; 73 | } 74 | Agent: { 75 | Hash: Hash; 76 | TopHash: Hash; 77 | String: string; 78 | } 79 | } 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /ui-src/src/components/idea-card.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { storiesOf } from '@storybook/react' 3 | // import { action } from '@storybook/addon-actions' 4 | import { specs, describe, it } from 'storybook-addon-specifications' 5 | import { configure, mount } from 'enzyme' 6 | import Adapter from 'enzyme-adapter-react-16' 7 | import IdeaCard from './idea-card' 8 | import expect from 'expect' 9 | 10 | configure({ adapter: new Adapter() }) 11 | 12 | storiesOf('Top Idea Card', module) 13 | .add('Display an idea', () => { 14 | const idea = { 15 | title: 'Poll Integration', 16 | productOwner: 'joatu-jamie', 17 | avatar: 'j-avatar.png', 18 | date: 'January 25, 2018 - Lead Time 6 weeks', 19 | description: 'To make it easier to gauge community support for an idea being able to attach a Poll to an idea would be cool.', 20 | up: 9, 21 | down: 0 22 | } 23 | specs(() => describe('Idea', function () { 24 | it('Clicking the Thumbs Up increases the votes shown in the Thumbs Up badge', () => { 25 | const wrapper = mount() 26 | wrapper.find('#thumbsUp').simulate('click') 27 | expect(wrapper.find('#thumbsUpBadge').value).toEqual(1) 28 | }) 29 | it('Clicking the Thumbs Down increases the votes shown in the Thumbs Down badge', () => { 30 | const wrapper = mount() 31 | wrapper.find('#thumbsDown').simulate('click') 32 | expect(wrapper.find('#thumbsDownBadge').value).toEqual(1) 33 | }) 34 | })) 35 | 36 | return getIdeaCard(idea) 37 | }) 38 | function getIdeaCard (idea) { 39 | return ( 40 | 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /test/rooms_private.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests":[ 3 | { 4 | "Convey":"0. Register User ", 5 | "Zome":"profiles", 6 | "FnName":"register", 7 | "Input":{"username":"cnorris", "firstName":"Chuck", "lastName":"Norris", "email":"chuck@norris.com"}, 8 | "Output":"%h6%", 9 | "Err":"" 10 | },{ 11 | "Convey":"1. Check if hes registered", 12 | "Zome":"profiles", 13 | "FnName":"isRegistered", 14 | "Input":"", 15 | "Output":true, 16 | "Err":"" 17 | }, 18 | { 19 | "Convey":"2. PRIVATE newRoom should return the room's hash", 20 | "Zome":"rooms", 21 | "FnName":"newRoom", 22 | "Input":{"name":"general", "access":"private"}, 23 | "Output":"%h13%", 24 | "Err":"" 25 | }, 26 | { 27 | "Convey":"3. get Agent Hash for testing ", 28 | "Zome":"profiles", 29 | "FnName":"getMyAgentHash", 30 | "Input":"", 31 | "Output":"%agent%", 32 | "Err":"" 33 | }, 34 | { 35 | "Convey":"4. get admin of the private room ", 36 | "Zome":"rooms", 37 | "FnName":"getRoomAdmin", 38 | "Input":{"room_name":"general"}, 39 | "Output":"%r1%", 40 | "Err":"" 41 | }, 42 | { 43 | "Convey":"5. get admin of the private room ", 44 | "Zome":"rooms", 45 | "FnName":"getRoomAdmin", 46 | "Input":{"room_name":"fake"}, 47 | "Output":"ERROR: invalid PRIVATE Room name", 48 | "Err":"" 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /test/rooms_private_membership.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests":[ 3 | { 4 | "Convey":"0. Register User ", 5 | "Zome":"profiles", 6 | "FnName":"register", 7 | "Input":{"username":"cnorris", "firstName":"Chuck", "lastName":"Norris", "email":"chuck@norris.com"}, 8 | "Output":"%h6%", 9 | "Err":"" 10 | },{ 11 | "Convey":"1. Check if hes registered", 12 | "Zome":"profiles", 13 | "FnName":"isRegistered", 14 | "Input":"", 15 | "Output":true, 16 | "Err":"" 17 | }, 18 | { 19 | "Convey":"2. PRIVATE newRoom should return the room's hash", 20 | "Zome":"rooms", 21 | "FnName":"newRoom", 22 | "Input":{"name":"general", "access":"private"}, 23 | "Output":"%h13%", 24 | "Err":"" 25 | }, 26 | { 27 | "Convey":"3. add Someone to the private room ", 28 | "Zome":"membership", 29 | "FnName":"addMember", 30 | "Input":{"room_name":"general","agent_key":"%key%","agent_hash":"%agent%"}, 31 | "Output":"%h2%", 32 | "Err":"" 33 | }, 34 | { 35 | "Convey":"4. Get the added members to the private room ", 36 | "Zome":"membership", 37 | "FnName":"getMembers", 38 | "Input":{"room_name":"general"}, 39 | "Output":"%agent%", 40 | "Err":"" 41 | }, 42 | { 43 | "Convey":"5. Get my Private Rooms", 44 | "Zome":"rooms", 45 | "FnName":"getMyPrivateRooms", 46 | "Input":"", 47 | "Output":"general", 48 | "Err":"" 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /ui-src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clutter", 3 | "version": "0.2.0", 4 | "private": true, 5 | "proxy": "http://localhost:4141", 6 | "dependencies": { 7 | "axios": "^0.17.1", 8 | "enzyme": "^3.3.0", 9 | "enzyme-adapter-react-16": "^1.1.1", 10 | "hc-redux-middleware": "^1.0.3", 11 | "jest": "^22.1.1", 12 | "lodash": "^4.17.5", 13 | "material-ui": "next", 14 | "material-ui-icons": "latest", 15 | "prop-types": "latest", 16 | "react": "latest", 17 | "react-dom": "latest", 18 | "react-redux": "latest", 19 | "react-router": "latest", 20 | "react-router-dom": "latest", 21 | "react-scripts": "latest", 22 | "redux": "^3.7.2", 23 | "redux-logger": "^3.0.6", 24 | "redux-promise": "^0.5.3", 25 | "storybook-addon-specifications": "^2.1.1" 26 | }, 27 | "scripts": { 28 | "start": "react-scripts start", 29 | "build": "react-scripts build && node copy-to-ui.js", 30 | "test": "react-scripts test --env=jsdom", 31 | "eject": "react-scripts eject", 32 | "standard": "standard --fix", 33 | "release:files": "node copy-release.js", 34 | "deploy-storybook": "storybook-to-ghpages", 35 | "storybook": "start-storybook -p 9009 -s public", 36 | "build-storybook": "build-storybook -s public" 37 | }, 38 | "standard": { 39 | "parser": "babel-eslint", 40 | "globals": [ 41 | "describe", 42 | "context", 43 | "before", 44 | "beforeEach", 45 | "after", 46 | "afterEach", 47 | "it", 48 | "expect" 49 | ] 50 | }, 51 | "devDependencies": { 52 | "@storybook/addon-actions": "^3.3.11", 53 | "@storybook/addon-links": "^3.3.11", 54 | "@storybook/react": "^3.3.11", 55 | "babel-core": "^6.26.0", 56 | "babel-eslint": "^8.2.1", 57 | "rimraf": "^2.6.2", 58 | "standard": "^10.0.3" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ui-src/src/layouts/profile.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import Grid from 'material-ui/Grid' 4 | import { withStyles } from 'material-ui/styles' 5 | import withRoot from '../withRoot' 6 | import Profile from '../containers/profileContainer' 7 | 8 | const styles = theme => ({ 9 | root: { 10 | width: '100%', 11 | marginTop: 0, 12 | flexGrow: 1 13 | }, 14 | flex: { 15 | flex: 1 16 | }, 17 | demo: { 18 | height: 800 19 | }, 20 | paper: { 21 | padding: 16, 22 | textAlign: 'center', 23 | color: theme.palette.text.secondary, 24 | height: '100%' 25 | }, 26 | suggestions: { 27 | padding: 5, 28 | margin: 15, 29 | textAlign: 'left', 30 | color: theme.palette.text.secondary, 31 | flex: 1, 32 | height: 40 33 | }, 34 | menuButton: { 35 | marginLeft: -12, 36 | marginRight: 20 37 | } 38 | }) 39 | 40 | class Index extends React.Component { 41 | state = { 42 | open: false, 43 | spacing: '16', 44 | auth: true, 45 | anchorEl: null, 46 | direction: 'row', 47 | justify: 'center', 48 | alignItems: 'stretch' 49 | }; 50 | 51 | handleChange = (event, checked) => { 52 | this.setState({ auth: checked }) 53 | }; 54 | 55 | handleMenu = event => { 56 | this.setState({ anchorEl: event.currentTarget }) 57 | }; 58 | 59 | handleClose = () => { 60 | this.setState({ anchorEl: null }) 61 | }; 62 | 63 | render () { 64 | const { classes } = this.props 65 | const { auth, anchorEl } = this.state 66 | const open = Boolean(anchorEl) 67 | const { alignItems, direction, justify } = this.state 68 | return ( 69 | 70 | 71 | 72 | 73 | 74 | ) 75 | } 76 | } 77 | 78 | Index.propTypes = { 79 | classes: PropTypes.object.isRequired 80 | } 81 | 82 | export default withRoot(withStyles(styles)(Index)) 83 | -------------------------------------------------------------------------------- /ui-src/src/components/lists/channel-list.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { storiesOf } from '@storybook/react' 3 | // import { action } from '@storybook/addon-actions' 4 | import { specs, describe, it } from 'storybook-addon-specifications' 5 | import { configure, mount } from 'enzyme' 6 | import Adapter from 'enzyme-adapter-react-16' 7 | import ChannelList from './channel-list' 8 | import expect from 'expect' 9 | import { MemoryRouter } from 'react-router'; 10 | 11 | configure({ adapter: new Adapter() }) 12 | 13 | storiesOf('Channel List', module) 14 | .addDecorator(story => ( 15 | {story()} 16 | )) 17 | .add('Display a list of different types of messages', () => { 18 | const channels = [ 19 | {group: 'Private', 20 | channels: [ 21 | { 22 | title: 'DevLife Do-op', 23 | alerts: 5 24 | }, 25 | { 26 | title: 'Documentation', 27 | alerts: 0 28 | }, 29 | { 30 | title: 'Governance', 31 | alerts: 1 32 | } 33 | ]}, 34 | {group: 'Public', 35 | channels: [ 36 | { 37 | title: 'App:HoloChat', 38 | alerts: 5 39 | }, 40 | { 41 | title: 'App:Clutter', 42 | alerts: 0 43 | }, 44 | { 45 | title: 'HC Core', 46 | alerts: 1 47 | } 48 | ]}, 49 | {group: 'Direct Message', 50 | channels: [ 51 | { 52 | title: '@artbrock', 53 | alerts: 5, 54 | status: 'online' 55 | }, 56 | { 57 | title: '@jonathanhaber', 58 | alerts: 0, 59 | status: 'away' 60 | }, 61 | { 62 | title: '@lucksus', 63 | alerts: 1, 64 | status: 'offline' 65 | } 66 | ]} 67 | ] 68 | return getChannelList(channels) 69 | }) 70 | function getChannelList (channels) { 71 | return ( 72 | 73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /ui-src/src/components/profile.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { withStyles } from 'material-ui/styles' 3 | import withRoot from '../withRoot' 4 | import PropTypes from 'prop-types' 5 | import TextField from 'material-ui/TextField' 6 | import Button from 'material-ui/Button' 7 | 8 | const styles = theme => ({ 9 | container: { 10 | display: 'flex', 11 | flexWrap: 'wrap' 12 | }, 13 | button: { 14 | margin: theme.spacing.unit 15 | }, 16 | textField: { 17 | marginLeft: theme.spacing.unit, 18 | marginRight: theme.spacing.unit, 19 | width: '100%' 20 | } 21 | }) 22 | 23 | class Profile extends Component { 24 | render () { 25 | const { classes } = this.props 26 | return ( 27 |
28 | 36 | 44 | 52 | 60 | 63 | 64 | ) 65 | } 66 | } 67 | 68 | Profile.propTypes = { 69 | classes: PropTypes.object.isRequired 70 | } 71 | 72 | export default withRoot(withStyles(styles)(Profile)) 73 | -------------------------------------------------------------------------------- /test/backnforth/person2.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests": [ 3 | { 4 | "Time": 500, 5 | "Convey":"0. Register you user Chuck", 6 | "Zome":"profiles", 7 | "FnName":"register", 8 | "Input":{"username":"PinkHair", 9 | "firstName":"Sakura", 10 | "lastName":"Haruno", 11 | "email":"Sakura@Haruno.com", 12 | "avatar":""}, 13 | "Output":"%h6%", 14 | "Err":"" 15 | }, 16 | { 17 | "Time": 900, 18 | "Convey":"1. newRoom should return the room's hash", 19 | "Zome":"rooms", 20 | "FnName":"newRoom", 21 | "Input":{"name":"Medical", 22 | "access":"public"}, 23 | "Output":"%h5%", 24 | "Err":"" 25 | }, 26 | { 27 | "Time": 1500, 28 | "Convey":"2. Adding message to the room ", 29 | "Zome":"messages", 30 | "FnName":"newMessage", 31 | "Input":{"access":"public", 32 | "message":{ 33 | "author":"%agent%", 34 | "content":{ 35 | "text":"Hi I am Sakura" 36 | }, 37 | "room_name":"Medical"} 38 | }, 39 | "Output":"%h1%", 40 | "Err":"" 41 | }, 42 | { 43 | "Time": 1700, 44 | "Convey":"3. Adding message to the room created by the other person", 45 | "Zome":"messages", 46 | "FnName":"newMessage", 47 | "Input":{"access":"public", 48 | "message":{ 49 | "author":"%agent%", 50 | "content":{ 51 | "text":"Hi Kakashi, I am Sakura" 52 | }, 53 | "room_name":"Hatake_clan"} 54 | }, 55 | "Output":"%h1%", 56 | "Err":"" 57 | }, 58 | { 59 | "Time": 3000, 60 | "Convey":"3. Get all the messages of the room by using the hash of the room as ID ", 61 | "Zome":"messages", 62 | "FnName":"getMessages", 63 | "Input":{"room_name":"Medical", 64 | "access":"public"}, 65 | "Regexp":"{\"Entry\":{\"author\":\"%agent%\",\"content\":{\"text\":\"Hi I am Sakura\"},\"room_name\":\"Medical\",\"timestamp\":\"[^\"]*\"},\"Hash\":\"[^\"]*\"}", 66 | "Err":"" 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /test/backnforth/person1.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests": [ 3 | { 4 | "Time": 500, 5 | "Convey":"0. Register you user Chuck", 6 | "Zome":"profiles", 7 | "FnName":"register", 8 | "Input":{"username":"WhiteFang", 9 | "firstName":"Kakashi", 10 | "lastName":"Hatake", 11 | "email":"Kakashi@Hatake.com", 12 | "avatar":""}, 13 | "Output":"%h6%", 14 | "Err":"" 15 | }, 16 | { 17 | "Time": 1000, 18 | "Convey":"1. newRoom should return the room's hash", 19 | "Zome":"rooms", 20 | "FnName":"newRoom", 21 | "Input":{"name":"Hatake_clan", 22 | "access":"public"}, 23 | "Output":"%h3%", 24 | "Err":"" 25 | }, 26 | { 27 | "Time": 1500, 28 | "Convey":"2. Adding message to the room", 29 | "Zome":"messages", 30 | "FnName":"newMessage", 31 | "Input":{"access":"public", 32 | "message":{ 33 | "author":"%agent%", 34 | "content":{ 35 | "text":"Hi I am Kakashi" 36 | }, 37 | "room_name":"Hatake_clan"} 38 | }, 39 | "Output":"%h1%", 40 | "Err":"" 41 | }, 42 | { 43 | "Time": 1700, 44 | "Convey":"3. Adding message to the room created by the other person", 45 | "Zome":"messages", 46 | "FnName":"newMessage", 47 | "Input":{"access":"public", 48 | "message":{ 49 | "author":"%agent%", 50 | "content":{ 51 | "text":"Hi,Sakura, I am Kakashi" 52 | }, 53 | "room_name":"Medical"} 54 | }, 55 | "Output":"%h1%", 56 | "Err":"" 57 | }, 58 | { 59 | "Time": 3000, 60 | "Convey":"3. Get all the messages of the room by using the hash of the room as ID ", 61 | "Zome":"messages", 62 | "FnName":"getMessages", 63 | "Input":{"room_name":"Hatake_clan", 64 | "access":"public"}, 65 | "Regexp":"{\"Entry\":{\"author\":\"%agent%\",\"content\":{\"text\":\"Hi I am Kakashi\"},\"room_name\":\"Hatake_clan\",\"timestamp\":\"[^\"]*\"},\"Hash\":\"[^\"]*\"}", 66 | "Err":"" 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /test/rooms_exceptions.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests":[ 3 | { 4 | "Convey":"0. newRoom should fail when room schema not fulfilled", 5 | "Zome":"rooms", 6 | "FnName":"newRoom", 7 | "Input":{"name":"asdf"}, 8 | "Regexp":"INVALID ACCESS:undefined", 9 | "Err":"" 10 | }, 11 | { 12 | "Convey":"1. getPublicRooms should return an empty list of rooms when there are none", 13 | "Zome":"rooms", 14 | "FnName":"getPublicRooms", 15 | "Input":"", 16 | "Output":[], 17 | "Err":"" 18 | }, 19 | { 20 | "Convey":"2. newRoom should fail when not created by a registered user", 21 | "Zome":"rooms", 22 | "FnName":"newRoom", 23 | "Input":{"name":"general", "access":"public"}, 24 | "Regexp":"{\"errorMessage\":\"Validation Failed\",\"function\":\"commit\",\"name\":\"HolochainError\",\"source\":{\"column\":\"[^\"]*\",\"functionName\":\"newRoom\",\"line\":\"[^\"]*\"}}" 25 | }, 26 | { 27 | "Convey":"3. Register User ", 28 | "Zome":"profiles", 29 | "FnName":"register", 30 | "Input":{"username":"cnorris", "firstName":"Chuck", "lastName":"Norris", "email":"chuck@norris.com"}, 31 | "Output":"%h1%", 32 | "Err":"" 33 | },{ 34 | "Convey":"4. Check if hes registered", 35 | "Zome":"profiles", 36 | "FnName":"isRegistered", 37 | "Input":"", 38 | "Output":true, 39 | "Err":"" 40 | }, 41 | { 42 | "Convey":"5. newRoom should return the room's hash", 43 | "Zome":"rooms", 44 | "FnName":"newRoom", 45 | "Input":{"name":"general", "access":"public"}, 46 | "Output":"%h3%", 47 | "Err":"" 48 | }, 49 | { 50 | "Convey":"6. getPublicRooms should return a list of rooms", 51 | "Zome":"rooms", 52 | "FnName":"getPublicRooms", 53 | "Input":"", 54 | "Output":[{"id":"%r1%","name":"general","access":"public"}], 55 | "Err":"" 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /test/private_backnforth/person2.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests": [ 3 | { 4 | "Time": 500, 5 | "Convey":"0. Register you user Chuck", 6 | "Zome":"profiles", 7 | "FnName":"register", 8 | "Input":{"username":"PinkHair", 9 | "firstName":"Sakura", 10 | "lastName":"Haruno", 11 | "email":"Sakura@Haruno.com", 12 | "avatar":""}, 13 | "Output":"%h6%", 14 | "Err":"" 15 | }, 16 | { 17 | "Time": 900, 18 | "Convey":"1. newRoom should return the room's hash", 19 | "Zome":"rooms", 20 | "FnName":"newRoom", 21 | "Input":{"name":"Medical_private", 22 | "access":"private"}, 23 | "Output":"%h13%", 24 | "Err":"" 25 | }, 26 | { 27 | "Time": 1500, 28 | "Convey":"2. Adding message to the room ", 29 | "Zome":"messages", 30 | "FnName":"newMessage", 31 | "Input":{"access":"private", 32 | "message":{ 33 | "author":"%agent%", 34 | "content":{ 35 | "text":"Hi I am Sakura" 36 | }, 37 | "room_name":"Medical_private"} 38 | }, 39 | "Output":"%h%", 40 | "Err":"" 41 | }, 42 | { 43 | "Time": 3900, 44 | "Convey":"3. Adding message to the room created by the other person", 45 | "Zome":"messages", 46 | "FnName":"newMessage", 47 | "Input":{"access":"private", 48 | "message":{ 49 | "author":"%agent%", 50 | "content":{ 51 | "text":"Hi Kakashi, I am Sakura" 52 | }, 53 | "room_name":"Hatake_clan_private"} 54 | }, 55 | "Output":"", 56 | "Err":{"errorMessage":"Validation Failed","function":"commit","name":"HolochainError","source":{"column":"16","functionName":"postPrivateMessage","line":"55"}} 57 | }, 58 | { 59 | "Time": 4600, 60 | "Convey":"4. Get all the messages of the room by using the hash of the room as ID ", 61 | "Zome":"messages", 62 | "FnName":"getMessages", 63 | "Input":{"room_name":"Medical_private", 64 | "access":"private"}, 65 | "Output":"", 66 | "Regexp":"{\"Entry\":{\"author\":\"%agent%\",\"content\":{\"text\":\"Hi I am Sakura\"},\"room_name\":\"Medical_private\",\"timestamp\":\"[^\"]*\"},\"Hash\":\"[^\"]*\"}", 67 | "Err":"" 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /test/private_backnforth/person1.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests": [ 3 | { 4 | "Time": 200, 5 | "Convey":"0. Register you user Chuck", 6 | "Zome":"profiles", 7 | "FnName":"register", 8 | "Input":{"username":"WhiteFang", 9 | "firstName":"Kakashi", 10 | "lastName":"Hatake", 11 | "email":"Kakashi@Hatake.com", 12 | "avatar":""}, 13 | "Output":"%h6%", 14 | "Err":"" 15 | }, 16 | { 17 | "Time": 1000, 18 | "Convey":"1. newRoom should return the room's hash", 19 | "Zome":"rooms", 20 | "FnName":"newRoom", 21 | "Input":{"name":"Hatake_clan_private", 22 | "access":"private"}, 23 | "Output":"%h7%", 24 | "Err":"" 25 | }, 26 | { 27 | "Time": 1200, 28 | "Convey":"2. Adding message to the room", 29 | "Zome":"messages", 30 | "FnName":"newMessage", 31 | "Input":{"access":"private", 32 | "message":{ 33 | "author":"%agent%", 34 | "content":{ 35 | "text":"Hi I am Kakashi" 36 | }, 37 | "room_name":"Hatake_clan_private"} 38 | }, 39 | "Output":"%h%", 40 | "Err":"" 41 | }, 42 | { 43 | "Time": 4000, 44 | "Convey":"3. Adding message to the room created by the other person", 45 | "Zome":"messages", 46 | "FnName":"newMessage", 47 | "Input":{"access":"private", 48 | "message":{ 49 | "author":"%agent%", 50 | "content":{ 51 | "text":"Hi,Sakura, I am Kakashi" 52 | }, 53 | "room_name":"Medical_private"} 54 | }, 55 | "Output":"", 56 | "Err":{"errorMessage":"Validation Failed","function":"commit","name":"HolochainError","source":{"column":"16","functionName":"postPrivateMessage","line":"55"}} 57 | }, 58 | { 59 | "Time": 4500, 60 | "Convey":"4. Get all the messages of the room by using the hash of the room as ID ", 61 | "Zome":"messages", 62 | "FnName":"getMessages", 63 | "Input":{"room_name":"Hatake_clan_private", 64 | "access":"private"}, 65 | "Output":"", 66 | "Regexp":"{\"Entry\":{\"author\":\"%agent%\",\"content\":{\"text\":\"Hi I am Kakashi\"},\"room_name\":\"Hatake_clan_private\",\"timestamp\":\"[^\"]*\"},\"Hash\":\"[^\"]*\"}", 67 | "Err":"" 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /test/profiles.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests":[ 3 | { 4 | "Convey":"0. Check if registered initially", 5 | "Zome":"profiles", 6 | "FnName":"isRegistered", 7 | "Input":"", 8 | "Output":false, 9 | "Err":"" 10 | }, 11 | { 12 | "Convey":"1. Not able to get Profile eather", 13 | "Zome":"profiles", 14 | "FnName":"getProfile", 15 | "Input":"%agent%", 16 | "Output":false, 17 | "Err":"" 18 | }, 19 | { 20 | "Convey":"2. Register you user Chuck", 21 | "Zome":"profiles", 22 | "FnName":"register", 23 | "Input":{"username":"cnorris", "firstName":"Chuck", "lastName":"Norris", "email":"chuck@norris.com","avatar":""}, 24 | "Output":"%h1%", 25 | "Err":"" 26 | }, 27 | { 28 | "Convey":"3. Check if hes registered", 29 | "Zome":"profiles", 30 | "FnName":"isRegistered", 31 | "Input":"", 32 | "Output":true, 33 | "Err":"" 34 | }, 35 | { 36 | "Convey":"4. Get the profile info", 37 | "Zome":"profiles", 38 | "FnName":"getProfile", 39 | "Input":"%agent%", 40 | "Regexp":"^\\{\"agent_hash\":\"[^\"]*\",\"agent_id\":\"[^\"]*\",\"avatar\":\"\",\"email\":\"chuck@norris.com\",\"firstName\":\"Chuck\",\"lastName\":\"Norris\",\"username\":\"cnorris\"\\}$", 41 | "Err":"" 42 | }, 43 | { 44 | "Convey":"5. Updating the profile", 45 | "Zome":"profiles", 46 | "FnName":"updateProfile", 47 | "Input":{"username":"darK_V", "firstName":"dark", "lastName":"vader", "email":"dark@vader.com","avatar":""}, 48 | "Output":"%h%", 49 | "Err":"" 50 | }, 51 | { 52 | "Convey":"6. Get my agent_id", 53 | "Zome":"profiles", 54 | "FnName":"getMyAgentHash", 55 | "Input":"", 56 | "Output":"%agent%", 57 | "Err":"" 58 | }, 59 | { 60 | "Convey":"7. Get the updated Profile", 61 | "Zome":"profiles", 62 | "FnName":"getProfile", 63 | "Input":"%r1%", 64 | "Regexp":"^\\{\"agent_hash\":\"[^\"]*\",\"agent_id\":\"[^\"]*\",\"avatar\":\"\",\"email\":\"dark@vader.com\",\"firstName\":\"dark\",\"lastName\":\"vader\",\"username\":\"darK_V\"\\}$", 65 | "Err":"" 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /test/messages_exceptions.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests":[ 3 | { 4 | "Convey":"0. Adding New messages without any room", 5 | "Zome":"messages", 6 | "FnName":"newMessage", 7 | "Input":{"access":"public","message":{"author":"%agent%","content":{"text":"Cheers!!"}}}, 8 | "Output":"ERROR: Room undefined doesn't exist", 9 | "Err":"" 10 | }, 11 | { 12 | "Convey":"1. Adding message without creating room", 13 | "Zome":"messages", 14 | "FnName":"newMessage", 15 | "Input":{"access":"public","message":{"author":"%agent%","content":{"text":"Cheers!!"}, "room_name":"Asdf"}}, 16 | "Output":"ERROR: Room Asdf doesn't exist" 17 | }, 18 | { 19 | "Convey":"2. Register User **Needs more testing**", 20 | "Zome":"profiles", 21 | "FnName":"register", 22 | "Input":{"username":"cnorris", "firstName":"Chuck", "lastName":"Norris", "email":"chuck@norris.com"}, 23 | "Output":"%h6%", 24 | "Err":"" 25 | }, 26 | { 27 | "Convey":"3. Get Profile of the user. ", 28 | "Zome":"profiles", 29 | "FnName":"getProfile", 30 | "Input":"%agent%", 31 | "Output":{"agent_hash":"%agent%","agent_id":"%key%","email":"chuck@norris.com","firstName":"Chuck","lastName":"Norris","username":"cnorris"}, 32 | "Err":"" 33 | }, 34 | { 35 | "Convey":"4. Creting new room **Needs More testing**", 36 | "Zome":"rooms", 37 | "FnName":"newRoom", 38 | "Input":{"name":"general", "access":"public"}, 39 | "Output":"%h5%", 40 | "Err":"" 41 | }, 42 | { 43 | "Convey":"5. Adding message to the room using the hash of the of the room ", 44 | "Zome":"messages", 45 | "FnName":"newMessage", 46 | "Input":{"access":"public","message":{"author":"%agent%","content":{"text":"Cheers!!"}, "room_name":"general"}}, 47 | "Output":"%h1%", 48 | "Err":"" 49 | }, 50 | { 51 | "Convey":"6. Get all the messages of the room by using the hash of the room as ID ", 52 | "Zome":"messages", 53 | "FnName":"getMessages", 54 | "Input":{"room_name":"general","access":"public"}, 55 | "Regexp":"{\"Entry\":{\"author\":\"QmNU6Gynfi33qknXvKn19NqLzRHEvb3Wea14rpJc7En1xK\",\"content\":{\"text\":\"Cheers!!\"},\"room_name\":\"general\",\"timestamp\":\"[^\"]*\"},\"Hash\":\"[^\"]*\"}", 56 | "Err":"" 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /test/coustom_room.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests": [{ 3 | "Convey": "0. Create new Room", 4 | "Zome": "coustom_room", 5 | "FnName": "createCoustomRoom", 6 | "Input": [], 7 | "RegExp": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", 8 | "Err": "" 9 | }, 10 | { 11 | "Convey": "1. Get the rooms|chats you are part of (UUID)", 12 | "Zome": "coustom_room", 13 | "FnName": "getMyRooms", 14 | "Input": "", 15 | "Output": "%r1%" 16 | }, 17 | { 18 | "Convey": "2. Get member for the room (UUID)", 19 | "Zome": "coustom_room", 20 | "FnName": "getMembers", 21 | "Input": "%r2%", 22 | "RegExp": "(?s).*" 23 | }, 24 | { 25 | "Convey": "3. Get the room details by UUID", 26 | "Zome": "coustom_room", 27 | "FnName": "getRoomDetails", 28 | "Input": "%r3%", 29 | "RegExp": "(?s).*" 30 | }, 31 | { 32 | "Convey": "4. post message to the room", 33 | "Zome": "coustom_room", 34 | "FnName": "postMessage", 35 | "Input": {"uuid":"%r3%","message":{"content":{"text":"Testing out the First message!!"}}}, 36 | "Output": "%h1%" 37 | }, 38 | { 39 | "Convey": "5. 'REDUNDANTE' Again Get the rooms|chats you are part of (UUID)", 40 | "Zome": "coustom_room", 41 | "FnName": "getMyRooms", 42 | "Input": "", 43 | "RegExp": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" 44 | 45 | }, 46 | { 47 | "Convey": "6. get message of the room", 48 | "Zome": "coustom_room", 49 | "FnName": "getMessages", 50 | "Input": "%r1%", 51 | "RegExp": "[{\"Entry\":{\"author\":\"QmUAfnSCvwhgHhrJ71YswaXgfPcheNicft4BHXPnp1UzGb\",\"content\":{\"text\":\"Testing out the First message!!\"},\"timestamp\":\"[^\"]*\"},\"EntryType\":\"cr_message\",\"Hash\":\"[^\"]*\",\"Source\":\"QmUAfnSCvwhgHhrJ71YswaXgfPcheNicft4BHXPnp1UzGb\"}]" 52 | }, 53 | { 54 | "Convey": "7. update message of the room", 55 | "Zome": "coustom_room", 56 | "FnName": "updateMessage", 57 | "Input": {"old_hash":"%r3%","new_message":{"content":{"text":"Testing out the First message Update !!"}}}, 58 | "RegExp": "%h%" 59 | }, 60 | { 61 | "Convey": "8. 'REDUNDANTE' Again Get the rooms|chats you are part of (UUID)", 62 | "Zome": "coustom_room", 63 | "FnName": "getMyRooms", 64 | "Input": "", 65 | "RegExp": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" 66 | 67 | }, 68 | { 69 | "Convey": "9. get message of the room", 70 | "Zome": "coustom_room", 71 | "FnName": "getMessages", 72 | "Input": "%r1%", 73 | "RegExp": "[{\"Entry\":{\"author\":\"QmUAfnSCvwhgHhrJ71YswaXgfPcheNicft4BHXPnp1UzGb\",\"content\":{\"text\":\"Testing out the First message Update !!\"},\"timestamp\":\"[^\"]*\"},\"EntryType\":\"cr_message\",\"Hash\":\"[^\"]*\",\"Source\":\"QmUAfnSCvwhgHhrJ71YswaXgfPcheNicft4BHXPnp1UzGb\"}]" 74 | } 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /test/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests":[ 3 | { 4 | "Convey":"0. Register User **Needs more testing**", 5 | "Zome":"profiles", 6 | "FnName":"register", 7 | "Input":{"username":"cnorris", "firstName":"Chuck", "lastName":"Norris", "email":"chuck@norris.com"}, 8 | "Output":"%h6%", 9 | "Err":"" 10 | }, 11 | { 12 | "Convey":"1. Get Profile of the user. ", 13 | "Zome":"profiles", 14 | "FnName":"getProfile", 15 | "Input":"%agent%", 16 | "Output":{"agent_hash":"%agent%","agent_id":"%key%","email":"chuck@norris.com","firstName":"Chuck","lastName":"Norris","username":"cnorris"}, 17 | "Err":"" 18 | }, 19 | { 20 | "Convey":"2. Creting new room **Needs More testing** ", 21 | "Zome":"rooms", 22 | "FnName":"newRoom", 23 | "Input":{"name":"general", "access":"public"}, 24 | "Output":"%h5%", 25 | "Err":"" 26 | }, 27 | { 28 | "Convey":"3. Creting new room ", 29 | "Zome":"rooms", 30 | "FnName":"newRoom", 31 | "Input":{"name":"SecondRoom", "access":"public"}, 32 | "Output":"%h3%", 33 | "Err":"" 34 | }, 35 | { 36 | "Convey":"4. Adding message to the room using the hash of the of the room ", 37 | "Zome":"messages", 38 | "FnName":"newMessage", 39 | "Input":{"access":"public","message":{"author":"%agent%","content":{"text":"Cheers!!"}, "room_name":"general"}}, 40 | "Output":"%h1%", 41 | "Err":"" 42 | }, 43 | { 44 | "Convey":"5. Get all the messages of the room by using the hash of the room as name ", 45 | "Zome":"messages", 46 | "FnName":"getMessages", 47 | "Input":{"room_name":"general","access":"public"}, 48 | "Output":"", 49 | "Regexp":"{\"Entry\":{\"author\":\"QmNU6Gynfi33qknXvKn19NqLzRHEvb3Wea14rpJc7En1xK\",\"content\":{\"text\":\"Cheers!!\"},\"room_name\":\"general\",\"timestamp\":\"[^\"]*\"},\"Hash\":\"[^\"]*\"}", 50 | "Err":"" 51 | }, 52 | { 53 | "Convey":"6. Update the messages of the room", 54 | "Zome":"messages", 55 | "FnName":"updateMessage", 56 | "Input":{"new_message":{"author":"%agent%","content":{"text":"Updated Cheers!!"}, "room_name":"general"},"old_Hash":"%r2%"}, 57 | "Output":"%h%", 58 | "Err":"" 59 | }, 60 | { 61 | "Convey":"7. Get all the messages of the room by using the hash of the room as name ", 62 | "Zome":"messages", 63 | "FnName":"getMessages", 64 | "Input":{"room_name":"general","access":"public"}, 65 | "Output":"", 66 | "Regexp":"{\"Entry\":{\"author\":\"QmNU6Gynfi33qknXvKn19NqLzRHEvb3Wea14rpJc7En1xK\",\"content\":{\"text\":\"Updated Cheers!!\"},\"room_name\":\"general\",\"timestamp\":\"[^\"]*\"},\"Hash\":\"[^\"]*\"}", 67 | "Err":"" 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /ui-src/src/components/lists/channel-list.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classNames from 'classnames' 3 | import PropTypes from 'prop-types' 4 | import withRoot from '../../withRoot' 5 | import { withStyles } from 'material-ui/styles' 6 | import List, { ListItem, ListItemText } from 'material-ui/List' 7 | import { Link } from 'react-router-dom' 8 | import Typography from 'material-ui/Typography' 9 | import Badge from 'material-ui/Badge' 10 | 11 | const styles = theme => ({ 12 | listItemMessage: { 13 | position: 'relative' 14 | }, 15 | group: { 16 | 17 | }, 18 | badge: { 19 | 20 | } 21 | }) 22 | 23 | function BadgedLink (props) { 24 | if (props.channel.alerts > 0) { 25 | return ( 26 | 27 | {props.channel.title} 28 | 29 | ) 30 | } else { 31 | return ( 32 | {props.channel.title} 33 | ) 34 | } 35 | } 36 | 37 | class ChannelList extends React.Component { 38 | render () { 39 | const { classes, channels } = this.props 40 | return ( 41 |
42 | 43 | 44 | {channels[0].group} 45 | 46 | 47 | 48 | {channels[0].channels.map((channel, index) => ( 49 | 50 | 51 | 52 | ))} 53 | 54 | 55 | 56 | 57 | 58 | {channels[1].group} 59 | 60 | 61 | 62 | {channels[1].channels.map((channel, index) => ( 63 | 64 | 65 | 66 | ))} 67 | 68 | 69 | 70 | 71 | 72 | {channels[2].group} 73 | 74 | 75 | 76 | {channels[2].channels.map((channel, index) => ( 77 | 78 | 79 | 80 | ))} 81 | 82 | 83 | 84 |
85 | ) 86 | } 87 | } 88 | 89 | ChannelList.propTypes = { 90 | classes: PropTypes.object.isRequired, 91 | channels: PropTypes.object.isRequired 92 | 93 | } 94 | 95 | export default withRoot(withStyles(styles)(ChannelList)) 96 | -------------------------------------------------------------------------------- /ui-src/src/components/lists/message-list.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { storiesOf } from '@storybook/react' 3 | // import { action } from '@storybook/addon-actions' 4 | import { specs, describe, it } from 'storybook-addon-specifications' 5 | import { configure, mount } from 'enzyme' 6 | import Adapter from 'enzyme-adapter-react-16' 7 | import MessageList from './message-list' 8 | import expect from 'expect' 9 | 10 | configure({ adapter: new Adapter() }) 11 | 12 | storiesOf('Message List', module) 13 | .add('Display a list of different types of messages', () => { 14 | const messages = [ 15 | {date: 'January 25th 2018', 16 | messages: [ 17 | { 18 | type: 'Message', 19 | author: 'Art Brock', 20 | avatar: 'art-brock-avatar.png', 21 | time: '5.04pm', 22 | content: { 23 | text: 'Text message with an imageText message with an imageText message with an imageText message with an image', 24 | image: 'art-brock-avatar.png'}, 25 | replies: [], 26 | idea: false, 27 | up: 7, 28 | down: 0 29 | }, 30 | { 31 | type: 'Message', 32 | author: 'Art Brock', 33 | avatar: 'art-brock-avatar.png', 34 | time: '5.04pm', 35 | content: { 36 | text: 'Text message with no image', 37 | image: ''}, 38 | replies: [], 39 | idea: false, 40 | up: 7, 41 | down: 0 42 | }, 43 | { 44 | type: 'IdeaCard', 45 | author: 'Philip Beadle', 46 | avatar: 'philip-beadle-avatar.png', 47 | time: '7.04pm', 48 | content: {idea: { 49 | title: 'Display both FOLLOWERS and FOLLOWING', 50 | productOwner: 'Art Brock', 51 | avatar: 'art-brock-avatar.png', 52 | date: 'January 30, 2018 - Lead Time 3 weeks', 53 | description: 'When clicking on Follow Someone, let\'s include a list of who is following you, not just who you are following. Either that or we add number/summary displays: Mews, Following, Followers under profile pic.', 54 | up: 37, 55 | down: 1 56 | } 57 | }, 58 | replies: [], 59 | idea: false, 60 | up: 9, 61 | down: 0 62 | } 63 | ]}, 64 | {date: 'January 26th 2018', 65 | messages: [ 66 | { 67 | type: 'Message', 68 | author: 'Art Brock', 69 | avatar: 'art-brock-avatar.png', 70 | time: '5.04pm', 71 | content: { 72 | text: 'Text message with an imageText message with an imageText message with an imageText message with an image', 73 | image: 'art-brock-avatar.png'}, 74 | replies: [], 75 | idea: false, 76 | up: 7, 77 | down: 0 78 | }, 79 | { 80 | type: 'Message', 81 | author: 'Art Brock', 82 | avatar: 'art-brock-avatar.png', 83 | time: '5.04pm', 84 | content: { 85 | text: 'Text message with no image', 86 | image: ''}, 87 | replies: [], 88 | idea: false, 89 | up: 7, 90 | down: 0 91 | } 92 | ]} 93 | ] 94 | return getMessageList(messages) 95 | }) 96 | function getMessageList (messages) { 97 | return ( 98 | 99 | ) 100 | } 101 | -------------------------------------------------------------------------------- /ui-src/src/components/message.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { storiesOf } from '@storybook/react' 3 | import { action } from '@storybook/addon-actions' 4 | import { specs, describe, it } from 'storybook-addon-specifications' 5 | import { configure, mount } from 'enzyme' 6 | import Adapter from 'enzyme-adapter-react-16' 7 | import Message from './message' 8 | import expect from 'expect' 9 | 10 | configure({ adapter: new Adapter() }) 11 | 12 | storiesOf('Message', module) 13 | .add('Display a message with no replies', () => { 14 | const message = { 15 | type: 'Message', 16 | author: 'Philip Beadle', 17 | avatar: 'philip-beadle-avatar.png', 18 | time: '7.04pm', 19 | content: {text: 'New release of Clutter using React as the UI - https://github.com/Holochain/clutter/releases'}, 20 | replies: [], 21 | up: 9, 22 | down: 0 23 | } 24 | 25 | return getMessage(message) 26 | }) 27 | .add('Display a message with 2 replies', () => { 28 | const message = { 29 | type: 'Message', 30 | author: 'Art Brock', 31 | avatar: 'art-brock-avatar.png', 32 | time: '5.04pm', 33 | content: {text: 'So, we\'re establishing two-way DHT links for following/followers... I think we should make them more visible. How about we put some counts below the profile pic for Mews / Following / Following ?'}, 34 | replies: [ 35 | { 36 | type: 'Message', 37 | author: 'Philip Beadle', 38 | avatar: 'philip-beadle-avatar.png', 39 | time: '7.04pm', 40 | content: {text: 'I was thinking the same thing. Since I saw @connorturland\'s new way of selecting someone to follow I thought we should show Followers too.'}, 41 | up: 9, 42 | down: 0 43 | }, 44 | { 45 | type: 'Message', 46 | author: 'Philip Beadle', 47 | avatar: 'philip-beadle-avatar.png', 48 | time: '7.04pm', 49 | content: {text: 'And now that I\'ve drawn a simple update to the page we should probably add in the ability to Block people as well'}, 50 | up: 9, 51 | down: 0 52 | } 53 | ], 54 | up: 7, 55 | down: 0 56 | } 57 | return getMessage(message) 58 | }) 59 | .add('Display a message with 2 replies and has enough interest to be an idea', () => { 60 | const message = { 61 | type: 'Message', 62 | author: 'Art Brock', 63 | avatar: 'art-brock-avatar.png', 64 | time: '5.04pm', 65 | content: {text: 'So, we\'re establishing two-way DHT links for following/followers... I think we should make them more visible. How about we put some counts below the profile pic for Mews / Following / Following ?'}, 66 | replies: [ 67 | { 68 | type: 'Message', 69 | author: 'Philip Beadle', 70 | avatar: 'philip-beadle-avatar.png', 71 | time: '7.04pm', 72 | content: {text: 'I was thinking the same thing. Since I saw @connorturland\'s new way of selecting someone to follow I thought we should show Followers too.'}, 73 | up: 9, 74 | down: 0 75 | }, 76 | { 77 | type: 'Message', 78 | author: 'Philip Beadle', 79 | avatar: 'philip-beadle-avatar.png', 80 | time: '7.04pm', 81 | content: {text: 'And now that I\'ve drawn a simple update to the page we should probably add in the ability to Block people as well'}, 82 | up: 9, 83 | down: 0 84 | } 85 | ], 86 | idea: true, 87 | up: 7, 88 | down: 0 89 | } 90 | return getMessage(message) 91 | }) 92 | function getMessage (message) { 93 | return ( 94 | 95 | ) 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # holochat - [![Deprecation](https://img.shields.io/badge/Status-deprecated-red.svg) in favor of holochain-basic-chat](https://github.com/holochain/holochain-basic-chat) 2 | 3 | This chat app was developed for the proto version of holochain and is kept for archival purposes only 4 | 5 | [![Build Status](https://travis-ci.org/Holochain/holochat.svg?branch=master)](https://travis-ci.org/Holochain/holochat) 6 | [![Code Status](https://img.shields.io/badge/Code-Pre--Alpha-orange.svg)](https://github.com/Holochain/holochat#feature-roadmap-and-current-progress) 7 | [![In Progress](https://img.shields.io/waffle/label/Holochain/holochat/in%20progress.svg)](http://waffle.io/Holochain/holochat) 8 | [![Gitter](https://badges.gitter.im/metacurrency/holochain.svg)](https://gitter.im/metacurrency/holochain?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge) 9 | [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0) 10 | 11 | ***Multi-room P2P chat on holochain** 12 | 13 | **[Code Status:](https://github.com/metacurrency/holochain/milestones?direction=asc&sort=completeness&state=all)** Pre-alpha. Not for production use. This application has not been audited for any security validation. 14 | 15 | ## Docker 16 | 17 | ``` 18 | TARGETDIR=$(pwd) docker-compose up 19 | 20 | ``` 21 | Now you can open browsers to 22 | ``` 23 | http://localhost:3142 - Bootstrap 24 | http://localhost:3141 - Holochat 25 | http://localhost:4141 - Holochat 26 | http://localhost:5141 - Holochat 27 | ``` 28 | 29 | ## Installation native 30 | 31 | Prerequiste: [Install holochain](https://github.com/metacurrency/holochain/#installation) on your machine. 32 | You can install holochat very simply with this: 33 | 34 | ``` shell 35 | hcdev init -cloneExample=holochat 36 | 37 | ``` 38 | 39 | ## Usage 40 | 41 | To do a test run of holochat simply type 42 | 43 | ``` shell 44 | cd holochat 45 | hcdev web 46 | ``` 47 | you should see something like: 48 | 49 | ``` shell 50 | Copying chain to: /home/bootstrap/.holochaindev 51 | ... 52 | Serving holochain with DNA hash:QmdFv5XcG6YZgMYQ9hPJfn6xkhMhDK99rjiHJHH9zorUad on port:4141 53 | ``` 54 | Then simply point your browser to http://localhost:4141 access the holochat UI. 55 | 56 | ### Tests 57 | To run all the stand alone tests: 58 | 59 | ``` shell 60 | hcdev test 61 | ``` 62 | 63 | Currently there is one scenario test: 64 | 65 | #### backnforth 66 | ``` shell 67 | hcdev -mdns=true scenario backnforth 68 | ``` 69 | This test spins up two nodes `person1` and `person` and tests that they can send messages back and forth 70 | 71 | ``` shell 72 | hcdev -mdns=true -debug scenario backnforth 73 | ``` 74 | 75 | ### Run e2e tests 76 | ``` 77 | cd ui-automation 78 | yarn test 79 | ``` 80 | 81 | ## Feature Roadmap and Current Progress 82 | 83 | 84 | ## Contribute 85 | We welcome pull requests and issue tickets. Find us on [gitter](https://gitter.im/metacurrency/holochain) to chat. 86 | 87 | Contributors to this project are expected to follow our [development protocols & practices](https://github.com/metacurrency/holochain/wiki/Development-Protocols). 88 | 89 | ## License 90 | [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0) 91 | 92 | Copyright (C) 2017, The MetaCurrency Project (Eric Harris-Braun, Arthur Brock, et. al.) 93 | 94 | This program is free software: you can redistribute it and/or modify it under the terms of the license provided in the LICENSE file (GPLv3). This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 95 | 96 | **Note:** We are considering other 'looser' licensing options (like MIT license) but at this stage are using GPL while we're getting the matter sorted out. 97 | -------------------------------------------------------------------------------- /test/rooms_private_messaging.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests":[ 3 | { 4 | "Convey":"0. Register User ", 5 | "Zome":"profiles", 6 | "FnName":"register", 7 | "Input":{"username":"cnorris", "firstName":"Chuck", "lastName":"Norris", "email":"chuck@norris.com"}, 8 | "Output":"%h6%", 9 | "Err":"" 10 | }, 11 | { 12 | "Convey":"1. PRIVATE newRoom should return the room's hash", 13 | "Zome":"rooms", 14 | "FnName":"newRoom", 15 | "Input":{"name":"general", "access":"private"}, 16 | "Output":"%h13%", 17 | "Err":"" 18 | }, 19 | { 20 | "Convey":"2. Get the added members to the private room ", 21 | "Zome":"membership", 22 | "FnName":"getMembers", 23 | "Input":{"room_name":"general"}, 24 | "Output":"%agent%", 25 | "Err":"" 26 | }, 27 | { 28 | "Convey":"3. Get my Private Rooms", 29 | "Zome":"rooms", 30 | "FnName":"getMyPrivateRooms", 31 | "Input":"", 32 | "Output":"general", 33 | "Err":"" 34 | }, 35 | { 36 | "Convey":"4. Adding message to the room thats not created ", 37 | "Zome":"messages", 38 | "FnName":"newMessage", 39 | "Input":{"access":"public","message":{"author":"%agent%","content":{"text":"Cheers!!"}, "room_name":"general"}}, 40 | "Output":"ERROR: Room general doesn't exist", 41 | "Err":"" 42 | }, 43 | { 44 | "Convey":"5. Adding message to the room with wrong access ", 45 | "Zome":"messages", 46 | "FnName":"newMessage", 47 | "Input":{"access":"WrongAccess","message":{"author":"%agent%","content":{"text":"Cheers!!"}, "room_name":"general"}}, 48 | "Output":"ERROR Invalid Access: WrongAccess", 49 | "Err":"" 50 | }, 51 | { 52 | "Convey":"6. Adding message to the room thats not created ", 53 | "Zome":"messages", 54 | "FnName":"newMessage", 55 | "Input":{"access":"private","message":{"author":"%agent%","content":{"text":"Cheers!!"}, "room_name":"general"}}, 56 | "Output":"%h1%", 57 | "Err":"" 58 | }, 59 | { 60 | "Convey":"7. if private room uses access : public returns nothing", 61 | "Zome":"messages", 62 | "FnName":"getMessages", 63 | "Input":{"room_name":"general","access":"public"}, 64 | "Output":[], 65 | "Err":"" 66 | }, 67 | { 68 | "Convey":"8. get Private room messages", 69 | "Zome":"messages", 70 | "FnName":"getMessages", 71 | "Input":{"room_name":"general","access":"private"}, 72 | "Regexp":"{\"Entry\":{\"author\":\"%agent%\",\"content\":{\"text\":\"Cheers!!\"},\"room_name\":\"general\",\"timestamp\":\"[^\"]*\"},\"Hash\":\"[^\"]*\"}", 73 | "Err":"" 74 | }, 75 | { 76 | "Convey":"9. Update the messages of the room", 77 | "Zome":"messages", 78 | "FnName":"updateMessage", 79 | "Input":{"new_message":{"author":"%agent%","content":{"text":"Updated Cheers!!"}, "room_name":"general"},"old_Hash":"%r3%"}, 80 | "Output":"%h%", 81 | "Err":"" 82 | }, 83 | { 84 | "Convey":"10. get updated Private room messages", 85 | "Zome":"messages", 86 | "FnName":"getMessages", 87 | "Input":{"room_name":"general","access":"private"}, 88 | "Regexp":"{\"Entry\":{\"author\":\"%agent%\",\"content\":{\"text\":\"Updated Cheers!!\"},\"room_name\":\"general\",\"timestamp\":\"[^\"]*\"},\"Hash\":\"[^\"]*\"}", 89 | "Err":"" 90 | } 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /dna/profiles/profiles.js: -------------------------------------------------------------------------------- 1 | //Register users 2 | //@param x={} 3 | function register(x) { 4 | x.agent_id = App.Key.Hash 5 | x.agent_hash=App.Agent.Hash 6 | var key = commit("profile", x); 7 | var reg=commit("registration_link", {Links:[{Base:anchor("Profiles",""),Link:key,Tag:"registered_users"}]}); 8 | return key; 9 | } 10 | 11 | // Find out if users is registered 12 | function isRegistered() { 13 | var registered_users = getLinks(anchor("Profiles",""), 'registered_users',{Load:true}) 14 | debug("Registered users are: "+JSON.stringify(registered_users)); 15 | if( registered_users instanceof Error) return false; 16 | for(var i=0; i < registered_users.length; i++) { 17 | var profile = registered_users[i].Entry 18 | debug("Registered user "+i+" is " + profile.username) 19 | if( profile.agent_id == App.Key.Hash) return true; 20 | } 21 | return false; 22 | } 23 | 24 | //Returns your agent Hash 25 | function getMyAgentHash(){ 26 | debug("App.Agent.Hash : "+App.Agent.Hash) 27 | return App.Agent.Hash; 28 | } 29 | 30 | // Get profile information for a user 31 | function getProfile(agent_hash) { 32 | var registered_users = getLinks(anchor("Profiles",""), "registered_users",{Load:true}); 33 | debug("registration entry:"+JSON.stringify(registered_users)); 34 | if( registered_users instanceof Error ) return false 35 | for(var i=0; i < registered_users.length; i++) { 36 | var profile = registered_users[i].Entry 37 | debug("Registered user "+i+" is " + profile.username) 38 | if( profile.agent_hash == agent_hash) return profile; 39 | } 40 | return false; 41 | } 42 | 43 | // Update profile information for an agent_id 44 | function updateProfile(x) { 45 | x.agent_id = App.Key.Hash 46 | x.agent_hash=App.Agent.Hash 47 | var oldHash = makeHash("profile",getProfile(x.agent_hash)); 48 | if(oldHash==false){ 49 | return "NotRegistered"; 50 | } 51 | var key = update("profile", x, oldHash); 52 | return key; 53 | } 54 | 55 | /*---------- Anchor API ----------*/ 56 | 57 | function anchor(anchorType, anchorText) { 58 | return call('anchors', 'anchor', { 59 | anchorType: anchorType, 60 | anchorText: anchorText 61 | }).replace(/"/g, ''); 62 | } 63 | 64 | function anchorExists(anchorType, anchorText) { 65 | return call('anchors', 'exists', { 66 | anchorType: anchorType, 67 | anchorText: anchorText 68 | }); 69 | } 70 | 71 | 72 | /*----------- Validation Functions---------------*/ 73 | 74 | function isSourcesOwnProfile(entry, sources) { 75 | return sources[0] == entry.agent_id; 76 | } 77 | 78 | function isRegistrationOnDNA(registration_entry) { 79 | debug("registration entry:"+JSON.stringify(registration_entry)); 80 | var links = registration_entry.Links; 81 | for(var i=0; i < links.length; i++) { 82 | var l = links[i] 83 | debug("link: "+JSON.stringify(l)) 84 | var ActualBase=anchor("Profiles",""); 85 | if (l.Base != ActualBase) { 86 | debug("validation failed, expected reg base to be: "+ActualBase+" but was: "+l.Base) 87 | return false; 88 | } 89 | } 90 | return true; 91 | } 92 | 93 | function genesis() { 94 | return true; 95 | } 96 | 97 | function validatePut(entry_type,entry,header,pkg,sources) { 98 | return validateCommit(entry_type,entry,header,pkg,sources) 99 | } 100 | function validateCommit(entry_type,entry,header,pkg,sources) { 101 | // registration_link all must happen on the DNA 102 | if (entry_type == "registration_link") { 103 | return isRegistrationOnDNA(entry) 104 | } 105 | 106 | // nobody can add somebody elses profile 107 | return isSourcesOwnProfile(entry, sources); 108 | } 109 | 110 | function validateLink(linkingEntryType,baseHash,linkHash,pkg,sources){ 111 | // registration_link all must happen on the DNA 112 | if (linkingEntryType == "registration_link") { 113 | return baseHash == anchor("Profiles","") 114 | } 115 | 116 | return true 117 | } 118 | function validateMod(entry_type,hash,newHash,pkg,sources) {return true;} 119 | function validateDel(entry_type,hash,pkg,sources) {return true;} 120 | function validatePutPkg(entry_type) {return null} 121 | function validateModPkg(entry_type) { return null} 122 | function validateDelPkg(entry_type) { return null} 123 | function validateLinkPkg(entry_type) { return null} 124 | -------------------------------------------------------------------------------- /docs/post_functions.md: -------------------------------------------------------------------------------- 1 | # Post Function for UI of the HoloChat 2 | 3 | ## ZomeName: profiles 4 | 5 | ### register(User_details) 6 | **Details: ** Call to register a user 7 | **Input:** 8 | User_details: 9 | > {{ 10 | "agent_id": {"type": "string"}, 11 | "agent_hash": {"type": "string"}, 12 | "username": {"type": "string"}, 13 | "firstName": {"type": "string"}, 14 | "lastName": {"type": "string"}, 15 | "email": {"type": "string"}, 16 | "avatar": {"type": "string"} 17 | }, 18 | "required": ["agent_id", "username", "firstName", "lastName", "email"] 19 | } 20 | 21 | **Returns:** Hash of the profile 22 | 23 | 24 | ### isRegistered() 25 | **Detail**: Check if the user is registered 26 | **Input :** None 27 | **Return**: true - > if User is register | false - > if User is not-register 28 | 29 | ### getMyAgentHash() 30 | **Details**: Get App.Agent.Hash of the User 31 | **Input :** None 32 | **Return**: Agent_Hash 33 | 34 | 35 | ### getProfile(agent_hash) 36 | **Detail**: Get the users profile details 37 | **Input :** App.Agent.Hash 38 | **Return**: 39 | > {{ 40 | "agent_id": {"type": "string"}, 41 | "agent_hash": {"type": "string"}, 42 | "username": {"type": "string"}, 43 | "firstName": {"type": "string"}, 44 | "lastName": {"type": "string"}, 45 | "email": {"type": "string"}, 46 | "avatar": {"type": "string"} 47 | }, 48 | "required": ["agent_id", "username", "firstName", "lastName", "email"] 49 | } 50 | 51 | 52 | ### updateProfile(user_details) 53 | **Detail**: If the user needs to update the profile details 54 | **Input:** 55 | User_details: 56 | > {{ 57 | "agent_id": {"type": "string"}, 58 | "agent_hash": {"type": "string"}, 59 | "username": {"type": "string"}, 60 | "firstName": {"type": "string"}, 61 | "lastName": {"type": "string"}, 62 | "email": {"type": "string"}, 63 | "avatar": {"type": "string"} 64 | }, 65 | "required": ["agent_id", "username", "firstName", "lastName", "email"] 66 | } 67 | 68 | **Return**: hash -> if succesfully updated 69 | "NotRegistered" -> if the user is not registered 70 | 71 | 72 | ## ZomeName: rooms 73 | 74 | ### getPublicRooms() 75 | **Details**: Returns all the rooms on the App 76 | **Input :** None 77 | **Returns**: 78 | > {"id":"Hash of the Room", 79 | "name":"Name of the Room", 80 | "purpose":"The Purpose of the Room"} 81 | 82 | ### newRoom(room) 83 | **Details**: Call to create new Rooms (public|private) 84 | **Input :** 85 | > {"name":"Name of the Room", 86 | "access":"public|private"} 87 | 88 | **Returns**: Hash of the room that can be used as an ID 89 | 90 | ### getRoomAdmin() 91 | **Details:** Gets you the admin of the room 92 | **Input:** 93 | >{room_name:""} 94 | 95 | **Returns:** Hash of the Admin Agent 96 | 97 | 98 | ### getMyPrivateRooms() 99 | **Details:** Gets the private rooms you a member of, that is stored locally. 100 | **Input:** None 101 | 102 | **Returns:** names of the private rooms that the user is a member of 103 | 104 | 105 | 106 | ## ZomeName: messages 107 | 108 | ### newMessage() 109 | **Details:** used to post messages (Note: timestamp is applied from the back end) 110 | **Input:** 111 | message.json: {"access":"public | private", 112 | "message":{{"author":{"type": "string"}, 113 | "content": { 114 | "text":{"type": "string"}, 115 | "mediaLink":{"type": "string"} 116 | }, 117 | "timestamp": {"type": "string"}, 118 | "room_name": {"type": "string"} 119 | }, 120 | "required": ["author","room_name","content"] 121 | }} 122 | > 123 | 124 | **Returns:** Hash of the entry --> us as ID of the message 125 | 126 | ### getMessage() 127 | **Details:** get messages for a specific Room 128 | **Input:** 129 | > {room_name:"","access":"public|privated"} 130 | 131 | **Returns:** Array of messages in this format [{Entry:{},Hash:""},{Entry:{},Hash:""},...] 132 | 133 | 134 | ### updateMessage() 135 | **Details:** update messages for a specific Room 136 | **Input:** 137 | >{new_message:"",old_Hash:""} 138 | 139 | (Note: the old_Hash is the ID that the messaged used & new Message has the same message.json format as above) 140 | 141 | **Returns:** Hash of the entry --> us as ID of the message 142 | 143 | 144 | ## ZomeName: membership 145 | 146 | ### addMember() 147 | **Details:** add members to a private room 148 | **Input:** 149 | > {room_name:"","agent_key":"%key%","agent_hash":"%agent%"} 150 | 151 | **Returns:** Hash 152 | 153 | ### getMembers() 154 | **Details:** get members of the private room 155 | **Input:** 156 | > {room_name:""} 157 | 158 | **Returns:** List Of members 159 | -------------------------------------------------------------------------------- /dna/membership/membership.js: -------------------------------------------------------------------------------- 1 | // Authorize a new agent_id to participate in this holochain 2 | // agent_id must match the string they use to "hc init" their holochain, and is currently their email by convention 3 | //@param : {room_name:"",agent_hash:"",agent_key:""} 4 | function addMember(x) { 5 | key = commit("membership_link",{Links:[{Base:anchor("Private_Room",x.room_name),Link:x.agent_hash,Tag:"members"}]}); 6 | send(x.agent_key,{"type":"add_private_rooms","room_name":x.room_name}); 7 | return key; 8 | } 9 | 10 | 11 | //@param : {room_name:""} 12 | function getMembers(x){ 13 | if(anchorExists("Private_Room",x.room_name)){ 14 | members = getLinks(anchor("Private_Room",x.room_name), "members",{Load:true}); 15 | var return_members; 16 | var i_Am_A_Member=false; 17 | members.forEach(function (element){ 18 | if(element.Hash==App.Agent.Hash) 19 | i_Am_A_Member=true; 20 | return_members=element.Hash; 21 | }); 22 | if(i_Am_A_Member){ 23 | return return_members; 24 | }else{ 25 | return "ERROR: You are not a Member of "+x.room_name; 26 | } 27 | 28 | }else{ 29 | return "ERROR: invalid PRIVATE Room name "+x.room_name; 30 | } 31 | } 32 | 33 | 34 | function receive(from, msg) { 35 | var type = msg.type; 36 | if (type=="add_private_rooms") { 37 | return addRoomToMembersLocalChain({"room_name":msg.room_name}); 38 | } 39 | return "unknown type" 40 | } 41 | 42 | // TODO: Needs validation - Check if uses is a member of the room 43 | //@param : room_name:" 44 | function addRoomToMembersLocalChain(x){ 45 | debug("Addign Private Room to Local Chain: "+x.room_name) 46 | key=commit("local_private_room",x.room_name); 47 | commit("local_membership_link",{Links:[{Base:App.Agent.Hash,Link:key,Tag:"my_private_rooms"}]}); 48 | return key; 49 | } 50 | 51 | 52 | 53 | /*---------- Anchor API ----------*/ 54 | 55 | function anchor(anchorType, anchorText) { 56 | return call('anchors', 'anchor', { 57 | anchorType: anchorType, 58 | anchorText: anchorText 59 | }).replace(/"/g, ''); 60 | } 61 | 62 | function anchorExists(anchorType, anchorText) { 63 | return call('anchors', 'exists', { 64 | anchorType: anchorType, 65 | anchorText: anchorText 66 | }); 67 | } 68 | 69 | /*----------Validation Functions-----------*/ 70 | 71 | function isRegisteredAdmin(entry_type,entry,header,sources){ 72 | 73 | admins=getLinks(entry.Links[0].Base,"admin",{Load:true}); 74 | for(i=0;i 2 | 3 | Holo Chat 4 | 5 | 6 | 7 | 8 | 9 | dna-to-openapi 10 | 11 | 12 |

13 | I 's Holo-Chat ? 14 | 16 |

17 | 18 | 19 |
20 |
21 |
22 |
23 |
Rooms
24 |
25 |
    26 |
27 | 28 | 29 |
30 |
31 |
32 |
33 |
34 |
Messages
35 |
36 |
    37 |
38 |
39 | 40 | 41 |
42 |
43 |
44 |
45 |
46 |
47 | 48 | 81 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /ui-src/src/layouts/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import Paper from 'material-ui/Paper' 4 | import AppBar from 'material-ui/AppBar' 5 | import Toolbar from 'material-ui/Toolbar' 6 | import IconButton from 'material-ui/IconButton' 7 | import MenuIcon from 'material-ui-icons/Menu' 8 | import Avatar from 'material-ui/Avatar' 9 | import Menu, { MenuItem } from 'material-ui/Menu' 10 | import Typography from 'material-ui/Typography' 11 | import { withStyles } from 'material-ui/styles' 12 | import withRoot from '../withRoot' 13 | import Profile from './profile' 14 | import Messages from './messages' 15 | import { Route, Link } from 'react-router-dom' 16 | import Grid from 'material-ui/Grid' 17 | import TextField from 'material-ui/TextField' 18 | const styles = theme => ({ 19 | root: { 20 | width: '100%', 21 | marginTop: 0, 22 | flexGrow: 1 23 | }, 24 | appBar: { 25 | height: '100%' 26 | }, 27 | flex: { 28 | flex: 1 29 | }, 30 | demo: { 31 | height: 900 32 | }, 33 | paper: { 34 | textAlign: 'left', 35 | color: theme.palette.text.secondary, 36 | height: '100%' 37 | }, 38 | suggestions: { 39 | padding: 8, 40 | textAlign: 'left', 41 | color: theme.palette.text.secondary, 42 | flex: 1, 43 | height: '100%' 44 | }, 45 | search: { 46 | paddingLeft: 8, 47 | paddingRight: 8, 48 | marginRight: 8, 49 | textAlign: 'left', 50 | color: theme.palette.text.secondary 51 | }, 52 | searchField: { 53 | width: '100%', 54 | marginRight: 16 55 | }, 56 | menuButton: { 57 | marginLeft: -12, 58 | marginRight: 20 59 | } 60 | }) 61 | 62 | class Index extends React.Component { 63 | state = { 64 | open: false, 65 | spacing: '16', 66 | auth: true, 67 | anchorEl: null, 68 | direction: 'row', 69 | justify: 'center', 70 | alignItems: 'stretch' 71 | }; 72 | 73 | handleChange = (event, checked) => { 74 | this.setState({ auth: checked }) 75 | }; 76 | 77 | handleMenu = event => { 78 | this.setState({ anchorEl: event.currentTarget }) 79 | }; 80 | 81 | handleClose = () => { 82 | this.setState({ anchorEl: null }) 83 | }; 84 | 85 | render () { 86 | const { classes } = this.props 87 | const { auth, anchorEl } = this.state 88 | const open = Boolean(anchorEl) 89 | const bull = 90 | return ( 91 |
92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Philip Beadle 101 | 102 | {auth && ( 103 |
104 | 105 | 106 | 107 | 108 | Profile 109 | Settings 110 | 111 |
112 | )} 113 |
114 |
115 |
116 | 117 | 118 | 119 | {bull}Your idea 'Add a profile page' was added to the 'New Ideas' list 👏 120 | 121 | 122 | {bull}We recommend you read about how to be a Product Owner 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 |
141 | 142 | 143 | 144 |
145 | ) 146 | } 147 | } 148 | 149 | Index.propTypes = { 150 | classes: PropTypes.object.isRequired 151 | } 152 | 153 | export default withRoot(withStyles(styles)(Index)) 154 | -------------------------------------------------------------------------------- /dna/anchors/anchors.js: -------------------------------------------------------------------------------- 1 | function anchor(anchor) { 2 | var anchorType = {anchorType: anchor.anchorType, anchorText: ''}; 3 | var rootAnchortype = {anchorType: 'anchorTypes', anchorText: ''}; 4 | var anchorHash = makeHash('anchor', anchor); 5 | var anchorGet = get(anchorHash); 6 | //debug('' + App.Agent.String + '->>DHT:Check to see if ' + anchor.anchorText + ' exists'); 7 | // //debug('anchorGet ' + JSON.stringify(anchorGet)); 8 | if(anchorGet === null){ 9 | var anchorType = {anchorType: anchor.anchorType, anchorText: ''}; 10 | var rootAnchortype = {anchorType: 'anchorTypes', anchorText: ''}; 11 | var anchorTypeGet = get(makeHash('anchor', anchorType)); 12 | //debug('anchorTypeGet ' + JSON.stringify(anchorTypeGet)); 13 | //debug('' + App.Agent.String + '-->>DHT:Check to see if ' + anchor.anchorType + ' has been setup'); 14 | if(anchorTypeGet === null){ 15 | var rootAnchorTypeHash = makeHash('anchor', rootAnchortype); 16 | //debug('' + App.Agent.String + '-->>DHT:Check to see if the Root of all anchors has been setup'); 17 | if (get(rootAnchorTypeHash) === null){ 18 | rootAnchorTypeHash = commit('anchor', rootAnchortype); 19 | //debug('' + App.Agent.String + '->>' + App.Agent.String + ':commit Root of all anchors to local chain'); 20 | //debug('' + App.Agent.String + '->>DHT:Publish Root of all anchors'); 21 | // //debug('Root Anchor Type Created: ' + rootAnchorTypeHash) 22 | } 23 | //debug('DHT-->>' + App.Agent.String + ':Return the Root Anchor Type'); 24 | var anchorTypeHash = commit('anchor', anchorType); 25 | //debug('' + App.Agent.String + '->>' + App.Agent.String + ':commit ' + anchor.anchorType + ' to local chain'); 26 | //debug('' + App.Agent.String + '->>DHT:Publish ' + anchor.anchorType + ''); 27 | // //debug('Anchor Type Created: ' + anchorTypeHash) 28 | 29 | commit('anchor_link', { Links:[{Base: rootAnchorTypeHash, Link: anchorTypeHash, Tag: anchorType.anchorType}]}); 30 | //debug('' + App.Agent.String + '->>DHT:Link ' + anchor.anchorType + ' to Root of all anchors'); 31 | 32 | } else { 33 | anchorTypeHash = makeHash('anchor', anchorType); 34 | //debug('DHT-->>' + App.Agent.String + ':Return the anchorType ' + anchor.anchorType + ''); 35 | } 36 | anchorHash = commit('anchor', anchor); 37 | //debug('' + App.Agent.String + '->>' + App.Agent.String + ':commit ' + anchor.anchorText + ' has been setup'); 38 | //debug('' + App.Agent.String + '->>DHT:Publish ' + anchor.anchorText + ''); 39 | // //debug('Anchor Created ' + anchorHash) 40 | commit('anchor_link', { Links:[{Base: anchorTypeHash, Link: anchorHash, Tag: anchor.anchorText}]}); 41 | //debug('' + App.Agent.String + '->>DHT:Link ' + anchor.anchorText + ' to ' + anchorType.anchorType + ''); 42 | } 43 | //debug('DHT-->>' + App.Agent.String + ':Return the anchor ' + anchor.anchorType + ' = ' + anchor.anchorText + ''); 44 | return anchorHash; 45 | } 46 | 47 | function exists(anchor){ 48 | //debug('' + App.Agent.String + '-->>DHT:Check to see if ' + anchor.anchorText + ' exists'); 49 | //debug('does it exist?'); 50 | //debug(get(makeHash('anchor', anchor))); 51 | var key = get(makeHash('anchor', anchor)); 52 | //debug(key); 53 | if(key !== null){ 54 | //debug('DHT-->>' + App.Agent.String + ':' + anchor.anchorText + ' exists'); 55 | return true; 56 | } 57 | //debug('DHT-->>' + App.Agent.String + ':' + anchor.anchorText + ' does not exist'); 58 | return false; 59 | } 60 | 61 | function anchors(type){ 62 | var links = getLinks(makeHash('anchor', {anchorType: type, anchorText: ''}), ''); 63 | // //debug(links) 64 | return links; 65 | } 66 | 67 | function genesis() { 68 | return true; 69 | } 70 | 71 | function validatePut(entry_type,entry,header,pkg,sources) { 72 | // //debug('Anchors validatePut:' + sources) 73 | return validateCommit(entry_type,entry,header,pkg,sources); 74 | } 75 | function validateCommit(entry_type,entry,header,pkg,sources) { 76 | // //debug('Anchors validatePut:' + sources) 77 | if (entry_type == 'anchor') { 78 | return true; 79 | } 80 | if (entry_type == 'anchor_link') { 81 | return true; 82 | } 83 | return false; 84 | } 85 | 86 | 87 | 88 | function validateLink(linkingEntryType,baseHash,linkHash,pkg,sources){ 89 | // //debug('Anchors validateLink:' + sources) 90 | return true; 91 | } 92 | function validateMod(entry_type,hash,newHash,pkg,sources){ 93 | // //debug('Anchors validateMod:' + sources) 94 | return true; 95 | } 96 | function validateDel(entry_type,hash,pkg,sources) { 97 | // //debug('Anchors validateDel:' + sources) 98 | return true; 99 | } 100 | function validatePutPkg(entry_type) { 101 | // //debug('Anchors validatePutPkg') 102 | return null; 103 | } 104 | function validateModPkg(entry_type) { 105 | // //debug('Anchors validateModPkg') 106 | return null; 107 | } 108 | function validateDelPkg(entry_type) { 109 | // //debug('Anchors validateDelPkg') 110 | return null; 111 | } 112 | function validateLinkPkg(entry_type) { 113 | // //debug('Anchors validateLinkPkg') 114 | return null; 115 | } 116 | -------------------------------------------------------------------------------- /ui/holo_chat.js: -------------------------------------------------------------------------------- 1 | var activeRoom; 2 | var active_room_name; 3 | 4 | function getMyProfile() { 5 | $.get("/fn/profiles/getProfile", "", function(profile){ 6 | $("#title-username").text(JSON.parse(profile).firstName) 7 | }); 8 | } 9 | /* 10 | function register() { 11 | $.post("/fn/identity/registerDpkiKeyTo", "", function(arr){ 12 | if(arr=="true"){ 13 | $("#varified").text("Registered") 14 | }else{$("#varified").text("Not Registered")} 15 | }); 16 | } 17 | */ 18 | function getRooms() { 19 | $.get("/fn/rooms/getPublicRooms", "", function(rooms){ 20 | rooms = JSON.parse(rooms) 21 | $("#rooms").empty() 22 | for(i=0;i"+ 26 | "#"+rooms[i].name+ 27 | "" 28 | ) 29 | } 30 | if(activeRoom) { 31 | setActiveRoom() 32 | } 33 | }); 34 | } 35 | 36 | function addRoom() { 37 | var room = { 38 | name: $("#room-name-input").val(), 39 | access: "public" 40 | } 41 | $("#room-name-input").val('') 42 | $.post("/fn/rooms/newRoom", JSON.stringify(room), getRooms) 43 | } 44 | 45 | function selectRoom(event) { 46 | $("#rooms li").removeClass("selected-room") 47 | activeRoom = $(this).data('id') 48 | active_room_name = $(this).data('name') 49 | 50 | setActiveRoom() 51 | } 52 | 53 | function setActiveRoom() { 54 | var roomElement = $("#rooms li[data-id="+activeRoom+"]") 55 | $(roomElement).addClass("selected-room") 56 | $("#messages-header").text("Messages in #"+$(roomElement).data("name")) 57 | getMessages() 58 | } 59 | 60 | function getMessages() { 61 | var hash = {room_name:active_room_name} 62 | console.log("Getting messages for room: "+hash) 63 | $.post("/fn/messages/getMessages", JSON.stringify(hash), function(messages){ 64 | $("#messages").empty() 65 | messages = JSON.parse(messages) 66 | messages = messages.sort(function(a,b){ 67 | timeA = new Date(a.Entry.timestamp) 68 | timeB = new Date(b.Entry.timestamp) 69 | return timeA > timeB 70 | }) 71 | for(var i=0;i"+ 73 | ""+messages[i].Entry.timestamp+""+ 74 | ""+messages[i].Entry.author+""+ 75 | ""+messages[i].Entry.content.text+""+ 76 | "") 77 | } 78 | }); 79 | } 80 | 81 | function sendMessage() { 82 | var text = $("#message-input").val() 83 | var message = { 84 | content: {"text":text}, 85 | room_name: active_room_name 86 | } 87 | 88 | $.post("/fn/messages/newMessage", JSON.stringify(message), function(){ 89 | $("#message-input").val("") 90 | getMessages() 91 | }) 92 | } 93 | 94 | 95 | 96 | function doRegister(){ 97 | var arg = { 98 | username: $("#signupUsername").val(), 99 | firstName: $("#signupFirstname").val(), 100 | lastName: $("#signupLastname").val(), 101 | email: $("#signupEmail").val() 102 | }; 103 | console.log('signup clicked'); 104 | $.post("/fn/profiles/register", JSON.stringify(arg), 105 | function(hash) { 106 | console.log('register: '+hash) 107 | $.post("/fn/profiles/isRegistered", "", 108 | function(registered) { 109 | console.log('registered: '+registered) 110 | if(JSON.parse(registered)) { 111 | getMyProfile() 112 | $('#registerDialog').modal('hide'); 113 | } else { 114 | $('#registerDialog').modal('show'); 115 | } 116 | }); 117 | }, 118 | "json" 119 | ); 120 | } 121 | 122 | /* 123 | //TODO this METHORD will retrive the post it has to be displayed 124 | function getTag (tag) { 125 | $.post("/fn/messages/getPostsByTag",tag,function(arr) { 126 | arr=JSON.parse(arr); 127 | console.log("posts: " + JSON.stringify(arr)); 128 | //TODO Display the posts 129 | 130 | } 131 | ); 132 | } 133 | function openTag(){$('#tagDialog').modal('show');} 134 | 135 | function passTag() { 136 | var hashtag = $("#tagHandle").val(); 137 | getTag(hashtag); 138 | $('#tagDialog').modal('hide'); 139 | } 140 | */ 141 | $(window).ready(function() { 142 | $.post("/fn/profiles/isRegistered", "", 143 | function(registered) { 144 | 145 | if(!JSON.parse(registered)){ 146 | $('#registerDialog').modal('show') 147 | } else { 148 | getMyProfile() 149 | getRooms() 150 | } 151 | 152 | } 153 | ).error(function(response) { 154 | $("#messages").html(response.responseText) 155 | }); 156 | 157 | $("#signupButton").click(doRegister) 158 | $("#room-name-button").click(addRoom) 159 | $("#rooms").on("click", "li", selectRoom) 160 | $("#message-button").click(sendMessage) 161 | $('#tagButton').click(openTag); 162 | $('#submitTag').click(passTag); 163 | $('#register-toKey-button').click(register); 164 | 165 | $("#room-name-input").keyup(function(event){ 166 | if(event.keyCode == 13) $("#room-name-button").click() 167 | }) 168 | 169 | $("#message-input").keyup(function(event){ 170 | if(event.keyCode == 13) $("#message-button").click() 171 | }) 172 | 173 | setInterval(getMessages, 1000) 174 | setInterval(getRooms, 1000) 175 | }); 176 | -------------------------------------------------------------------------------- /ui-src/src/components/message.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { withStyles } from 'material-ui/styles' 3 | import withRoot from '../withRoot' 4 | import PropTypes from 'prop-types' 5 | import Avatar from 'material-ui/Avatar' 6 | import List, { ListItem, ListItemText, ListItemAvatar } from 'material-ui/List' 7 | import Badge from 'material-ui/Badge' 8 | import Collapse from 'material-ui/transitions/Collapse' 9 | import Popover from 'material-ui/Popover' 10 | import Typography from 'material-ui/Typography' 11 | import LightbulbOutline from 'material-ui-icons/LightbulbOutline' 12 | import ThumbUp from 'material-ui-icons/ThumbUp' 13 | import ThumbDown from 'material-ui-icons/ThumbDown' 14 | import IconButton from 'material-ui/IconButton' 15 | import Card, { CardMedia, CardContent } from 'material-ui/Card' 16 | import IdeaCard from './idea-card' 17 | 18 | const styles = theme => ({ 19 | container: { 20 | display: 'flex', 21 | flexWrap: 'wrap' 22 | }, 23 | button: { 24 | minWidth: 25, 25 | width: 25 26 | }, 27 | card: { 28 | minWidth: 200, 29 | maxWidth: 400, 30 | display: 'flex', 31 | marginLeft: 100 32 | }, 33 | media: { 34 | height: 100 35 | }, 36 | reply: { 37 | marginLeft: theme.spacing.unit * 5 38 | }, 39 | textField: { 40 | marginLeft: theme.spacing.unit, 41 | marginRight: theme.spacing.unit, 42 | width: '100%' 43 | }, 44 | popover: { 45 | pointerEvents: 'none' 46 | }, 47 | badge: { 48 | margin: `0 ${theme.spacing.unit * 2}px` 49 | }, 50 | message: { 51 | marginLeft: 19, 52 | marginTop: -8, 53 | 54 | }, 55 | messageText: { 56 | marginLeft: 19, 57 | marginTop: -8, 58 | fontSize: '0.62rem', 59 | margin: 0, 60 | whiteSpace: 'pre-wrap', 61 | width: '100%', 62 | wordBreak: 'break-word', 63 | color: 'rgb(61, 60, 64)', 64 | }, 65 | messageAuthor:{ 66 | fontSize:14, 67 | color:'lightgrey' 68 | }, 69 | messageImage:{ 70 | display: 'inline', 71 | marginLeft: 19, 72 | marginTop: 5, 73 | borderRadius:10 74 | }, 75 | messageNoImage:{ 76 | display: 'none' 77 | }, 78 | votingIcon:{ 79 | color:'blue' 80 | } 81 | }) 82 | 83 | function VoteControls (props) { 84 | // handleThumbsUp = () => { 85 | // console.log('up') 86 | // // this.togglePopover() 87 | // } 88 | // handleThumbsDown = () => { 89 | // console.log('down') 90 | // // this.togglePopover() 91 | // } 92 | if (props.isHovered) { 93 | return ( 94 |
95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 |
106 | ) 107 | } else { 108 | return null 109 | } 110 | } 111 | 112 | function MessageComponent (props) { 113 | switch (props.message.type) { 114 | case 'Message': 115 | return ( 116 |
117 | {props.message.content.text} 118 | 119 |
) 120 | case 'IdeaCard': 121 | return ( 122 |
123 | 124 |
) 125 | default: 126 | return
No message type found
127 | } 128 | } 129 | 130 | class Message extends Component { 131 | constructor (props) { 132 | super(props) 133 | this.state = { 134 | isHovered: false 135 | } 136 | this.onMessageBlur = this.onMessageBlur.bind(this) 137 | this.onMessageHover = this.onMessageHover.bind(this) 138 | } 139 | 140 | onMessageHover (event) { 141 | this.setState({ isHovered: true }) 142 | } 143 | onMessageBlur (event) { 144 | this.setState({ isHovered: false }) 145 | } 146 | 147 | // style: function() { 148 | // if (this.state.isHovered) { 149 | // return { backgroundColor: "red" } 150 | // } else { 151 | // return { backgroundColor: "grey" } 152 | // } 153 | // } 154 | 155 | render () { 156 | const { classes, message } = this.props 157 | 158 | return ( 159 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | {message.replies.map((reply, index) => ( 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | ))} 186 | 187 | 188 | ) 189 | } 190 | } 191 | 192 | Message.propTypes = { 193 | classes: PropTypes.object.isRequired 194 | } 195 | 196 | export default withRoot(withStyles(styles)(Message)) 197 | -------------------------------------------------------------------------------- /ui-src/src/components/idea-card.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import withRoot from '../withRoot'; 4 | import { withStyles } from 'material-ui/styles'; 5 | import Card, { CardHeader, CardActions, CardContent } from 'material-ui/Card'; 6 | import Button from 'material-ui/Button'; 7 | import Typography from 'material-ui/Typography'; 8 | import Avatar from 'material-ui/Avatar'; 9 | import Share from 'material-ui-icons/Share'; 10 | import Badge from 'material-ui/Badge'; 11 | import ThumbUp from 'material-ui-icons/ThumbUp'; 12 | import ThumbDown from 'material-ui-icons/ThumbDown'; 13 | import IconButton from 'material-ui/IconButton'; 14 | import InfoOutline from 'material-ui-icons/InfoOutline'; 15 | import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List'; 16 | import ListSubheader from 'material-ui/List/ListSubheader'; 17 | import Dialog, { 18 | DialogActions, 19 | DialogContent, 20 | DialogContentText, 21 | DialogTitle, 22 | } from 'material-ui/Dialog'; 23 | import Message from '../components/message'; 24 | 25 | const styles = theme => ({ 26 | card: { 27 | display: 'block', 28 | width: 362, 29 | minWidth: 275, 30 | }, 31 | button: { 32 | minWidth: 25, 33 | width: 25, 34 | }, 35 | bullet: { 36 | display: 'inline-block', 37 | margin: '0 2px', 38 | transform: 'scale(0.8)', 39 | }, 40 | title: { 41 | marginBottom: 16, 42 | fontSize: 14, 43 | color: theme.palette.text.secondary, 44 | }, 45 | pos: { 46 | marginBottom: 12, 47 | color: theme.palette.text.secondary, 48 | }, 49 | badge: { 50 | margin: `0 ${theme.spacing.unit * 2}px`, 51 | }, 52 | }); 53 | 54 | const message = { 55 | author: 'Art Brock', 56 | avatar: 'art-brock-avatar.png', 57 | time: '5.04pm', 58 | content: { 59 | text: 60 | "So, we're establishing two-way DHT links for following/followers... I think we should make them more visible. How about we put some counts below the profile pic for Mews / Following / Following ?", 61 | }, 62 | idea: true, 63 | up: 7, 64 | down: 0, 65 | replies: [ 66 | { 67 | author: 'Philip Beadle', 68 | avatar: 'philip-beadle-avatar.png', 69 | time: '7.04pm', 70 | text: 71 | "I was thinking the same thing. Since I saw @connorturland's new way of selecting someone to follow I thought we should show Followers too.", 72 | image: 'followers-mockup.png', 73 | up: 9, 74 | down: 0, 75 | }, 76 | { 77 | author: 'Philip Beadle', 78 | avatar: 'philip-beadle-avatar.png', 79 | time: '7.04pm', 80 | text: 81 | "And now that I've drawn a simple update to the page we should probably add in the ability to Block people as well", 82 | image: '', 83 | up: 9, 84 | down: 0, 85 | }, 86 | { 87 | author: 'Connor Turland', 88 | avatar: 'connor-turland-avatar.png', 89 | time: '7.12am', 90 | text: 91 | 'Eric already exposed some of that data over the API I think, and his version actually showed a bit of that stuff, but just a little bit clunky. I think maybe that stuff would be better on a User profile page', 92 | image: '', 93 | up: 9, 94 | down: 0, 95 | }, 96 | { 97 | author: 'Connor Turland', 98 | avatar: 'connor-turland-avatar.png', 99 | time: '7.13am', 100 | text: 'followers, counts, etc', 101 | image: '', 102 | up: 9, 103 | down: 0, 104 | }, 105 | { 106 | author: 'Philip Beadle', 107 | avatar: 'philip-beadle-avatar.png', 108 | time: '7.23am', 109 | text: 110 | 'Agreed. I think a pattern of each app having a profile page would be good to get some consistency as well.', 111 | image: '', 112 | up: 9, 113 | down: 0, 114 | }, 115 | ], 116 | }; 117 | 118 | class IdeaCard extends Component { 119 | state = { 120 | open: false, 121 | }; 122 | handleClickOpen = () => { 123 | this.setState({ open: true }); 124 | }; 125 | handleClose = () => { 126 | this.setState({ open: false }); 127 | }; 128 | handleThumbsUp = () => { 129 | console.log('up'); 130 | // this.togglePopover() 131 | }; 132 | handleThumbsDown = () => { 133 | console.log('down'); 134 | // this.togglePopover() 135 | }; 136 | render() { 137 | const { classes, idea } = this.props; 138 | return ( 139 |
140 | 141 | } 143 | title={idea.productOwner} 144 | subheader={idea.date} 145 | /> 146 | 147 | 148 | {idea.title} 149 | 150 | {idea.description} 151 | 152 | 153 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 168 | 172 | 173 | 174 | 175 | 179 | 183 | 184 | 185 | 186 | 187 | 188 | 192 | New Idea 193 | 194 | 195 | Add more detail to the Idea so it can be promoted to a Top Idea. 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 207 | 210 | 211 | 212 |
213 | ); 214 | } 215 | } 216 | IdeaCard.propTypes = { 217 | classes: PropTypes.object.isRequired, 218 | }; 219 | 220 | export default withRoot(withStyles(styles)(IdeaCard)); 221 | -------------------------------------------------------------------------------- /dna/rooms/rooms.js: -------------------------------------------------------------------------------- 1 | /* 2 | The anchors for the Rooms are using anchor("Room",room.name) with tag : "room" 3 | 4 | the Messages can be stored to the same anchor with a diffrent tag 5 | */ 6 | 7 | // Create a new chat Space / Channel 8 | function newRoom(room) { 9 | var key 10 | 11 | if(room.access=="public"){ 12 | try{ 13 | key=commit("room", room); 14 | commit("room_links",{Links:[{Base:anchor("Room",room.name),Link:key,Tag:"room"}]}) 15 | }catch(e){ 16 | return (e); 17 | } 18 | return key; 19 | }else if (room.access=="private") { 20 | key=commit("room", room); 21 | commit("room_links",{Links:[{Base:anchor("Private_Room",room.name),Link:key,Tag:"room"}]}) 22 | //Setting Creator as Admin; 23 | setRoomAdmin({"room_name":room.name}); 24 | //Setting the Creator as Member of the room 25 | call("membership","addMember",{"room_name":room.name,"agent_hash":App.Agent.Hash,"agent_key":App.Key.Hash}); 26 | 27 | return key; 28 | }else { 29 | return "INVALID ACCESS:"+room.access; 30 | } 31 | 32 | } 33 | 34 | // Get list of Public chat Spaces / Rooms / Channels 35 | //Can be Optimized 36 | function getPublicRooms() { 37 | if(anchorExists("Room","")){ 38 | var rooms_anchor = getLinks(anchor("Room",""), "",{Load:true}); 39 | //debug("rooms_anchor: " + JSON.stringify(rooms_anchor)) 40 | if( rooms_anchor instanceof Error ){ 41 | return [] 42 | } else { 43 | var return_rooms = new Array(rooms_anchor.length); 44 | return_rooms=[]; 45 | for( i=0; i=1){ 114 | admin.forEach(function (element){ 115 | return_admin=element.Hash; 116 | }); 117 | }else{ 118 | return "ERROR: invalid PRIVATE Room name"; 119 | } 120 | debug("Admins are : "+return_admin); 121 | return return_admin; 122 | } 123 | 124 | // TODO: 125 | //Can be changed to add Admins for more features 126 | //@param : {room_name:"",agent_hash:""} 127 | function addAdmin(x){ 128 | commit("admin",{Links:[{Base:anchor("Private_Room",x.room_name),Link:x.agent_hash,Tag:"admin"}]}) 129 | } 130 | 131 | 132 | /*---------- Anchor API ----------*/ 133 | 134 | function anchor(anchorType, anchorText) { 135 | return call('anchors', 'anchor', { 136 | anchorType: anchorType, 137 | anchorText: anchorText 138 | }).replace(/"/g, ''); 139 | } 140 | 141 | function anchorExists(anchorType, anchorText) { 142 | return call('anchors', 'exists', { 143 | anchorType: anchorType, 144 | anchorText: anchorText 145 | }); 146 | } 147 | 148 | /*----------Validation Functions-----------*/ 149 | 150 | function isValidAdmin(entry_type,entry,header,sources){ 151 | if(sources==App.Key.Hash){ 152 | debug("Admin is Valid:"+sources) 153 | return true; 154 | } 155 | else { 156 | debug("ERROR: Admin is not valid : "+sources) 157 | return false; 158 | } 159 | } 160 | 161 | function isValidRoom(room) { 162 | debug("Checking if "+room+" is a valid...") 163 | var rooms = getLinks(anchor("Room",""), "",{Load:true}); 164 | ///debug("Rooms: " + JSON.stringify(rooms)) 165 | if( rooms instanceof Error ){ 166 | return false 167 | } else { 168 | for( i=0; i>RequestingAppDNA:Profile Spec 121 | RequestingAppDNA->>IdentityMgrDNA:Bridge Commit(Profile Spec) 122 | IdentityMgrDNA-->>RequestingAppDNA:Hash key of Profile Spec 123 | Note over RequestingAppDNA:Redirect using Profile Spec Hash Key 124 | RequestingAppDNA-->>IdentityMgrUI:Redirect to IdentityMgr 125 | IdentityMgrUI-->>IdentityMgrDNA:Request Profile Spec 126 | IdentityMgrUI-->>IdentityMgrDNA:Request Personas 127 | Note over IdentityMgrUI, IdentityMgrDNA:No existing Personas 128 | IdentityMgrDNA-->>IdentityMgrUI:Profile Spec 129 | IdentityMgrDNA-->>IdentityMgrUI:Default Persona 130 | Note over IdentityMgrUI:Displays a form showing each requested field in Profile Spec with the reason 131 | IdentityMgrUI->>IdentityMgrUI:Person enters profile data 132 | Note over IdentityMgrUI:New Persona is called "Work" 133 | IdentityMgrUI->>IdentityMgrUI:Person names new Persona 134 | IdentityMgrUI->>IdentityMgrDNA:Commit Profile Mapping & Persona 135 | Note over IdentityMgrDNA:Redirect using Profile Mapping Hash Key 136 | IdentityMgrDNA-->>RequestingAppUI:Redirect to RequestingAppUI 137 | RequestingAppUI->>RequestingAppDNA:Commit Profile Mapping Hash Key 138 | 139 | ``` 140 | The Profile Mapping looks like this: 141 | 142 | ```jsx= 143 | [ 144 | { 145 | "appLabel": "firstname", 146 | "idLabel": "Work.firstname" 147 | }, 148 | { 149 | "appLabel": "address", 150 | "idLabel": "Work.address" 151 | }, 152 | { 153 | "appLabel": "suburb", 154 | "idLabel": "Work.suburb" 155 | }, 156 | { 157 | "appLabel": "city", 158 | "idLabel": "Work.city" 159 | } 160 | ] 161 | ``` 162 | 163 | In this example the following object is returned to the RequestingAppUI when it sends the Hash Key of the above Profile Mapping 164 | 165 | #### Profile Object 166 | ```jsx= 167 | { 168 | "firstname": "Phil", 169 | "address": "123 Holochain Road", 170 | "suburb": "Burwood", 171 | "city": "Melbourne" 172 | } 173 | ``` 174 | 175 | The following sequence shows how the RequestingApp would show an existing profile for a person by querying the field names in the request mapping. 176 | Iterate through each field and query for the Persona using the Prefix of the field. ie Work.firstname, query for 177 | 178 | ```mermaid 179 | sequenceDiagram 180 | Participant RequestingAppUI 181 | Participant RequestingAppDNA 182 | Participant IdentityMgrDNA 183 | 184 | RequestingAppUI-->>RequestingAppDNA:Profile Mapping hash key 185 | RequestingAppDNA-->>IdentityMgrDNA:Profile Mapping hash key 186 | IdentityMgrDNA-->>IdentityMgrDNA:Retrieve Profile Mapping 187 | Note over IdentityMgrDNA:query each field as shown below 188 | IdentityMgrDNA-->>IdentityMgrDNA:query fields 189 | IdentityMgrDNA-->>RequestingAppDNA:Profile object 190 | RequestingAppDNA-->>RequestingAppUI:Profile object 191 | ``` 192 | ```jsx= 193 | 194 | #Update each field by iterating the array and parsing the 195 | personaLabel into the Identity name & field name. 196 | 197 | [ 198 | { 199 | "appLabel": "firstname", 200 | "personaRef": "Work.firstname" 201 | }, 202 | { 203 | "appLabel": "address", 204 | "personaRef": "Personal.address" 205 | }, 206 | { 207 | "appLabel": "suburb", 208 | "personaRef": "Personal.suburb" 209 | }, 210 | { 211 | "appLabel": "city", 212 | "personaRef": "Personal.city" 213 | } 214 | ] 215 | var firstname = query({Return:{Entries:true}, 216 | {Constrain:{EntryTypes:["Persona"], 217 | Equals:{"name":"Personal"}})[0]['firstname'] 218 | 219 | ``` 220 | 221 | ## Managing your personas 222 | 223 | You can add edit and delete personas in the "My Personal Data" app. 224 | 225 | ```mermaid 226 | sequenceDiagram 227 | Participant IdentityMgrUI 228 | Participant IdentityMgrDNA 229 | 230 | IdentityMgrUI-->>IdentityMgrDNA:Request Personas (Query) 231 | IdentityMgrDNA-->>IdentityMgrUI: Personas 232 | IdentityMgrUI-->>IdentityMgrUI: Select / Add Persona 233 | IdentityMgrUI->>IdentityMgrUI: Enter information 234 | IdentityMgrUI->>IdentityMgrDNA: Commit Persona 235 | 236 | ``` 237 | 238 | # Peer Created Content 239 | Every piece of data you create is kept by you and shared by you at your discretion. This includes a HOLO rate to use the piece of data. Apps then use "request spec files" to request to read your data. You map any fields that don't auto map and grant premission for a period of time. 240 | Apps that ask for peer content such as HoloChat and Clutter will store the data you create in your "HoloVault" 241 | 242 | # Permalinks App 243 | Permanent public storage of a piece of peer created content data. 244 | -------------------------------------------------------------------------------- /dna/coustom_room/coustom_room.js: -------------------------------------------------------------------------------- 1 | //------------------------------ 2 | // Public Functions 3 | //------------------------------ 4 | // Creates new rooms when users decides to start a conversation 5 | //@param members : list of members Public.Hash 6 | function createCoustomRoom(members) { 7 | members.push(App.Key.Hash); 8 | var uuid = uuidGenerator(); 9 | var coustom_room_details = { 10 | name: uuid 11 | }; 12 | debug("Your Room UUID: " + uuid); 13 | var uuid_hash = commit("coustom_room_uuid", uuid); 14 | //debug("uuid_hash: " + uuid_hash); 15 | commit("coustom_room_link", { Links: [{ Base: App.DNA.Hash, Link: uuid_hash, Tag: "room_uuid" }] }); 16 | var details_hash = commit("coustom_room_details", coustom_room_details); 17 | //debug("details_hash: " + details_hash); 18 | commit("coustom_room_link", { Links: [{ Base: uuid_hash, Link: details_hash, Tag: "room_details" }] }); 19 | addMembers(uuid, members); 20 | return uuid; 21 | } 22 | //TODO : Test for non creator of the room adding a member in the room 23 | //Adds Members to a room using the UUID of the room 24 | function addMembers(uuid, members) { 25 | var uuid_hash = makeHash("coustom_room_uuid", uuid); 26 | members.forEach(function (member) { 27 | try { 28 | commit("room_to_member_link", { Links: [{ Base: uuid_hash, Link: member, Tag: "room_members" }] }); 29 | } 30 | catch (e) { 31 | debug(e); 32 | return e; 33 | } 34 | commit("member_to_room_link", { Links: [{ Base: member, Link: uuid_hash, Tag: "my_rooms" }] }); 35 | }); 36 | } 37 | //Returns the rooms that you are part of 38 | function getMyRooms() { 39 | var my_rooms; 40 | try { 41 | my_rooms = getLinks(App.Key.Hash, "my_rooms", { Load: true }); 42 | } 43 | catch (e) { 44 | return e; 45 | } 46 | // debug("My Room Chats : " + JSON.stringify(my_rooms)); 47 | var return_my_rooms = my_rooms.map(function (room) { 48 | return room.Entry; 49 | }); 50 | // debug("UUID's: " + JSON.stringify(return_my_rooms)); 51 | return return_my_rooms; 52 | } 53 | // Call to get all the member for a perticual UUID 54 | function getMembers(uuid) { 55 | var members; 56 | try { 57 | members = getLinks(makeHash("coustom_room_uuid", uuid), "room_members", { Load: true }); 58 | } 59 | catch (e) { 60 | return e; 61 | } 62 | debug("Members for " + uuid + ": " + JSON.stringify(members)); 63 | return members; 64 | } 65 | // Call to get details for a perticual UUID 66 | function getRoomDetails(uuid) { 67 | var details; 68 | try { 69 | details = getLinks(makeHash("coustom_room_uuid", uuid), "room_details", { Load: true }); 70 | } 71 | catch (e) { 72 | return e; 73 | } 74 | debug("Room Details for " + uuid + ": " + JSON.stringify(details)); 75 | return details; 76 | } 77 | //@param payload:{uuid:string,message:any} 78 | function postMessage(payload) { 79 | debug(payload); 80 | payload.message.timestamp = new Date(); 81 | payload.message.author = App.Key.Hash; 82 | debug(payload.message); 83 | var hash; 84 | try { 85 | hash = commit("cr_message", payload.message); 86 | commit("cr_message_link", { Links: [{ Base: makeHash("coustom_room_uuid", payload.uuid), Link: hash, Tag: "messages" }] }); 87 | } 88 | catch (e) { 89 | return e; 90 | } 91 | return hash; 92 | } 93 | function getMessages(uuid) { 94 | var messages; 95 | try { 96 | messages = getLinks(makeHash("coustom_room_uuid", uuid), "messages", { Load: true }); 97 | } 98 | catch (e) { 99 | debug("ERROR: " + e); 100 | return e; 101 | } 102 | debug("Messages : " + JSON.stringify(messages)); 103 | return messages; 104 | } 105 | //@param payload:{new_message:"",old_hash:""} 106 | function updateMessage(payload) { 107 | debug(payload); 108 | payload.new_message.timestamp = new Date(); 109 | payload.new_message.author = App.Key.Hash; 110 | var hash = update("cr_message", payload.new_message, payload.old_hash); 111 | return hash; 112 | } 113 | //------------------------------ 114 | // Helper Functions 115 | //------------------------------ 116 | //Generates new UUID () 117 | function uuidGenerator() { 118 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 119 | var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); 120 | return v.toString(16); 121 | }); 122 | } 123 | //For Testing 124 | function getKey() { 125 | return App.Key.Hash; 126 | } 127 | // ----------------------------------------------------------------- 128 | // The Genesis Function https://developer.holochain.org/genesis 129 | // ----------------------------------------------------------------- 130 | function genesis() { 131 | return true; 132 | } 133 | // ----------------------------------------------------------------- 134 | // Validation functions for every change to the local chain or DHT 135 | // ----------------------------------------------------------------- 136 | // Check if the pub_hash is a member of the 137 | function isValidAdmin(base_hash, entry_source) { 138 | debug("Checking if Agent is an Admin.."); 139 | //Checking if the Creator is trying to add people to the room 140 | var source; 141 | try { 142 | source = get(base_hash, { GetMask: HC.GetMask.Sources }); 143 | } 144 | catch (e) { 145 | return false; 146 | } 147 | //Added the creater of the room as a member of the room 148 | if (JSON.stringify(source) === JSON.stringify(entry_source)) { 149 | //debug("Adding Room Creator as a member of the room") 150 | return true; 151 | } 152 | //Checking to see if members are trying to add new members to the room 153 | var members; 154 | try { 155 | members = getLinks(base_hash, "room_members", { Load: true }); 156 | } 157 | catch (e) { 158 | debug("Rooms Dosnt Exist " + e); 159 | return false; 160 | } 161 | var access = members.some(function (member) { 162 | return member.Hash == entry_source; 163 | }); 164 | return access; 165 | } 166 | // Check to validate if the same user that created the message is modifying the message 167 | function isValidModifier(replaces, sources) { 168 | var old_message; 169 | try { 170 | old_message = get(replaces); 171 | } 172 | catch (e) { 173 | debug("ERROR: isValidModifier() " + e); 174 | } 175 | if (old_message.author == sources[0]) 176 | return true; 177 | else 178 | return false; 179 | } 180 | function validateCommit(entryName, entry, header, pkg, sources) { 181 | debug("entry_type:" + entryName + "entry" + JSON.stringify(entry) + "header" + JSON.stringify(header) + "PKG: " + JSON.stringify(pkg) + "sources" + sources); 182 | return validate(entryName, entry, header, pkg, sources); 183 | } 184 | function validate(entryName, entry, header, pkg, sources) { 185 | switch (entryName) { 186 | case "coustom_room_uuid": 187 | return true; 188 | case "coustom_room_details": 189 | return true; 190 | case "coustom_room_link": 191 | return true; 192 | case "room_to_member_link": 193 | return isValidAdmin(entry.Links[0].Base, sources); 194 | case "member_to_room_link": 195 | //isValidAdmin(entry); 196 | return true; 197 | case "cr_message": 198 | return true; 199 | case "cr_message_link": 200 | return true; 201 | default: 202 | return false; 203 | } 204 | } 205 | function validatePut(entryName, entry, header, pkg, sources) { 206 | return true; 207 | } 208 | function validateMod(entryName, entry, header, replaces, pkg, sources) { 209 | debug("entry_type:" + entryName + "entry" + JSON.stringify(entry) + "header" + JSON.stringify(header) + "replaces: " + replaces + "PKG: " + JSON.stringify(pkg) + "sources" + sources); 210 | switch (entryName) { 211 | case "cr_message": 212 | return isValidModifier(replaces, sources); 213 | default: 214 | return false; 215 | } 216 | } 217 | function validateDel(entryName, hash, pkg, sources) { 218 | return false; 219 | } 220 | function validateLink(entryName, baseHash, links, pkg, sources) { 221 | //debug("entryName: "+entryName+" baseHash: "+ baseHash+" links: "+ links+" sources: "+ sources); 222 | switch (entryName) { 223 | case "coustom_room_link": 224 | return true; 225 | case "room_to_member_link": 226 | return true; 227 | case "member_to_room_link": 228 | return true; 229 | case "cr_message_link": 230 | return true; 231 | default: 232 | return false; 233 | } 234 | } 235 | function validatePutPkg(entryName) { 236 | return null; 237 | } 238 | function validateModPkg(entryName) { 239 | return null; 240 | } 241 | function validateDelPkg(entryName) { 242 | return null; 243 | } 244 | function validateLinkPkg(entryName) { 245 | return null; 246 | } 247 | -------------------------------------------------------------------------------- /dna/coustom_room/coustom_room.ts: -------------------------------------------------------------------------------- 1 | //------------------------------ 2 | // Public Functions 3 | //------------------------------ 4 | 5 | // Creates new rooms when users decides to start a conversation 6 | //@param members : list of members Public.Hash 7 | function createCoustomRoom(members: Hash[]): UUID { 8 | members.push(App.Key.Hash) 9 | let uuid: string = uuidGenerator(); 10 | var coustom_room_details: any = { 11 | name: uuid 12 | } 13 | debug("Your Room UUID: " + uuid); 14 | let uuid_hash = commit("coustom_room_uuid", uuid); 15 | //debug("uuid_hash: " + uuid_hash); 16 | commit("coustom_room_link", { Links: [{ Base: App.DNA.Hash, Link: uuid_hash, Tag: "room_uuid" }] }); 17 | 18 | let details_hash = commit("coustom_room_details", coustom_room_details); 19 | //debug("details_hash: " + details_hash); 20 | commit("coustom_room_link", { Links: [{ Base: uuid_hash, Link: details_hash, Tag: "room_details" }] }); 21 | 22 | addMembers(uuid, members); 23 | 24 | return uuid; 25 | } 26 | 27 | //TODO : Test for non creator of the room adding a member in the room 28 | //Adds Members to a room using the UUID of the room 29 | function addMembers(uuid: UUID, members: Hash[]) { 30 | let uuid_hash: string = makeHash("coustom_room_uuid", uuid); 31 | members.forEach((member) => { 32 | try { 33 | commit("room_to_member_link", { Links: [{ Base: uuid_hash, Link: member, Tag: "room_members" }] }); 34 | } catch (e) { 35 | debug(e) 36 | return e 37 | } 38 | commit("member_to_room_link", { Links: [{ Base: member, Link: uuid_hash, Tag: "my_rooms" }] }); 39 | }); 40 | } 41 | 42 | //Returns the rooms that you are part of 43 | function getMyRooms() { 44 | let my_rooms: any; 45 | try { 46 | my_rooms = getLinks(App.Key.Hash, "my_rooms", { Load: true }); 47 | } catch (e) { 48 | return e; 49 | } 50 | // debug("My Room Chats : " + JSON.stringify(my_rooms)); 51 | let return_my_rooms: string[] = my_rooms.map((room) => { 52 | return room.Entry 53 | }); 54 | // debug("UUID's: " + JSON.stringify(return_my_rooms)); 55 | return return_my_rooms; 56 | } 57 | 58 | // Call to get all the member for a perticual UUID 59 | function getMembers(uuid: UUID): string[] { 60 | let members: any; 61 | try { 62 | members = getLinks(makeHash("coustom_room_uuid", uuid), "room_members", { Load: true }); 63 | } catch (e) { 64 | return e; 65 | } 66 | debug("Members for " + uuid + ": " + JSON.stringify(members)); 67 | return members; 68 | } 69 | 70 | // Call to get details for a perticual UUID 71 | function getRoomDetails(uuid: UUID): string[] { 72 | let details: any; 73 | try { 74 | details = getLinks(makeHash("coustom_room_uuid", uuid), "room_details", { Load: true }); 75 | } catch (e) { 76 | return e; 77 | } 78 | debug("Room Details for " + uuid + ": " + JSON.stringify(details)); 79 | return details; 80 | } 81 | 82 | //@param payload:{uuid:string,message:any} 83 | function postMessage(payload: message): Hash { 84 | debug(payload) 85 | payload.message.timestamp = new Date(); 86 | payload.message.author = App.Key.Hash; 87 | debug(payload.message) 88 | 89 | let hash: Hash; 90 | try { 91 | hash = commit("cr_message", payload.message); 92 | commit("cr_message_link", { Links: [{ Base: makeHash("coustom_room_uuid", payload.uuid), Link: hash, Tag: "messages" }] }); 93 | } catch (e) { 94 | return e; 95 | } 96 | return hash; 97 | } 98 | 99 | function getMessages(uuid: UUID): any { 100 | let messages: any; 101 | try { 102 | messages = getLinks(makeHash("coustom_room_uuid", uuid), "messages", { Load: true }); 103 | } catch (e) { 104 | debug("ERROR: " + e); 105 | return e; 106 | } 107 | debug("Messages : " + JSON.stringify(messages)) 108 | return messages; 109 | } 110 | 111 | //@param payload:{new_message:"",old_hash:""} 112 | function updateMessage(payload: updateMessage): Hash { 113 | debug(payload); 114 | payload.new_message.timestamp = new Date(); 115 | payload.new_message.author = App.Key.Hash; 116 | let hash: Hash = update("cr_message", payload.new_message, payload.old_hash); 117 | return hash; 118 | } 119 | 120 | //------------------------------ 121 | // Helper Functions 122 | //------------------------------ 123 | 124 | //Generates new UUID () 125 | function uuidGenerator() { 126 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 127 | var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); 128 | return v.toString(16); 129 | }); 130 | } 131 | //For Testing 132 | function getKey() { 133 | return App.Key.Hash; 134 | } 135 | // ----------------------------------------------------------------- 136 | // The Genesis Function https://developer.holochain.org/genesis 137 | // ----------------------------------------------------------------- 138 | 139 | function genesis() { 140 | return true; 141 | } 142 | 143 | // ----------------------------------------------------------------- 144 | // Validation functions for every change to the local chain or DHT 145 | // ----------------------------------------------------------------- 146 | 147 | // Check if the pub_hash is a member of the 148 | function isValidAdmin(base_hash: string, entry_source: string): boolean { 149 | debug("Checking if Agent is an Admin.."); 150 | //Checking if the Creator is trying to add people to the room 151 | let source: any; 152 | try { 153 | source = get(base_hash, { GetMask: HC.GetMask.Sources }); 154 | } catch (e) { 155 | return false; 156 | } 157 | //Added the creater of the room as a member of the room 158 | if (JSON.stringify(source) === JSON.stringify(entry_source)) { 159 | //debug("Adding Room Creator as a member of the room") 160 | return true; 161 | } 162 | //Checking to see if members are trying to add new members to the room 163 | let members: any; 164 | try { 165 | members = getLinks(base_hash, "room_members", { Load: true }); 166 | } catch (e) { 167 | debug("Rooms Dosnt Exist " + e); 168 | return false; 169 | } 170 | let access: boolean = members.some((member) => { 171 | return member.Hash == entry_source 172 | }); 173 | return access; 174 | } 175 | // Check to validate if the same user that created the message is modifying the message 176 | function isValidModifier(replaces: string, sources: any): boolean { 177 | let old_message: any; 178 | try { 179 | old_message = get(replaces) 180 | } catch (e) { 181 | debug("ERROR: isValidModifier() " + e) 182 | } 183 | if (old_message.author == sources[0]) 184 | return true; 185 | else 186 | return false; 187 | } 188 | function validateCommit(entryName: any, entry: any, header: any, pkg: any, sources: any): boolean { 189 | debug("entry_type:" + entryName + "entry" + JSON.stringify(entry) + "header" + JSON.stringify(header) + "PKG: " + JSON.stringify(pkg) + "sources" + sources); 190 | return validate(entryName, entry, header, pkg, sources); 191 | } 192 | 193 | function validate(entryName: any, entry: any, header: any, pkg: any, sources: any): boolean { 194 | switch (entryName) { 195 | case "coustom_room_uuid": 196 | return true; 197 | case "coustom_room_details": 198 | return true; 199 | case "coustom_room_link": 200 | return true; 201 | case "room_to_member_link": 202 | return isValidAdmin(entry.Links[0].Base, sources); 203 | case "member_to_room_link": 204 | //isValidAdmin(entry); 205 | return true; 206 | case "cr_message": 207 | return true; 208 | case "cr_message_link": 209 | return true; 210 | default: 211 | return false; 212 | } 213 | } 214 | 215 | function validatePut(entryName: any, entry: any, header: any, pkg: any, sources: any): boolean { 216 | return true; 217 | } 218 | 219 | function validateMod(entryName: any, entry: any, header: any, replaces: any, pkg: any, sources: any): boolean { 220 | debug("entry_type:" + entryName + "entry" + JSON.stringify(entry) + "header" + JSON.stringify(header) + "replaces: " + replaces + "PKG: " + JSON.stringify(pkg) + "sources" + sources); 221 | switch (entryName) { 222 | case "cr_message": 223 | return isValidModifier(replaces, sources); 224 | default: 225 | return false; 226 | } 227 | } 228 | 229 | function validateDel(entryName: any, hash: any, pkg: any, sources: any): boolean { 230 | return false; 231 | } 232 | 233 | function validateLink(entryName: any, baseHash: any, links: any, pkg: any, sources: any): boolean { 234 | //debug("entryName: "+entryName+" baseHash: "+ baseHash+" links: "+ links+" sources: "+ sources); 235 | switch (entryName) { 236 | case "coustom_room_link": 237 | return true; 238 | case "room_to_member_link": 239 | return true; 240 | case "member_to_room_link": 241 | return true; 242 | case "cr_message_link": 243 | return true; 244 | default: 245 | return false; 246 | } 247 | } 248 | 249 | function validatePutPkg(entryName) { 250 | return null; 251 | } 252 | 253 | function validateModPkg(entryName) { 254 | return null; 255 | } 256 | 257 | function validateDelPkg(entryName) { 258 | return null; 259 | } 260 | 261 | function validateLinkPkg(entryName) { 262 | return null; 263 | } 264 | -------------------------------------------------------------------------------- /dna/messages/messages.js: -------------------------------------------------------------------------------- 1 | // Create a new post in a Space / Channel 2 | //@param x={acccess:"public|private","message":{room_name:"",content:{text:"",mediaLink:""}}} 3 | function newMessage(x) { 4 | if(x.access=="public"){ 5 | return postPublicMessage(x); 6 | }else if(x.access=="private"){ 7 | return postPrivateMessage(x); 8 | }else { 9 | return ("ERROR Invalid Access: "+x.access); 10 | } 11 | } 12 | 13 | function postPrivateMessage(x){ 14 | x.message.timestamp = new Date(); 15 | x.message.author=App.Agent.Hash; 16 | var key; 17 | try{ 18 | if(anchorExists("Private_Room",x.message.room_name)=="true"){ 19 | key = commit("private_message", x.message); 20 | link=commit("private_message_links",{Links:[{Base:anchor("Private_Room",x.message.room_name),Link:key,Tag:"messages"}]}); 21 | }else{ 22 | return "ERROR: Room "+x.message.room_name+" doesn't exist"; 23 | } 24 | }catch(e){ 25 | return e; 26 | } 27 | // TODO - INDEXING #HASHTAG 28 | return link; 29 | } 30 | 31 | function postPublicMessage(x){ 32 | x.message.timestamp = new Date(); 33 | x.message.author=App.Agent.Hash; 34 | var key; 35 | try{ 36 | if(anchorExists("Room",x.message.room_name)=="true"){ 37 | key = commit("message", x.message); 38 | commit("message_links",{Links:[{Base:anchor("Room",x.message.room_name),Link:key,Tag:"messages"}]}); 39 | }else{ 40 | return "ERROR: Room "+x.message.room_name+" doesn't exist"; 41 | } 42 | }catch(e){ 43 | return e; 44 | } 45 | // TODO INDEXING -#HASHTAGE 46 | // debug("Starting HASHtag search"); 47 | // call("hashtag","callingHashTag",x); 48 | return key; 49 | } 50 | 51 | //Get Messages for a Rooms 52 | //@param x={room_name:"","access":"public | private"} 53 | function getMessages(x){ 54 | if(x.access=="public"){ 55 | return getPublicMessages(x); 56 | } 57 | else if (x.access=="private"){ 58 | return getPrivateMessages(x); 59 | } 60 | else { 61 | return "ERROR: Invalid Access for room "+x.access; 62 | } 63 | 64 | } 65 | 66 | function getPrivateMessages(x){ 67 | if(isValidPrivateProfile(anchor("Private_Room",x.room_name))) 68 | { 69 | if(anchorExists("Private_Room",x.room_name)=="true"){ 70 | try{ 71 | messages=getLinks(anchor("Private_Room",x.room_name),"messages",{Load:true}); 72 | }catch(e){ 73 | return e; 74 | } 75 | //debug("Messages::"+JSON.stringify(messages)) 76 | var return_messages=[]; 77 | messages.forEach(function (element){ 78 | var tempMessage={Hash:"",Entry:""}; 79 | tempMessage.Hash=element.Hash; 80 | tempMessage.Entry=element.Entry 81 | return_messages.push(tempMessage); 82 | }); 83 | debug("Return_messages::"+JSON.stringify(return_messages)) 84 | return return_messages; 85 | } 86 | return []; 87 | } 88 | else{ 89 | return "ERROR: Not A Member of the Room : "+x.room_name; 90 | } 91 | } 92 | 93 | function getPublicMessages(x){ 94 | if(anchorExists("Room","")=="true"){ 95 | try{ 96 | messages=getLinks(anchor("Room",x.room_name),"messages",{Load:true}); 97 | }catch(e){ 98 | return e; 99 | } 100 | //debug("Messages::"+JSON.stringify(messages)) 101 | var return_messages=[]; 102 | messages.forEach(function (element){ 103 | var tempMessage={Hash:"",Entry:""}; 104 | tempMessage.Hash=element.Hash; 105 | tempMessage.Entry=element.Entry 106 | return_messages.push(tempMessage); 107 | }); 108 | debug("Return_messages::"+JSON.stringify(return_messages)) 109 | return return_messages; 110 | } 111 | return []; 112 | } 113 | 114 | // Edit a post (create new one which "replaces" the old) 115 | // receives message like in newMessage and old_message's hash 116 | //@param: x:{new_message:{},old_Hash:""} 117 | function updateMessage(x) { 118 | debug(x); 119 | x.new_message.timestamp=new Date(); 120 | x.author=App.Agent.Hash; 121 | key=update("message",x.new_message,x.old_Hash); 122 | return key 123 | } 124 | 125 | 126 | 127 | /*---------- Anchor API ----------*/ 128 | 129 | function anchor(anchorType, anchorText) { 130 | return call('anchors', 'anchor', { 131 | anchorType: anchorType, 132 | anchorText: anchorText 133 | }).replace(/"/g, ''); 134 | } 135 | 136 | function anchorExists(anchorType, anchorText) { 137 | return call('anchors', 'exists', { 138 | anchorType: anchorType, 139 | anchorText: anchorText 140 | }); 141 | } 142 | 143 | 144 | /********** Validation Functions *************/ 145 | 146 | function isValidPrivateProfile(room_base){ 147 | 148 | var i_Am_A_Member=false; 149 | try{ 150 | members = getLinks(room_base, "members",{Load:true}); 151 | members.forEach(function (element){ 152 | if(element.Hash==App.Agent.Hash){ 153 | i_Am_A_Member=true; 154 | } 155 | }); 156 | }catch(e){ 157 | return false; 158 | } 159 | if(i_Am_A_Member){ 160 | return true; 161 | } 162 | else{ 163 | return false; 164 | } 165 | 166 | } 167 | 168 | function isValidRoom(room) { 169 | debug("Checking if "+room+" is a valid...") 170 | var rooms = getLinks(anchor("Room",""), "",{Load:true}); 171 | ///debug("Rooms: " + JSON.stringify(rooms)) 172 | if( rooms instanceof Error ){ 173 | return false 174 | } else { 175 | for( i=0; i ({ 30 | root: { 31 | width: '100%', 32 | marginTop: 5, 33 | flexGrow: 1 34 | }, 35 | flex: { 36 | flex: 1 37 | }, 38 | demo: { 39 | height: 860 40 | }, 41 | paper: { 42 | padding: 16, 43 | marginTop: 0, 44 | textAlign: 'left', 45 | color: theme.palette.text.secondary, 46 | height: '100%', 47 | overflow: 'auto' 48 | }, 49 | teams: { 50 | marginLeft: -8, 51 | background: theme.palette.action.primary, 52 | height: '100%' 53 | }, 54 | channels: { 55 | height: '100%', 56 | background: grey[300] 57 | }, 58 | messages: { 59 | height: '100%', 60 | position: 'relative' 61 | }, 62 | messageInput: { 63 | position: 'fixed', 64 | width: '50%', 65 | left: '50%', 66 | bottom: '-8%', 67 | transform: 'translate(-50%, -50%)' 68 | 69 | }, 70 | date: { 71 | width: '100%', 72 | textAlign: 'centre' 73 | }, 74 | messageTextField: { 75 | marginLeft: theme.spacing.unit, 76 | marginRight: theme.spacing.unit 77 | }, 78 | ideas: { 79 | height: '100%', 80 | marginRight: 8 81 | }, 82 | suggestions: { 83 | padding: 5, 84 | margin: 15, 85 | textAlign: 'left', 86 | color: theme.palette.text.secondary, 87 | flex: 1, 88 | height: 40 89 | }, 90 | menuButton: { 91 | marginLeft: -12, 92 | marginRight: 20 93 | }, 94 | nested: { 95 | paddingLeft: theme.spacing.unit * 4 96 | }, 97 | badge: { 98 | margin: `0 ${theme.spacing.unit * 2}px` 99 | }, 100 | listItemMessage: { 101 | position: 'relative' 102 | } 103 | }) 104 | 105 | const channels = [ 106 | {group: 'Private', 107 | channels: [ 108 | { 109 | title: 'DevLife Do-op', 110 | alerts: 5 111 | }, 112 | { 113 | title: 'Documentation', 114 | alerts: 0 115 | }, 116 | { 117 | title: 'Governance', 118 | alerts: 1 119 | } 120 | ]}, 121 | {group: 'Public', 122 | channels: [ 123 | { 124 | title: 'App:HoloChat', 125 | alerts: 5 126 | }, 127 | { 128 | title: 'App:Clutter', 129 | alerts: 0 130 | }, 131 | { 132 | title: 'HC Core', 133 | alerts: 1 134 | } 135 | ]}, 136 | {group: 'Direct Message', 137 | channels: [ 138 | { 139 | title: '@artbrock', 140 | alerts: 5, 141 | status: 'online' 142 | }, 143 | { 144 | title: '@jonathanhaber', 145 | alerts: 0, 146 | status: 'away' 147 | }, 148 | { 149 | title: '@lucksus', 150 | alerts: 1, 151 | status: 'offline' 152 | } 153 | ]} 154 | ] 155 | 156 | const topIdeas = [ 157 | { 158 | title: 'Poll Integration', 159 | productOwner: 'joatu-jamie', 160 | avatar: 'j-avatar.png', 161 | date: 'January 25, 2018 - Lead Time 6 weeks', 162 | description: 'To make it easier to gauge community support for an idea being able to attach a Poll to an idea would be cool.', 163 | up: 9, 164 | down: 0 165 | } 166 | ] 167 | 168 | const artTopIdea = { 169 | title: 'Display both FOLLOWERS and FOLLOWING', 170 | productOwner: 'Art Brock', 171 | avatar: 'art-brock-avatar.png', 172 | date: 'January 30, 2018 - Lead Time 3 weeks', 173 | description: 'When clicking on Follow Someone, let\'s include a list of who is following you, not just who you are following. Either that or we add number/summary displays: Mews, Following, Followers under profile pic.', 174 | up: 37, 175 | down: 1 176 | } 177 | 178 | const artNewIdea = { 179 | title: 'Display both FOLLOWERS and FOLLOWING', 180 | date: 'January 22, 2018', 181 | description: 'When clicking on Follow Someone, let\'s include a list of who is following you, not just who you are following.' 182 | } 183 | 184 | const newIdeas = [ 185 | { 186 | title: 'Add a profile page', 187 | date: 'January 25, 2018', 188 | description: 'Add a profile page. Add a profile page. Add a profile page. Add a profile page. Add a profile page. Add a profile page. Add a profile page. Add a profile page. ', 189 | isTopIdea: false, 190 | up: 9, 191 | down: 0 192 | }, 193 | { 194 | title: 'Detect @mentions in post text', 195 | date: 'January 25, 2018', 196 | description: 'Detect @mentions in post text. Detect @mentions in post text Detect @mentions in post text Detect @mentions in post text Detect @mentions in post text Detect @mentions in post text Detect @mentions in post text Detect @mentions in post text', 197 | isTopIdea: false, 198 | up: 9, 199 | down: 0 200 | }, 201 | { 202 | title: 'Reply to mew (add reply-to link + link to replies)', 203 | date: 'January 25, 2018', 204 | description: 'Reply to mew (add reply-to link + link to replies) Reply to mew (add reply-to link + link to replies) Reply to mew (add reply-to link + link to replies) Reply to mew (add reply-to link + link to replies) Reply to mew (add reply-to link + link to replies)', 205 | isTopIdea: false, 206 | up: 9, 207 | down: 0 208 | }, 209 | { 210 | title: 'Display both FOLLOWERS and FOLLOWING', 211 | date: 'January 22, 2018', 212 | description: 'So, we\'re establishing two-way DHT links for following/followers... I think we should make them more visible. How about we put some counts below the profile pic for Mews / Following / Following ?', 213 | isTopIdea: true, 214 | up: 9, 215 | down: 0} 216 | ] 217 | 218 | const messages = [ 219 | {date: 'January 25th 2018', 220 | messages: [ 221 | { 222 | type: 'Message', 223 | author: 'Art Brock', 224 | avatar: 'art-brock-avatar.png', 225 | time: '5.04pm', 226 | content: { 227 | text: 'Text message with an imageText message with an imageText message with an imageText message with an image', 228 | image: 'art-brock-avatar.png'}, 229 | replies: [], 230 | idea: false, 231 | up: 7, 232 | down: 0 233 | }, 234 | { 235 | type: 'Message', 236 | author: 'Art Brock', 237 | avatar: 'art-brock-avatar.png', 238 | time: '5.04pm', 239 | content: { 240 | text: 'Text message with no image', 241 | image: ''}, 242 | replies: [], 243 | idea: false, 244 | up: 7, 245 | down: 0 246 | } 247 | ]}, 248 | {date: 'January 26th 2018', 249 | messages: [ 250 | { 251 | type: 'Message', 252 | author: 'Art Brock', 253 | avatar: 'art-brock-avatar.png', 254 | time: '5.04pm', 255 | content: { 256 | text: 'So, we\'re establishing two-way DHT links for following/followers... I think we should make them more visible. How about we put some counts below the profile pic for Mews / Following / Following ?', 257 | image: ''}, 258 | idea: true, 259 | up: 7, 260 | down: 0, 261 | replies: [ 262 | { 263 | type: 'Message', 264 | author: 'Philip Beadle', 265 | avatar: 'philip-beadle-avatar.png', 266 | time: '7.04pm', 267 | content: { 268 | text: 'I was thinking the same thing. Since I saw @connorturland\'s new way of selecting someone to follow I thought we should show Followers too.', 269 | image: ''}, 270 | image: 'followers-mockup.png', 271 | up: 9, 272 | down: 0 273 | }, 274 | { 275 | type: 'Message', 276 | author: 'Philip Beadle', 277 | avatar: 'philip-beadle-avatar.png', 278 | time: '7.04pm', 279 | content: { 280 | text: 'And now that I\'ve drawn a simple update to the page we should probably add in the ability to Block people as well', 281 | image: ''}, 282 | up: 9, 283 | down: 0 284 | }, 285 | { 286 | type: 'Message', 287 | author: 'Connor Turland', 288 | avatar: 'connor-turland-avatar.png', 289 | time: '7.12am', 290 | content: { 291 | text: 'Eric already exposed some of that data over the API I think, and his version actually showed a bit of that stuff, but just a little bit clunky. I think maybe that stuff would be better on a User profile page', 292 | image: ''}, 293 | up: 9, 294 | down: 0 295 | }, 296 | { 297 | type: 'Message', 298 | author: 'Connor Turland', 299 | avatar: 'connor-turland-avatar.png', 300 | time: '7.13am', 301 | content: { 302 | text: 'followers, counts, etc', 303 | image: ''}, 304 | up: 9, 305 | down: 0 306 | }, 307 | { 308 | type: 'Message', 309 | author: 'Philip Beadle', 310 | avatar: 'philip-beadle-avatar.png', 311 | time: '7.23am', 312 | content: { 313 | text: 'Agreed. I think a pattern of each app having a profile page would be good to get some consistency as well.', 314 | image: ''}, 315 | up: 9, 316 | down: 0 317 | } 318 | ] 319 | }, 320 | { 321 | type: 'Message', 322 | author: 'Mark Finnern', 323 | avatar: 'mark-finnern-avatar.png', 324 | time: '7.36am', 325 | content: { 326 | text: 'like to get greater clarity on some of the application design patterns (at least the early ones) that are getting used so far. He is also interested in getting a deeper feel of how bridging between apps might work as well as the ins and outs of DPKI. Information around that would also come handy at the Meetup on Wednesday', 327 | image: ''}, 328 | idea: false, 329 | up: 9, 330 | down: 0, 331 | replies: [] 332 | } 333 | ]} 334 | ] 335 | class Index extends React.Component { 336 | state = { 337 | open: false, 338 | spacing: '16', 339 | auth: true, 340 | anchorEl: null, 341 | direction: 'row', 342 | justify: 'center', 343 | alignItems: 'stretch', 344 | newIdeas: newIdeas, 345 | topIdeas: topIdeas 346 | }; 347 | handleChange = (event, checked) => { 348 | this.setState({ auth: checked }) 349 | } 350 | handleMenu = event => { 351 | this.setState({ anchorEl: event.currentTarget }) 352 | } 353 | handleClickOpen = () => { 354 | this.setState({ open: true }) 355 | } 356 | handleClose = () => { 357 | this.setState({ open: false }) 358 | }; 359 | handlePromoteNewIdea = () => { 360 | newIdeas.push(artNewIdea) 361 | this.setState({newIdeas: newIdeas}) 362 | } 363 | handlePromoteTopIdea = () => { 364 | topIdeas.push(artTopIdea) 365 | newIdeas.splice(0, 1) 366 | this.setState({topIdeas: topIdeas}) 367 | this.setState({newIdeas: newIdeas}) 368 | } 369 | 370 | render () { 371 | const { classes } = this.props 372 | const { alignItems, direction, justify } = this.state 373 | return ( 374 | 375 | 376 | 377 | 378 | 379 | 380 | Teams 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | Top Ideas Ready to Build 407 | {this.state.topIdeas.map((idea, index) => ( 408 | 409 | 410 | 411 | ))} 412 | 413 |
414 | 415 | New Ideas Ready for Detail 416 | {this.state.newIdeas.map((idea, index) => ( 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | ))} 427 | 428 | 433 | New Idea 434 | 435 | 436 | Add more detail to the Idea so it can be promoted to a Top Idea. 437 | 438 | 449 | 457 | 467 | 468 | 469 | 472 | 475 | 476 | 477 |
478 |
479 |
480 |
481 |
482 |
483 | ) 484 | } 485 | } 486 | 487 | Index.propTypes = { 488 | classes: PropTypes.object.isRequired 489 | } 490 | 491 | export default withRoot(withStyles(styles)(Index)) 492 | --------------------------------------------------------------------------------