├── .gitmodules
├── packages
├── entities
│ ├── .gitignore
│ ├── src
│ │ ├── documentId.ts
│ │ ├── documentTypes.ts
│ │ ├── images.ts
│ │ ├── timestamp.ts
│ │ ├── ciUser.ts
│ │ ├── documentContent.ts
│ │ ├── file.ts
│ │ ├── datasetContent.ts
│ │ ├── documentPart.ts
│ │ ├── dataset.ts
│ │ ├── visualization.ts
│ │ ├── visualizationContent.ts
│ │ ├── index.ts
│ │ ├── datasetInfo.ts
│ │ ├── documentInfo.ts
│ │ ├── visualizationInfo.ts
│ │ └── user.ts
│ ├── classDiagram.dia
│ ├── tsconfig.json
│ └── package.json
├── ui
│ ├── src
│ │ ├── dist-symlink
│ │ ├── index.js
│ │ ├── utils
│ │ │ └── preventDefault.js
│ │ ├── constants.js
│ │ ├── atoms
│ │ │ ├── fullPage.js
│ │ │ └── avatar.js
│ │ ├── css
│ │ │ ├── fullPage.sass
│ │ │ ├── codemirror-tweaks.sass
│ │ │ ├── index.sass
│ │ │ ├── visualizationView.sass
│ │ │ ├── editorGrid.sass
│ │ │ └── ideGrid.sass
│ │ ├── testingApp
│ │ │ ├── testingApp.js
│ │ │ ├── rootReducer.js
│ │ │ ├── saveSimulationEpic.js
│ │ │ └── files.js
│ │ ├── redux
│ │ │ ├── reducers
│ │ │ │ ├── visualizationId.js
│ │ │ │ ├── visualizationTitle.js
│ │ │ │ ├── visualizationWidth.js
│ │ │ │ ├── visualizationHeight.js
│ │ │ │ ├── visualizationOwnerUser.js
│ │ │ │ ├── visualizationDescription.js
│ │ │ │ ├── runId.js
│ │ │ │ ├── splitPaneDragging.js
│ │ │ │ ├── saveStatus.js
│ │ │ │ ├── activeFileName.js
│ │ │ │ ├── index.js
│ │ │ │ └── files.js
│ │ │ ├── index.js
│ │ │ ├── epics
│ │ │ │ ├── runEpic.js
│ │ │ │ ├── autoSaveEpic.js
│ │ │ │ ├── confirmFileDeleteEpic.js
│ │ │ │ ├── promptForNewFileNameEpic.js
│ │ │ │ ├── confirmVisualizationDeleteEpic.js
│ │ │ │ ├── index.js
│ │ │ │ ├── promptForNewHeightEpic.js
│ │ │ │ ├── promptForRenameEpic.js
│ │ │ │ ├── updateTitleEpic.js
│ │ │ │ └── updateDescriptionEpic.js
│ │ │ ├── selectors.js
│ │ │ └── actionTypes.js
│ │ ├── visualizationRunner
│ │ │ ├── getSrcDoc.js
│ │ │ ├── computeScale.js
│ │ │ ├── computeSrcDoc.js
│ │ │ ├── runnerIframe.js
│ │ │ └── index.js
│ │ ├── visualizationFullscreen
│ │ │ └── index.js
│ │ ├── visualizationEditor
│ │ │ ├── editorGrid.js
│ │ │ └── fileList.js
│ │ ├── exports.js
│ │ ├── ide
│ │ │ └── ideGrid.js
│ │ └── visualizationView
│ │ │ └── index.js
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── CONTRIBUTING.md
│ └── package.json
├── web
│ ├── css
│ │ ├── navBrand.sass
│ │ ├── datasetContentTextPreview.sass
│ │ ├── commentLink.sass
│ │ ├── transitions.sass
│ │ ├── variables.sass
│ │ ├── visualizationPreview.sass
│ │ └── index.sass
│ ├── static
│ │ ├── favicon.ico
│ │ ├── favicon.png
│ │ └── example.html
│ ├── pages
│ │ ├── upload-dataset
│ │ │ ├── toFileName.js
│ │ │ ├── suggestedName.js
│ │ │ ├── fileUploader.js
│ │ │ └── body.js
│ │ ├── auth
│ │ │ ├── error.js
│ │ │ ├── callback.js
│ │ │ └── index.js
│ │ ├── _document.js
│ │ ├── create-visualization
│ │ │ └── body.js
│ │ ├── thumbnailsTest.js
│ │ └── visualization
│ │ │ └── forkInvitation.js
│ ├── README.md
│ ├── utils
│ │ ├── files.js
│ │ ├── baseUrl.js
│ │ ├── getJSON.js
│ │ └── userFromSession.js
│ ├── components
│ │ ├── atoms
│ │ │ ├── slightMargin.js
│ │ │ ├── datasetContentTextPreview.js
│ │ │ ├── textContainer.js
│ │ │ ├── commentLink.js
│ │ │ ├── permalinkPreview.js
│ │ │ ├── titledPage.js
│ │ │ ├── visualizationPreview.js
│ │ │ └── unfurl.js
│ │ ├── molecules
│ │ │ ├── actionBox.js
│ │ │ └── signIn.js
│ │ ├── page.js
│ │ └── organisms
│ │ │ └── navBar.js
│ ├── test
│ │ └── flaring.csv
│ ├── redux
│ │ ├── selectors.js
│ │ ├── rootReducer.js
│ │ ├── epics
│ │ │ ├── runBuildEpic.js
│ │ │ ├── forkSuccessEpic.js
│ │ │ ├── startBuildEpic.js
│ │ │ ├── runNonJS.js
│ │ │ ├── deleteVisualizationSuccessEpic.js
│ │ │ ├── deleteVisualizationEpic.js
│ │ │ ├── forkEpic.js
│ │ │ ├── index.js
│ │ │ ├── saveEpic.js
│ │ │ └── buildEpic.js
│ │ ├── actionTypes.js
│ │ ├── reducers.js
│ │ └── actionCreators.js
│ ├── server
│ │ ├── setupRaven.js
│ │ ├── shareDBServer.js
│ │ ├── next-auth.providers.js
│ │ └── next-auth.functions.js
│ ├── routes
│ │ ├── index.js
│ │ └── routeGenerators.js
│ ├── LICENSE_NEXTJS_STARTER.txt
│ ├── next.config.js
│ └── models
│ │ └── user.js
├── controllers
│ ├── src
│ │ ├── apiController
│ │ │ ├── userIdFromReq.js
│ │ │ ├── userAPIController
│ │ │ │ ├── index.js
│ │ │ │ └── getProfileDataController.js
│ │ │ ├── index.js
│ │ │ ├── datasetAPIController
│ │ │ │ ├── index.js
│ │ │ │ ├── createDatasetController.js
│ │ │ │ └── getDatasetController.js
│ │ │ └── visualizationAPIController
│ │ │ │ ├── getVisualizationController.js
│ │ │ │ ├── getAllVisualizationInfosController.js
│ │ │ │ ├── createVisualizationController.js
│ │ │ │ ├── forkVisualizationController.js
│ │ │ │ ├── saveVisualizationController.js
│ │ │ │ ├── deleteVisualizationController.js
│ │ │ │ ├── getPreviewController.js
│ │ │ │ ├── getThumbnailController.js
│ │ │ │ ├── exportVisualizationController.js
│ │ │ │ └── index.js
│ │ ├── index.js
│ │ └── userController.js
│ ├── package.json
│ └── package-lock.json
├── imageGenerationService
│ ├── src
│ │ ├── index.js
│ │ ├── dimensions.js
│ │ ├── computeImageDimensions.js
│ │ ├── resize.js
│ │ ├── generateScreenshot.js
│ │ ├── generateImages.js
│ │ └── service.js
│ └── package.json
├── useCases
│ ├── docs
│ │ ├── useCasesDiagram.dia
│ │ └── useCaseTemplate.md
│ ├── src
│ │ ├── utils
│ │ │ ├── generateId.ts
│ │ │ └── removeExtension.ts
│ │ ├── interactors
│ │ │ ├── createDataset
│ │ │ │ ├── datasetDefaults.ts
│ │ │ │ └── index.ts
│ │ │ ├── exportVisualization
│ │ │ │ ├── zipFiles.ts
│ │ │ │ └── index.ts
│ │ │ ├── getUser.ts
│ │ │ ├── getAllVisualizationInfos.ts
│ │ │ ├── getPreview.ts
│ │ │ ├── getThumbnail.ts
│ │ │ ├── saveVisualization.ts
│ │ │ ├── createVisualization
│ │ │ │ ├── visualizationDefaults.ts
│ │ │ │ └── index.ts
│ │ │ ├── createUser.ts
│ │ │ ├── getDataset.ts
│ │ │ ├── deleteVisualization.ts
│ │ │ ├── getVisualization.ts
│ │ │ ├── getUserProfileData.ts
│ │ │ ├── forkVisualization.ts
│ │ │ └── index.ts
│ │ ├── interactor.ts
│ │ ├── index.ts
│ │ └── gatewayInterfaces
│ │ │ ├── imageGeneratorGateway.ts
│ │ │ ├── imageStorageGateway.ts
│ │ │ ├── userGateway.ts
│ │ │ ├── datasetGateway.ts
│ │ │ └── visualizationGateway.ts
│ ├── tsconfig.json
│ ├── package.json
│ ├── createVisualization.md
│ └── saveVisualization.md
├── presenters
│ ├── src
│ │ ├── index.js
│ │ ├── datasetViewModel.js
│ │ ├── visualizationViewModel.js
│ │ ├── d3Packages.js
│ │ └── bundle.js
│ ├── tsconfig.json
│ └── package.json
├── database
│ ├── src
│ │ ├── collectionName.js
│ │ ├── createUser.js
│ │ ├── createVisualization.js
│ │ ├── getUser.js
│ │ ├── fetchShareDBQuery.js
│ │ ├── save.js
│ │ ├── fetchShareDBDoc.js
│ │ ├── getUserByUserName.js
│ │ ├── createDataset.js
│ │ ├── saveVisualization.js
│ │ ├── getDatasetInfosByUserId.js
│ │ ├── getAllVisualizationInfos.js
│ │ ├── setImagesUpdatedTimestamp.js
│ │ ├── getPreview.js
│ │ ├── getThumbnail.js
│ │ ├── getVisualization.js
│ │ ├── getVisualizationInfosByUserId.js
│ │ ├── updateImages.js
│ │ ├── deleteVisualization.js
│ │ ├── getDataset.js
│ │ └── index.js
│ └── package.json
├── gateways
│ ├── tsconfig.json
│ ├── src
│ │ ├── index.ts
│ │ ├── databaseUserGateway.ts
│ │ ├── databaseImageStorageGateway.ts
│ │ ├── databaseDatasetGateway.ts
│ │ └── databaseVisualizationGateway.ts
│ ├── package.json
│ └── test
│ │ └── test.ts
├── i18n
│ ├── package.json
│ └── index.ts
└── serverGateways
│ ├── package.json
│ └── src
│ ├── index.js
│ └── shareDB.js
├── .eslintignore
├── lerna.json
├── .gitmodules-ci
├── graveyard
├── oldUseCases
│ ├── src
│ │ ├── document.js
│ │ └── index.js
│ ├── package.json
│ └── test
│ │ └── test.js
├── computeSlug.js
├── clientServerGatewaySingleton.js
├── clientGateway.js
└── computeReferences
│ ├── test
│ └── test.ts
│ └── src
│ ├── index.ts
│ └── computeReferences.ts
├── .gitignore
├── deploy.sh
├── .gitlab-ci.yml
├── .eslintrc.js
├── LICENSE
├── ecosystem.config.js
└── package.json
/.gitmodules:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/entities/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/packages/ui/src/dist-symlink:
--------------------------------------------------------------------------------
1 | ../dist/
--------------------------------------------------------------------------------
/packages/ui/src/index.js:
--------------------------------------------------------------------------------
1 | import './testingApp/index';
2 |
--------------------------------------------------------------------------------
/packages/web/css/navBrand.sass:
--------------------------------------------------------------------------------
1 | .nav-brand img
2 | height: 32px
3 |
--------------------------------------------------------------------------------
/packages/entities/src/documentId.ts:
--------------------------------------------------------------------------------
1 | // A unique identifier for a document.
2 | export type DocumentId = string;
3 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "lerna": "2.11.0",
3 | "packages": [
4 | "packages/*"
5 | ],
6 | "version": "0.0.0"
7 | }
8 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/userIdFromReq.js:
--------------------------------------------------------------------------------
1 | export const userIdFromReq = req => req.user && req.user.id;
2 |
--------------------------------------------------------------------------------
/packages/web/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vizhub-core/vizhub-v1/HEAD/packages/web/static/favicon.ico
--------------------------------------------------------------------------------
/packages/web/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vizhub-core/vizhub-v1/HEAD/packages/web/static/favicon.png
--------------------------------------------------------------------------------
/packages/entities/classDiagram.dia:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vizhub-core/vizhub-v1/HEAD/packages/entities/classDiagram.dia
--------------------------------------------------------------------------------
/.gitmodules-ci:
--------------------------------------------------------------------------------
1 | [submodule "packages/vizhub-ui"]
2 | path = packages/ui
3 | url = https://github.com/datavis-tech/vizhub-ui.git
4 |
--------------------------------------------------------------------------------
/graveyard/oldUseCases/src/document.js:
--------------------------------------------------------------------------------
1 | export const defaults = {
2 | title: '',
3 | slug: '',
4 | description: ''
5 | };
6 |
--------------------------------------------------------------------------------
/graveyard/oldUseCases/src/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | createVisualization,
3 | updateVisualization
4 | } from './visualization';
5 |
--------------------------------------------------------------------------------
/packages/web/pages/upload-dataset/toFileName.js:
--------------------------------------------------------------------------------
1 | export const toFileName = name => name.replace(/ /g, '-').toLowerCase() + '.csv';
2 |
--------------------------------------------------------------------------------
/packages/entities/src/documentTypes.ts:
--------------------------------------------------------------------------------
1 | export const VISUALIZATION_TYPE = 'visualization';
2 | export const DATASET_TYPE = 'dataset';
3 |
--------------------------------------------------------------------------------
/packages/entities/src/images.ts:
--------------------------------------------------------------------------------
1 | export interface Images {
2 | thumbnail: string; // base64-encoded PNG
3 | preview: string;
4 | }
5 |
--------------------------------------------------------------------------------
/packages/controllers/src/index.js:
--------------------------------------------------------------------------------
1 | export { apiController } from './apiController';
2 | export { userController } from './userController';
3 |
--------------------------------------------------------------------------------
/packages/imageGenerationService/src/index.js:
--------------------------------------------------------------------------------
1 | require('dotenv').load();
2 | import { startService } from './service';
3 | startService({});
4 |
--------------------------------------------------------------------------------
/packages/ui/src/utils/preventDefault.js:
--------------------------------------------------------------------------------
1 | export const preventDefault = fn => event => {
2 | event.preventDefault();
3 | fn();
4 | };
5 |
--------------------------------------------------------------------------------
/packages/useCases/docs/useCasesDiagram.dia:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vizhub-core/vizhub-v1/HEAD/packages/useCases/docs/useCasesDiagram.dia
--------------------------------------------------------------------------------
/packages/ui/src/constants.js:
--------------------------------------------------------------------------------
1 | // Times are in milliseconds.
2 | export const runDebounceTime = 800;
3 | export const autoSaveDebounceTime = 4000;
4 |
--------------------------------------------------------------------------------
/packages/useCases/src/utils/generateId.ts:
--------------------------------------------------------------------------------
1 | import * as uuidV4 from 'uuid/v4';
2 | export const generateId = () => uuidV4().replace(/-/g, '');
3 |
--------------------------------------------------------------------------------
/packages/useCases/src/utils/removeExtension.ts:
--------------------------------------------------------------------------------
1 | export const removeExtension = fileName =>
2 | fileName.substr(0, fileName.lastIndexOf('.'));
3 |
--------------------------------------------------------------------------------
/packages/web/README.md:
--------------------------------------------------------------------------------
1 | Derived from https://github.com/iaincollins/nextjs-starter
2 |
3 | # Development
4 |
5 | Use `yarn dev` to start dev server.
6 |
--------------------------------------------------------------------------------
/packages/entities/src/timestamp.ts:
--------------------------------------------------------------------------------
1 | // Returns the Unix timestamp for the present.
2 | export const timestamp = () => Math.floor((new Date()).getTime() / 1000);
3 |
--------------------------------------------------------------------------------
/packages/web/css/datasetContentTextPreview.sass:
--------------------------------------------------------------------------------
1 | .dataset-content-text-preview
2 | text-align: left
3 | pre
4 | height: 200px
5 | padding: 0px 10px
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | dist
4 | yarn.lock
5 | yarn-error.log
6 | lerna-debug.log
7 | .next
8 | *.swp
9 | *.autosave
10 | .env
11 |
--------------------------------------------------------------------------------
/packages/web/utils/files.js:
--------------------------------------------------------------------------------
1 | export const hasName = name => file => file.name === name
2 | export const findFile = (name, files) => files.filter(hasName(name))[0];
3 |
--------------------------------------------------------------------------------
/packages/web/components/atoms/slightMargin.js:
--------------------------------------------------------------------------------
1 | export const SlightMargin = ({children}) => (
2 |
3 | {children}
4 |
5 | );
6 |
--------------------------------------------------------------------------------
/packages/web/utils/baseUrl.js:
--------------------------------------------------------------------------------
1 | export const baseUrl = req =>
2 | (req ? `${req.protocol}://${req.get('Host')}` : '')
3 | .replace('http://vizhub.com', 'https://vizhub.com');
4 |
--------------------------------------------------------------------------------
/packages/ui/src/atoms/fullPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const FullPage = ({children}) => (
4 |
5 | {children}
6 |
7 | );
8 |
--------------------------------------------------------------------------------
/packages/ui/src/css/fullPage.sass:
--------------------------------------------------------------------------------
1 | .full-page
2 | display: flex
3 | flex-direction: column
4 | position: fixed
5 | top: 0px
6 | bottom: 0px
7 | left: 0px
8 | right: 0px
9 |
--------------------------------------------------------------------------------
/packages/presenters/src/index.js:
--------------------------------------------------------------------------------
1 | export { VisualizationViewModel } from './visualizationViewModel';
2 | export { DatasetViewModel } from './datasetViewModel';
3 | export { bundle } from './bundle';
4 |
--------------------------------------------------------------------------------
/packages/web/components/atoms/datasetContentTextPreview.js:
--------------------------------------------------------------------------------
1 | export const DatasetContentTextPreview = ({text}) => (
2 |
5 | );
6 |
--------------------------------------------------------------------------------
/packages/web/components/atoms/textContainer.js:
--------------------------------------------------------------------------------
1 | export const TextContainer = ({children}) => (
2 |
3 | {children}
4 |
5 | );
6 |
--------------------------------------------------------------------------------
/packages/web/css/commentLink.sass:
--------------------------------------------------------------------------------
1 | .comment-link
2 | background-color: gray
3 | color: white
4 | position: fixed
5 | bottom: 0px
6 | left: 0px
7 | padding: 2px 10px
8 | a
9 | color: white
10 |
--------------------------------------------------------------------------------
/packages/useCases/src/interactors/createDataset/datasetDefaults.ts:
--------------------------------------------------------------------------------
1 | export const datasetDefaults = {
2 | title: 'Untitled',
3 | slug: undefined,
4 | description: 'No description',
5 | file: '',
6 | format: 'csv'
7 | };
8 |
--------------------------------------------------------------------------------
/packages/web/utils/getJSON.js:
--------------------------------------------------------------------------------
1 | import { baseUrl } from './baseUrl';
2 |
3 | export const getJSON = async (relativeUrl, req) => {
4 | const url = baseUrl(req) + relativeUrl;
5 | return await (await fetch(url)).json();
6 | }
7 |
--------------------------------------------------------------------------------
/packages/ui/src/testingApp/testingApp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FullPage, IDEContainer } from '../exports';
3 |
4 | export const TestingApp = () => (
5 |
6 |
7 |
8 | );
9 |
--------------------------------------------------------------------------------
/graveyard/computeSlug.js:
--------------------------------------------------------------------------------
1 | const slugFromTitle = title => title.toLowerCase().replace(/ /g, '-');
2 |
3 | const computeSlug = data => data.slug
4 | ? data.slug
5 | : data.title
6 | ? slugFromTitle(data.title)
7 | : undefined;
8 |
--------------------------------------------------------------------------------
/packages/ui/src/css/codemirror-tweaks.sass:
--------------------------------------------------------------------------------
1 | .CodeMirror
2 | height: 100%
3 | font-family: $family-monospace
4 | line-height: 1
5 | font-size: $font-size-for-code
6 |
7 | .react-codemirror2, .code-editor-container
8 | height: 100%
9 |
--------------------------------------------------------------------------------
/packages/web/components/atoms/commentLink.js:
--------------------------------------------------------------------------------
1 | export const CommentLink = () => (
2 |
5 | );
6 |
--------------------------------------------------------------------------------
/packages/useCases/src/interactor.ts:
--------------------------------------------------------------------------------
1 | export interface RequestModel {};
2 |
3 | export interface ResponseModel {};
4 |
5 | export interface Interactor {
6 | execute(req: RequestModel | undefined): Promise;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/entities/src/ciUser.ts:
--------------------------------------------------------------------------------
1 | export const ciUser = {
2 | id: '47895473289547832938754',
3 | fullName: 'CI',
4 | email: 'ci@testing.com',
5 | userName: 'ci',
6 | avatarUrl: 'https://avatars0.githubusercontent.com/u/639823?v=4'
7 | };
8 |
--------------------------------------------------------------------------------
/packages/entities/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | "sourceMap": true,
5 | "strictNullChecks": true,
6 | "lib": [ "ES2018" ]
7 | },
8 | "include": [
9 | "./src/"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/useCases/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | "sourceMap": true,
5 | "strictNullChecks": true,
6 | "lib": [ "ES2018" ]
7 | },
8 | "include": [
9 | "./src/"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | git checkout master
4 | git pull
5 |
6 | lerna bootstrap
7 | cd packages/web
8 | npm run build
9 | pm2 restart all
10 |
11 | # If no processes are set up in PM2, run this:
12 | # pm2 start ecosystem.config.js --env production
13 |
--------------------------------------------------------------------------------
/packages/ui/src/testingApp/rootReducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { uiRedux } from '../exports';
3 |
4 | const {
5 | reducers: {
6 | ide
7 | }
8 | } = uiRedux;
9 |
10 | export const rootReducer = combineReducers({ ide });
11 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/userAPIController/index.js:
--------------------------------------------------------------------------------
1 | import { getProfileDataController } from './getProfileDataController';
2 |
3 | export const userAPIController = (expressApp, userGateway) => {
4 | getProfileDataController(expressApp, userGateway);
5 | };
6 |
--------------------------------------------------------------------------------
/packages/web/components/atoms/permalinkPreview.js:
--------------------------------------------------------------------------------
1 | import { datasetUrl } from '../../routes/routeGenerators';
2 |
3 | export const PermalinkPreview = ({userName, slug}) => (
4 |
7 | );
8 |
--------------------------------------------------------------------------------
/packages/database/src/collectionName.js:
--------------------------------------------------------------------------------
1 | export const DOCUMENT_INFO = 'documentInfo';
2 | export const DOCUMENT_CONTENT = 'documentContent';
3 | export const USER = 'user';
4 | export const THUMBNAIL_IMAGES = 'thumbnailImages';
5 | export const PREVIEW_IMAGES = 'previewImages';
6 |
--------------------------------------------------------------------------------
/packages/useCases/src/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Interactor,
3 | RequestModel,
4 | ResponseModel
5 | } from './interactor';
6 |
7 | export {
8 | VisualizationGateway
9 | } from './gatewayInterfaces/visualizationGateway';
10 |
11 | export * from './interactors';
12 |
--------------------------------------------------------------------------------
/packages/gateways/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | "sourceMap": true,
5 | "strictNullChecks": true,
6 | "lib": [ "ESNext", "dom" ],
7 | "allowJs": true
8 | },
9 | "include": [
10 | "./src/"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/useCases/src/gatewayInterfaces/imageGeneratorGateway.ts:
--------------------------------------------------------------------------------
1 | import { Visualization, Images } from 'datavis-tech-entities';
2 |
3 | export interface ImageGeneratorGateway {
4 | generateImages(visualization: Visualization, waitTime: number | undefined): Promise;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/entities/src/documentContent.ts:
--------------------------------------------------------------------------------
1 | import { DocumentPart } from './documentPart';
2 |
3 | export class DocumentContent extends DocumentPart {
4 | constructor(data) {
5 | super({
6 | id: data.id,
7 | documentType: data.documentType
8 | });
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/web/pages/upload-dataset/suggestedName.js:
--------------------------------------------------------------------------------
1 | const stripExtension = fileName => fileName.substr(0, fileName.lastIndexOf('.'));
2 | const capitalize = name => name.charAt(0).toUpperCase() + name.substr(1);
3 | export const suggestedName = fileName => capitalize(stripExtension(fileName));
4 |
--------------------------------------------------------------------------------
/packages/web/css/transitions.sass:
--------------------------------------------------------------------------------
1 | $transition-duration: 200ms
2 |
3 | a
4 | transition: color $transition-duration
5 |
6 | .button,
7 | .control input
8 | transition: border-color $transition-duration
9 |
10 | .dropdown-item
11 | transition: background-color $transition-duration
12 |
--------------------------------------------------------------------------------
/packages/presenters/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | "sourceMap": true,
5 | "strictNullChecks": true,
6 | "lib": [ "ES2018" ],
7 | "allowJs": true,
8 | "target": "es5"
9 | },
10 | "include": [
11 | "./src/"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/database/src/createUser.js:
--------------------------------------------------------------------------------
1 | import { USER } from './collectionName';
2 |
3 | export const createUser = connection => user => {
4 | return new Promise(resolve => {
5 | // TODO handle errors here
6 | connection.get(USER, user.id).create(user);
7 | resolve(user);
8 | });
9 | }
10 |
--------------------------------------------------------------------------------
/packages/web/test/flaring.csv:
--------------------------------------------------------------------------------
1 | Year,Total,Gas,Liquids,Solids,Cement Production,Gas Flaring,Per Capita
2 | 2010,9128,1696,3107,3812,446,67,1.32
3 | 2011,9503,1756,3134,4055,494,64,1.36
4 | 2012,9673,1783,3200,4106,519,65,1.36
5 | 2013,9773,1806,3220,4126,554,68,1.36
6 | 2014,9855,1823,3280,4117,568,68,1.36
7 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | image: node:10
2 |
3 | before_script:
4 | - mv .gitmodules-ci .gitmodules # Use HTTPS in CI, SSH in dev (so no password prompt)
5 | - git submodule sync
6 | - git submodule update --init
7 | - npm install -g lerna
8 | - lerna bootstrap
9 |
10 | test:
11 | script:
12 | - npm run test
13 |
--------------------------------------------------------------------------------
/packages/gateways/src/index.ts:
--------------------------------------------------------------------------------
1 | export { DatabaseVisualizationGateway } from './databaseVisualizationGateway';
2 | export { DatabaseDatasetGateway } from './databaseDatasetGateway';
3 | export { DatabaseUserGateway } from './databaseUserGateway';
4 | export { DatabaseImageStorageGateway } from './databaseImageStorageGateway';
5 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/reducers/visualizationId.js:
--------------------------------------------------------------------------------
1 | import { SET_VISUALIZATION_ID } from '../actionTypes';
2 |
3 | export const visualizationId = (state = '', action) => {
4 | switch (action.type) {
5 | case SET_VISUALIZATION_ID:
6 | return action.id;
7 | default:
8 | return state;
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/packages/useCases/docs/useCaseTemplate.md:
--------------------------------------------------------------------------------
1 | # Name of Use Case
2 |
3 | Actor: Foo
4 |
5 | Description goes here
6 |
7 | ## Triggers
8 |
9 | ## Preconditions
10 |
11 | ## Postconditions
12 |
13 | ## Normal Course of Events
14 |
15 | ## Alternative Courses of Events
16 |
17 | ## Exception
18 |
19 | ## Includes
20 |
--------------------------------------------------------------------------------
/packages/i18n/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "datavis-tech-i18n",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "",
6 | "main": "index.ts",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "author": "Curran Kelleher",
11 | "license": "UNLICENSED"
12 | }
13 |
--------------------------------------------------------------------------------
/packages/web/utils/userFromSession.js:
--------------------------------------------------------------------------------
1 | import { User } from 'datavis-tech-entities';
2 |
3 | // Creates a User entity from the Express session.
4 | export const userFromSession = session => (
5 | session.user
6 | ? new User(Object.assign({ authenticated: true }, session.user))
7 | : new User({ authenticated: false })
8 | );
9 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/reducers/visualizationTitle.js:
--------------------------------------------------------------------------------
1 | import { SET_VISUALIZATION_TITLE } from '../actionTypes';
2 |
3 | export const visualizationTitle = (state = '', action) => {
4 | switch (action.type) {
5 | case SET_VISUALIZATION_TITLE:
6 | return action.title;
7 | default:
8 | return state;
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/reducers/visualizationWidth.js:
--------------------------------------------------------------------------------
1 | import { SET_VISUALIZATION_WIDTH } from '../actionTypes';
2 |
3 | export const visualizationWidth = (state = -1, action) => {
4 | switch (action.type) {
5 | case SET_VISUALIZATION_WIDTH:
6 | return action.width;
7 | default:
8 | return state;
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/reducers/visualizationHeight.js:
--------------------------------------------------------------------------------
1 | import { SET_VISUALIZATION_HEIGHT } from '../actionTypes';
2 |
3 | export const visualizationHeight = (state = -1, action) => {
4 | switch (action.type) {
5 | case SET_VISUALIZATION_HEIGHT:
6 | return action.height;
7 | default:
8 | return state;
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/packages/web/redux/selectors.js:
--------------------------------------------------------------------------------
1 | export const getCsrfToken = state => state.csrfToken;
2 | export const getVisualization = state => state.visualization;
3 | export const getUser = state => state.user;
4 | export const getUserName = state => (getUser(state) || {}).userName;
5 | export const getShowForkInvitation = state => state.showForkInvitation;
6 |
--------------------------------------------------------------------------------
/packages/database/src/createVisualization.js:
--------------------------------------------------------------------------------
1 | import { DOCUMENT_INFO, DOCUMENT_CONTENT } from './collectionName';
2 | import { saveVisualization } from './saveVisualization';
3 |
4 | export const createVisualization = connection => visualization =>
5 | saveVisualization(connection)({ visualization })
6 | .then(() => ({ id: visualization.id }));
7 |
--------------------------------------------------------------------------------
/packages/web/components/molecules/actionBox.js:
--------------------------------------------------------------------------------
1 | export const ActionBox = ({title, children}) => (
2 |
6 |
7 |
{title}
8 | { children }
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/packages/entities/src/file.ts:
--------------------------------------------------------------------------------
1 | export class File {
2 |
3 | // The name of the file.
4 | // For example "index.html", "index.js", "foo.js", "styles.css"
5 | name: string;
6 |
7 | // The text file content.
8 | text: string;
9 |
10 | constructor (data) {
11 | this.name = data.name;
12 | this.text = data.text;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/useCases/src/gatewayInterfaces/imageStorageGateway.ts:
--------------------------------------------------------------------------------
1 | import { DocumentId, Images } from 'datavis-tech-entities';
2 |
3 | export interface ImageStorageGateway {
4 | updateImages({ id: DocumentId, images: Images}): Promise;
5 | getThumbnail({ id: DocumentId} ): Promise;
6 | getPreview({ id: DocumentId} ): Promise;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/reducers/visualizationOwnerUser.js:
--------------------------------------------------------------------------------
1 | import { SET_VISUALIZATION_OWNER_USER } from '../actionTypes';
2 |
3 | export const visualizationOwnerUser = (state = {}, action) => {
4 | switch (action.type) {
5 | case SET_VISUALIZATION_OWNER_USER:
6 | return action.user;
7 | default:
8 | return state;
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/reducers/visualizationDescription.js:
--------------------------------------------------------------------------------
1 | import { SET_VISUALIZATION_DESCRIPTION } from '../actionTypes';
2 |
3 | export const visualizationDescription = (state = '', action) => {
4 | switch (action.type) {
5 | case SET_VISUALIZATION_DESCRIPTION:
6 | return action.description;
7 | default:
8 | return state;
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/reducers/runId.js:
--------------------------------------------------------------------------------
1 | import { RUN_FILES } from '../actionTypes';
2 |
3 | const generateRunId = () => Math.random().toString(36).substr(2);
4 | export const runId = (state = generateRunId(), action) => {
5 | switch (action.type) {
6 | case RUN_FILES:
7 | return generateRunId();
8 | default:
9 | return state;
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/packages/ui/src/visualizationRunner/getSrcDoc.js:
--------------------------------------------------------------------------------
1 | import { computeSrcDoc } from './computeSrcDoc';
2 |
3 | export const GetSrcDoc = (() => {
4 | let srcDoc;
5 | let prevRunId;
6 | return (files, runId) => {
7 | if (runId !== prevRunId) {
8 | srcDoc = computeSrcDoc(files);
9 | prevRunId = runId;
10 | }
11 | return srcDoc;
12 | };
13 | });
14 |
--------------------------------------------------------------------------------
/packages/imageGenerationService/src/dimensions.js:
--------------------------------------------------------------------------------
1 | const defaultWidth = 960;
2 | const defaultHeight = 500;
3 | const aspectRatio = defaultWidth / defaultHeight;
4 |
5 | const dimensions = width => ({
6 | width,
7 | height: Math.round(width / aspectRatio)
8 | });
9 |
10 | export const thumbnailDimensions = dimensions(230);
11 | export const previewDimensions = dimensions(960);
12 |
--------------------------------------------------------------------------------
/packages/web/redux/rootReducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { uiRedux } from 'vizhub-ui';
3 | import { csrfToken, visualization, showForkInvitation, user } from './reducers';
4 | const { reducers: { ide } } = uiRedux;
5 |
6 | export const rootReducer = combineReducers({
7 | ide,
8 | csrfToken,
9 | visualization,
10 | showForkInvitation,
11 | user
12 | });
13 |
--------------------------------------------------------------------------------
/packages/useCases/src/gatewayInterfaces/userGateway.ts:
--------------------------------------------------------------------------------
1 | import { User, UserId } from 'datavis-tech-entities';
2 |
3 | export interface UserGateway {
4 | createUser(user: User): Promise;
5 | getUser(id: UserId): Promise;
6 | getUserByUserName(userName: string): Promise;
7 |
8 | //saveUser(request: SaveUserRequestModel):
9 | // Promise;
10 | }
11 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/index.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './actionTypes';
2 | import * as actionCreators from './actionCreators';
3 | import * as reducers from './reducers/index';
4 | import * as selectors from './selectors';
5 | import * as epics from './epics/index';
6 |
7 | export const uiRedux = {
8 | actionTypes,
9 | actionCreators,
10 | reducers,
11 | selectors,
12 | epics
13 | };
14 |
--------------------------------------------------------------------------------
/packages/database/src/getUser.js:
--------------------------------------------------------------------------------
1 | import { User } from 'datavis-tech-entities';
2 | import { USER } from './collectionName';
3 | import { fetchShareDBDoc } from './fetchShareDBDoc';
4 |
5 | export const getUser = connection => async (id) => {
6 | try {
7 | const userDoc = await fetchShareDBDoc(USER, id, connection);
8 | return new User(userDoc.data);
9 | } catch (error) {
10 | return null;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/ui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | VizHub UI
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/packages/useCases/src/interactors/exportVisualization/zipFiles.ts:
--------------------------------------------------------------------------------
1 | import { constants } from 'fs';
2 | import * as AdmZip from 'adm-zip';
3 | import { File } from 'datavis-tech-entities';
4 |
5 | export const zipFiles = (files: File[]) => {
6 | const zip = new AdmZip();
7 | files.forEach(file => {
8 | zip.addFile(file.name, Buffer.alloc(file.text.length, file.text));
9 | });
10 | return zip.toBuffer();
11 | }
12 |
--------------------------------------------------------------------------------
/packages/ui/src/visualizationRunner/computeScale.js:
--------------------------------------------------------------------------------
1 | // Shrink and grow to fill available width and height.
2 | export const computeScale = options => {
3 | const { boundsWidth, boundsHeight, width, height } = options;
4 |
5 | const aspect = width / height;
6 | const boundsAspect = boundsWidth / boundsHeight;
7 |
8 | return aspect > boundsAspect
9 | ? boundsWidth / width
10 | : boundsHeight / height;
11 | };
12 |
--------------------------------------------------------------------------------
/packages/database/src/fetchShareDBQuery.js:
--------------------------------------------------------------------------------
1 | import { i18n } from 'datavis-tech-i18n';
2 |
3 | export const fetchShareDBQuery = (collection, mongoQuery, connection) => (
4 | new Promise((resolve, reject) => {
5 | const query = connection
6 | .createFetchQuery(collection, mongoQuery, {}, (error, results) => {
7 | error ? reject(error) : resolve(results);
8 | //query.destroy();
9 | });
10 | })
11 | );
12 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/reducers/splitPaneDragging.js:
--------------------------------------------------------------------------------
1 | import {
2 | SPLIT_PANE_DRAG_STARTED,
3 | SPLIT_PANE_DRAG_FINISHED
4 | } from '../actionTypes';
5 |
6 | export const splitPaneDragging = (state = false, action) => {
7 | switch (action.type) {
8 | case SPLIT_PANE_DRAG_STARTED:
9 | return true;
10 | case SPLIT_PANE_DRAG_FINISHED:
11 | return false;
12 | default:
13 | return state;
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/packages/presenters/src/datasetViewModel.js:
--------------------------------------------------------------------------------
1 | import { Dataset } from 'datavis-tech-entities';
2 |
3 | export class DatasetViewModel {
4 | constructor(dataset) {
5 | this.title = dataset.info.title;
6 | this.slug = dataset.info.slug;
7 | this.format = dataset.info.format;
8 | this.sourceName = dataset.info.sourceName;
9 | this.sourceUrl = dataset.info.sourceUrl;
10 | this.text = dataset.content.text;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/ui/src/atoms/avatar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const defaultScale = 30;
4 | export const Avatar = ({avatarUrl, scale = defaultScale}) => (
5 |
15 | );
16 |
--------------------------------------------------------------------------------
/packages/entities/src/datasetContent.ts:
--------------------------------------------------------------------------------
1 | import { DocumentContent } from './documentContent';
2 | import { DATASET_TYPE } from './documentTypes';
3 |
4 | export class DatasetContent extends DocumentContent {
5 |
6 | // The text content of this dataset.
7 | text: string;
8 |
9 | constructor(data) {
10 | super({
11 | id: data.id,
12 | documentType: DATASET_TYPE
13 | });
14 | this.text = data.text;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/controllers/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "datavis-tech-controllers",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "src/index.js",
6 | "author": "Curran Kelleher",
7 | "license": "MIT",
8 | "dependencies": {
9 | "diff-match-patch": "^1.0.1",
10 | "json0-ot-diff": "^1.0.3",
11 | "datavis-tech-use-cases": "1",
12 | "datavis-tech-presenters": "1",
13 | "datavis-tech-entities": "1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/graveyard/clientServerGatewaySingleton.js:
--------------------------------------------------------------------------------
1 | import ClientGateway from './client';
2 |
3 | // Singleton.
4 | let gateway;
5 |
6 | export const getGateway = () => {
7 | if (!gateway) {
8 | if (process.browser) {
9 | gateway = ClientGateway();
10 | } else {
11 |
12 | // Dynamic require so it doesn't end up in the client bundle.
13 | gateway = require('./server').ServerGateway();
14 | }
15 | }
16 | return gateway;
17 | };
18 |
--------------------------------------------------------------------------------
/packages/ui/src/css/index.sass:
--------------------------------------------------------------------------------
1 | @import "./codemirror-ubuntu-theme";
2 |
3 | $font-size-for-code: 1.2rem
4 | $family-monospace: "Ubuntu Mono", monospace
5 | $family-sans: "Ubuntu", sans-serif
6 |
7 | @import "./codemirror-tweaks.sass";
8 |
9 | $editor-break-small: 700px
10 | $editor-split-border: 1px solid #5e4e59
11 | @import "./editorGrid.sass";
12 | @import "./ideGrid.sass";
13 |
14 | @import "./fullPage.sass";
15 | @import "./visualizationView.sass";
16 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/epics/runEpic.js:
--------------------------------------------------------------------------------
1 | import { debounceTime, mapTo } from 'rxjs/operators';
2 | import { CHANGE_FILE_TEXT, SET_VISUALIZATION_HEIGHT } from '../actionTypes';
3 | import { runFiles } from '../actionCreators';
4 | import { runDebounceTime } from '../../constants';
5 |
6 | export const runEpic = action$ =>
7 | action$.ofType(CHANGE_FILE_TEXT, SET_VISUALIZATION_HEIGHT).pipe(
8 | debounceTime(runDebounceTime),
9 | mapTo(runFiles())
10 | );
11 |
--------------------------------------------------------------------------------
/packages/ui/src/visualizationFullscreen/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { VisualizationRunner } from '../visualizationRunner/index.js';
3 |
4 | export const VisualizationFullscreen = props => {
5 | const { width, height, files } = props;
6 |
7 | return (
8 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/packages/web/redux/epics/runBuildEpic.js:
--------------------------------------------------------------------------------
1 | import { mapTo, debounceTime } from 'rxjs/operators';
2 | import { uiRedux } from 'vizhub-ui';
3 |
4 | const {
5 | actionTypes: {
6 | BUILD_FINISHED,
7 | SET_VISUALIZATION_HEIGHT
8 | },
9 | actionCreators: {
10 | runFiles
11 | }
12 | } = uiRedux;
13 |
14 | export const runBuildEpic = action$ =>
15 | action$.ofType(BUILD_FINISHED, SET_VISUALIZATION_HEIGHT).pipe(
16 | mapTo(runFiles())
17 | );
18 |
--------------------------------------------------------------------------------
/packages/ui/src/visualizationEditor/editorGrid.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const EditorGrid = ({children}) => (
4 |
5 | { children }
6 |
7 | );
8 |
9 | EditorGrid.Left = ({children}) => (
10 |
11 | { children }
12 |
13 | );
14 |
15 | EditorGrid.Center = ({children}) => (
16 |
17 | { children }
18 |
19 | );
20 |
--------------------------------------------------------------------------------
/packages/web/server/setupRaven.js:
--------------------------------------------------------------------------------
1 | // This reports errors to https://sentry.io for alerts.
2 | import Raven from 'raven';
3 |
4 | export const setupRaven = expressApp => {
5 | Raven.config('https://fa728910e7374f7a8be9ef439d153436@sentry.io/1246750').install();
6 | expressApp.use(Raven.requestHandler());
7 |
8 | expressApp.get('/testRaven', (req, res) => {
9 | throw new Error('Broke!');
10 | });
11 |
12 | expressApp.use(Raven.errorHandler());
13 | };
14 |
--------------------------------------------------------------------------------
/packages/web/static/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Example Static File
5 |
6 |
7 | Example Static File
8 | If you want to serve static files - including images, or fonts - just place them in the /static directory.
9 | This file is accessible from /static/example.html.
10 |
11 |
12 |
--------------------------------------------------------------------------------
/graveyard/clientGateway.js:
--------------------------------------------------------------------------------
1 | import ShareDB from '@teamwork/sharedb/lib/client';
2 | import { Gateway } from 'datavis-tech-gateways';
3 | import { Database } from 'datavis-tech-database';
4 |
5 | const ClientGateway = () => {
6 | const socket = new WebSocket('ws://' + window.location.host);
7 | const connection = new ShareDB.Connection(socket);
8 | const database = Database(connection);
9 | return Gateway(database);
10 | };
11 |
12 | export default ClientGateway;
13 |
--------------------------------------------------------------------------------
/packages/database/src/save.js:
--------------------------------------------------------------------------------
1 | import jsonDiff from 'json0-ot-diff';
2 | import diffMatchPatch from 'diff-match-patch';
3 |
4 | export const save = (doc, data) => new Promise((resolve, reject) => {
5 | const callback = error => error
6 | ? reject(error)
7 | : resolve({ status: 'success' });
8 |
9 | if (!doc.type) {
10 | doc.create(data, callback);
11 | } else {
12 | doc.submitOp(jsonDiff(doc.data, data, diffMatchPatch), callback);
13 | }
14 | });
15 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/epics/autoSaveEpic.js:
--------------------------------------------------------------------------------
1 | import { debounceTime, mapTo } from 'rxjs/operators';
2 | import { CHANGE_FILE_TEXT, SET_VISUALIZATION_HEIGHT } from '../actionTypes';
3 | import { save } from '../actionCreators';
4 | import { autoSaveDebounceTime } from '../../constants';
5 |
6 | export const autoSaveEpic = action$ =>
7 | action$.ofType(CHANGE_FILE_TEXT, SET_VISUALIZATION_HEIGHT).pipe(
8 | debounceTime(autoSaveDebounceTime),
9 | mapTo(save())
10 | );
11 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/epics/confirmFileDeleteEpic.js:
--------------------------------------------------------------------------------
1 | import { map, filter } from 'rxjs/operators';
2 | import { DELETE_FILE } from '../actionTypes';
3 | import { fileDeleted } from '../actionCreators';
4 |
5 | export const confirmFileDeleteEpic = action$ =>
6 | action$.ofType(DELETE_FILE).pipe(
7 | map(action => (
8 | window.confirm('Are you sure?')
9 | ? fileDeleted(action.fileName)
10 | : null
11 | )),
12 | filter(Boolean)
13 | );
14 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/epics/promptForNewFileNameEpic.js:
--------------------------------------------------------------------------------
1 | import { map } from 'rxjs/operators';
2 | import { CREATE_NEW_FILE } from '../actionTypes';
3 | import { newFileCreated } from '../actionCreators';
4 |
5 | export const promptForNewFileNameEpic = action$ =>
6 | action$.ofType(CREATE_NEW_FILE).pipe(
7 | map(action => {
8 | const fileName = window.prompt('Please enter a file name', 'myNewFile.js');
9 | return newFileCreated(fileName);
10 | })
11 | );
12 |
--------------------------------------------------------------------------------
/packages/imageGenerationService/src/computeImageDimensions.js:
--------------------------------------------------------------------------------
1 | export const computeImageDimensions = ({ actual, desired }) =>
2 | actual.width / actual.height < desired.width / desired.height
3 | ?
4 | {
5 | width: desired.width,
6 | height: Math.round(actual.height / actual.width * desired.width)
7 | }
8 | :
9 | {
10 | width: Math.round(actual.width / actual.height * desired.height),
11 | height: desired.height
12 | };
13 |
--------------------------------------------------------------------------------
/packages/ui/src/testingApp/saveSimulationEpic.js:
--------------------------------------------------------------------------------
1 | import { delay, mapTo } from 'rxjs/operators';
2 | import { SAVE } from '../redux/actionTypes';
3 | import { saveSuccess } from '../redux/actionCreators';
4 |
5 | // Simulates saving the data to the server, with a delay of 1 second,
6 | // for testing the "Saving..." -> "Saved" part of the UI.
7 | export const saveSimulationEpic = action$ =>
8 | action$.ofType(SAVE).pipe(
9 | delay(1000),
10 | mapTo(saveSuccess())
11 | );
12 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/index.js:
--------------------------------------------------------------------------------
1 | import { visualizationAPIController } from './visualizationAPIController/index';
2 | import { datasetAPIController } from './datasetAPIController/index';
3 | import { userAPIController } from './userAPIController/index';
4 |
5 | export const apiController = (expressApp, gateways) => {
6 | visualizationAPIController(expressApp, gateways);
7 | datasetAPIController(expressApp, gateways);
8 | userAPIController(expressApp, gateways);
9 | };
10 |
--------------------------------------------------------------------------------
/packages/ui/src/exports.js:
--------------------------------------------------------------------------------
1 | export { uiRedux } from './redux/index';
2 | export { IDEContainer } from './ide/index';
3 | export { VisualizationRunner } from './visualizationRunner/index';
4 | export { VisualizationFullscreen } from './visualizationFullscreen/index';
5 | export { computeSrcDoc } from './visualizationRunner/computeSrcDoc';
6 | export { FullPage } from './atoms/fullPage';
7 | export { Avatar } from './atoms/avatar';
8 | export { runDebounceTime, autoSaveDebounceTime } from './constants';
9 |
--------------------------------------------------------------------------------
/graveyard/oldUseCases/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "datavis-tech-use-cases",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "",
6 | "main": "src/index.js",
7 | "scripts": {
8 | "test": "mocha -r esm"
9 | },
10 | "author": "Curran Kelleher",
11 | "license": "UNLICENSED",
12 | "dependencies": {
13 | "datavis-tech-entities": "1",
14 | "datavis-tech-i18n": "1",
15 | "esm": "^3.0.51",
16 | "mocha": "^5.2.0",
17 | "uuid": "^3.2.1"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/ui/rollup.config.js:
--------------------------------------------------------------------------------
1 | import buble from 'rollup-plugin-buble';
2 |
3 | export default {
4 | input: 'src/exports.js',
5 | external: [
6 | 'react',
7 | 'react-measure',
8 | 'magic-sandbox',
9 | 'react-codemirror2',
10 | 'redux',
11 | 'classnames',
12 | 'react-redux',
13 | 'lodash/fp/unionBy',
14 | 'rxjs/operators'
15 | ],
16 | plugins: [
17 | buble()
18 | ],
19 | output: [
20 | { file: 'dist/index.js', format: 'cjs' }
21 | ]
22 | };
23 |
--------------------------------------------------------------------------------
/packages/entities/src/documentPart.ts:
--------------------------------------------------------------------------------
1 | import { DocumentId } from './documentId';
2 |
3 | export class DocumentPart {
4 |
5 | // The unique ID of the document
6 | // that this document part is a part of.
7 | id: DocumentId;
8 |
9 | // The type of this document.
10 | // Either documentTypes.VISUALIZATION_TYPE or documentTypes.DATASET_TYPE
11 | documentType: string;
12 |
13 | constructor(data) {
14 | this.id = data.id;
15 | this.documentType = data.documentType;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/datasetAPIController/index.js:
--------------------------------------------------------------------------------
1 | import { createDatasetController } from './createDatasetController';
2 | import { getDatasetController } from './getDatasetController';
3 | // import { saveDatasetController } from './saveDatasetController';
4 |
5 | export const datasetAPIController = (expressApp, gateways) => {
6 | createDatasetController(expressApp, gateways);
7 | getDatasetController(expressApp, gateways);
8 | // saveDatasetController(expressApp, datasetGateway);
9 | };
10 |
--------------------------------------------------------------------------------
/packages/imageGenerationService/src/resize.js:
--------------------------------------------------------------------------------
1 | import sharp from 'sharp';
2 | import { computeImageDimensions } from './computeImageDimensions';
3 |
4 | export const resize = async options => {
5 | const {
6 | actualDimensions: actual,
7 | desiredDimensions: desired,
8 | screenshotBuffer
9 | } = options;
10 |
11 | const { width, height } = computeImageDimensions({ actual, desired });
12 |
13 | return await sharp(screenshotBuffer)
14 | .resize(width, height)
15 | .toBuffer();
16 | };
17 |
--------------------------------------------------------------------------------
/packages/ui/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
23 | yarn.lock
24 |
25 | # editor turd
26 | *.swp
27 |
28 | # built files
29 | dist
30 |
31 | package-lock.json
32 |
--------------------------------------------------------------------------------
/packages/presenters/src/visualizationViewModel.js:
--------------------------------------------------------------------------------
1 | import { Visualization, Files, DocumentId } from 'datavis-tech-entities';
2 |
3 | export class VisualizationViewModel {
4 | constructor(visualization) {
5 | this.id = visualization.id;
6 | this.files = visualization.content.files;
7 | this.title = visualization.info.title;
8 | this.description = visualization.info.description;
9 | this.width = 960; // visualization.info.width;
10 | this.height = visualization.info.height || 500;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/web/redux/epics/forkSuccessEpic.js:
--------------------------------------------------------------------------------
1 | import Router from 'next/router';
2 | import { map, filter } from 'rxjs/operators';
3 | import { uiRedux } from 'vizhub-ui';
4 | import { visualizationRoute } from '../../routes/routeGenerators';
5 |
6 | const {
7 | actionTypes: {
8 | FORK_SUCCESS
9 | }
10 | } = uiRedux;
11 |
12 | export const forkSuccessEpic = (action$, state$) =>
13 | action$.ofType(FORK_SUCCESS).pipe(
14 | map(action => Router.push(visualizationRoute(action))),
15 | filter(Boolean)
16 | );
17 |
--------------------------------------------------------------------------------
/packages/database/src/fetchShareDBDoc.js:
--------------------------------------------------------------------------------
1 | import { i18n } from 'datavis-tech-i18n';
2 |
3 | export const fetchShareDBDoc = (collection, id, connection) => (
4 | new Promise((resolve, reject) => {
5 | const shareDBDoc = connection.get(collection, id);
6 | shareDBDoc.fetch(error => {
7 | error ? reject(error) : resolve(shareDBDoc);
8 | // : shareDBDoc.type
9 | // ? resolve(shareDBDoc)
10 | // : reject({ message: i18n('errorDocNotFound'), statusCode: 404 });
11 | });
12 | })
13 | );
14 |
--------------------------------------------------------------------------------
/packages/web/redux/actionTypes.js:
--------------------------------------------------------------------------------
1 | const prefix = name => `vizhub-app/${name}`;
2 |
3 | export const START_BUILD = prefix('START_BUILD');
4 | export const BUILD_ERROR = prefix('BUILD_ERROR');
5 | export const SET_CSRF_TOKEN = prefix('SET_CSRF_TOKEN');
6 | export const SET_VISUALIZATION = prefix('SET_VISUALIZATION');
7 | export const VISUALIZATION_DELETE_SUCCESS = prefix('VISUALIZATION_DELETE_SUCCESS');
8 | export const VISUALIZATION_DELETE_ERROR = prefix('VISUALIZATION_DELETE_ERROR');
9 | export const SET_USER = prefix('SET_USER');
10 |
--------------------------------------------------------------------------------
/packages/web/redux/epics/startBuildEpic.js:
--------------------------------------------------------------------------------
1 | import { startBuild } from '../actionCreators';
2 | import { uiRedux, runDebounceTime } from 'vizhub-ui';
3 | import { mapTo, debounceTime, filter } from 'rxjs/operators';
4 |
5 | const {
6 | actionTypes: {
7 | CHANGE_FILE_TEXT
8 | }
9 | } = uiRedux;
10 |
11 | export const startBuildEpic = action$ =>
12 | action$.ofType(CHANGE_FILE_TEXT).pipe(
13 | filter(action => action.fileName.endsWith('.js')),
14 | debounceTime(runDebounceTime),
15 | mapTo(startBuild())
16 | );
17 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/epics/confirmVisualizationDeleteEpic.js:
--------------------------------------------------------------------------------
1 | import { map, filter } from 'rxjs/operators';
2 | import { DELETE_VISUALIZATION } from '../actionTypes';
3 | import { visualizationDeleted } from '../actionCreators';
4 |
5 | export const confirmVisualizationDeleteEpic = action$ =>
6 | action$.ofType(DELETE_VISUALIZATION).pipe(
7 | map(action => (
8 | window.confirm('Are you sure you want to delete this visualization?')
9 | ? visualizationDeleted()
10 | : null
11 | )),
12 | filter(Boolean)
13 | );
14 |
--------------------------------------------------------------------------------
/packages/web/css/variables.sass:
--------------------------------------------------------------------------------
1 | $family-sans-serif: "Ubuntu", sans-serif
2 | $family-monospace: "Ubuntu Mono", monospace
3 | $body-background-color: hsl(0, 0%, 98%)
4 | $radius: 0px
5 | $radius-large: 0px
6 | $input-shadow: none
7 |
8 | @import "../node_modules/bulma/sass/utilities/initial-variables"
9 | @import "../node_modules/bulma/sass/utilities/functions"
10 |
11 | $link: #005261
12 | $link-visited: $grey
13 | $link-focus-border: $grey
14 | $box-shadow: 0 0 0 1px $grey-lighter
15 |
16 | @import "../node_modules/bulma/sass/utilities/derived-variables"
17 |
--------------------------------------------------------------------------------
/packages/web/routes/index.js:
--------------------------------------------------------------------------------
1 | import NextRoutes from 'next-routes';
2 |
3 | export const routes = NextRoutes()
4 | .add('auth/callback', '/auth/callback')
5 | .add('auth/error', '/auth/error')
6 | .add('auth', '/auth')
7 | .add('create-visualization', '/create-visualization')
8 | .add('upload-dataset', '/upload-dataset')
9 | .add('thumbnailsTest', '/thumbnailsTest')
10 | .add('profile', '/:userName')
11 | .add('fullscreen', '/:userName/:id/fullscreen')
12 | .add('visualization', '/:userName/:id')
13 | .add('dataset', '/:userName/datasets/:slug')
14 |
--------------------------------------------------------------------------------
/packages/web/components/atoms/titledPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Head from 'next/head';
3 | import { CommentLink } from './commentLink';
4 |
5 | export const TitledPage = ({title, children, disableCommentLink}) => {
6 | if (!title) {
7 | throw new Error('The "title" prop is required for TitledPage.');
8 | }
9 | return (
10 |
11 |
12 | {title}
13 |
14 | { children }
15 | { disableCommentLink ? null : }
16 |
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/packages/web/redux/epics/runNonJS.js:
--------------------------------------------------------------------------------
1 | import { mapTo, filter, debounceTime } from 'rxjs/operators';
2 | import { uiRedux, runDebounceTime } from 'vizhub-ui';
3 |
4 | const {
5 | actionTypes: {
6 | CHANGE_FILE_TEXT
7 | },
8 | actionCreators: {
9 | runFiles
10 | }
11 | } = uiRedux;
12 |
13 | export const runNonJSEpic = action$ =>
14 | action$.ofType(CHANGE_FILE_TEXT).pipe(
15 | filter(action => !action.fileName.endsWith('.js')),
16 | filter(action => action.fileName !== 'README.md'),
17 | debounceTime(runDebounceTime),
18 | mapTo(runFiles())
19 | );
20 |
--------------------------------------------------------------------------------
/packages/database/src/getUserByUserName.js:
--------------------------------------------------------------------------------
1 | import { i18n } from 'datavis-tech-i18n';
2 | import { USER } from './collectionName';
3 | import { fetchShareDBQuery } from './fetchShareDBQuery';
4 |
5 | export const getUserByUserName = connection => async (userName) => {
6 | const mongoQuery = { userName };
7 | const results = await fetchShareDBQuery(USER, mongoQuery, connection);
8 | if (results.length === 0) {
9 | throw {
10 | message: i18n('errorUserNotFound'),
11 | statusCode: 404
12 | };
13 | }
14 | const userDoc = results[0];
15 | return userDoc.data;
16 | }
17 |
--------------------------------------------------------------------------------
/packages/database/src/createDataset.js:
--------------------------------------------------------------------------------
1 | import { DOCUMENT_INFO, DOCUMENT_CONTENT } from './collectionName';
2 |
3 | export const createDataset = connection => dataset => {
4 | const id = dataset.id;
5 | const slug = dataset.info.slug;
6 |
7 | return new Promise(resolve => {
8 |
9 | // TODO handle errors here.
10 | connection.get(DOCUMENT_INFO, id).create(dataset.info);
11 | connection.get(DOCUMENT_CONTENT, id).create(dataset.content);
12 |
13 | // TODO only resolve after document created,
14 | // to avoid race conditions.
15 | resolve({ slug });
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/packages/web/redux/epics/deleteVisualizationSuccessEpic.js:
--------------------------------------------------------------------------------
1 | import Router from 'next/router';
2 | import { map, filter } from 'rxjs/operators';
3 | import { profileRoute } from '../../routes/routeGenerators';
4 | import { getUserName } from '../selectors';
5 | import { VISUALIZATION_DELETE_SUCCESS } from '../actionTypes';
6 |
7 | export const deleteVisualizationSuccessEpic = (action$, state$) =>
8 | action$.ofType(VISUALIZATION_DELETE_SUCCESS).pipe(
9 | map(action => Router.push(profileRoute({
10 | userName: getUserName(state$.value)
11 | }))),
12 | filter(Boolean)
13 | );
14 |
--------------------------------------------------------------------------------
/packages/database/src/saveVisualization.js:
--------------------------------------------------------------------------------
1 | import { DOCUMENT_INFO, DOCUMENT_CONTENT } from './collectionName';
2 | import { fetchShareDBDoc } from './fetchShareDBDoc';
3 | import { save } from './save';
4 |
5 | export const saveVisualization = connection => ({ visualization }) => (
6 | Promise.all([
7 | fetchShareDBDoc(DOCUMENT_INFO, visualization.id, connection),
8 | fetchShareDBDoc(DOCUMENT_CONTENT, visualization.id, connection)
9 | ])
10 | .then(([info, content]) => Promise.all([
11 | save(info, visualization.info),
12 | save(content, visualization.content)
13 | ]))
14 | );
15 |
--------------------------------------------------------------------------------
/packages/entities/src/dataset.ts:
--------------------------------------------------------------------------------
1 | import { DatasetInfo } from './datasetInfo';
2 | import { DatasetContent } from './datasetContent';
3 | import { DocumentId } from './documentId';
4 |
5 | export class Dataset {
6 |
7 | // The unique ID of this dataset.
8 | id: DocumentId;
9 |
10 | // The info part of this dataset.
11 | info: DatasetInfo;
12 |
13 | // The content part of this dataset.
14 | content: DatasetContent;
15 |
16 | constructor(data) {
17 | this.id = data.datasetInfo.id;
18 | this.info = data.datasetInfo;
19 | this.content = data.datasetContent;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/database/src/getDatasetInfosByUserId.js:
--------------------------------------------------------------------------------
1 | import { i18n } from 'datavis-tech-i18n';
2 | import { DatasetInfo, DATASET_TYPE } from 'datavis-tech-entities';
3 | import { DOCUMENT_INFO } from './collectionName';
4 | import { fetchShareDBQuery } from './fetchShareDBQuery';
5 |
6 | export const getDatasetInfosByUserId = connection => async (id) => {
7 | const mongoQuery = {
8 | owner: id,
9 | documentType: DATASET_TYPE
10 | };
11 | const results = await fetchShareDBQuery(DOCUMENT_INFO, mongoQuery, connection);
12 | return results.map(shareDBDoc => new DatasetInfo(shareDBDoc.data));
13 | }
14 |
--------------------------------------------------------------------------------
/packages/database/src/getAllVisualizationInfos.js:
--------------------------------------------------------------------------------
1 | import { i18n } from 'datavis-tech-i18n';
2 | import { VisualizationInfo, VISUALIZATION_TYPE } from 'datavis-tech-entities';
3 | import { DOCUMENT_INFO } from './collectionName';
4 | import { fetchShareDBQuery } from './fetchShareDBQuery';
5 |
6 | export const getAllVisualizationInfos = connection => async () => {
7 | const mongoQuery = {
8 | documentType: VISUALIZATION_TYPE
9 | };
10 | const results = await fetchShareDBQuery(DOCUMENT_INFO, mongoQuery, connection);
11 | return results.map(shareDBDoc => new VisualizationInfo(shareDBDoc.data));
12 | }
13 |
--------------------------------------------------------------------------------
/packages/serverGateways/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vizhub-server-gateways",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "src/index.js",
6 | "scripts": {
7 | },
8 | "author": "",
9 | "license": "UNLICENSED",
10 | "dependencies": {
11 | "@teamwork/sharedb": "^2.0.1",
12 | "@teamwork/sharedb-mingo-memory": "^1.0.5",
13 | "@teamwork/sharedb-mongo": "^2.0.3",
14 | "@teamwork/websocket-json-stream": "^1.1.0",
15 | "mongodb": "^3.1.3",
16 | "datavis-tech-gateways": "1",
17 | "datavis-tech-database": "1"
18 | },
19 | "devDependencies": {
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/epics/index.js:
--------------------------------------------------------------------------------
1 | export { runEpic } from './runEpic';
2 | export { autoSaveEpic } from './autoSaveEpic';
3 | export { promptForNewFileNameEpic } from './promptForNewFileNameEpic';
4 | export { promptForRenameEpic } from './promptForRenameEpic';
5 | export { promptForNewHeightEpic } from './promptForNewHeightEpic';
6 | export { confirmFileDeleteEpic } from './confirmFileDeleteEpic';
7 | export { confirmVisualizationDeleteEpic } from './confirmVisualizationDeleteEpic';
8 | export { updateTitleEpic } from './updateTitleEpic';
9 | export { updateDescriptionEpic } from './updateDescriptionEpic';
10 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/reducers/saveStatus.js:
--------------------------------------------------------------------------------
1 | import {
2 | CHANGE_FILE_TEXT,
3 | SAVE,
4 | SAVE_SUCCESS,
5 | SAVE_ERROR,
6 | SET_VISUALIZATION_HEIGHT
7 | } from '../actionTypes';
8 |
9 | export const saveStatus = (state = 'Saved', action) => {
10 | switch (action.type) {
11 | case SAVE:
12 | return 'Saving';
13 | case SAVE_SUCCESS:
14 | return 'Saved';
15 | case SAVE_ERROR:
16 | return action.error;
17 | case CHANGE_FILE_TEXT:
18 | return '';
19 | case SET_VISUALIZATION_HEIGHT:
20 | return '';
21 | default:
22 | return state;
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/reducers/activeFileName.js:
--------------------------------------------------------------------------------
1 | import { SET_ACTIVE_FILE, FILE_RENAMED, FILE_DELETED } from '../actionTypes';
2 |
3 | export const activeFileName = (state = null, action) => {
4 | switch (action.type) {
5 | case SET_ACTIVE_FILE:
6 | return action.fileName;
7 | case FILE_RENAMED:
8 | if (state === action.oldFileName) {
9 | return action.newFileName;
10 | }
11 | break;
12 | case FILE_DELETED:
13 | if (state === action.fileName) {
14 | return 'index.html';
15 | }
16 | break;
17 | default:
18 | return state;
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/epics/promptForNewHeightEpic.js:
--------------------------------------------------------------------------------
1 | import { map } from 'rxjs/operators';
2 | import { SET_HEIGHT_PROMPT } from '../actionTypes';
3 | import { setVisualizationHeight } from '../actionCreators';
4 | import { getVisualizationHeight } from '../selectors';
5 |
6 | export const promptForNewHeightEpic = (action$, state$) =>
7 | action$.ofType(SET_HEIGHT_PROMPT).pipe(
8 | map(() => {
9 | const oldHeight = getVisualizationHeight(state$.value);
10 | const newHeight = window.prompt('Please enter a new height', oldHeight);
11 | return setVisualizationHeight(+newHeight);
12 | })
13 | );
14 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/visualizationAPIController/getVisualizationController.js:
--------------------------------------------------------------------------------
1 | import { GetVisualization } from 'datavis-tech-use-cases';
2 |
3 | export const getVisualizationController = (expressApp, gateways) => {
4 | const getVisualization = new GetVisualization(gateways);
5 | expressApp.get('/api/visualization/get/:id', async (req, res) => {
6 | try {
7 | const requestModel = { id: req.params.id };
8 | const responseModel = await getVisualization.execute(requestModel);
9 | res.json(responseModel);
10 | } catch (error) {
11 | res.json({ error })
12 | }
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/packages/entities/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "datavis-tech-entities",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "",
6 | "main": "dist/index.js",
7 | "scripts": {
8 | "build-ts": "tsc",
9 | "postinstall": "npm run build-ts",
10 | "test": "mocha -r ts-node/register test/**/test.ts"
11 | },
12 | "author": "Curran Kelleher",
13 | "license": "UNLICENSED",
14 | "dependencies": {},
15 | "devDependencies": {
16 | "@types/mocha": "^5.2.5",
17 | "@types/node": "^10.7.0",
18 | "mocha": "^5.2.0",
19 | "ts-node": "^7.0.1",
20 | "typescript": "^3.0.1"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/epics/promptForRenameEpic.js:
--------------------------------------------------------------------------------
1 | import { map, filter } from 'rxjs/operators';
2 | import { RENAME_FILE } from '../actionTypes';
3 | import { fileRenamed } from '../actionCreators';
4 |
5 | export const promptForRenameEpic = action$ =>
6 | action$.ofType(RENAME_FILE).pipe(
7 | map(action => {
8 | const oldFileName = action.fileName;
9 | const newFileName = window.prompt('Please enter a new file name.', oldFileName);
10 | return newFileName && newFileName !== oldFileName
11 | ? fileRenamed(oldFileName, newFileName)
12 | : null;
13 | }),
14 | filter(Boolean)
15 | );
16 |
--------------------------------------------------------------------------------
/packages/ui/src/visualizationRunner/computeSrcDoc.js:
--------------------------------------------------------------------------------
1 | import magicSandbox from 'magic-sandbox';
2 |
3 | const template = files => {
4 | const indexHtml = files.find(file => file.name === 'index.html');
5 | return indexHtml ? indexHtml.text : '';
6 | };
7 |
8 | const transform = files => (
9 | files
10 | .filter(file => file.name !== 'index.html')
11 | .reduce((accumulator, file) => {
12 | accumulator[file.name] = {
13 | content: file.text
14 | };
15 | return accumulator;
16 | }, {})
17 | );
18 |
19 | export const computeSrcDoc = files => (
20 | magicSandbox(template(files), transform(files))
21 | );
22 |
--------------------------------------------------------------------------------
/packages/database/src/setImagesUpdatedTimestamp.js:
--------------------------------------------------------------------------------
1 | import jsonDiff from 'json0-ot-diff';
2 | import diffMatchPatch from 'diff-match-patch';
3 | import { DOCUMENT_INFO } from './collectionName';
4 | import { fetchShareDBDoc } from './fetchShareDBDoc';
5 | import { save } from './save';
6 |
7 | export const setImagesUpdatedTimestamp = connection => options => {
8 | const { id, imagesUpdatedTimestamp } = options;
9 |
10 | return fetchShareDBDoc(DOCUMENT_INFO, id, connection)
11 | .then(doc => {
12 | const newData = Object.assign({}, doc.data, { imagesUpdatedTimestamp });
13 | return save(doc, newData);
14 | });
15 |
16 | };
17 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/visualizationAPIController/getAllVisualizationInfosController.js:
--------------------------------------------------------------------------------
1 | import { GetAllVisualizationInfos } from 'datavis-tech-use-cases';
2 |
3 | export const getAllVisualizationInfosController = (expressApp, gateways) => {
4 | const getAllVisualizationInfos = new GetAllVisualizationInfos(gateways);
5 | expressApp.get('/api/visualization/metadata', async (req, res) => {
6 | try {
7 | const { visualizationInfos } = await getAllVisualizationInfos.execute();
8 | res.json(visualizationInfos);
9 | } catch (error) {
10 | console.log(error);
11 | res.json({ error })
12 | }
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/packages/database/src/getPreview.js:
--------------------------------------------------------------------------------
1 | import jsonDiff from 'json0-ot-diff';
2 | import diffMatchPatch from 'diff-match-patch';
3 | import { PREVIEW_IMAGES } from './collectionName';
4 | import { fetchShareDBDoc } from './fetchShareDBDoc';
5 |
6 | export const getPreview = connection => options => {
7 | const { id } = options;
8 | const doc = connection.get(PREVIEW_IMAGES, id);
9 | return new Promise((resolve, reject) => {
10 | doc.fetch(error => {
11 | if (!doc.type) {
12 | reject(new Error('Preview does not exist for document ' + id));
13 | } else {
14 | resolve(doc.data);
15 | }
16 | });
17 | });
18 | };
19 |
--------------------------------------------------------------------------------
/packages/presenters/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "datavis-tech-presenters",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "dist/index.js",
6 | "private": true,
7 | "scripts": {
8 | "test": "mocha -r esm test/test.js"
9 | },
10 | "author": "Curran Kelleher",
11 | "license": "UNLICENSED",
12 | "dependencies": {
13 | "datavis-tech-entities": "1",
14 | "datavis-tech-use-cases": "1",
15 | "mocha": "^5.2.0",
16 | "path-posix": "^1.0.0",
17 | "rollup": "^1.1.2",
18 | "rollup-plugin-buble": "^0.19.6"
19 | },
20 | "devDependencies": {
21 | "esm": "^3.2.4",
22 | "mocha": "^5.2.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/database/src/getThumbnail.js:
--------------------------------------------------------------------------------
1 | import jsonDiff from 'json0-ot-diff';
2 | import diffMatchPatch from 'diff-match-patch';
3 | import { THUMBNAIL_IMAGES } from './collectionName';
4 | import { fetchShareDBDoc } from './fetchShareDBDoc';
5 |
6 | export const getThumbnail = connection => options => {
7 | const { id } = options;
8 | const doc = connection.get(THUMBNAIL_IMAGES, id);
9 | return new Promise((resolve, reject) => {
10 | doc.fetch(error => {
11 | if (!doc.type) {
12 | reject(new Error('Thumbnail does not exist for document ' + id));
13 | } else {
14 | resolve(doc.data);
15 | }
16 | });
17 | });
18 | };
19 |
--------------------------------------------------------------------------------
/packages/database/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "datavis-tech-database",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "src/index.js",
6 | "private": true,
7 | "scripts": {
8 | "test": "mocha -r esm"
9 | },
10 | "author": "Curran Kelleher",
11 | "license": "UNLICENSED",
12 | "dependencies": {
13 | "@teamwork/sharedb": "^2.0.1",
14 | "@teamwork/sharedb-mingo-memory": "^1.0.5",
15 | "datavis-tech-entities": "1",
16 | "datavis-tech-gateways": "1",
17 | "datavis-tech-i18n": "1",
18 | "diff-match-patch": "^1.0.1",
19 | "esm": "^3.0.76",
20 | "json0-ot-diff": "^1.0.3",
21 | "mocha": "^5.2.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/entities/src/visualization.ts:
--------------------------------------------------------------------------------
1 | import { VisualizationInfo } from './visualizationInfo';
2 | import { VisualizationContent } from './visualizationContent';
3 | import { DocumentId } from './documentId';
4 |
5 | export class Visualization {
6 |
7 | // The unique ID of this visualization.
8 | id: DocumentId;
9 |
10 | // The info part of this visualization.
11 | info: VisualizationInfo;
12 |
13 | // The content part of this visualization.
14 | content: VisualizationContent;
15 |
16 | constructor(data) {
17 | this.id = data.visualizationInfo.id;
18 | this.info = data.visualizationInfo;
19 | this.content = data.visualizationContent;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/gateways/src/databaseUserGateway.ts:
--------------------------------------------------------------------------------
1 | import { UserGateway } from 'datavis-tech-use-cases';
2 |
3 | import { User, UserId } from 'datavis-tech-entities';
4 |
5 | export class DatabaseUserGateway implements UserGateway {
6 | database: any;
7 |
8 | constructor(database) {
9 | this.database = database;
10 | }
11 |
12 | async createUser(user: User): Promise {
13 | return await this.database.createUser(user);
14 | }
15 |
16 | async getUser(id: UserId) {
17 | return await this.database.getUser(id);
18 | }
19 |
20 | async getUserByUserName(userName: string) {
21 | return await this.database.getUserByUserName(userName);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/i18n/index.ts:
--------------------------------------------------------------------------------
1 | const strings = {
2 | errorNoOwner: {
3 | en: 'No owner specified, cannot create document.'
4 | },
5 | errorNoId: {
6 | en: 'No id specified, cannot get document.'
7 | },
8 | errorDocNotFound: {
9 | en: 'The requested document does not exist'
10 | },
11 | errorUserNotFound: {
12 | en: 'The requested user does not exist'
13 | },
14 | errorNotOwnerCantSave: {
15 | en: 'Not saved'
16 | },
17 | errorNotOwnerCantDelete: {
18 | en: 'You must be the owner in order to delete.'
19 | }
20 | };
21 |
22 | export const defaultLocale = 'en';
23 |
24 | export const i18n = (name, locale = defaultLocale) => strings[name][locale];
25 |
--------------------------------------------------------------------------------
/packages/gateways/src/databaseImageStorageGateway.ts:
--------------------------------------------------------------------------------
1 | import { ImageStorageGateway } from 'datavis-tech-use-cases';
2 |
3 | import { Images, DocumentId } from 'datavis-tech-entities';
4 |
5 | export class DatabaseImageStorageGateway implements ImageStorageGateway {
6 | database: any;
7 |
8 | constructor(database) {
9 | this.database = database;
10 | }
11 |
12 | async updateImages(options){
13 | return await this.database.updateImages(options);
14 | }
15 |
16 | async getThumbnail(options){
17 | return await this.database.getThumbnail(options);
18 | }
19 |
20 | async getPreview(options){
21 | return await this.database.getPreview(options);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/web/server/shareDBServer.js:
--------------------------------------------------------------------------------
1 | //import WebSocket from 'ws';
2 | //import JSONStream from '@teamwork/websocket-json-stream';
3 | //import { getShareDB } from './shareDB';
4 | //
5 | // This is for later, when we use ShareDB in the client side.
6 |
7 | const start = () => {
8 | throw new Error('ShareDB server not implemented');
9 | // TODO figure out where to put this. Separate package?
10 | // const webSocketServer = new WebSocket.Server({
11 | // server: httpServer
12 | // });
13 |
14 | // webSocketServer.on('connection', webSocket => {
15 | // getShareDB().listen(new JSONStream(webSocket));
16 | // });
17 | };
18 |
19 | export const ShareDBServer = { start };
20 |
--------------------------------------------------------------------------------
/packages/database/src/getVisualization.js:
--------------------------------------------------------------------------------
1 | import {
2 | Visualization,
3 | VisualizationInfo,
4 | VisualizationContent
5 | } from 'datavis-tech-entities';
6 | import { DOCUMENT_INFO, DOCUMENT_CONTENT } from './collectionName';
7 | import { fetchShareDBDoc } from './fetchShareDBDoc';
8 |
9 | export const getVisualization = connection => ({ id }) => (
10 | Promise.all([
11 | fetchShareDBDoc(DOCUMENT_INFO, id, connection),
12 | fetchShareDBDoc(DOCUMENT_CONTENT, id, connection)
13 | ])
14 | .then(([info, content]) => new Visualization({
15 | visualizationInfo: new VisualizationInfo(info.data),
16 | visualizationContent: new VisualizationContent(content.data)
17 | }))
18 | );
19 |
--------------------------------------------------------------------------------
/packages/database/src/getVisualizationInfosByUserId.js:
--------------------------------------------------------------------------------
1 | import { i18n } from 'datavis-tech-i18n';
2 | import { VisualizationInfo, VISUALIZATION_TYPE } from 'datavis-tech-entities';
3 | import { DOCUMENT_INFO } from './collectionName';
4 | import { fetchShareDBQuery } from './fetchShareDBQuery';
5 |
6 | export const getVisualizationInfosByUserId = connection => async (id) => {
7 | const mongoQuery = {
8 | owner: id,
9 | documentType: VISUALIZATION_TYPE
10 | };
11 | const results = await fetchShareDBQuery(DOCUMENT_INFO, mongoQuery, connection);
12 | return results
13 | .map(shareDBDoc => new VisualizationInfo(shareDBDoc.data))
14 | .reverse(); // Show most recent first
15 | }
16 |
--------------------------------------------------------------------------------
/packages/database/src/updateImages.js:
--------------------------------------------------------------------------------
1 | import jsonDiff from 'json0-ot-diff';
2 | import diffMatchPatch from 'diff-match-patch';
3 | import { THUMBNAIL_IMAGES, PREVIEW_IMAGES } from './collectionName';
4 | import { fetchShareDBDoc } from './fetchShareDBDoc';
5 | import { save } from './save';
6 |
7 | export const updateImages = connection => options => {
8 | const { id, images } = options;
9 | Promise.all([
10 | fetchShareDBDoc(THUMBNAIL_IMAGES, id, connection),
11 | fetchShareDBDoc(PREVIEW_IMAGES, id, connection)
12 | ])
13 | .then(([thumbnailDoc, previewDoc]) => Promise.all([
14 | save(thumbnailDoc, images.thumbnail),
15 | save(previewDoc, images.preview)
16 | ]))
17 | };
18 |
--------------------------------------------------------------------------------
/packages/serverGateways/src/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | DatabaseVisualizationGateway,
3 | DatabaseDatasetGateway,
4 | DatabaseUserGateway,
5 | DatabaseImageStorageGateway
6 | } from 'datavis-tech-gateways';
7 | import { Database } from 'datavis-tech-database';
8 | import { getConnection } from './shareDB';
9 |
10 | export const serverGateways = () => {
11 | const database = Database(getConnection());
12 | return {
13 | visualizationGateway: new DatabaseVisualizationGateway(database),
14 | datasetGateway: new DatabaseDatasetGateway(database),
15 | userGateway: new DatabaseUserGateway(database),
16 | imageStorageGateway: new DatabaseImageStorageGateway(database),
17 | };
18 | };
19 |
--------------------------------------------------------------------------------
/packages/web/css/visualizationPreview.sass:
--------------------------------------------------------------------------------
1 | // Inspired by https://vega.github.io/vega/
2 | .visualization-preview
3 | display: inline-block
4 | margin: 10px
5 | width: 230px
6 |
7 | .visualization-preview-image
8 | height: 120px
9 | padding: 0px
10 | background-position: left top
11 | background-size: cover
12 | overflow: hidden
13 | position: relative
14 | transition: background-position 3s
15 | outline: 1px solid #ddd
16 |
17 | .visualization-preview:hover .visualization-preview-image
18 | background-position: right bottom
19 |
20 | .visualization-preview-title
21 | width: 230px
22 | overflow: hidden
23 | white-space: nowrap
24 | text-overflow: ellipsis
25 | font-size: 0.8em
26 |
--------------------------------------------------------------------------------
/packages/imageGenerationService/src/generateScreenshot.js:
--------------------------------------------------------------------------------
1 | import { computeSrcDoc } from 'vizhub-ui';
2 | import puppeteer from 'puppeteer';
3 |
4 | export const generateScreenshot = async ({ visualizationViewModel, waitTime }) => {
5 | const { width, height, files } = visualizationViewModel;
6 |
7 | const html = computeSrcDoc(files);
8 |
9 | const browser = await puppeteer.launch();
10 | const page = await browser.newPage();
11 |
12 | await page.setViewport({ width, height });
13 | await page.setContent(html);
14 | await page.waitFor(waitTime);
15 |
16 | const screenshotBuffer = await page.screenshot();
17 | await page.close();
18 | await browser.close();
19 |
20 | return screenshotBuffer;
21 | };
22 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/visualizationAPIController/createVisualizationController.js:
--------------------------------------------------------------------------------
1 | import { CreateVisualization } from 'datavis-tech-use-cases';
2 | import { userIdFromReq } from '../userIdFromReq';
3 |
4 | export const createVisualizationController = (expressApp, gateways) => {
5 | const createVisualization = new CreateVisualization(gateways);
6 |
7 | expressApp.get('/api/visualization/create', async (req, res) => {
8 | try {
9 | const requestModel = { owner: userIdFromReq(req) };
10 | const responseModel = await createVisualization.execute(requestModel);
11 | res.json(responseModel);
12 | } catch (error) {
13 | res.json({ error: error.message });
14 | }
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/packages/controllers/src/userController.js:
--------------------------------------------------------------------------------
1 | import { User } from 'datavis-tech-entities';
2 | import { CreateUser, GetUser } from 'datavis-tech-use-cases';
3 |
4 | export const userController = userGateway => {
5 | const createUser = new CreateUser({ userGateway });
6 | const getUser = new GetUser({ userGateway });
7 | return {
8 | createUser: async (_, oAuthProfile) => {
9 | const requestModel = { oAuthProfile };
10 | const responseModel = await createUser.execute(requestModel);
11 | return responseModel.user;
12 | },
13 | getUser: async ({ id }) => {
14 | const requestModel = { id };
15 | const responseModel = await getUser.execute(requestModel);
16 | return responseModel.user;
17 | }
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/packages/database/src/deleteVisualization.js:
--------------------------------------------------------------------------------
1 | import { DOCUMENT_INFO, DOCUMENT_CONTENT } from './collectionName';
2 | import { fetchShareDBDoc } from './fetchShareDBDoc';
3 |
4 | export const deleteDoc = doc => new Promise((resolve, reject) => {
5 | doc.del(error => {
6 | error ? reject(error) : resolve({ status: 'success' })
7 | });
8 | });
9 |
10 | export const deleteVisualization = connection => ({ id }) => {
11 | return Promise
12 | .all([
13 | fetchShareDBDoc(DOCUMENT_INFO, id, connection),
14 | fetchShareDBDoc(DOCUMENT_CONTENT, id, connection)
15 | ])
16 | .then(([info, content]) => Promise.all([
17 | deleteDoc(info),
18 | deleteDoc(content)
19 | ]))
20 | .then(() => ({ status: 'success' }));
21 | };
22 |
--------------------------------------------------------------------------------
/packages/useCases/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "datavis-tech-use-cases",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "",
6 | "main": "dist/index.js",
7 | "scripts": {
8 | "build-ts": "tsc",
9 | "postinstall": "npm run build-ts",
10 | "test": "mocha -r ts-node/register test/**/test.ts"
11 | },
12 | "author": "Curran Kelleher",
13 | "license": "UNLICENSED",
14 | "dependencies": {
15 | "adm-zip": "^0.4.11",
16 | "datavis-tech-entities": "1",
17 | "datavis-tech-i18n": "1",
18 | "uuid": "^3.3.2"
19 | },
20 | "devDependencies": {
21 | "@types/mocha": "^5.2.5",
22 | "@types/node": "^10.7.0",
23 | "mocha": "^5.2.0",
24 | "ts-node": "^7.0.1",
25 | "typescript": "^3.0.1"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/web/components/page.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NextAuth } from 'next-auth/client';
3 | import { userFromSession } from '../utils/userFromSession';
4 |
5 | import '../css/index.sass';
6 |
7 | // Google Analytics
8 | if (process.browser) {
9 | window.dataLayer = window.dataLayer || [];
10 | function gtag(){dataLayer.push(arguments);}
11 | gtag('js', new Date());
12 | gtag('config', 'UA-73285761-2');
13 | }
14 |
15 | export default class Page extends React.Component {
16 | static async getInitialProps({req, query}) {
17 | const session = await NextAuth.init({req});
18 | return {
19 | user: userFromSession(session),
20 | csrfToken: session.csrfToken,
21 | lang: 'en',
22 | query
23 | };
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/gateways/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "datavis-tech-gateways",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "dist/index.js",
6 | "private": true,
7 | "scripts": {
8 | "build-ts": "tsc",
9 | "postinstall": "npm run build-ts",
10 | "test": "mocha -r ts-node/register test/**/test.ts"
11 | },
12 | "author": "Curran Kelleher",
13 | "license": "UNLICENSED",
14 | "dependencies": {
15 | "@types/uuid": "^3.4.3",
16 | "datavis-tech-entities": "1",
17 | "datavis-tech-use-cases": "1",
18 | "datavis-tech-i18n": "1"
19 | },
20 | "devDependencies": {
21 | "@types/mocha": "^5.2.4",
22 | "@types/node": "^10.5.1",
23 | "mocha": "^5.2.0",
24 | "ts-node": "^7.0.0",
25 | "typescript": "^2.9.2"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/serverGateways/src/shareDB.js:
--------------------------------------------------------------------------------
1 | import ShareDB from '@teamwork/sharedb';
2 | import ShareDBMingoMemory from '@teamwork/sharedb-mingo-memory';
3 | import ShareDBMongo from '@teamwork/sharedb-mongo';
4 |
5 | // Singletons.
6 | let shareDB;
7 | let connection;
8 |
9 | export const getShareDB = () => {
10 | if (!shareDB) {
11 | if (process.env.MONGO_URI) {
12 | shareDB = ShareDB({
13 | db: new ShareDBMongo(process.env.MONGO_URI)
14 | });
15 | } else {
16 | shareDB = ShareDB({
17 | db: new ShareDBMingoMemory()
18 | });
19 | }
20 | }
21 | return shareDB;
22 | };
23 |
24 | export const getConnection = () => {
25 | if (!connection) {
26 | connection = getShareDB().connect();
27 | }
28 | return connection;
29 | };
30 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/visualizationAPIController/forkVisualizationController.js:
--------------------------------------------------------------------------------
1 | import { ForkVisualization } from 'datavis-tech-use-cases';
2 | import { userIdFromReq } from '../userIdFromReq';
3 |
4 | export const forkVisualizationController = (expressApp, gateways) => {
5 | const forkVisualization = new ForkVisualization(gateways);
6 |
7 | expressApp.post('/api/visualization/fork', async (req, res) => {
8 | try {
9 | const requestModel = {
10 | visualization: req.body.visualization,
11 | owner: userIdFromReq(req)
12 | };
13 | const responseModel = await forkVisualization.execute(requestModel);
14 | res.json(responseModel);
15 | } catch (error) {
16 | res.json({ error: error.message });
17 | }
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/visualizationAPIController/saveVisualizationController.js:
--------------------------------------------------------------------------------
1 | import { SaveVisualization } from 'datavis-tech-use-cases';
2 | import { userIdFromReq } from '../userIdFromReq';
3 |
4 | export const saveVisualizationController = (expressApp, gateways) => {
5 | const saveVisualization = new SaveVisualization(gateways);
6 |
7 | expressApp.post('/api/visualization/save', async (req, res) => {
8 | try {
9 | const requestModel = {
10 | visualization: req.body.visualization,
11 | userId: userIdFromReq(req)
12 | };
13 | const responseModel = await saveVisualization.execute(requestModel);
14 | res.json(responseModel);
15 | } catch (error) {
16 | res.json({ error: error.message })
17 | }
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/packages/web/components/atoms/visualizationPreview.js:
--------------------------------------------------------------------------------
1 | import { visualizationRoute, thumbnailUrl } from '../../routes/routeGenerators';
2 |
3 | export const VisualizationPreview = ({ info, userName }) => (
4 |
9 |
18 |
19 | { info.title }
20 |
21 |
22 | )
23 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/userAPIController/getProfileDataController.js:
--------------------------------------------------------------------------------
1 | import { GetUserProfileData } from 'datavis-tech-use-cases';
2 |
3 | export const getProfileDataController = (expressApp, gateways) => {
4 |
5 | const getUserProfileData = new GetUserProfileData(gateways);
6 |
7 | expressApp.get('/api/user/getProfileData/:userName', async (req, res) => {
8 | try {
9 | const { userName } = req.params;
10 | const requestModel = { userName };
11 | const responseModel = await getUserProfileData.execute(requestModel);
12 | res.json(responseModel);
13 | } catch (error) {
14 | res.json({
15 | error: {
16 | message: error.message,
17 | statusCode: error.statusCode
18 | }
19 | });
20 | }
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/visualizationAPIController/deleteVisualizationController.js:
--------------------------------------------------------------------------------
1 | import { DeleteVisualization } from 'datavis-tech-use-cases';
2 | import { userIdFromReq } from '../userIdFromReq';
3 |
4 | export const deleteVisualizationController = (expressApp, gateways) => {
5 | const deleteVisualization = new DeleteVisualization(gateways);
6 |
7 | expressApp.post('/api/visualization/delete', async (req, res) => {
8 | try {
9 | const requestModel = {
10 | id: req.body.id,
11 | userId: userIdFromReq(req)
12 | };
13 | const responseModel = await deleteVisualization.execute(requestModel);
14 | res.json(responseModel);
15 | } catch (error) {
16 | res.json({
17 | error: error.message
18 | });
19 | }
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/datasetAPIController/createDatasetController.js:
--------------------------------------------------------------------------------
1 | import { CreateDataset } from 'datavis-tech-use-cases';
2 | import { userIdFromReq } from '../userIdFromReq';
3 |
4 | export const createDatasetController = (expressApp, gateways) => {
5 | const createDataset = new CreateDataset(gateways);
6 |
7 | expressApp.post('/api/dataset/create', async (req, res) => {
8 | try {
9 | const owner = userIdFromReq(req);
10 | const { title, file, sourceName, sourceUrl } = req.body;
11 |
12 | const requestModel = { owner, title, file, sourceName, sourceUrl };
13 | const responseModel = await createDataset.execute(requestModel);
14 | res.json(responseModel);
15 | } catch (error) {
16 | res.json({ error: error.message });
17 | }
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/packages/presenters/src/d3Packages.js:
--------------------------------------------------------------------------------
1 | // Derived from https://github.com/d3/d3/blob/master/index.js
2 | export const d3Packages = [
3 | 'd3',
4 | // 'd3-array',
5 | // 'd3-axis',
6 | // 'd3-brush',
7 | // 'd3-chord',
8 | // 'd3-collection',
9 | // 'd3-color',
10 | // 'd3-contour',
11 | // 'd3-dispatch',
12 | // 'd3-drag',
13 | // 'd3-dsv',
14 | // 'd3-ease',
15 | // 'd3-fetch',
16 | // 'd3-force',
17 | // 'd3-format',
18 | // 'd3-geo',
19 | // 'd3-hierarchy',
20 | // 'd3-interpolate',
21 | // 'd3-path',
22 | // 'd3-polygon',
23 | // 'd3-quadtree',
24 | // 'd3-random',
25 | // 'd3-scale',
26 | // 'd3-scale-chromatic',
27 | // 'd3-selection',
28 | // 'd3-shape',
29 | // 'd3-time',
30 | // 'd3-time-format',
31 | // 'd3-timer',
32 | // 'd3-transition',
33 | // 'd3-voronoi',
34 | // 'd3-zoom'
35 | ];
36 |
--------------------------------------------------------------------------------
/packages/web/LICENSE_NEXTJS_STARTER.txt:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2018, Iain Collins
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--------------------------------------------------------------------------------
/packages/useCases/src/gatewayInterfaces/datasetGateway.ts:
--------------------------------------------------------------------------------
1 | import { UserId, DocumentId, File, DatasetInfo } from 'datavis-tech-entities';
2 |
3 | import {
4 | CreateDatasetRequestModel,
5 | CreateDatasetResponseModel,
6 | GetDatasetRequestModel,
7 | GetDatasetResponseModel,
8 | //SaveDatasetRequestModel,
9 | //SaveDatasetResponseModel
10 | } from '../interactors';
11 |
12 | export interface DatasetGateway {
13 | createDataset(request: CreateDatasetRequestModel):
14 | Promise;
15 |
16 | getDataset(options: {
17 | owner: UserId,
18 | slug: string
19 | }): Promise;
20 |
21 | getDatasetInfosByUserId(id: UserId): Promise<[DatasetInfo]>;
22 |
23 | //saveDataset(request: SaveDatasetRequestModel):
24 | // Promise;
25 | }
26 |
--------------------------------------------------------------------------------
/packages/web/components/atoms/unfurl.js:
--------------------------------------------------------------------------------
1 | export const Unfurl = ({ title, description, image, url }) => (
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | )
18 |
--------------------------------------------------------------------------------
/packages/imageGenerationService/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vizhub-image-generation-service",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "start": "NODE_ENV=production node -r esm src/index.js",
8 | "dev": "NODE_ENV=development node -r esm src/index.js",
9 | "test": "mocha -r esm test/test.js"
10 | },
11 | "author": "Curran Kelleher",
12 | "license": "UNLICENSED",
13 | "dependencies": {
14 | "datavis-tech-entities": "1",
15 | "datavis-tech-presenters": "1",
16 | "datavis-tech-use-cases": "1",
17 | "dotenv": "^6.1.0",
18 | "puppeteer": "^1.8.0",
19 | "sharp": "^0.20.8",
20 | "vizhub-server-gateways": "1",
21 | "vizhub-ui": "1"
22 | },
23 | "devDependencies": {
24 | "esm": "^3.0.84",
25 | "mocha": "^5.2.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/ui/src/ide/ideGrid.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SplitPane from 'react-split-pane';
3 |
4 | export const IDEGrid = props => {
5 | const {
6 | children,
7 | onSplitPaneDragStarted,
8 | onSplitPaneDragFinished
9 | } = props;
10 |
11 | return (
12 |
13 |
19 | { children }
20 |
21 |
22 | );
23 | };
24 |
25 | IDEGrid.Left = ({children}) => (
26 |
27 | { children }
28 |
29 | );
30 |
31 | IDEGrid.Right = ({children}) => (
32 |
33 | { children }
34 |
35 | );
36 |
--------------------------------------------------------------------------------
/packages/web/pages/auth/error.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Page from '../../components/page';
3 | import { TitledPage } from '../../components/atoms/titledPage';
4 |
5 | export default class extends Page {
6 |
7 | static async getInitialProps({req, query}) {
8 | let props = await super.getInitialProps({req});
9 | props.action = query.action || null;
10 | props.type = query.type || null;
11 | props.service = query.service || null;
12 | return props;
13 | }
14 |
15 | render() {
16 | return (
17 |
18 |
19 |
Error signing in
20 |
An error occured while trying to sign in.
21 |
22 |
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/web/pages/upload-dataset/fileUploader.js:
--------------------------------------------------------------------------------
1 | const onChange = onFileChosen => event => {
2 | const file = event.target.files[0];
3 | const reader = new FileReader();
4 | reader.onload = () => {
5 | onFileChosen({
6 | name: file.name,
7 | text: reader.result
8 | });
9 | };
10 | reader.readAsText(file);
11 | };
12 |
13 | export const FileUploader = ({ onFileChosen }) => (
14 |
15 |
27 |
28 | );
29 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/visualizationAPIController/getPreviewController.js:
--------------------------------------------------------------------------------
1 | import { GetPreview } from 'datavis-tech-use-cases';
2 |
3 | export const getPreviewController = (expressApp, gateways) => {
4 | const getPreview = new GetPreview(gateways);
5 | expressApp.get('/api/visualization/preview/:id.png', async (req, res) => {
6 | try {
7 | const id = req.params.id;
8 | const requestModel = { id };
9 | const responseModel = await getPreview.execute(requestModel);
10 | const preview = responseModel.preview;
11 |
12 | const img = new Buffer(preview, 'base64');
13 |
14 | res.writeHead(200, {
15 | 'Content-Type': 'image/png',
16 | 'Content-Length': img.length
17 | });
18 |
19 | res.end(img);
20 | } catch (error) {
21 | res.json({ error })
22 | }
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/visualizationAPIController/getThumbnailController.js:
--------------------------------------------------------------------------------
1 | import { GetThumbnail } from 'datavis-tech-use-cases';
2 |
3 | export const getThumbnailController = (expressApp, gateways) => {
4 | const getThumbnail = new GetThumbnail(gateways);
5 | expressApp.get('/api/visualization/thumbnail/:id.png', async (req, res) => {
6 | try {
7 | const id = req.params.id;
8 | const requestModel = { id };
9 | const responseModel = await getThumbnail.execute(requestModel);
10 | const thumbnail = responseModel.thumbnail;
11 |
12 | const img = new Buffer(thumbnail, 'base64');
13 |
14 | res.writeHead(200, {
15 | 'Content-Type': 'image/png',
16 | 'Content-Length': img.length
17 | });
18 |
19 | res.end(img);
20 | } catch (error) {
21 | res.json({ error })
22 | }
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/packages/entities/src/visualizationContent.ts:
--------------------------------------------------------------------------------
1 | import { DocumentContent } from './documentContent';
2 | import { File } from './file';
3 | import { VISUALIZATION_TYPE } from './documentTypes';
4 |
5 | export class VisualizationContent extends DocumentContent {
6 |
7 | // A representation of "files".
8 | //
9 | // Expected files include:
10 | //
11 | // index.html - Required, the main HTML page.
12 | // index.js - Optional, the JS entry point.
13 | // May import other JS files in this Visualization using ES6 imports,
14 | // `import { something } from './${filename}';`
15 | // styles.css - Optional, CSS file, could be any name ending with .css.
16 | files: File[];
17 |
18 | constructor(data) {
19 | super({
20 | id: data.id,
21 | documentType: VISUALIZATION_TYPE
22 | });
23 |
24 | this.files = data.files;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/entities/src/index.ts:
--------------------------------------------------------------------------------
1 | export { User, UserId } from './user';
2 | export { ciUser } from './ciUser';
3 |
4 | export { DocumentPart } from './documentPart';
5 | export { DocumentInfo } from './documentInfo';
6 | export { DocumentContent } from './documentContent';
7 |
8 | export { VisualizationInfo } from './visualizationInfo';
9 | export { VisualizationContent } from './visualizationContent';
10 | export { Visualization } from './visualization';
11 | export { File } from './file';
12 |
13 | export { DatasetInfo } from './datasetInfo';
14 | export { DatasetContent } from './datasetContent';
15 | export { Dataset } from './dataset';
16 |
17 | export { VISUALIZATION_TYPE, DATASET_TYPE } from './documentTypes';
18 |
19 | import * as testData from './testData';
20 | export { testData };
21 |
22 | export { timestamp } from './timestamp';
23 |
24 | export { Images } from './images';
25 |
--------------------------------------------------------------------------------
/packages/controllers/src/apiController/visualizationAPIController/exportVisualizationController.js:
--------------------------------------------------------------------------------
1 | import { ExportVisualization } from 'datavis-tech-use-cases';
2 |
3 | export const exportVisualizationController = (expressApp, gateways) => {
4 | const exportVisualization = new ExportVisualization(gateways);
5 | expressApp.get('/api/visualization/export/:id', async (req, res) => {
6 | try {
7 | const requestModel = { id: req.params.id };
8 | const responseModel = await exportVisualization.execute(requestModel);
9 |
10 | const { zipFileBuffer, zipFileName } = responseModel;
11 |
12 | res.set({
13 | 'Content-Disposition': `attachment; filename="${zipFileName}"`,
14 | 'Content-Type': 'application/zip'
15 | });
16 |
17 | res.send(zipFileBuffer);
18 |
19 | } catch (error) {
20 | res.json({ error })
21 | }
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/packages/database/src/getDataset.js:
--------------------------------------------------------------------------------
1 | import { Dataset, DatasetInfo, DatasetContent, DATASET_TYPE } from 'datavis-tech-entities';
2 | import { DOCUMENT_INFO, DOCUMENT_CONTENT } from './collectionName';
3 | import { fetchShareDBDoc } from './fetchShareDBDoc';
4 | import { fetchShareDBQuery } from './fetchShareDBQuery';
5 |
6 | export const getDataset = connection => async ({ owner, slug }) => {
7 |
8 | const mongoQuery = {
9 | documentType: DATASET_TYPE,
10 | slug,
11 | owner
12 | };
13 | const results = await fetchShareDBQuery(DOCUMENT_INFO, mongoQuery, connection);
14 | const info = results[0];
15 |
16 | const content = await fetchShareDBDoc(DOCUMENT_CONTENT, info.id, connection);
17 |
18 | return {
19 | dataset: new Dataset({
20 | datasetInfo: new DatasetInfo(info.data),
21 | datasetContent: new DatasetContent(content.data)
22 | })
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/packages/ui/src/css/visualizationView.sass:
--------------------------------------------------------------------------------
1 | .visualization-view
2 | position: relative
3 | display: flex
4 | flex-direction: column
5 | min-height: 100%;
6 | font-family: $family-sans
7 | iframe.visualization-runner
8 | position: absolute
9 | .visualization-view-body
10 | flex: 1
11 | margin-left: 5px
12 | margin-top: 5px
13 | .visualization-view-title
14 | font-size: 2.5em
15 | .visualization-view-description
16 | margin-top: 12px
17 | .responsive-youtube
18 | margin-left: -5px
19 | position: relative
20 | padding-bottom: 56.25% /* 16:9 */
21 | padding-top: 25px
22 | height: 0
23 | .responsive-youtube iframe
24 | position: absolute
25 | top: 0
26 | left: 0
27 | width: 100%
28 | height: 100%
29 | .license-info
30 | color: #cccccc
31 | margin-left: 5px
32 | margin-top: 5px
33 |
--------------------------------------------------------------------------------
/packages/web/routes/routeGenerators.js:
--------------------------------------------------------------------------------
1 | export const absolute = relative => 'https://vizhub.com' + relative;
2 |
3 | export const thumbnailUrl = id => `/api/visualization/thumbnail/${id}.png`;
4 | export const previewUrl = id => `/api/visualization/preview/${id}.png`;
5 |
6 | export const profileRoute = ({userName}) =>
7 | `/${userName}`;
8 |
9 | export const visualizationRoute = ({userName, id}) =>
10 | `/${userName}/${id}`;
11 |
12 | export const visualizationRouteFullscreen = ({userName, id}) =>
13 | `/${userName}/${id}/fullscreen`;
14 |
15 | export const datasetRoute = ({userName, slug}) =>
16 | `/${userName}/datasets/${slug}`;
17 |
18 | export const datasetUrl = ({userName, slug}) =>
19 | `https://vizhub.com${datasetRoute({userName, slug})}`;
20 |
21 | export const datasetDownloadUrl = ({userName, slug, format, baseUrl}) =>
22 | `${baseUrl}${datasetRoute({userName, slug})}.${format}`;
23 |
--------------------------------------------------------------------------------
/packages/web/next.config.js:
--------------------------------------------------------------------------------
1 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
2 | const withSass = require('@zeit/next-sass');
3 | const withCSS = require('@zeit/next-css');
4 | const commonsChunkConfig = require('@zeit/next-css/commons-chunk-config');
5 | const { ANALYZE } = process.env;
6 |
7 | module.exports = withSass(withCSS({
8 | webpack: config => {
9 | config.resolve.extensions = [ '.mjs', '.js', '.jsx', '.json' ];
10 |
11 | // Enable use of both .sass and .css.
12 | // Should be a temporary workaround.
13 | // See https://github.com/zeit/next-plugins/issues/127
14 | config = commonsChunkConfig(config, /\.(sass|css)$/);
15 |
16 | if (ANALYZE) {
17 | config.plugins.push(new BundleAnalyzerPlugin({
18 | analyzerMode: 'server',
19 | analyzerPort: 8888,
20 | openAnalyzer: true
21 | }));
22 | }
23 |
24 | return config;
25 | }
26 | }));
27 |
--------------------------------------------------------------------------------
/packages/web/models/user.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A simple client-side only model used to list users in the Admin view, which
3 | * is acessible if logged in on a profile that has { "admin": true } set on it.
4 | * It is not used for anything else.
5 | */
6 | import fetch from 'isomorphic-fetch';
7 |
8 | export default class {
9 |
10 | static async list({
11 | page = 0,
12 | size = 10
13 | } = {}) {
14 | return fetch(`/admin/users?page=${page}&size=${size}`, {
15 | credentials: 'same-origin'
16 | })
17 | .then(response => {
18 | if (response.ok) {
19 | return Promise.resolve(response.json());
20 | } else {
21 | return Promise.reject(Error('HTTP error when trying to list users'));
22 | }
23 | })
24 | .then(data => {
25 | return data;
26 | })
27 | .catch(() => Promise.reject(Error('Error trying to list users')));
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'globals': {
3 | // Globals for Mocha
4 | 'it': false,
5 | 'describe': false,
6 | 'after': false
7 | },
8 | 'env': {
9 | 'browser': true,
10 | 'es6': true,
11 | 'node': true
12 | },
13 | 'extends': ['eslint:recommended', 'plugin:react/recommended'],
14 | 'parserOptions': {
15 | 'ecmaFeatures': {
16 | 'jsx': true
17 | },
18 | 'ecmaVersion': 2018,
19 | 'sourceType': 'module'
20 | },
21 | 'plugins': [
22 | 'react'
23 | ],
24 | 'rules': {
25 | 'indent': [
26 | 'error',
27 | 2
28 | ],
29 | 'linebreak-style': [
30 | 'error',
31 | 'unix'
32 | ],
33 | 'quotes': [
34 | 'error',
35 | 'single'
36 | ],
37 | 'semi': [
38 | 'error',
39 | 'always'
40 | ],
41 | 'no-console': 0,
42 | 'react/react-in-jsx-scope': 0,
43 | 'react/prop-types': 0
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/epics/updateTitleEpic.js:
--------------------------------------------------------------------------------
1 | import { merge } from 'rxjs';
2 | import { debounceTime, map, filter } from 'rxjs/operators';
3 | import { CHANGE_FILE_TEXT, INIT_FILES } from '../actionTypes';
4 | import { setTitle } from '../actionCreators';
5 | import { runDebounceTime } from '../../constants';
6 | import { getFile } from '../selectors';
7 |
8 | const extractTitle = htmlFile => {
9 | const titleMatch = htmlFile.text.match(/(.*?)<\/title>/i);
10 | return titleMatch ? titleMatch[1] : 'Untitled';
11 | };
12 |
13 | export const updateTitleEpic = (action$, state$) =>
14 | merge(
15 | action$.ofType(INIT_FILES),
16 | action$.ofType(CHANGE_FILE_TEXT).pipe(
17 | filter(action => action.fileName === 'index.html'),
18 | debounceTime(runDebounceTime)
19 | ),
20 | )
21 | .pipe(
22 | map(action =>
23 | setTitle(extractTitle(getFile(state$.value, 'index.html')))
24 | )
25 | );
26 |
--------------------------------------------------------------------------------
/packages/ui/src/css/editorGrid.sass:
--------------------------------------------------------------------------------
1 | .editor-grid
2 | display: flex
3 | height: 100%
4 | background-color: #300a24
5 | @media only screen and (max-width: $editor-break-small)
6 | flex-direction: column
7 | .editor-grid-left
8 | @media screen and (max-width: $editor-break-small)
9 | border-bottom: $editor-split-border
10 | @media screen and (min-width: $editor-break-small)
11 | border-right: $editor-split-border
12 | color: white
13 | font-size: $font-size-for-code
14 | font-family: $family-monospace
15 | .file-list:focus
16 | outline: none
17 | .file, .action-link
18 | padding-left: 10px
19 | padding-right: 10px
20 | cursor: pointer
21 | user-select: none
22 | .file.active
23 | background-color: #6c6c6c
24 | .action-link
25 | color: #7a5173
26 | display: block
27 | .editor-grid-center
28 | flex: 1 1 auto
29 | min-width: 0
30 |
31 |
--------------------------------------------------------------------------------
/packages/useCases/src/interactors/getUser.ts:
--------------------------------------------------------------------------------
1 | import { User, UserId, ciUser } from 'datavis-tech-entities';
2 | import { Interactor, RequestModel, ResponseModel } from '../interactor';
3 | import { UserGateway } from '../gatewayInterfaces/userGateway'
4 |
5 | export interface GetUserRequestModel extends RequestModel {
6 | id: UserId,
7 | }
8 |
9 | export interface GetUserResponseModel extends ResponseModel {
10 | user: User
11 | }
12 |
13 | export class GetUser implements Interactor {
14 | userGateway: UserGateway;
15 |
16 | constructor({ userGateway }) {
17 | this.userGateway = userGateway;
18 | }
19 |
20 | async execute(requestModel: GetUserRequestModel) {
21 | const { id } = requestModel;
22 | const responseModel: GetUserResponseModel = {
23 | user: (
24 | id === ciUser.id
25 | ? ciUser
26 | : await this.userGateway.getUser(id)
27 | )
28 | };
29 | return responseModel;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/graveyard/computeReferences/test/test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 | import { computeReferences } from '../src/computeReferences';
3 | //import { presentVisualization } from '../src';
4 |
5 | describe('Presenter', () => {
6 | describe('computeReferences', () => {
7 | it('should compute references', async () => {
8 | const references = await computeReferences()
9 | assert.deepEqual(references, [ 'd3-selection', './bar' ]);
10 | });
11 | });
12 |
13 | //describe('presentVisualization', () => {
14 | // it('should compute references', async () => {
15 | // //const gateway = {
16 | // // fetchDocument: id => Promise.resolve({}),
17 | // // fetchUser: id => Promise.resolve({})
18 | // //};
19 | // //const presenter = Presenter(gateway);
20 | // const { references } = await presentVisualization()
21 | // assert.deepEqual(references, [ 'd3-selection', './bar' ]);
22 | // });
23 | //});
24 | });
25 |
--------------------------------------------------------------------------------
/packages/useCases/src/interactors/getAllVisualizationInfos.ts:
--------------------------------------------------------------------------------
1 | import { User, VisualizationInfo, ciUser } from 'datavis-tech-entities';
2 | import { Interactor, RequestModel, ResponseModel } from '../interactor';
3 | import { UserGateway } from '../gatewayInterfaces/userGateway'
4 | import { VisualizationGateway } from '../gatewayInterfaces/visualizationGateway'
5 | import { DatasetGateway } from '../gatewayInterfaces/datasetGateway'
6 |
7 | export interface GetAllVisualizationInfosResponseModel extends ResponseModel {
8 | visualizationInfos: [VisualizationInfo]
9 | }
10 |
11 | export class GetAllVisualizationInfos implements Interactor {
12 | visualizationGateway: VisualizationGateway;
13 |
14 | constructor({ visualizationGateway }) {
15 | this.visualizationGateway = visualizationGateway;
16 | }
17 |
18 | async execute() {
19 | return {
20 | visualizationInfos: await this.visualizationGateway.getAllVisualizationInfos()
21 | };
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/imageGenerationService/src/generateImages.js:
--------------------------------------------------------------------------------
1 | import { VisualizationViewModel } from 'datavis-tech-presenters';
2 | import { generateScreenshot } from './generateScreenshot';
3 | import { resize } from './resize';
4 | import { thumbnailDimensions, previewDimensions } from './dimensions';
5 |
6 | export const defaultWaitTime = 10000;
7 |
8 | export const generateImages = async (visualization, waitTime) => {
9 | const visualizationViewModel = new VisualizationViewModel(visualization);
10 |
11 | const screenshotBuffer = await generateScreenshot({
12 | visualizationViewModel,
13 | waitTime: waitTime || defaultWaitTime
14 | });
15 |
16 | const thumbnailBuffer = await resize({
17 | actualDimensions: visualizationViewModel,
18 | desiredDimensions: thumbnailDimensions,
19 | screenshotBuffer
20 | });
21 |
22 | return {
23 | thumbnail: thumbnailBuffer.toString('base64'),
24 | preview: screenshotBuffer.toString('base64')
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/packages/web/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Head, Main, NextScript } from 'next/document';
2 |
3 | export default class DefaultDocument extends Document {
4 | static async getInitialProps (ctx) {
5 | return await Document.getInitialProps(ctx);
6 | }
7 |
8 | render() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {this.props.customValue}
20 |
21 |
22 |
23 |
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/entities/src/datasetInfo.ts:
--------------------------------------------------------------------------------
1 | import { DocumentInfo } from './documentInfo';
2 | import { DATASET_TYPE } from './documentTypes';
3 |
4 | type Format = 'csv' | 'tsv' | 'json' | 'geojson' | 'topojson' | 'txt';
5 |
6 | export class DatasetInfo extends DocumentInfo {
7 |
8 | // The format of this dataset.
9 | format: Format;
10 |
11 | // The name of the source of this dataset.
12 | sourceName: string;
13 |
14 | // The URL of the page from which this dataset was downloaded.
15 | sourceUrl: string
16 |
17 | constructor(data) {
18 |
19 | super({
20 | documentType: DATASET_TYPE,
21 | id: data.id,
22 | owner: data.owner,
23 | title: data.title,
24 | slug: data.slug,
25 | description: data.description,
26 | createdTimestamp: data.createdTimestamp,
27 | lastUpdatedTimestamp: data.lastUpdatedTimestamp
28 | });
29 |
30 | this.format = data.format;
31 | this.sourceName = data.sourceName;
32 | this.sourceUrl = data.sourceUrl;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/graveyard/computeReferences/src/index.ts:
--------------------------------------------------------------------------------
1 | import { computeReferences } from './computeReferences';
2 | import { CVResponse } from 'datavis-tech-use-cases';
3 |
4 | export interface VisualizationViewModel {
5 | references: string[]
6 | }
7 |
8 | export async function presentVisualization (): Promise {
9 | const references = await computeReferences();
10 | return { references };
11 | };
12 |
13 | //export const presentVisualization = gateway => ({
14 | // presentVisualization: id => (
15 | // gateway.fetchDocument(id).then(async visualization => {
16 | // //return await Promise
17 | // // .all([
18 | // // Promise.all(references.map(gateway.fetchDocument)),
19 | // // gateway.fetchUser(visualization.owner)
20 | // // ])
21 | // // .then((referencedDocuments, owner) => ({
22 | // // visualization,
23 | // // references,
24 | // // referencedDocuments,
25 | // // owner
26 | // // }));
27 | // })
28 | // )
29 | //});
30 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import { files } from './files';
4 | import { activeFileName } from './activeFileName';
5 | import { visualizationWidth } from './visualizationWidth';
6 | import { visualizationHeight } from './visualizationHeight';
7 | import { visualizationOwnerUser } from './visualizationOwnerUser';
8 | import { runId } from './runId';
9 | import { saveStatus } from './saveStatus';
10 | import { visualizationTitle } from './visualizationTitle';
11 | import { visualizationId } from './visualizationId';
12 | import { visualizationDescription } from './visualizationDescription';
13 | import { splitPaneDragging } from './splitPaneDragging';
14 |
15 | export const ide = combineReducers({
16 | files,
17 | activeFileName,
18 | visualizationWidth,
19 | visualizationHeight,
20 | visualizationOwnerUser,
21 | runId,
22 | saveStatus,
23 | visualizationTitle,
24 | visualizationId,
25 | visualizationDescription,
26 | splitPaneDragging
27 | });
28 |
--------------------------------------------------------------------------------
/packages/ui/src/visualizationRunner/runnerIframe.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | export class RunnerIFrame extends Component {
3 |
4 | constructor(props) {
5 | super(props);
6 | this.iFrameRef = React.createRef();
7 | this.setSrcDoc = () => {
8 | this.iFrameRef.current.setAttribute('srcDoc', this.props.srcDoc);
9 | };
10 | }
11 |
12 | componentDidMount() {
13 | this.setSrcDoc();
14 | }
15 |
16 | componentDidUpdate(prevProps) {
17 | if (prevProps.runId !== this.props.runId) {
18 | console.clear();
19 | this.setSrcDoc();
20 | }
21 | }
22 |
23 | render() {
24 | const { width, height, scale } = this.props;
25 | return (
26 |
37 | );
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/packages/web/server/next-auth.providers.js:
--------------------------------------------------------------------------------
1 | // Load environment variables from a .env file if one exists
2 | require('dotenv').load();
3 |
4 | const DEV_GITHUB_ID = '75389c43f767c2dd2347';
5 | const DEV_GITHUB_SECRET = '99a50654eb1244953dcbbe9093703ff83c537b08';
6 |
7 | module.exports = () => {
8 | let providers = [];
9 |
10 | providers.push({
11 | providerName: 'GitHub',
12 | providerOptions: {
13 | scope: ['email', 'public_profile']
14 | },
15 | Strategy: require('passport-github').Strategy,
16 | strategyOptions: {
17 | clientID: process.env.GITHUB_ID || DEV_GITHUB_ID,
18 | clientSecret: process.env.GITHUB_SECRET || DEV_GITHUB_SECRET,
19 | profileFields: ['id', 'displayName', 'email', 'link']
20 | },
21 | getProfile(profile) {
22 | // Normalize profile into one with {id, name, email} keys
23 | return {
24 | id: profile.id,
25 | name: profile._json.name,
26 | email: profile._json.email
27 | };
28 | }
29 | });
30 |
31 | return providers;
32 | };
33 |
--------------------------------------------------------------------------------
/packages/web/redux/reducers.js:
--------------------------------------------------------------------------------
1 | import {
2 | SET_CSRF_TOKEN,
3 | SET_VISUALIZATION,
4 | SET_USER
5 | } from './actionTypes';
6 | import { uiRedux } from 'vizhub-ui';
7 |
8 | const { actionTypes: { SAVE_ERROR } } = uiRedux;
9 |
10 | export const csrfToken = (state = null, action) => {
11 | switch (action.type) {
12 | case SET_CSRF_TOKEN:
13 | return action.csrfToken || null;
14 | default:
15 | return state;
16 | }
17 | };
18 |
19 | export const visualization = (state = null, action) => {
20 | switch (action.type) {
21 | case SET_VISUALIZATION:
22 | return action.visualization;
23 | default:
24 | return state;
25 | }
26 | };
27 |
28 | export const user = (state = null, action) => {
29 | switch (action.type) {
30 | case SET_USER:
31 | return action.user;
32 | default:
33 | return state;
34 | }
35 | };
36 |
37 | export const showForkInvitation = (state = false, action) => {
38 | switch (action.type) {
39 | case SAVE_ERROR:
40 | return true;
41 | default:
42 | return state;
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/packages/entities/src/documentInfo.ts:
--------------------------------------------------------------------------------
1 | import { DocumentPart } from './documentPart';
2 | import { UserId } from './user';
3 |
4 | export class DocumentInfo extends DocumentPart {
5 |
6 | // The ID of the user that owns this document.
7 | owner: UserId;
8 |
9 | // The title of the document.
10 | title: string;
11 |
12 | // The URL slug for the document.
13 | slug: string | undefined;
14 |
15 | // The Markdown description of the document.
16 | description: string;
17 |
18 | // The Unix timestamp at which this document was created.
19 | createdTimestamp: number;
20 |
21 | // The Unix timestamp at which this document was last updated.
22 | lastUpdatedTimestamp: number;
23 |
24 | constructor(data) {
25 | super({
26 | id: data.id,
27 | documentType: data.documentType
28 | });
29 | this.owner = data.owner;
30 | this.title = data.title;
31 | this.slug = data.slug;
32 | this.description = data.description;
33 | this.createdTimestamp = data.createdTimestamp;
34 | this.lastUpdatedTimestamp = data.lastUpdatedTimestamp;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/useCases/createVisualization.md:
--------------------------------------------------------------------------------
1 | # Create Visualization
2 |
3 | Actor: Student, Teacher
4 |
5 | A new visualization is to be created.
6 |
7 | ## Triggers
8 |
9 | Access the user interface element for creating a new visualization.
10 |
11 | ## Preconditions
12 |
13 | The user is authenticated.
14 |
15 | ## Postconditions
16 |
17 | A new visualization was created.
18 |
19 | ## Normal Course of Events
20 |
21 | * A user interface for creating a new visualization appears.
22 | * The user is asked whether they want to
23 | * start from scratch, or
24 | * fork a template.
25 | * The user confirms that the visualization should be created.
26 | * The user is informed that the visualization was created.
27 | * The user is presented with the visualization editor interface.
28 |
29 | ### Choose a Template
30 |
31 | * The user may chooses a template to start from.
32 | * In this case, the template visualization will be "forked".
33 |
34 | ## Exceptional Case
35 |
36 | * If the user is not authenticated, the user should be informed that they need to log in to create a visualization.
--------------------------------------------------------------------------------
/packages/ui/src/visualizationEditor/fileList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 |
4 | const BACKSPACE = 8;
5 | const DELETE = 46;
6 |
7 | export const FileList = props => {
8 | const {
9 | files,
10 | activeFileName,
11 | onFileClick,
12 | onFileDoubleClick,
13 | onFileDelete
14 | } = props;
15 |
16 | const isActive = file => file.name === activeFileName;
17 |
18 | const onKeyDown = event => {
19 | if (event.keyCode === BACKSPACE || event.keyCode === DELETE) {
20 | onFileDelete(activeFileName);
21 | }
22 | };
23 |
24 | return (
25 |
26 | {
27 | files.map(file => (
28 |
onFileClick(file.name)}
32 | onDoubleClick={() => onFileDoubleClick(file.name)}
33 | >
34 | { file.name }
35 |
36 | ))
37 | }
38 |
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/packages/ui/src/testingApp/files.js:
--------------------------------------------------------------------------------
1 | const defaultIndexHTML =
2 | `
3 |
4 |
5 |
6 |
7 | Untitled
8 |
9 |
10 |
11 |
12 | Hello
13 |
14 | `;
15 |
16 | const defaultStylesCSS =
17 | `body {
18 | background-color: red;
19 | margin: 0px;
20 | font-size: 5em;
21 | }`;
22 |
23 | const defaultIndexJS =
24 | `console.log('Hello World!');`;
25 |
26 | const defaultReadmeMD =
27 | `This is a cool [dataviz](https://twitter.com/search?q=%23dataviz)!
28 |
29 | `;
30 |
31 | export const files = [
32 | { name: 'index.html', text: defaultIndexHTML },
33 | { name: 'styles.css', text: defaultStylesCSS },
34 | { name: 'index.js', text: defaultIndexJS },
35 | { name: 'README.md', text: defaultReadmeMD },
36 | ];
37 |
--------------------------------------------------------------------------------
/packages/ui/src/redux/epics/updateDescriptionEpic.js:
--------------------------------------------------------------------------------
1 | import marked from 'marked';
2 | import { merge } from 'rxjs';
3 | import { debounceTime, map, filter } from 'rxjs/operators';
4 | import { CHANGE_FILE_TEXT, INIT_FILES } from '../actionTypes';
5 | import { setDescription } from '../actionCreators';
6 | import { runDebounceTime } from '../../constants';
7 | import { getFile } from '../selectors';
8 |
9 | const responsiveYouTube = html => html
10 | .replace(/