From ca5a7508913be40bf0bbc6df377bbfb1f6812944 Mon Sep 17 00:00:00 2001 From: Kara Brightwell Date: Thu, 31 Dec 2020 14:25:41 +0000 Subject: [PATCH 001/229] delete stuff --- .babelrc | 3 - .github/workflows/test.yml | 25 - .gitignore | 3 - .meteor/.finished-upgraders | 18 - .meteor/.gitignore | 1 - .meteor/.id | 7 - .meteor/packages | 30 - .meteor/platforms | 2 - .meteor/release | 1 - .meteor/versions | 97 - .meteorignore | 2 - .storybook/addons.js | 3 - .storybook/config.js | 18 - app.json | 28 - client/.eslintrc.json | 5 - client/main.js | 14 - imports/api/accounts-config.js | 11 - imports/api/email-env.js | 51 - imports/api/hsts.js | 14 - imports/api/publications.js | 155 - imports/api/utils/publish.js | 22 - imports/api/utils/search.js | 7 - imports/lib/.eslintrc.json | 6 - imports/lib/access.js | 5 - imports/lib/collections.js | 31 - imports/lib/methods.js | 179 - imports/lib/schema.js | 45 - imports/lib/session.js | 19 - imports/lib/utils/collection-methods.js | 90 - imports/lib/utils/generate-slug.js | 7 - imports/lib/utils/method.js | 20 - imports/lib/utils/unsplash.js | 53 - imports/lib/utils/validators/campaign-doc.js | 32 - imports/lib/utils/validators/card.js | 69 - imports/lib/utils/validators/common.js | 12 - imports/lib/utils/validators/doc.js | 15 - imports/lib/utils/validators/index.js | 5 - imports/ui/.eslintrc.json | 5 - imports/ui/app.js | 182 - imports/ui/blocks/clock.js | 58 - imports/ui/blocks/factions.js | 115 - imports/ui/blocks/index.js | 7 - imports/ui/blocks/quests/index.js | 292 - imports/ui/blocks/quests/splash.js | 125 - imports/ui/blocks/time/advance.js | 34 - imports/ui/blocks/time/control.js | 112 - imports/ui/blocks/time/display.js | 64 - imports/ui/blocks/time/index.js | 2 - imports/ui/blocks/weather/index.js | 252 - imports/ui/collection/block-layout.js | 124 - imports/ui/collection/card-history.js | 98 - imports/ui/collection/card-list.js | 28 - imports/ui/collection/card-search.js | 53 - imports/ui/collection/type-select.js | 16 - imports/ui/control/form.js | 155 - imports/ui/control/image-select.js | 177 - imports/ui/control/modal.js | 17 - imports/ui/control/privacy.js | 98 - imports/ui/control/tabs.js | 42 - imports/ui/data/calendar.js | 43 - imports/ui/data/campaign.js | 27 - imports/ui/data/card-search.js | 23 - imports/ui/data/card.js | 26 - imports/ui/data/computation.js | 15 - imports/ui/data/image.js | 24 - imports/ui/data/owner.js | 22 - imports/ui/document/campaign-settings.js | 44 - imports/ui/document/card.js | 98 - imports/ui/document/markdown.js | 28 - imports/ui/document/user.js | 49 - imports/ui/pages/campaign-players.js | 108 - imports/ui/pages/campaign-settings.js | 23 - imports/ui/pages/campaign.js | 73 - imports/ui/pages/card.js | 225 - imports/ui/pages/control.js | 48 - imports/ui/pages/dashboard.js | 13 - imports/ui/pages/edit-card.js | 160 - imports/ui/pages/enrol.js | 66 - imports/ui/pages/get-started.js | 70 - imports/ui/pages/home.js | 63 - imports/ui/pages/layout.js | 119 - imports/ui/pages/login.js | 46 - imports/ui/pages/new-campaign.js | 15 - imports/ui/pages/splash.js | 131 - imports/ui/pages/verify.js | 34 - imports/ui/utils/colors.js | 1 - imports/ui/utils/error.js | 11 - imports/ui/utils/hooks.js | 16 - imports/ui/utils/id-first.js | 2 - imports/ui/utils/logged-in.js | 11 - imports/ui/utils/match.js | 4 - imports/ui/utils/preventing-default.js | 6 - imports/ui/utils/select.js | 2 - imports/ui/utils/title.js | 8 - imports/ui/visual/form.js | 84 - imports/ui/visual/global.js | 48 - imports/ui/visual/gravatar.js | 49 - imports/ui/visual/grid.js | 56 - imports/ui/visual/heading.js | 38 - imports/ui/visual/icon.js | 10 - imports/ui/visual/logo.js | 11 - imports/ui/visual/menu.js | 71 - imports/ui/visual/modal.js | 50 - imports/ui/visual/ornamented.js | 72 - imports/ui/visual/primitives.js | 244 - imports/ui/visual/ribbon.js | 26 - imports/ui/visual/shortcut.js | 13 - imports/ui/visual/splash.js | 202 - package-lock.json | 17760 ----------------- package.json | 131 - server/main.js | 5 - stories/Form.stories.js | 37 - 112 files changed, 23722 deletions(-) delete mode 100644 .babelrc delete mode 100644 .github/workflows/test.yml delete mode 100644 .gitignore delete mode 100644 .meteor/.finished-upgraders delete mode 100644 .meteor/.gitignore delete mode 100644 .meteor/.id delete mode 100644 .meteor/packages delete mode 100644 .meteor/platforms delete mode 100644 .meteor/release delete mode 100644 .meteor/versions delete mode 100644 .meteorignore delete mode 100644 .storybook/addons.js delete mode 100644 .storybook/config.js delete mode 100644 app.json delete mode 100644 client/.eslintrc.json delete mode 100644 client/main.js delete mode 100644 imports/api/accounts-config.js delete mode 100644 imports/api/email-env.js delete mode 100644 imports/api/hsts.js delete mode 100644 imports/api/publications.js delete mode 100644 imports/api/utils/publish.js delete mode 100644 imports/api/utils/search.js delete mode 100644 imports/lib/.eslintrc.json delete mode 100644 imports/lib/access.js delete mode 100644 imports/lib/collections.js delete mode 100644 imports/lib/methods.js delete mode 100644 imports/lib/schema.js delete mode 100644 imports/lib/session.js delete mode 100644 imports/lib/utils/collection-methods.js delete mode 100644 imports/lib/utils/generate-slug.js delete mode 100644 imports/lib/utils/method.js delete mode 100644 imports/lib/utils/unsplash.js delete mode 100644 imports/lib/utils/validators/campaign-doc.js delete mode 100644 imports/lib/utils/validators/card.js delete mode 100644 imports/lib/utils/validators/common.js delete mode 100644 imports/lib/utils/validators/doc.js delete mode 100644 imports/lib/utils/validators/index.js delete mode 100644 imports/ui/.eslintrc.json delete mode 100644 imports/ui/app.js delete mode 100644 imports/ui/blocks/clock.js delete mode 100644 imports/ui/blocks/factions.js delete mode 100644 imports/ui/blocks/index.js delete mode 100644 imports/ui/blocks/quests/index.js delete mode 100644 imports/ui/blocks/quests/splash.js delete mode 100644 imports/ui/blocks/time/advance.js delete mode 100644 imports/ui/blocks/time/control.js delete mode 100644 imports/ui/blocks/time/display.js delete mode 100644 imports/ui/blocks/time/index.js delete mode 100644 imports/ui/blocks/weather/index.js delete mode 100644 imports/ui/collection/block-layout.js delete mode 100644 imports/ui/collection/card-history.js delete mode 100644 imports/ui/collection/card-list.js delete mode 100644 imports/ui/collection/card-search.js delete mode 100644 imports/ui/collection/type-select.js delete mode 100644 imports/ui/control/form.js delete mode 100644 imports/ui/control/image-select.js delete mode 100644 imports/ui/control/modal.js delete mode 100644 imports/ui/control/privacy.js delete mode 100644 imports/ui/control/tabs.js delete mode 100644 imports/ui/data/calendar.js delete mode 100644 imports/ui/data/campaign.js delete mode 100644 imports/ui/data/card-search.js delete mode 100644 imports/ui/data/card.js delete mode 100644 imports/ui/data/computation.js delete mode 100644 imports/ui/data/image.js delete mode 100644 imports/ui/data/owner.js delete mode 100644 imports/ui/document/campaign-settings.js delete mode 100644 imports/ui/document/card.js delete mode 100644 imports/ui/document/markdown.js delete mode 100644 imports/ui/document/user.js delete mode 100644 imports/ui/pages/campaign-players.js delete mode 100644 imports/ui/pages/campaign-settings.js delete mode 100644 imports/ui/pages/campaign.js delete mode 100644 imports/ui/pages/card.js delete mode 100644 imports/ui/pages/control.js delete mode 100644 imports/ui/pages/dashboard.js delete mode 100644 imports/ui/pages/edit-card.js delete mode 100644 imports/ui/pages/enrol.js delete mode 100644 imports/ui/pages/get-started.js delete mode 100644 imports/ui/pages/home.js delete mode 100644 imports/ui/pages/layout.js delete mode 100644 imports/ui/pages/login.js delete mode 100644 imports/ui/pages/new-campaign.js delete mode 100644 imports/ui/pages/splash.js delete mode 100644 imports/ui/pages/verify.js delete mode 100644 imports/ui/utils/colors.js delete mode 100644 imports/ui/utils/error.js delete mode 100644 imports/ui/utils/hooks.js delete mode 100644 imports/ui/utils/id-first.js delete mode 100644 imports/ui/utils/logged-in.js delete mode 100644 imports/ui/utils/match.js delete mode 100644 imports/ui/utils/preventing-default.js delete mode 100644 imports/ui/utils/select.js delete mode 100644 imports/ui/utils/title.js delete mode 100644 imports/ui/visual/form.js delete mode 100644 imports/ui/visual/global.js delete mode 100644 imports/ui/visual/gravatar.js delete mode 100644 imports/ui/visual/grid.js delete mode 100644 imports/ui/visual/heading.js delete mode 100644 imports/ui/visual/icon.js delete mode 100644 imports/ui/visual/logo.js delete mode 100644 imports/ui/visual/menu.js delete mode 100644 imports/ui/visual/modal.js delete mode 100644 imports/ui/visual/ornamented.js delete mode 100644 imports/ui/visual/primitives.js delete mode 100644 imports/ui/visual/ribbon.js delete mode 100644 imports/ui/visual/shortcut.js delete mode 100644 imports/ui/visual/splash.js delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 server/main.js delete mode 100644 stories/Form.stories.js diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 76385558..00000000 --- a/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "plugins": ["transform-class-properties", "transform-react-jsx-source"] -} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 528eef82..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Tests - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [12.x] - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm ci - - run: npm test diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 500fd26d..00000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/ -.idea/ -.env diff --git a/.meteor/.finished-upgraders b/.meteor/.finished-upgraders deleted file mode 100644 index 4538749a..00000000 --- a/.meteor/.finished-upgraders +++ /dev/null @@ -1,18 +0,0 @@ -# This file contains information which helps Meteor properly upgrade your -# app when you run 'meteor update'. You should check it into version control -# with your project. - -notices-for-0.9.0 -notices-for-0.9.1 -0.9.4-platform-file -notices-for-facebook-graph-api-2 -1.2.0-standard-minifiers-package -1.2.0-meteor-platform-split -1.2.0-cordova-changes -1.2.0-breaking-changes -1.3.0-split-minifiers-package -1.4.0-remove-old-dev-bundle-link -1.4.1-add-shell-server-package -1.4.3-split-account-service-packages -1.5-add-dynamic-import-package -1.7-split-underscore-from-meteor-base diff --git a/.meteor/.gitignore b/.meteor/.gitignore deleted file mode 100644 index 40830374..00000000 --- a/.meteor/.gitignore +++ /dev/null @@ -1 +0,0 @@ -local diff --git a/.meteor/.id b/.meteor/.id deleted file mode 100644 index a0298be5..00000000 --- a/.meteor/.id +++ /dev/null @@ -1,7 +0,0 @@ -# This file contains a token that is unique to your project. -# Check it into your repository along with the rest of this directory. -# It can be used for purposes such as: -# - ensuring you don't accidentally deploy one app on top of another -# - providing package authors with aggregated statistics - -1yu2zlygi4tflx1j8yw diff --git a/.meteor/packages b/.meteor/packages deleted file mode 100644 index c2dde3bc..00000000 --- a/.meteor/packages +++ /dev/null @@ -1,30 +0,0 @@ -# Meteor packages used by this project, one per line. -# Check this file (and the other files in this directory) into your repository. -# -# 'meteor add' and 'meteor remove' will edit this file for you, -# but you can also edit it by hand. - -meteor-base@1.4.0 # Packages every Meteor app needs to have -mobile-experience@1.0.5 # Packages for a great mobile UX -mongo@1.6.2 # The database Meteor supports right now -blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views -reactive-var@1.0.11 # Reactive variable for tracker -tracker@1.2.0 # Meteor's client-side reactive programming library - -standard-minifier-css@1.5.3 # CSS minifier run for production mode -standard-minifier-js@2.4.1 # JS minifier run for production mode -es5-shim@4.8.0 # ECMAScript 5 compatibility for older browsers -ecmascript@0.12.4 # Enable ECMAScript2015+ syntax in app code -shell-server@0.4.0 # Server-side component of the `meteor shell` command - -react-meteor-data -dynamic-import@0.5.1 -session@1.2.0 -less@2.7.12 -nagoshi:rpg-awesome -accounts-password@1.5.1 -fortawesome:fontawesome -underscore@1.0.10 -pauldowman:dotenv -quarterto:hooks -http diff --git a/.meteor/platforms b/.meteor/platforms deleted file mode 100644 index efeba1b5..00000000 --- a/.meteor/platforms +++ /dev/null @@ -1,2 +0,0 @@ -server -browser diff --git a/.meteor/release b/.meteor/release deleted file mode 100644 index 97064e19..00000000 --- a/.meteor/release +++ /dev/null @@ -1 +0,0 @@ -METEOR@1.8.1 diff --git a/.meteor/versions b/.meteor/versions deleted file mode 100644 index e12bb5f9..00000000 --- a/.meteor/versions +++ /dev/null @@ -1,97 +0,0 @@ -accounts-base@1.4.4 -accounts-password@1.5.1 -allow-deny@1.1.0 -autoupdate@1.6.0 -babel-compiler@7.3.4 -babel-runtime@1.3.0 -base64@1.0.12 -binary-heap@1.0.11 -blaze@2.3.3 -blaze-html-templates@1.1.2 -blaze-tools@1.0.10 -boilerplate-generator@1.6.0 -caching-compiler@1.2.1 -caching-html-compiler@1.1.3 -callback-hook@1.1.0 -check@1.3.1 -ddp@1.4.0 -ddp-client@2.3.3 -ddp-common@1.4.0 -ddp-rate-limiter@1.0.7 -ddp-server@2.3.0 -deps@1.0.12 -diff-sequence@1.1.1 -dynamic-import@0.5.1 -ecmascript@0.12.7 -ecmascript-runtime@0.7.0 -ecmascript-runtime-client@0.8.0 -ecmascript-runtime-server@0.7.1 -ejson@1.1.0 -email@1.2.3 -es5-shim@4.8.0 -fetch@0.1.1 -fortawesome:fontawesome@4.7.0 -geojson-utils@1.0.10 -hot-code-push@1.0.4 -html-tools@1.0.11 -htmljs@1.0.11 -http@1.4.2 -id-map@1.1.0 -inter-process-messaging@0.1.0 -jquery@1.11.11 -launch-screen@1.1.1 -less@2.8.0 -livedata@1.0.18 -localstorage@1.2.0 -logging@1.1.20 -meteor@1.9.3 -meteor-base@1.4.0 -minifier-css@1.4.2 -minifier-js@2.4.1 -minimongo@1.4.5 -mobile-experience@1.0.5 -mobile-status-bar@1.0.14 -modern-browsers@0.1.4 -modules@0.13.0 -modules-runtime@0.10.3 -mongo@1.6.3 -mongo-decimal@0.1.1 -mongo-dev-server@1.1.0 -mongo-id@1.0.7 -nagoshi:rpg-awesome@0.1.0 -npm-bcrypt@0.9.3 -npm-mongo@3.1.2 -observe-sequence@1.0.16 -ordered-dict@1.1.0 -pauldowman:dotenv@1.0.1 -promise@0.11.2 -quarterto:hooks@0.2.3 -random@1.1.0 -rate-limit@1.0.9 -react-meteor-data@2.0.1 -reactive-dict@1.3.0 -reactive-var@1.0.11 -reload@1.3.0 -retry@1.1.0 -routepolicy@1.1.0 -service-configuration@1.0.11 -session@1.2.0 -sha@1.0.9 -shell-server@0.4.0 -socket-stream-client@0.2.2 -spacebars@1.0.15 -spacebars-compiler@1.1.3 -srp@1.0.12 -standard-minifier-css@1.5.3 -standard-minifier-js@2.4.1 -templating@1.3.2 -templating-compiler@1.3.3 -templating-runtime@1.3.2 -templating-tools@1.1.2 -tmeasday:check-npm-versions@0.3.2 -tracker@1.2.0 -ui@1.0.13 -underscore@1.0.10 -url@1.2.0 -webapp@1.7.4 -webapp-hashing@1.0.9 diff --git a/.meteorignore b/.meteorignore deleted file mode 100644 index 8c5dd96d..00000000 --- a/.meteorignore +++ /dev/null @@ -1,2 +0,0 @@ -stories -.storybook \ No newline at end of file diff --git a/.storybook/addons.js b/.storybook/addons.js deleted file mode 100644 index b2c72cd5..00000000 --- a/.storybook/addons.js +++ /dev/null @@ -1,3 +0,0 @@ -import '@storybook/addon-actions/register' -import '@storybook/addon-links/register' -import '@storybook/addon-console' diff --git a/.storybook/config.js b/.storybook/config.js deleted file mode 100644 index 1f9bec18..00000000 --- a/.storybook/config.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import { configure, addDecorator } from '@storybook/react' -import GlobalStyles from '../client/visual/global' - -// automatically import all files ending in *.stories.js -configure(require.context('../stories', true, /\.stories\.js$/), module) - -const Wrapper = styled.div` - margin: 1rem; -` - -addDecorator(storyFn => ( - - - {storyFn()} - -)) diff --git a/app.json b/app.json deleted file mode 100644 index 79d7ed9e..00000000 --- a/app.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "almanac", - "scripts": {}, - "env": { - "HEROKU_APP_NAME": { - "required": true - }, - "UNSPLASH_ACCESS_KEY": { - "required": false - }, - "MAILTRAP_API_TOKEN": { - "required": false - } - }, - "formation": {}, - "addons": [ - "mongolab", - "mailtrap" - ], - "buildpacks": [ - { - "url": "https://github.com/heroku/heroku-buildpack-nodejs.git" - }, - { - "url": "https://github.com/AdmitHub/meteor-buildpack-horse.git" - } - ] -} \ No newline at end of file diff --git a/client/.eslintrc.json b/client/.eslintrc.json deleted file mode 100644 index bb7dd23e..00000000 --- a/client/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "browser": true - } -} diff --git a/client/main.js b/client/main.js deleted file mode 100644 index 628a5d83..00000000 --- a/client/main.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' -import { render } from 'react-dom' -import Modal from 'react-modal' -import 'formdata-polyfill' - -import App from '../imports/ui/app' - -const root = document.createElement('div') -root.id = 'react-root' -document.body.appendChild(root) - -Modal.setAppElement('#' + root.id) - -render(, root) diff --git a/imports/api/accounts-config.js b/imports/api/accounts-config.js deleted file mode 100644 index 52aade07..00000000 --- a/imports/api/accounts-config.js +++ /dev/null @@ -1,11 +0,0 @@ -import { Meteor } from 'meteor/meteor' -import { Accounts } from 'meteor/accounts-base' - -Object.assign(Accounts.urls, { - enrollAccount(token) { - return Meteor.absoluteUrl(`verify/${token}`) - }, -}) - -Accounts.emailTemplates.siteName = 'Almanac' -Accounts.emailTemplates.from = 'Almanac ' diff --git a/imports/api/email-env.js b/imports/api/email-env.js deleted file mode 100644 index 50b437a5..00000000 --- a/imports/api/email-env.js +++ /dev/null @@ -1,51 +0,0 @@ -import url from 'url' -import { HTTP } from 'meteor/http' - -const missingVars = (...vars) => vars.filter(key => !process.env[key]) - -const missingMailgunVars = missingVars( - 'MAILGUN_SMTP_SERVER', - 'MAILGUN_SMTP_PORT', - 'MAILGUN_SMTP_LOGIN', - 'MAILGUN_SMTP_PASSWORD', -) - -const missingMailtrapVars = missingVars('MAILTRAP_API_TOKEN') - -if (missingMailgunVars.length === 0) { - process.env.MAIL_URL = url.format({ - protocol: 'smtp', - slashes: true, - hostname: process.env.MAILGUN_SMTP_SERVER, - port: process.env.MAILGUN_SMTP_PORT, - auth: `${process.env.MAILGUN_SMTP_LOGIN}:${process.env.MAILGUN_SMTP_PASSWORD}`, - }) - - console.log(`mailgunning via ${process.env.MAIL_URL}`) // eslint-disable-line no-console -} else if (missingMailtrapVars.length === 0) { - const mailtrapUrl = url.format({ - protocol: 'https', - hostname: 'mailtrap.io', - pathname: '/api/v1/inboxes.json', - query: { api_token: process.env.MAILTRAP_API_TOKEN }, - }) - - const { - data: [inbox], - } = HTTP.get(mailtrapUrl) - - process.env.MAIL_URL = url.format({ - protocol: 'smtp', - slashes: true, - hostname: inbox.domain, - port: inbox.smtp_ports[0], - auth: `${inbox.username}:${inbox.password}`, - }) -} else { - // eslint-disable-next-line no-console - console.log( - `email environment variables ${missingMailgunVars.concat( - missingMailtrapVars, - )} missing, falling back to outputting emails to stdout`, - ) -} diff --git a/imports/api/hsts.js b/imports/api/hsts.js deleted file mode 100644 index a52a7f90..00000000 --- a/imports/api/hsts.js +++ /dev/null @@ -1,14 +0,0 @@ -import { WebApp } from 'meteor/webapp' - -const THIRTY_DAYS = 30 * 24 * 60 * 60 - -if (process.env.NODE_ENV === 'production') { - WebApp.connectHandlers.use((req, res, next) => { - res.setHeader( - 'Strict-Transport-Security', - `max-age=${THIRTY_DAYS}; includeSubDomains`, - ) - - next() - }) -} diff --git a/imports/api/publications.js b/imports/api/publications.js deleted file mode 100644 index eb25adcf..00000000 --- a/imports/api/publications.js +++ /dev/null @@ -1,155 +0,0 @@ -import { Meteor } from 'meteor/meteor' -import { - Cards, - Campaigns, - Session, - Layouts, - CardHistory, -} from '../lib/collections' -import access from '../lib/access' -import * as unsplash from '../lib/utils/unsplash' -import publish from './utils/publish' - -const ownedCampaigns = ({ userId }) => - Campaigns.find({ - owner: userId, - }) - -const memberCampaigns = ({ userId }) => - Campaigns.find({ - $or: [{ owner: userId }, { member: userId }], - }) - -const visibleDocs = collection => ({ userId }) => { - const campaignIds = memberCampaigns({ userId }).map(c => c._id) - - return collection.find({ - $or: [{ owner: userId }, { campaignId: { $in: campaignIds } }], - }) -} - -const visibleCardQuery = ({ userId }) => { - const ownedCampaignIds = ownedCampaigns({ userId }).map(c => c._id) - const memberCampaignIds = memberCampaigns({ userId }).map(c => c._id) - return { - $or: [ - { owner: userId }, - { campaignId: { $in: ownedCampaignIds } }, - { - campaignId: { $in: memberCampaignIds }, - 'access.view': access.CAMPAIGN, - }, - { 'access.view': access.PUBLIC }, - ], - } -} - -publish({ - users: { - all: () => Meteor.users.find({}, { fields: { username: 1 } }), - }, - - campaigns: { - all: memberCampaigns, - - join({ args: [{ campaignId, secret }] }) { - return Campaigns.find({ - _id: campaignId, - inviteSecret: secret, - }) - }, - - members({ userId }) { - const campaigns = memberCampaigns({ userId }).fetch() - - const allCampaignUsers = campaigns.reduce( - (users, campaign) => - users - .concat(campaign.owner) - .concat(campaign.member) - .concat(campaign.removedMember || []), - [], - ) - - return Meteor.users.find({ - _id: { $in: allCampaignUsers }, - }) - }, - }, - - cards: { - /* - I can see a card if: - - - i'm the owner or the GM - - it's visible to the campaign, it's in a campaign i'm a member of - - it's public - */ - all({ userId, args: [query] }) { - return Cards.find( - { - $and: [ - query && { $text: { $search: query } }, - visibleCardQuery({ userId }), - ].filter(part => part), - }, - query - ? { - fields: { - score: { $meta: 'textScore' }, - }, - sort: { - score: { $meta: 'textScore' }, - }, - } - : {}, - ) - }, - - history({ userId }) { - const visibleCards = Cards.find(visibleCardQuery({ userId })).fetch() - const visibleCardIDs = visibleCards.map(card => card._id) - return CardHistory.find({ - 'data._id': { $in: visibleCardIDs }, - }) - }, - }, - - session: { - all: visibleDocs(Session), - }, - - layout: { - all: visibleDocs(Layouts), - }, - - unsplash: { - search({ args: [query], added, ready }) { - const photos = unsplash.search(query) - - photos.forEach(photo => { - photo.fromSearch = query - added('unsplash-photos', photo.id, photo) - }) - - ready() - }, - - getCollectionPhotos({ args: [collectionId], added, ready }) { - const photos = unsplash.getCollectionPhotos(collectionId) - - photos.forEach(photo => { - photo.fromCollection = collectionId - added('unsplash-photos', photo.id, photo) - }) - - ready() - }, - - getPhoto({ args: [photoId], added, ready }) { - const photo = unsplash.getPhoto(photoId) - added('unsplash-photos', photo.id, photo) - ready() - }, - }, -}) diff --git a/imports/api/utils/publish.js b/imports/api/utils/publish.js deleted file mode 100644 index 075cc333..00000000 --- a/imports/api/utils/publish.js +++ /dev/null @@ -1,22 +0,0 @@ -import { Meteor } from 'meteor/meteor' - -const publish = (publications, path = []) => - Object.keys(publications).forEach(key => { - const nextPath = path.concat(key) - if (typeof publications[key] === 'function') { - Meteor.publish(nextPath.join('.'), function(...args) { - return publications[key]({ - userId: this.userId, - added: this.added.bind(this), - changed: this.changed.bind(this), - removed: this.removed.bind(this), - ready: this.ready.bind(this), - args, - }) - }) - } else { - publish(publications[key], nextPath) - } - }) - -export default publish diff --git a/imports/api/utils/search.js b/imports/api/utils/search.js deleted file mode 100644 index 3a29b80e..00000000 --- a/imports/api/utils/search.js +++ /dev/null @@ -1,7 +0,0 @@ -import Fuse from 'fuse.js' - -export default (collection, options) => { - const fuse = new Fuse(collection.find().fetch(), options) - - return term => fuse.search(term) -} diff --git a/imports/lib/.eslintrc.json b/imports/lib/.eslintrc.json deleted file mode 100644 index fa31d25c..00000000 --- a/imports/lib/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "env": { - "browser": true, - "node": true - } -} diff --git a/imports/lib/access.js b/imports/lib/access.js deleted file mode 100644 index a51e6922..00000000 --- a/imports/lib/access.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - PRIVATE: 0, - CAMPAIGN: 1, - PUBLIC: 2, -} diff --git a/imports/lib/collections.js b/imports/lib/collections.js deleted file mode 100644 index fa8e20ee..00000000 --- a/imports/lib/collections.js +++ /dev/null @@ -1,31 +0,0 @@ -import { Mongo } from 'meteor/mongo' -import { Meteor } from 'meteor/meteor' - -export const Cards = new Mongo.Collection('cards') -export const CardHistory = new Mongo.Collection('card-history') -export const Campaigns = new Mongo.Collection('campaigns') -export const Session = new Mongo.Collection('session') -export const Layouts = new Mongo.Collection('layout') -export const UnsplashPhotos = new Mongo.Collection('unsplash-photos') - -if (Meteor.isServer) { - Cards._ensureIndex({ - title: 'text', - text: 'text', - }) -} - -if (Meteor.isClient) { - window.collections = exports - - window.showCollection = (collection, ...args) => - console.table(collection.find(...args)) // eslint-disable-line no-console - - window.showCollections = selector => - Object.keys(window.collections) - .filter(k => window.collections[k] instanceof Mongo.Collection) - .forEach(k => { - console.log(k) // eslint-disable-line no-console - window.showCollection(window.collections[k], selector) - }) -} diff --git a/imports/lib/methods.js b/imports/lib/methods.js deleted file mode 100644 index 398e6f1f..00000000 --- a/imports/lib/methods.js +++ /dev/null @@ -1,179 +0,0 @@ -import { Meteor } from 'meteor/meteor' -import { Random } from 'meteor/random' -import { Accounts } from 'meteor/accounts-base' -import { Campaigns, Cards, Session, Layouts, CardHistory } from './collections' -import method from './utils/method' -import collectionMethods from './utils/collection-methods' -import generateSlug from './utils/generate-slug' -import * as validators from './utils/validators' -import * as unsplash from './utils/unsplash' - -export const Campaign = collectionMethods(Campaigns, validators.doc) -export const Card = collectionMethods(Cards, validators.card, CardHistory) -export const Layout = collectionMethods(Layouts, validators.campaignDoc) - -const serverToken = Random.secret() - -function _addMember(campaign, user, secret, token) { - const originalCampaign = Campaigns.findOne(campaign._id) - - const amUser = this.userId === user._id - const amOwner = this.userId === originalCampaign.owner - const hasSecret = secret === originalCampaign.inviteSecret - const hasToken = token === serverToken - - // Owner can add anyone, anyone can add themself but only if - // they have the secret, server can do anything it likes lol - if (amOwner || ((amUser || hasToken) && hasSecret)) { - Campaigns.update(campaign._id, { - $pull: { removedMember: user._id }, - $addToSet: { member: user._id }, - }) - } else - throw new Meteor.Error( - 'doc-access-denied', - `Can't add a member to that campaign`, - ) -} - -export const addMember = method('addMember', _addMember) - -export const removeMember = method('removeMember', (campaign, user) => { - // TODO: verify can do stuff - Campaigns.update(campaign._id, { - $pull: { member: user._id }, - $addToSet: { removedMember: user._id }, - }) -}) - -const addCardHistory = method('addCardHistory', async function(history) { - CardHistory.insert({ - ...history, - owner: this.userId, - date: new Date(), - }) -}) - -export const addRelated = method('addRelated', async function(card, related) { - await Cards.update(card._id, { - $addToSet: { related: related._id }, - }) - - const cardData = Cards.findOne(card._id) - const relatedData = Cards.findOne(related._id) - - await addCardHistory({ - verb: 'link', - campaignId: card.campaignId, - data: cardData, - extra: relatedData, - }) -}) - -export const removeRelated = method('removeRelated', async function( - card, - related, -) { - Cards.update(card._id, { - $pull: { related: related._id }, - }) - - const cardData = Cards.findOne(card._id) - const relatedData = Cards.findOne(related._id) - - await addCardHistory({ - verb: 'unlink', - campaignId: card.campaignId, - data: cardData, - extra: relatedData, - }) -}) - -export const deleteCardWithRelated = method( - 'deleteCardWithRelated', - (card, { ofType: type }) => { - Cards.remove({ - $or: [ - { _id: card._id }, - { - type, - _id: { $in: card.related || [] }, - }, - ], - }) - }, -) - -export const setSession = method('setSession', (campaignId, _key, data) => { - const existing = Session.findOne({ - campaignId, - _key, - }) - - if (campaignId) { - if (existing) { - Session.update(existing._id, { $set: { data } }) - } else { - Session.insert({ data, campaignId, _key }) - } - } else { - console.trace('No campaign id') // eslint-disable-line no-console - } -}) - -export const createAccount = method('createAccount', function(user, campaign) { - if (!this.isSimulation) { - // Accounts.createUser only works on the server - const userId = Accounts.createUser(user) - - // Use Campaigns.insert not Campaign.create to bypass validation lol - const defaultCampaign = Campaigns.insert( - generateSlug( - Object.assign( - { - owner: userId, - member: [], - }, - campaign, - ), - ), - ) - - Meteor.users.update(userId, { - $set: { 'profile.defaultCampaign': defaultCampaign }, - }) - Accounts.sendEnrollmentEmail(userId, user.email) - } -}) - -export const createAccountAndJoin = method('createAccountAndJoin', function( - user, - campaign, - secret, -) { - if (!secret) { - throw new Meteor.Error('no-secret', 'tell createAccountAndJoin a secret ;)') - } - - if (!this.isSimulation) { - // Accounts.createUser only works on the server - const userId = Accounts.createUser(user) - - _addMember(campaign, { _id: userId }, secret, serverToken) - Meteor.users.update(userId, { - $set: { 'profile.defaultCampaign': campaign._id }, - }) - - Accounts.sendEnrollmentEmail(userId, user.email) - } -}) - -export const errorTest = method('errorTest', () => { - throw new Meteor.Error('test-error', 'You done goofed') -}) - -export const unsplashDownload = method('unsplashDownload', function(photoId) { - if (!this.isSimulation) { - unsplash.download(photoId) - } -}) diff --git a/imports/lib/schema.js b/imports/lib/schema.js deleted file mode 100644 index 964a0341..00000000 --- a/imports/lib/schema.js +++ /dev/null @@ -1,45 +0,0 @@ -export default { - log: { - name: 'Log', - fields: { - completed: { - label: 'Date', - type: 'text', - }, - }, - }, - - quest: { - name: 'Quest', - fields: { - completed: { - label: 'Completed', - type: 'checkbox', - format: val => (val ? '✔︎' : '✘'), - }, - }, - }, - - objective: { - name: 'Objective', - fields: { - completed: { - label: 'Completed', - type: 'checkbox', - format: val => (val ? '✔︎' : '✘'), - }, - }, - }, - - faction: { - name: 'Faction', - fields: { - relationship: { - label: 'Relationship', - type: 'number', - min: -2, - max: +2, - }, - }, - }, -} diff --git a/imports/lib/session.js b/imports/lib/session.js deleted file mode 100644 index 35ee7105..00000000 --- a/imports/lib/session.js +++ /dev/null @@ -1,19 +0,0 @@ -import { Session } from './collections' -import { setSession } from './methods' - -export default campaignId => ({ - get(_key) { - if (!campaignId) return null - - const result = Session.findOne({ - campaignId, - _key, - }) - - return result ? result.data : null - }, - - set(_key, data) { - setSession(campaignId, _key, data) - }, -}) diff --git a/imports/lib/utils/collection-methods.js b/imports/lib/utils/collection-methods.js deleted file mode 100644 index a2fbb11c..00000000 --- a/imports/lib/utils/collection-methods.js +++ /dev/null @@ -1,90 +0,0 @@ -import method from './method' -import generateSlug from './generate-slug' - -const objectToMongoUpdate = ( - obj, - top = { $set: {}, $unset: undefined }, - path = [], -) => - Object.keys(obj).reduce(({ $set, $unset }, key) => { - const value = obj[key] - const currentPath = path.concat(key) - - if (value === null) { - $unset = $unset || {} - $unset[currentPath.join('.')] = true - } else if (value && Object.getPrototypeOf(value) === Object) { - objectToMongoUpdate(value, { $set, $unset }, currentPath) - } else { - $set[currentPath.join('.')] = value - } - - return { $set, $unset } - }, top) - -export default (collection, validate, historyCollection) => { - const baseCreate = method(`${collection._name}.create`, function(data) { - validate.create(data, this.userId) - - data.owner = this.userId - data.updated = new Date() - collection.insert(data) - - if (historyCollection) { - historyCollection.insert({ - verb: 'add', - date: new Date(), - owner: this.userId, - campaignId: data.campaignId, - data, - }) - } - - return data - }) - - return { - // HACK: generate slug before passing to method so it's consistent on client and server - create: data => baseCreate(generateSlug(data)), - - update: method(`${collection._name}.update`, function({ _id }, edit) { - const data = collection.findOne(_id) - edit.updated = new Date() - - const update = objectToMongoUpdate(edit) - - if (!update.$unset) { - delete update.$unset - } - - validate.edit(data, this.userId, edit) - collection.update(_id, update) - - if (historyCollection) { - historyCollection.insert({ - verb: 'edit', - date: new Date(), - owner: this.userId, - campaignId: data.campaignId, - data: collection.findOne(_id), - }) - } - }), - - delete: method(`${collection._name}.delete`, function({ _id }) { - const data = collection.findOne(_id) - validate.edit(data, this.userId) - collection.remove(_id) - - if (historyCollection) { - historyCollection.insert({ - verb: 'delete', - date: new Date(), - owner: this.userId, - campaignId: data.campaignId, - data: {}, - }) - } - }), - } -} diff --git a/imports/lib/utils/generate-slug.js b/imports/lib/utils/generate-slug.js deleted file mode 100644 index b0022d59..00000000 --- a/imports/lib/utils/generate-slug.js +++ /dev/null @@ -1,7 +0,0 @@ -import { Random } from 'meteor/random' -import paramCase from 'param-case' - -export default data => - Object.assign(data, { - _id: `${paramCase(data.title)}-${Random.id(8)}`, - }) diff --git a/imports/lib/utils/method.js b/imports/lib/utils/method.js deleted file mode 100644 index 903e3e16..00000000 --- a/imports/lib/utils/method.js +++ /dev/null @@ -1,20 +0,0 @@ -import { Meteor } from 'meteor/meteor' -import { toast } from 'react-toastify' - -export default function method(name, fn) { - Meteor.methods({ - [name]: fn, - }) - - return (...args) => - new Promise((resolve, reject) => - Meteor.call(name, ...args, (err, ...results) => { - if (err) { - toast.error(err.reason) - reject(err) - } else { - resolve(results.length === 1 ? results[0] : results) - } - }), - ) -} diff --git a/imports/lib/utils/unsplash.js b/imports/lib/utils/unsplash.js deleted file mode 100644 index 1a2af4a5..00000000 --- a/imports/lib/utils/unsplash.js +++ /dev/null @@ -1,53 +0,0 @@ -import url from 'url' -import { Meteor } from 'meteor/meteor' -import { HTTP } from 'meteor/http' - -const unsplash = (pathname, options) => { - try { - return HTTP.get( - url.format({ - protocol: 'https', - hostname: 'api.unsplash.com', - pathname, - }), - { - headers: { - Authorization: `Client-ID ${process.env.UNSPLASH_ACCESS_KEY}`, - }, - ...options, - }, - ).data - } catch (error) { - if (error.response && error.response.content === 'Rate Limit Exceeded') { - throw new Meteor.Error( - 'unsplash-rate-limit', - 'Image search is currently unavailable. Try again in a few minutes', - ) - } - - throw error - } -} - -export const getCollectionPhotos = collectionId => - unsplash(`collections/${collectionId}/photos`, { - params: { - per_page: 100, - order_by: 'popular', - }, - }) - -export const getPhoto = photoId => unsplash(`photos/${photoId}`) - -export const search = query => - unsplash(`photos/search`, { - params: { - per_page: 100, - order_by: 'popular', - query, - }, - }) - -export const download = photoId => unsplash(`photos/${photoId}/download`) - -export default unsplash diff --git a/imports/lib/utils/validators/campaign-doc.js b/imports/lib/utils/validators/campaign-doc.js deleted file mode 100644 index 8e4f23cd..00000000 --- a/imports/lib/utils/validators/campaign-doc.js +++ /dev/null @@ -1,32 +0,0 @@ -import { Meteor } from 'meteor/meteor' -import { Campaigns } from '../../collections' -import { isLoggedIn } from './common' -import { edit as canEditDoc } from './doc' - -const canAccessCampaignDoc = (data, userId, verb) => { - const campaign = Campaigns.findOne(data.campaignId) - - if (campaign && campaign.owner === userId) { - return true - } - - if (campaign && campaign.member.includes(userId)) { - return true - } - - throw new Meteor.Error( - 'campaign-access-denied', - `Can't ${verb} a document in that campaign`, - ) -} - -export const create = (data, userId) => { - isLoggedIn(data, userId, 'create') - canAccessCampaignDoc(data, userId, 'create') -} - -export const edit = (data, userId) => { - isLoggedIn(data, userId, 'edit') - canAccessCampaignDoc(data, userId, 'edit') - canEditDoc(data, userId) -} diff --git a/imports/lib/utils/validators/card.js b/imports/lib/utils/validators/card.js deleted file mode 100644 index 904aca0e..00000000 --- a/imports/lib/utils/validators/card.js +++ /dev/null @@ -1,69 +0,0 @@ -import { Meteor } from 'meteor/meteor' -import { Campaigns } from '../../collections' -import access from '../../access' - -// Create validation is the same as any doc that belongs to a campaign -export { create } from './campaign-doc' - -/* -I can edit a card if: - -- i'm the owner or the GM of the campaign -- it's editable by the campaign, and i'm a member of the campaign it's in -- it's editable by the public (???) - */ -export const canEdit = (data, userId, diff) => { - const campaign = Campaigns.findOne(data.campaignId) - - if (!data.access) { - // Fallback for unmigrated cards, treat them as private - if (data.owner === userId) { - return true - } - - return false - } - - if ( - diff && - diff.access && - (data.access.view !== diff.access.view || - data.access.edit !== diff.access.edit) && - userId !== data.owner - ) { - throw new Meteor.Error( - 'card-access-denied', - 'Only the owner can change the access of a card', - ) - } - - if (data.access.edit >= access.PRIVATE) { - if (data.owner === userId) { - return true - } - - if (campaign && campaign.owner === userId) { - return true - } - } - - if (data.access.edit >= access.CAMPAIGN) { - if (campaign && campaign.member.includes(userId)) { - return true - } - } - - if (data.access.edit === access.PUBLIC) { - return true - } - - return false -} - -export const edit = (data, userId, diff) => { - if (canEdit(data, userId, diff)) { - return true - } - - throw new Meteor.Error('card-access-denied', `Can't edit that card`) -} diff --git a/imports/lib/utils/validators/common.js b/imports/lib/utils/validators/common.js deleted file mode 100644 index 1efed86e..00000000 --- a/imports/lib/utils/validators/common.js +++ /dev/null @@ -1,12 +0,0 @@ -import { Meteor } from 'meteor/meteor' - -export const isLoggedIn = (data, userId, verb) => { - if (userId) { - return true - } - - throw new Meteor.Error( - 'not-logged-in', - `Can't ${verb} something if you're not logged in`, - ) -} diff --git a/imports/lib/utils/validators/doc.js b/imports/lib/utils/validators/doc.js deleted file mode 100644 index 33e469ad..00000000 --- a/imports/lib/utils/validators/doc.js +++ /dev/null @@ -1,15 +0,0 @@ -import { Meteor } from 'meteor/meteor' -import { isLoggedIn } from './common' - -export const create = (data, userId) => isLoggedIn(data, userId, 'create') - -export const edit = (data, userId) => { - isLoggedIn(data, userId, 'edit') - - if (data.owner === userId) { - return true - } - - // Shrink and transform into a corn cob - throw new Meteor.Error('doc-access-denied', `Can't edit that document`) -} diff --git a/imports/lib/utils/validators/index.js b/imports/lib/utils/validators/index.js deleted file mode 100644 index 6506572b..00000000 --- a/imports/lib/utils/validators/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import * as doc from './doc' -import * as campaignDoc from './campaign-doc' -import * as card from './card' - -export { doc, campaignDoc, card } diff --git a/imports/ui/.eslintrc.json b/imports/ui/.eslintrc.json deleted file mode 100644 index bb7dd23e..00000000 --- a/imports/ui/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "browser": true - } -} diff --git a/imports/ui/app.js b/imports/ui/app.js deleted file mode 100644 index 5d29c038..00000000 --- a/imports/ui/app.js +++ /dev/null @@ -1,182 +0,0 @@ -import React, { Component } from 'react' -import useRoutes from 'boulevard-react' -import { Error } from './utils/error' -import GlobalStyles from './visual/global' -import { errorTest } from '../lib/methods' -import Layout from './pages/layout' - -import Dashboard from './pages/dashboard' -import Control, { LaunchDashboardLink } from './pages/control' -import Home from './pages/home' -import Login, { loggedInRedirect } from './pages/login' -import GetStarted from './pages/get-started' -import Campaign from './pages/campaign' -import CampaignSettings from './pages/campaign-settings' -import CampaignPlayers from './pages/campaign-players' -import NewCampaign from './pages/new-campaign' -import Verify from './pages/verify' -import Enrol from './pages/enrol' -import Card from './pages/card' -import EditCard from './pages/edit-card' - -const routes = { - '/:campaignId/dashboard'({ campaignId }) { - return ( - - - - ) - }, - - '/:campaignId/dashboard-control'({ campaignId }) { - return ( - }> - - - ) - }, - - '/:campaignId/join/:secret'({ campaignId, secret }) { - return ( - - - - ) - }, - - '/:campaignId/settings'({ campaignId }) { - return ( - - - - ) - }, - - '/:campaignId/players'({ campaignId }) { - return ( - - - - ) - }, - - '/:campaignId/new'({ campaignId }) { - return ( - - - - ) - }, - - '/:campaignId/:cardId'({ campaignId, cardId }) { - if (!cardId) return false - - return ( - - - - ) - }, - - '/:campaignId/:cardId/edit'({ campaignId, cardId }) { - if (!cardId) return false - - return ( - - - - ) - }, - - '/:campaignId'({ campaignId }) { - if (!campaignId) return false - - return ( - - - - ) - }, - - '/new-campaign'() { - return ( - - - - ) - }, - - '/get-started'(params, { title }) { - return ( - loggedInRedirect() || ( - - - - ) - ) - }, - - '/login'() { - return ( - loggedInRedirect() || ( - - - - ) - ) - }, - - '/verify/:token'({ token }) { - return ( - loggedInRedirect() || ( - - - - ) - ) - }, - - '/debug'() { - return ( - - - - ) - }, - - '/'() { - return ( - - - - ) - }, -} - -class RenderError extends Component { - state = { error: null } - - static getDerivedStateFromError(error) { - return { error } - } - - render() { - if (this.state.error) { - return - } - - return this.props.children - } -} - -export default function App() { - const { children } = useRoutes(routes) - return ( - - - {children} - - ) -} diff --git a/imports/ui/blocks/clock.js b/imports/ui/blocks/clock.js deleted file mode 100644 index 21c905f6..00000000 --- a/imports/ui/blocks/clock.js +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useState } from 'react' -import useInterval from 'use-interval' -import styled, { keyframes } from 'styled-components' - -const blink = keyframes` - 0% { opacity: 0; } - 49% { opacity: 0; } - 50% { opacity: 1; } - 100% { opacity: 0; } -` - -const Time = styled.span` - font-size: 2em; - line-height: 1; - color: ${({ late, reallyLate, lateAF }) => - lateAF ? '#c00' : reallyLate ? '#900' : late ? '#600' : 'black'}; - - transition: color linear 60s; -` - -const Hour = styled.span`` -const Minute = styled.span`` -const Colon = styled.span` - animation: 1s ${blink} infinite ease-out; - vertical-align: 0.1em; - margin-left: -0.1em; -` - -const AmPm = styled.span` - font-variant: small-caps; - font-size: 0.66em; -` - -const Clock = () => { - const [date, setDate] = useState(new Date()) - useInterval(() => setDate(new Date()), 1000) - - const h = date.getHours() % 12 - const m = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes() - const ampm = date.getHours() < 12 ? 'am' : 'pm' - - const late = date.getHours() > 20 - const reallyLate = date.getHours() > 21 - const lateAF = date.getHours() > 22 - - return ( - - ) -} - -const display = () => null - -export { Clock as control, display } diff --git a/imports/ui/blocks/factions.js b/imports/ui/blocks/factions.js deleted file mode 100644 index 17792cc4..00000000 --- a/imports/ui/blocks/factions.js +++ /dev/null @@ -1,115 +0,0 @@ -import React from 'react' -import formJson from '@quarterto/form-json' -import styled from 'styled-components' -import Ornamented from '../visual/ornamented' -import Icon from '../visual/icon' -import { useCampaignId } from '../data/campaign' -import { Button } from '../visual/primitives' -import { Card } from '../../lib/methods' -import access from '../../lib/access' -import { useCards } from '../data/card' - -const relationshipLabel = { - '-2': 'Hostile', - '-1': 'Unfriendly', - 0: 'Neutral', - 1: 'Friendly', - 2: 'Allied', -} - -const relationshipIcon = { - '-2': 'crossed-swords', - '-1': 'cracked-shield', - 0: 'castle-emblem', - 1: 'beer', - 2: 'two-hearts', -} - -const Right = styled.span` - float: right; -` - -const ModRelationship = ({ amount, faction }) => { - function modRelationship() { - const relationship = (faction.relationship || 0) + amount - - if ( - amount + faction.relationship < 3 && - amount + faction.relationship > -3 - ) { - Card.update(faction, { relationship }) - } - } - - return ( - - ) -} - -const Relationship = ({ control, faction }) => ( - - {relationshipLabel[faction.relationship || 0]}{' '} - - {control && ( - - - - - )} - -) - -const Remove = ({ faction }) => ( - -) - -const ShowFactions = ({ control = false }) => { - const campaignId = useCampaignId() - const { cards: factions } = useCards({ type: 'faction' }) - - function onCreate(ev) { - ev.preventDefault() - const data = formJson(ev.target) - ev.target.reset() - - Card.create({ - ...data, - relationship: 0, - type: 'faction', - campaignId, - access: { view: access.CAMPAIGN, edit: access.PRIVATE }, - }) - } - - return ( -
- Factions - -
    - {factions.map(faction => ( -
  • - {faction.title} - - {control && } -
  • - ))} - - {control && ( -
    - - -
    - )} -
-
- ) -} - -const FactionsControl = () => - -export { ShowFactions as display, FactionsControl as control } diff --git a/imports/ui/blocks/index.js b/imports/ui/blocks/index.js deleted file mode 100644 index 27445cf7..00000000 --- a/imports/ui/blocks/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import * as time from './time' -import * as quests from './quests' -import * as weather from './weather' -import * as factions from './factions' -import * as clock from './clock' - -export { time, quests, weather, factions, clock } diff --git a/imports/ui/blocks/quests/index.js b/imports/ui/blocks/quests/index.js deleted file mode 100644 index ca8100ef..00000000 --- a/imports/ui/blocks/quests/index.js +++ /dev/null @@ -1,292 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import Ornamented from '../../visual/ornamented' -import { useCampaignDate } from '../../data/calendar' -import { useCampaignSession, useCampaignId } from '../../data/campaign' -import { useCards } from '../../data/card' -import access from '../../../lib/access' -import formJson from '@quarterto/form-json' -import { deleteCardWithRelated, Card, addRelated } from '../../../lib/methods' -import Markdown from '../../document/markdown' - -import QuestSplash from './splash' - -const Completed = styled.span` - float: right; - font-size: 0.7em; -` - -const Large = styled(Markdown)` - font-size: 1.2em; -` - -const Objective = ({ objective, quest, control }) => { - const CampaignDate = useCampaignDate() - const campaignSession = useCampaignSession() - - function onCompleteObjective() { - Card.update(objective, { - completed: true, - completedDate: campaignSession.get('date') || 0, - }) - } - - function onStartObjective() { - Card.update(objective, { - 'access.view': access.CAMPAIGN, - }) - } - - function onDeleteObjective() { - if (confirm(`Delete ${objective.title} from ${quest.title}?`)) { - Card.delete(objective) - } - } - - return ( - <> - {control && ( - <> - {!objective.completed && objective.access.view > access.PRIVATE && ( - - )} - - {objective.access.view === access.PRIVATE && - quest.access.view > access.PRIVATE && ( - - )} - - - - )} - - {objective.completed ? {objective.title} : objective.title} - - {objective.completed && ( - - {new CampaignDate(objective.completedDate) - .format`${'h'}:${'mm'}${'a'}, ${'dddd'}, ${'Do'} of ${'MM'}, ${'YY'}`} - - )} - - ) -} - -const m = (a, b, c) => (a ? b(a) || c : c) - -const ObjectiveList = styled.ul` - margin: 1em 0; - padding: 0; - list-style: none; -` - -const ObjectiveListItem = styled.li` - &::before { - content: '${({ completed, first }) => (completed ? '✕' : first ? '❖' : '◆')}'; - margin-right: 0.25em; - } -` - -const Quest = ({ quest, control, first }) => { - const campaignId = useCampaignId() - const campaignSession = useCampaignSession() - - const { cards: objectives } = useCards( - { - type: 'objective', - _id: { $in: quest.related || [] }, - 'access.view': { $gte: control ? access.PRIVATE : access.CAMPAIGN }, - }, - { - deps: [quest.related], - }, - ) - - function onDeleteQuest() { - if (confirm(`Delete ${quest.title} and all objectives?`)) { - deleteCardWithRelated(quest, { ofType: 'objective' }) - } - } - - function onCompleteQuest() { - Card.update(quest, { - completed: true, - completedDate: campaignSession.get('date') || 0, - }) - } - - function onSelectQuest() { - Card.update(quest, { - updated: new Date(), - }) - } - - function onStartQuest() { - Card.update(quest, { - 'access.view': access.CAMPAIGN, - }) - } - - async function onCreateObjective(ev) { - ev.preventDefault() - const data = formJson(ev.target) - ev.target.reset() - - const objective = await Card.create({ - ...data, - completed: false, - type: 'objective', - campaignId, - access: { edit: access.PRIVATE, view: access.PRIVATE }, - }) - - addRelated(quest, objective) - } - - return !control && - (quest.completed || - objectives.every( - objective => - objective.completed || objective.access.view === access.PRIVATE, - )) ? null : ( -
- - {quest.completed ? {quest.title} : quest.title} - - {control && ( - <> - {!first && ( - - )} - - {!quest.completed && quest.access.view > access.PRIVATE && ( - - )} - - {quest.access.view === access.PRIVATE && ( - - )} - - { - - } - - )} - - - - {first && - m( - objectives.find(({ completed }) => !completed), - objective => objective.text, - quest.text, - )} - - - - {control && !quest.completed && ( -
  • -
    - - - -
    -
  • - )} - {objectives - .filter(({ completed }) => !completed) - .map((objective, index) => ( - - - - ))} - - {objectives - .filter(({ completed }) => completed) - .map(objective => ( - - - - ))} -
    -
    - ) -} - -const QuestsList = ({ control, ...props }) => { - const campaignId = useCampaignId() - const { ready, cards: quests } = useCards( - { - type: 'quest', - 'access.view': { $gte: control ? access.PRIVATE : access.CAMPAIGN }, - }, - { - sort: { - completed: 1, - updated: -1, - }, - }, - ) - - function onCreateQuest(ev) { - ev.preventDefault() - const data = formJson(ev.target) - ev.target.reset() - - Card.create({ - ...data, - type: 'quest', - campaignId, - access: { edit: access.PRIVATE, view: access.PRIVATE }, - }) - } - - return ( -
    - {control && ( -
    - - - -
    - )} - {ready && - quests.map((quest, index) => ( - - ))} - {!control && } -
    - ) -} - -const QuestsControl = props => - -export { QuestsList as display, QuestsControl as control } diff --git a/imports/ui/blocks/quests/splash.js b/imports/ui/blocks/quests/splash.js deleted file mode 100644 index 3038c3ea..00000000 --- a/imports/ui/blocks/quests/splash.js +++ /dev/null @@ -1,125 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react' -import styled from 'styled-components' -import Portal from 'react-portal' -import Modal from '../../visual/modal' -import Ornamented from '../../visual/ornamented' -import { Cards } from '../../../lib/collections' -import { useComputation } from '../../data/computation' -import access from '../../../lib/access' -import { useSubscription } from '../../utils/hooks' - -const QuestHeader = styled(Ornamented)` - font-family: 'Libre Baskerville', serif; - font-size: 5em; - margin: 0; - line-height: 1; -` - -const ObjectiveHeader = styled.h2` - font-family: 'Source Sans Pro', sans-serif; - font-weight: 300; - font-size: 4em; - margin: 0; - line-height: 1; -` - -const Description = styled.h3` - font-family: 'Source Sans Pro', sans-serif; - font-weight: 300; - font-size: 2.4em; - line-height: 1; -` - -const Splash = ({ action, quest, objective, animationState }) => ( - - {!objective && ( - - {action === 'complete' ? 'Completed:' : 'Started:'} - - )} - {quest.title} - {objective && ( - - {action === 'complete' ? 'Completed: ' : 'Started: '} - {objective.title} - - )} - - {(objective || quest).text} - -) - -const quest = new Audio('/sound/quest.mp3') - -const QuestSplash = () => { - const [splash, setSplash] = useState(null) - const [animationState, setAnimationState] = useState('closed') - const timer = useRef(null) - - useEffect(() => { - clearTimeout(timer.current) - - switch (animationState) { - case 'opening': - quest.play() - timer.current = setTimeout(setAnimationState, 5000, 'closing') - break - - case 'closing': - timer.current = setTimeout(setAnimationState, 5000, 'closed') - break - - case 'closed': - setSplash(null) - break - } - }, [animationState]) - - const ready = useSubscription('cards.all') - - useComputation(({ setSplash, setAnimationState, campaignId }) => { - const notify = (id, action) => { - const item = Cards.findOne(id) - const quest = - item.type === 'objective' - ? Cards.findOne({ type: 'quest', related: id }) - : item - - const objective = item.type === 'objective' ? item : null - - setSplash({ quest, objective, action }) - setAnimationState('opening') - } - - let initial = true - - const computation = Cards.find({ - type: { $in: ['quest', 'objective'] }, - 'access.view': { $gte: access.CAMPAIGN }, - campaignId, - }).observeChanges({ - added(id) { - if (!initial && ready) { - notify(id, 'start') - } - }, - - changed(id, { completed }) { - if (!initial && ready && completed) { - notify(id, 'complete') - } - }, - }) - - initial = false - return computation - }) - - return ( - - - - ) -} - -export default QuestSplash diff --git a/imports/ui/blocks/time/advance.js b/imports/ui/blocks/time/advance.js deleted file mode 100644 index 61036856..00000000 --- a/imports/ui/blocks/time/advance.js +++ /dev/null @@ -1,34 +0,0 @@ -import React, { useState } from 'react' -import { useCampaignSession } from '../../data/campaign' -import useInterval from 'use-interval' -import { useCampaignDate } from '../../data/calendar' - -const AdvanceTime = () => { - const [enabled, setEnabled] = useState(false) - const campaignSession = useCampaignSession() - const CampaignDate = useCampaignDate() - - useInterval(() => { - if (enabled) { - campaignSession.set( - 'date', - new CampaignDate(campaignSession.get('date') || 0).add({ - minute: 1, - }).timestamp, - ) - } - }, 30000) - - return ( - - ) -} - -export default AdvanceTime diff --git a/imports/ui/blocks/time/control.js b/imports/ui/blocks/time/control.js deleted file mode 100644 index 96037874..00000000 --- a/imports/ui/blocks/time/control.js +++ /dev/null @@ -1,112 +0,0 @@ -import React, { useState, useEffect } from 'react' -import { useCampaignSession } from '../../data/campaign' -import { Button, List, Group } from '../../visual/primitives' -import { Input } from '../../visual/form' -import { useCampaignDate } from '../../data/calendar' -import preventingDefault from '../../utils/preventing-default' -import Time from './display' -import AdvanceTime from './advance' -import { useTracker } from '../../utils/hooks' - -const Inc = ({ multiplier = 1, period }) => { - const campaignSession = useCampaignSession() - const CampaignDate = useCampaignDate() - - function onIncrement() { - campaignSession.set( - 'date', - new CampaignDate(campaignSession.get('date') || 0).add({ - [period]: multiplier, - }).timestamp, - ) - } - - return ( - - ) -} - -const startOfDay = timestamp => timestamp - (timestamp % 86400) - -const Morning = () => { - const campaignSession = useCampaignSession() - - function onMorning() { - campaignSession.set( - 'date', - startOfDay(campaignSession.get('date') || 0) + 86400 + 28800, - ) - } - - return ( - - ) -} - -const DateForm = () => { - const campaignSession = useCampaignSession() - const CampaignDate = useCampaignDate() - const date = useTracker( - () => new CampaignDate(campaignSession.get('date') || 0), - ) - const [_date, setDate] = useState(date.P) - - useEffect(() => { - setDate(date.P) - }, [date]) - - function onSubmit() { - campaignSession.set('date', new CampaignDate(_date).timestamp) - } - - return ( -
    - setDate(ev.target.value)} - /> - -
    - ) -} - -const TimeControl = () => ( -
    -
    -) - -export default TimeControl diff --git a/imports/ui/blocks/time/display.js b/imports/ui/blocks/time/display.js deleted file mode 100644 index fafab92d..00000000 --- a/imports/ui/blocks/time/display.js +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import { H1, H3 } from '../../visual/heading' -import Ornamented, { bordered } from '../../visual/ornamented' -import { useCampaignSession } from '../../data/campaign' -import { useCampaignDate } from '../../data/calendar' -import { useTracker } from '../../utils/hooks' - -const TimeOfDay = styled(H1)` - margin: 0; - font-size: 6em; - line-height: 1; - letter-spacing: -0.1em; - font-weight: normal; -` - -const Year = styled(H3)` - ${bordered} - font-family: 'Libre Baskerville', serif; - font-variant: normal; - margin: 0; -` - -const DateGroup = styled.time` - text-align: center; -` - -const Compact = styled.div` - line-height: 0.8; -` - -const ornaments = ['h', 'f', 'a', 't', 'n', 'c', 'o', 'p', 'e', 'r', 'k', 'l'] - -const OrnamentedMonth = ({ date }) => ( - - -
    {date.format`${'dddd'} ${'Do'}`}
    - {date.format`${'MMMM'}`} -
    -
    -) - -const Time = () => { - const campaignSession = useCampaignSession() - const CampaignDate = useCampaignDate() - const date = useTracker( - () => new CampaignDate(campaignSession.get('date') || 0), - ) - - return ( - - - - {date.format`${'h'}:${'mm'}`} - {date.a} - - - {date.YYYY} - - - ) -} - -export default Time diff --git a/imports/ui/blocks/time/index.js b/imports/ui/blocks/time/index.js deleted file mode 100644 index f1a56bd9..00000000 --- a/imports/ui/blocks/time/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default as control } from './control' -export { default as display } from './display' diff --git a/imports/ui/blocks/weather/index.js b/imports/ui/blocks/weather/index.js deleted file mode 100644 index 6b622457..00000000 --- a/imports/ui/blocks/weather/index.js +++ /dev/null @@ -1,252 +0,0 @@ -import React, { useState, useEffect } from 'react' -import styled from 'styled-components' -import Ornamented from '../../visual/ornamented' -import preventingDefault from '../../utils/preventing-default' -import { useCampaignSession } from '../../data/campaign' -import { useCampaignDate } from '../../data/calendar' -import { useTracker } from '../../utils/hooks' - -// For now we just use the first moon in the schema, -// later we may want to support multiple moons 😱. -// We also have to have a default because moon phases -// are configurable in Dream Date -const moonPhaseIcon = date => { - const moonPhaseIndex = Object.values(date.moonPhaseIndices)[0] || 0 - return ( - ['🌕', '🌖', '🌗', '🌘', '🌑', '🌒', '🌓', '🌔'][moonPhaseIndex] || '🌕' - ) -} - -const compassDir = heading => - [ - 'N', - 'NNE', - 'NE', - 'ENE', - 'E', - 'ESE', - 'SE', - 'SSE', - 'S', - 'SSW', - 'SW', - 'WSW', - 'W', - 'WNW', - 'NW', - 'NNW', - ][Math.round((heading * 16) / 360) % 16] - -const weatherCondition = ({ temperature, humidity }) => - [ - ['winter', 'sun-cloud', 'day', 'sun', 'dry', 'fire'], - ['sun-snow', 'cloud-wind', 'sun-cloud', 'sun-fog', 'sun-fog', 'tornado'], - [ - 'cloud-snow', - 'sun-cloud-rain', - 'sun-cloud-rain', - 'sun-cloud', - 'heavy-rain', - 'lightning', - ], - [ - 'snow-storm', - 'cloud-rain', - 'wet', - 'lightning', - 'lightning', - 'heavy-lightning', - ], - ][Math.min(3, Math.floor((humidity * 4) / 100))][ - Math.min(5, Math.floor(((20 + temperature) * 6) / 80)) - ] - -const defaultWeather = { - temperature: 10, - humidity: 50, - windHeading: 0, - windSpeed: 10, -} - -// Fast linear that ︵__ -// goes a bit too far / -// and slinks back / -const youreSoVane = 'cubic-bezier(.52, 1.65, .29, .9)' - -const WindArrow = styled.span` - display: inline-block; - will-change: transform; - transform: rotate(${({ heading }) => heading}deg); - transition: transform 2s ${youreSoVane}; -` - -const WindDirection = ({ heading }) => ( - - - {compassDir((heading + 180) % 360)} - -) - -const WeatherIcon = styled.div` - display: inline; - vertical-align: -0.85em; - font-size: 3em; - text-align: center; - - img { - width: 1em; - height: 1em; - } -` - -const WeatherThings = styled.div` - display: flex; - justify-content: space-between; - margin-top: 2rem; - margin-bottom: -2rem; - position: relative; - z-index: 2; -` - -const WeatherThing = styled.div` - line-height: 0; - font-size: 1.25em; -` - -const WeatherWrapper = styled.div` - margin-top: 1rem; -` - -const WeatherCondition = ({ temperature, humidity }) => { - const condition = weatherCondition({ temperature, humidity }) - return {condition} -} - -const useWeather = () => { - const campaignSession = useCampaignSession() - const CampaignDate = useCampaignDate() - - return useTracker(() => { - const date = new CampaignDate(campaignSession.get('date')) - return { - weather: campaignSession.get('weather') || defaultWeather, - date, - } - }) -} - -// TODO: seasons, sunset time - -const Weather = () => { - const { - weather: { temperature, humidity, windHeading }, - date, - } = useWeather() - - return ( - - - {temperature}°C - - - - - - - {date.isNight ? ( - moonPhaseIcon(date) - ) : ( - - )} - - - - ) -} - -const FixedWidthLabel = styled.label` - display: inline-block; - width: ${({ size = 3.5 }) => size}em; -` - -const WeatherForm = () => { - const { weather } = useWeather() - const [_weather, setWeather] = useState(weather) - - const campaignSession = useCampaignSession() - - useEffect(() => { - setWeather(oldWeather => Object.assign(oldWeather, weather)) - }, [weather]) - - function onSubmit() { - campaignSession.set('weather', _weather) - } - - return ( -
    -
    - {_weather.temperature}°C - setWeather({ temperature: ev.target.valueAsNumber })} - /> -
    -
    - {_weather.humidity}% - setWeather({ humidity: ev.target.valueAsNumber })} - /> -
    -
    - - - - setWeather({ windHeading: ev.target.valueAsNumber })} - /> -
    -
    - - {_weather.windSpeed} - KN - - setWeather({ windSpeed: ev.target.valueAsNumber })} - /> -
    - -
    - ) -} - -const WeatherControl = () => ( - <> - - - -) - -export { WeatherControl as control, Weather as display } diff --git a/imports/ui/collection/block-layout.js b/imports/ui/collection/block-layout.js deleted file mode 100644 index 319ddf2c..00000000 --- a/imports/ui/collection/block-layout.js +++ /dev/null @@ -1,124 +0,0 @@ -import React, { useState } from 'react' -import styled, { createGlobalStyle } from 'styled-components' -import { - default as GridLayout, - WidthProvider as widthProvider, -} from 'react-grid-layout' -import { Layouts } from '../../lib/collections' -import * as blocks from '../blocks' -import { Layout } from '../../lib/methods' - -import 'react-grid-layout/css/styles.css' -import 'react-resizable/css/styles.css' -import { useCampaignId } from '../data/campaign' -import { useSubscription, useCursor } from '../utils/hooks' - -const ReactGrid = createGlobalStyle` - .react-grid-item { - overflow: auto; - } - - .grid-control .react-grid-item { - border: 1px solid #e9e1da; - background: #f9f1ea; - } - - .react-grid-item.react-grid-placeholder { - background: teal; - } - - .react-resizable-handle { - z-index: 100; - } -` - -const GridLayoutWidth = widthProvider(GridLayout) - -const ComponentSelect = ({ onSelect }) => { - const [selected, select] = useState('') - - return ( - <> - - - - - ) -} - -const CloseButton = styled.button` - position: absolute; - top: 0; - right: 0; - z-index: 1000; -` - -const Bleed = styled.div` - grid-column: bleed; -` - -export default ({ which, ...props }) => { - const campaignId = useCampaignId() - const ready = useSubscription('layout.all') - const layout = useCursor(Layouts.find({ campaignId }), [ready, campaignId]) - - function updateLayout(layout) { - layout.forEach(({ i, ...item }) => { - Layout.update({ _id: i }, item) - }) - } - - function addComponent(component) { - Layout.create({ component, x: 0, y: 0, w: 2, h: 1, campaignId }) - } - - function removeComponent(layout) { - Layout.delete(layout) - } - - return ( - - {which === 'control' && } - ({ i: _id, ...item }))} - isDraggable={which === 'control'} - isResizable={which === 'control'} - rowHeight={60} - draggableCancel='input, button, select' - {...(which === 'control' ? { onLayoutChange: updateLayout } : {})} - > - {layout.map(item => ( -
    - {which === 'control' && ( - removeComponent(item)}>× - )} - {blocks[item.component] - ? React.createElement(blocks[item.component][which], props) - : 'unknown component'} -
    - ))} -
    -
    - ) -} diff --git a/imports/ui/collection/card-history.js b/imports/ui/collection/card-history.js deleted file mode 100644 index 0b546f84..00000000 --- a/imports/ui/collection/card-history.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react' -import { useSubscription, useCursor } from '../utils/hooks' -import relativeDate from 'tiny-relative-date' -import { Link } from 'use-history' -import styled from 'styled-components' - -import Icon from '../visual/icon' -import { Owner } from '../document/user' -import { CardHistory } from '../../lib/collections' -import match from '../utils/match' -import { useCampaignId } from '../data/campaign' - -const getHistoryIcon = match({ - add: 'file-text-o', - edit: 'edit', - link: 'link', - unlink: 'chain-broken', -}) - -const IconList = styled.ul` - padding: 0; - margin: 1em 0 1em 0.75em; - list-style: none; - - li { - margin-left: 0.75em; - margin-bottom: 0.5em; - } - - .fa, - .ra { - margin-right: 0.25em; - margin-left: -1.25em; - } -` - -const ChangeMeta = styled.span` - font-size: 0.8em; - color: rgba(0, 0, 0, 0.6); -` - -const ChangeData = data => ( - <> - {data.type && `the ${data.type} `} - {data.title} - -) - -const useHistory = (query, deps) => { - const ready = useSubscription('cards.history') - const history = useCursor( - CardHistory.find(query, { sort: [['date', 'desc']] }), - [ready, ...deps], - ) - - return { ready, history } -} - -const HistoryList = ({ history, ...props }) => ( - - {history.map(change => ( -
  • - - {change.verb + 'ed '} - {change.data && } - {change.extra && ( - <> - {' '} - and - - )} -
    - - {' '} - - -
  • - ))} -
    -) - -export default props => { - const campaignId = useCampaignId() - const { history } = useHistory({ campaignId }, [campaignId]) - - return -} - -export const CardHistoryList = ({ card, ...props }) => { - const { history } = useHistory({ 'data._id': card._id }, [card._id]) - - return -} diff --git a/imports/ui/collection/card-list.js b/imports/ui/collection/card-list.js deleted file mode 100644 index ce0898a6..00000000 --- a/imports/ui/collection/card-list.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' - -import ShowCard from '../document/card' -import { FlexGrid } from '../visual/grid' -import { useCardSearch } from '../data/card-search' - -const CardList = ({ search }) => { - const { cards, ready } = useCardSearch({ search }) - - return ( - - {ready - ? cards.map(card => ) - : Array.from({ length: 20 }, (_, i) => ( - 0.5 ? ' ' : '', - cover: Math.random() > 0.5 ? { from: 'mock' } : '', - }} - /> - ))} - - ) -} - -export default CardList diff --git a/imports/ui/collection/card-search.js b/imports/ui/collection/card-search.js deleted file mode 100644 index 084278cd..00000000 --- a/imports/ui/collection/card-search.js +++ /dev/null @@ -1,53 +0,0 @@ -import React, { Children } from 'react' -import Shortcut from '../visual/shortcut' -import { Input } from '../visual/form' - -import { MenuItem, MenuButton } from '../visual/menu' - -const createCtrlEnterHandler = action => event => { - // ⌘↩︎ or Ctrl+Enter - if (event.which === 13 && (event.ctrlKey || event.metaKey)) { - action() - } -} - -const MaybeBackwardsFragment = ({ reverse, children }) => ( - <>{reverse ? Children.toArray(children).reverse() : children} -) - -const Search = ({ - value, - onChange, - actionLabel, - searchAction, - placeholder = 'Search…', - right = false, - ...props -}) => ( - - - { - if (onChange) { - onChange(ev.target.value) - } - }} - onKeyDown={searchAction && createCtrlEnterHandler(searchAction)} - {...props} - /> - - {value && searchAction && ( - - - {navigator.platform === 'MacIntel' ? '⌘↩︎' : 'Ctrl + Enter'} - - {actionLabel || 'Quick add'} - - )} - -) - -export default Search diff --git a/imports/ui/collection/type-select.js b/imports/ui/collection/type-select.js deleted file mode 100644 index c72bb49c..00000000 --- a/imports/ui/collection/type-select.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import _ from 'lodash' -import schema from '../../lib/schema' -import { BonelessSelect } from '../visual/form' - -export default props => ( - - - - {_.map(schema, (type, id) => ( - - ))} - -) diff --git a/imports/ui/control/form.js b/imports/ui/control/form.js deleted file mode 100644 index 515d89fc..00000000 --- a/imports/ui/control/form.js +++ /dev/null @@ -1,155 +0,0 @@ -import React, { - useState, - createContext, - useContext, - useEffect, - useCallback, -} from 'react' -import onlyValidAttributes from '@apaleslimghost/only-valid-attributes' - -export const getInputValue = el => - el[ - { - number: 'valueAsNumber', - range: 'valueAsNumber', - date: 'valueAsDate', - checkbox: 'checked', - radio: 'value', - }[el.type] || 'value' - ] - -export const getSelectValue = el => el.options[el.selectedIndex].value - -const Fields = createContext({}) -const SetFields = createContext(() => {}) - -export const useFormFields = () => useContext(Fields) -export const useFormSet = () => useContext(SetFields) - -const qq = (a, b) => (a === undefined ? b : a) - -export const Input = ({ - name, - fieldRef, - tag: Tag = 'input', - onChange, - ...props -}) => { - const fields = useFormFields() - const setFields = useFormSet() - - return ( - { - if (setFields) { - if (props.type !== 'radio' || ev.target.checked) { - setFields({ - [name]: getInputValue(ev.target), - }) - } - } - - if (onChange) { - onChange(ev) - } - }} - /> - ) -} - -export const Select = ({ tag: Tag = 'select', ...props }) => { - const fields = useFormFields() - const setFields = useFormSet() - - return ( - { - if (props.onChange) { - props.onChange(ev) - } - - if (setFields) { - setFields({ - [props.name]: getSelectValue(ev.target), - }) - } - }} - > - {props.children} - - ) -} - -export const Form = ({ - initialData = {}, - name, - tag: Tag = 'form', - onChange, - onSubmit: _onSubmit, - onDidSubmit, - ...props -}) => { - const setContextFields = useFormSet() - const [fields, _setFields] = useState(initialData) - - useEffect(() => { - if (setContextFields && name) { - setContextFields({ - [name]: fields, - }) - } - - if (onChange) { - onChange(fields) - } - }, [fields, name, onChange, setContextFields]) - - const setFields = useCallback( - childFields => { - _setFields(currentFields => ({ ...currentFields, ...childFields })) - }, - [_setFields], - ) - - async function onSubmit(ev) { - // TODO validation? - ev.preventDefault() - - await _onSubmit(fields) - _setFields(initialData) - - if (onDidSubmit) { - onDidSubmit(fields) - } - } - - return ( - - - - - - ) -} diff --git a/imports/ui/control/image-select.js b/imports/ui/control/image-select.js deleted file mode 100644 index 69cc0478..00000000 --- a/imports/ui/control/image-select.js +++ /dev/null @@ -1,177 +0,0 @@ -import React, { useState } from 'react' -import qs from 'querystring' -import { useSubscription, useCursor } from '../utils/hooks' -import styled, { css } from 'styled-components' -import colours from '@quarterto/colours' - -import { UnsplashPhotos } from '../../lib/collections' -import { FlexGrid } from '../visual/grid' -import preventingDefault from '../utils/preventing-default' -import { unsplashDownload } from '../../lib/methods' - -import { Button, List } from '../visual/primitives' -import Icon from '../visual/icon' -import Tabs from './tabs' -import Modal from './modal' -import { useFormFields, useFormSet } from './form' - -const FlexImg = styled.img` - width: 100%; - height: auto; - display: block; -` - -const ImgSelect = styled.button.attrs(() => ({ type: 'button' }))` - border: 0 none; - background: none; - padding: 0; - - ${({ selected }) => - selected && - css` - outline: 5px solid ${colours.sky.primary}; - `}; -` - -const getThumb = ({ urls }, { w = 450, h = 150 } = {}) => - urls.raw + - '&' + - qs.stringify({ - fit: 'crop', - crop: 'entropy', - w, - h, - }) - -const useSetImage = name => { - const setFields = useFormSet() - - return image => { - setFields({ - [name]: image, - }) - - if (image) { - unsplashDownload(image.id) - } - } -} - -const ImageSelectSection = ({ photos, name, onSelect }) => { - const fields = useFormFields() - const setImage = useSetImage(name) - - return ( - - {photos.map(photo => ( - { - const image = { from: 'unsplash', id: photo.id } - setImage(image) - - if (onSelect) { - onSelect(image) - } - })} - > - - - ))} - - ) -} - -const useUnsplashSearch = query => { - const ready = useSubscription('unsplash.search', query) - const photos = useCursor(UnsplashPhotos.find({ fromSearch: query }), [ready]) - - return { ready, photos } -} - -const useAlmanacCollection = () => { - const ready = useSubscription('unsplash.getCollectionPhotos', '2021417') - const photos = useCursor(UnsplashPhotos.find({ fromCollection: '2021417' }), [ - ready, - ]) - return { ready, photos } -} - -const SearchImage = props => { - const [query, setQuery] = useState('') - const { ready, photos } = useUnsplashSearch(query) - return ( - <> - setQuery(ev.target.value)} - /> - {query && - (ready ? ( - - ) : ( - 'loading...' - ))} - - ) -} - -const CollectionImage = props => { - const { ready, photos } = useAlmanacCollection() - if (!ready) return 'Loading...' - - return -} - -const ImageSelectTabs = props => { - return ( - - {{ - Suggested: , - Search: , - }} - - ) -} - -export default ImageSelectTabs - -export const ImageSelectModal = ({ name }) => { - const fields = useFormFields() - const setImage = useSetImage(name) - - return ( - - fields[name] ? ( - - - - - ) : ( - - ) - } - render={({ close }) => ( - - )} - /> - ) -} diff --git a/imports/ui/control/modal.js b/imports/ui/control/modal.js deleted file mode 100644 index 17f87289..00000000 --- a/imports/ui/control/modal.js +++ /dev/null @@ -1,17 +0,0 @@ -import React, { useState } from 'react' -import Modal from 'react-modal' - -export default ({ control: Control = 'button', render, ...props }) => { - const [open, setOpen] = useState(false) - return ( - <> - setOpen(true)} /> - setOpen(false)} {...props}> - {render({ - ...props, - close: () => setOpen(false), - })} - - - ) -} diff --git a/imports/ui/control/privacy.js b/imports/ui/control/privacy.js deleted file mode 100644 index a0da1765..00000000 --- a/imports/ui/control/privacy.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import accessLevels from '../../lib/access' -import Icon from '../visual/icon' -import { LabelledInput as Label } from '../visual/primitives' -import match from '../utils/match' -import { Form, Input, getInputValue, useFormFields, useFormSet } from './form' - -const getPrivacyIcon = match({ - [accessLevels.PRIVATE]: 'lock', - [accessLevels.CAMPAIGN]: 'double-team', - [accessLevels.PUBLIC]: 'globe', -}) - -const getPrivacyLabel = match({ - [accessLevels.PRIVATE]: 'Only You', - [accessLevels.CAMPAIGN]: 'Campaign', - [accessLevels.PUBLIC]: 'Public', -}) - -const defaultAccess = { view: accessLevels.PRIVATE, edit: accessLevels.PRIVATE } - -export const PrivacyIcon = ({ level }) => - -const Range = styled(Input)` - width: ${({ max }) => (4 * max) / accessLevels.PUBLIC}em; - vertical-align: middle; -` - -const AccessSelect = ({ maxLevel = accessLevels.PUBLIC, ...props }) => ( - -) - -const AccessGrid = styled.div` - display: grid; - grid-template-areas: - 'label range text' - 'label range text'; - grid-column-gap: 0.5em; - align-items: center; -` - -const AccessLabel = styled(Label)` - display: block; - text-align: right; -` - -const AccessText = styled.div` - .ra, - .fa { - margin-right: 0.25em; - } -` - -const AccessForm = ({ access = defaultAccess, ...props }) => { - const { view, edit } = useFormFields() - const setFields = useFormSet() - - return ( -
    - - Visible to -
    - { - setFields({ - edit: Math.min(getInputValue(ev.target), edit), - }) - }} - /> -
    - - - {getPrivacyLabel(view)} - - Editable by -
    - -
    - - - {getPrivacyLabel(edit)} - -
    -
    - ) -} - -export default AccessForm diff --git a/imports/ui/control/tabs.js b/imports/ui/control/tabs.js deleted file mode 100644 index df1a1488..00000000 --- a/imports/ui/control/tabs.js +++ /dev/null @@ -1,42 +0,0 @@ -import React, { useState } from 'react' -import styled, { css } from 'styled-components' - -import preventingDefault from '../utils/preventing-default' - -const TabBar = styled.div` - border-bottom: 1px solid black; - margin: 0.5em 0; -` - -const Tab = styled.button` - padding: 0.5em 1em; - border: 0 none; - ${({ selected }) => - selected && - css` - box-shadow: inset 0 -2px 0 black; - `} - background: none; - font: inherit; -` - -export default ({ children }) => { - const [tab, setTab] = useState(Object.keys(children)[0]) - - return ( -
    - - {Object.keys(children).map(t => ( - setTab(t))} - > - {t} - - ))} - - {children[tab]} -
    - ) -} diff --git a/imports/ui/data/calendar.js b/imports/ui/data/calendar.js deleted file mode 100644 index 2ea94c2a..00000000 --- a/imports/ui/data/calendar.js +++ /dev/null @@ -1,43 +0,0 @@ -import HarptosDate from 'dream-date/calendar/harptos' -import HarptosCommonDate from 'dream-date/calendar/harptos-common' -import OdreianDate from 'dream-date/calendar/odreian' -import TideDate from 'dream-date/calendar/tide' -import GregorianDate from 'dream-date/calendar/gregorian' -import { useCampaign } from './campaign' - -export const calendars = { - harptosV1: { - name: 'Harptos', - dateConstructor: HarptosDate, - }, - harptosCommonV1: { - name: 'Harptos (common)', - dateConstructor: HarptosCommonDate, - }, - odreianV1: { - name: 'Odreian', - dateConstructor: OdreianDate, - }, - tideV1: { - name: 'Tide', - dateConstructor: TideDate, - }, - gregorianV1: { - name: 'Gregorian', - dateConstructor: GregorianDate, - }, -} - -const defaultCalendarId = 'odreianV1' - -export const calendarList = Object.entries(calendars).map(([id, detail]) => - Object.assign({ id }, detail), -) - -const getCampaignDateConstructor = calendarId => - (calendars[calendarId] || calendars[defaultCalendarId]).dateConstructor - -export const useCampaignDate = () => { - const { calendar } = useCampaign() - return getCampaignDateConstructor(calendar) -} diff --git a/imports/ui/data/campaign.js b/imports/ui/data/campaign.js deleted file mode 100644 index df5f4fac..00000000 --- a/imports/ui/data/campaign.js +++ /dev/null @@ -1,27 +0,0 @@ -import { createContext, useContext } from 'react' -import { useSubscription, useFindOne } from '../utils/hooks' -import getCampaignSession from '../../lib/session' -import { Campaigns } from '../../lib/collections' - -export const useCampaignData = ({ campaignId, secret }) => { - const campaignReady = useSubscription('campaigns.all') - const joinReady = useSubscription('campaigns.join', { campaignId, secret }) - const ready = campaignReady && (!secret || joinReady) - const campaign = useFindOne(Campaigns, campaignId, [campaignId, ready]) - - return { campaign, ready } -} - -export const checkCampaignExists = ({ campaign, ready, campaignId }) => { - if (campaignId && ready && !campaign) { - throw new Error(`Campaign ${campaignId} not found`) - } -} - -export const CampaignContext = createContext(null) -export const useCampaign = () => useContext(CampaignContext) -export const useCampaignId = () => useCampaign()._id -export const useCampaignSession = () => { - const id = useCampaignId() - return getCampaignSession(id) -} diff --git a/imports/ui/data/card-search.js b/imports/ui/data/card-search.js deleted file mode 100644 index 1b36f8a1..00000000 --- a/imports/ui/data/card-search.js +++ /dev/null @@ -1,23 +0,0 @@ -import { useSubscription, useCursor } from '../utils/hooks' -import { Cards } from '../../lib/collections' -import { useCampaignId } from './campaign' - -export const useCardSearch = ({ search }) => { - const campaignId = useCampaignId() - const ready = useSubscription('cards.all', search) - const cards = useCursor( - Cards.find( - { campaignId }, - search - ? { - sort: [['score', 'desc']], - } - : { - sort: [['title', 'asc']], - }, - ), - [ready, search, campaignId], - ) - - return { ready, cards } -} diff --git a/imports/ui/data/card.js b/imports/ui/data/card.js deleted file mode 100644 index 168be101..00000000 --- a/imports/ui/data/card.js +++ /dev/null @@ -1,26 +0,0 @@ -import { useCursor, useFindOne, useSubscription } from '../utils/hooks' -import { Cards } from '../../lib/collections' -import { useCampaignId } from './campaign' - -export const useCards = (query, { deps = [], ...options } = {}) => { - const campaignId = useCampaignId() - const ready = useSubscription('cards.all') - const cards = useCursor(Cards.find({ campaignId, ...query }, options), [ - ready, - ...deps, - ]) - return { ready, cards } -} - -export const useCard = (_id, deps = []) => { - const campaignId = useCampaignId() - const ready = useSubscription('cards.all') - const card = useFindOne(Cards, { campaignId, _id }, [ - _id, - campaignId, - ready, - ...deps, - ]) - - return { ready, card } -} diff --git a/imports/ui/data/computation.js b/imports/ui/data/computation.js deleted file mode 100644 index 4888ac6e..00000000 --- a/imports/ui/data/computation.js +++ /dev/null @@ -1,15 +0,0 @@ -import { Tracker } from 'meteor/tracker' -import { useTracker } from '../utils/hooks' - -export const useComputation = (startComputation, dependencies = []) => - useTracker(() => { - let computation - - process.nextTick(() => { - Tracker.autorun(() => { - computation = startComputation() - }) - }) - - return () => computation.stop() - }, dependencies) diff --git a/imports/ui/data/image.js b/imports/ui/data/image.js deleted file mode 100644 index 79fe519d..00000000 --- a/imports/ui/data/image.js +++ /dev/null @@ -1,24 +0,0 @@ -import { useSubscription } from '../utils/hooks' -import { UnsplashPhotos } from '../../lib/collections' - -const getImageSubscription = (image = { from: 'nowhere' }) => { - switch (image.from) { - case undefined: - // backwards compatibility for implicit unsplash images - return getImageSubscription({ from: 'unsplash', id: image }) - - case 'unsplash': - return { - image: UnsplashPhotos.findOne(image.id), - subscription: ['unsplash.getPhoto', image.id], - } - } - - return { image: null, subscription: [false, null] } -} - -export const useImage = imageQuery => { - const { subscription, image } = getImageSubscription(imageQuery) - const ready = useSubscription(...subscription) - return { ready, image } -} diff --git a/imports/ui/data/owner.js b/imports/ui/data/owner.js deleted file mode 100644 index ea2ccd0c..00000000 --- a/imports/ui/data/owner.js +++ /dev/null @@ -1,22 +0,0 @@ -import { Meteor } from 'meteor/meteor' -import { useSubscription, useFindOne, useTracker } from '../utils/hooks' - -export const useOwner = item => { - const ready = useSubscription('users.all') - const owner = useFindOne(Meteor.users, item && item.owner, [ready, item]) - return { ready, owner } -} - -export const useAmOwner = item => { - const me = useTracker(() => Meteor.userId()) - const { ready, owner } = useOwner(item) - - return { ready, amOwner: owner && owner._id === me } -} - -export const useAssertAmOwner = item => { - const { ready, amOwner } = useAmOwner(item) - if (item && ready && !amOwner) { - throw new Error(`You're not allowed to do that`) - } -} diff --git a/imports/ui/document/campaign-settings.js b/imports/ui/document/campaign-settings.js deleted file mode 100644 index 908e6b07..00000000 --- a/imports/ui/document/campaign-settings.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react' -import { Form, useFormFields } from '../control/form' -import { Input, Select } from '../visual/form' -import { Button, LabelledInput as Label } from '../visual/primitives' -import { calendarList } from '../data/calendar' -import ImageSelect from '../control/image-select' - -export default ({ campaign, ...props }) => { - const { username } = useFormFields() - - return ( -
    - - - {/* TODO most of this can be in an 'advanced settings' accordion? */} - - - - - - - {props.onSubmit && } - - ) -} diff --git a/imports/ui/document/card.js b/imports/ui/document/card.js deleted file mode 100644 index aa3405b4..00000000 --- a/imports/ui/document/card.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react' -import styled, { css } from 'styled-components' - -import { Link } from 'use-history' -import { useImage } from '../data/image' -import { SplashBackground, Hero, HeroTitle } from '../visual/splash' -import { Card as CardPrimitive } from '../visual/primitives' -import Markdown from './markdown' - -const CardHeaderBackground = styled(SplashBackground)` - margin: -1rem -1rem 0; - padding-top: 0.5rem; - transition: filter 0.2s; - will-change: filter; - border-top-left-radius: 2px; - border-top-right-radius: 2px; - - ${({ image }) => - image && - css` - height: 6rem; - `} - - a:hover & { - filter: contrast(120%) brightness(95%) saturate(110%); - } - - &:last-child { - margin-bottom: -1rem; - border-bottom-left-radius: 2px; - border-bottom-right-radius: 2px; - } -` - -const CardHeader = ({ card, ...props }) => { - const { image } = useImage(card.cover) - - return -} - -const CardLink = styled(Link)` - text-decoration: none; - color: inherit; - ${({ card }) => - card && - css` - grid-row: span ${3 + Boolean(card.cover) + Boolean(card.text)}; - `}; - - ${CardPrimitive} { - height: 100%; - } -` - -const ListImageBackground = styled(SplashBackground)` - min-height: 2rem; - padding: 0.5rem; - font-family: 'Libre Baskerville', serif; - font-size: 0.8em; - - a:hover & { - filter: contrast(120%) brightness(95%) saturate(110%); - } -` - -const ListImage = ({ card, ...props }) => { - const { image } = useImage(card.cover) - - return -} - -export const CardListItem = ({ card, onClick }) => ( -
  • - - {card.title} - -
  • -) - -export default ({ card }) => ( - - - - - {card.title} - - - - - - -) diff --git a/imports/ui/document/markdown.js b/imports/ui/document/markdown.js deleted file mode 100644 index d1d6b62b..00000000 --- a/imports/ui/document/markdown.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' -import Markdown from 'react-markdown' -import behead from 'remark-behead' -import styled from 'styled-components' - -export default ({ excerpt, ...props }) => ( - ( - <> - {excerpt - ? children.find(child => child.type === 'p') || children[0] - : children} - - ), - blockquote: styled.blockquote` - border-left: 3px solid rgba(0, 0, 0, 20%); - margin: 0; - padding: 0em 1em; - overflow: hidden; // prevent paragraph margins collapsing - font-size: 1.1em; - font-family: 'Libre Baskerville', serif; - `, - }} - {...props} - /> -) diff --git a/imports/ui/document/user.js b/imports/ui/document/user.js deleted file mode 100644 index 8e3d9959..00000000 --- a/imports/ui/document/user.js +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import colours from '@quarterto/colours' -import Gravatar from '../visual/gravatar' -import { Label, LabelBody } from '../visual/primitives' -import { useOwner } from '../data/owner' -import { useSubscription } from '../utils/hooks' - -const UserText = styled.span` - font-style: ${({ verified }) => (verified ? 'normal' : 'italic')}; - color: ${({ verified }) => (verified ? 'inherit' : colours.steel[2])}; -` - -// Styled-components correctly handles arbitrary props passed to dom nodes -// and i can't be fucked working out how to implement that so -const Slurp = styled.span`` - -const User = ({ - user, - component: Component = Slurp, - small, - children, - ...props -}) => { - const primaryEmail = user.emails ? user.emails[0] : {} - return ( - - - - {user.username || primaryEmail.address} - - {!primaryEmail.verified && ( - - )} - - {children} - - ) -} - -export default User - -export const Owner = ({ of: ofThing, ...props }) => { - const ready = useSubscription('campaigns.members') - const { owner } = useOwner(ofThing) - return ready ? : null -} diff --git a/imports/ui/pages/campaign-players.js b/imports/ui/pages/campaign-players.js deleted file mode 100644 index e4d0668b..00000000 --- a/imports/ui/pages/campaign-players.js +++ /dev/null @@ -1,108 +0,0 @@ -import { Meteor } from 'meteor/meteor' -import { useTracker } from '../utils/hooks' -import { Random } from 'meteor/random' -import React from 'react' -import { useCampaign } from '../data/campaign' -import User from '../document/user' -import { Campaign, removeMember, addMember } from '../../lib/methods' -import { Button } from '../visual/primitives' -import { H2 } from '../visual/heading' -import { Main } from '../visual/grid' -import { useAssertAmOwner } from '../data/owner' - -const RemoveUser = ({ user }) => { - const campaign = useCampaign() - - function removeUser() { - const reallyRemove = confirm( - `Remove ${user.username || user.emails[0].address} from ${ - campaign.title - }?`, - ) - - if (reallyRemove) { - removeMember(campaign, user) - } - } - - return -} - -const ReinstateUser = ({ user }) => { - const campaign = useCampaign() - - return -} - -const Players = ({ actionComponent: Action, playerIds }) => { - const campaign = useCampaign() - const players = useTracker( - () => - Meteor.users - .find({ - _id: { $in: playerIds }, - }) - .fetch(), - [playerIds], - ) - - return ( -
      - {players.map(user => ( -
    • - - {user._id !== campaign.owner && } -
    • - ))} -
    - ) -} - -const InviteLink = () => { - const campaign = useCampaign() - - return ( -
    - {campaign.inviteSecret && ( - - {`${location.protocol}//${location.host}/${campaign._id}/join/`} - {campaign.inviteSecret} - - )} - - -
    - ) -} - -export default () => { - const campaign = useCampaign() - useAssertAmOwner(campaign) - - return ( -
    -

    Current players

    - - -

    Removed players

    - - -

    Invitations

    - -
    - ) -} diff --git a/imports/ui/pages/campaign-settings.js b/imports/ui/pages/campaign-settings.js deleted file mode 100644 index 15f2c9bc..00000000 --- a/imports/ui/pages/campaign-settings.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react' -import { navigate as go } from 'use-history' -import { useCampaign } from '../data/campaign' -import CampaignSettings from '../document/campaign-settings' -import { Campaign } from '../../lib/methods' -import { useAssertAmOwner } from '../data/owner' -import { Main } from '../visual/grid' - -export default () => { - const campaign = useCampaign() - useAssertAmOwner(campaign) - - function onSubmit(data) { - Campaign.update(campaign, data) - go(`/${campaign._id}`) - } - - return ( -
    - -
    - ) -} diff --git a/imports/ui/pages/campaign.js b/imports/ui/pages/campaign.js deleted file mode 100644 index a81230ea..00000000 --- a/imports/ui/pages/campaign.js +++ /dev/null @@ -1,73 +0,0 @@ -import React, { useEffect, useState } from 'react' -import _ from 'lodash' - -import { useCampaign } from '../data/campaign' -import { CampaignSplash } from '../visual/splash' -import Title from '../utils/title' -import CardList from '../collection/card-list' -import { SplashToolbar, MenuLink, Space, Center } from '../visual/menu' -import Icon from '../visual/icon' -import Search from '../collection/card-search' -import HistoryList from '../collection/card-history' -import { Main, Aside } from '../visual/grid' -import { Card } from '../../lib/methods' -import { navigate as go, useCurrentUrl } from 'use-history' - -export default () => { - let { url, state } = useCurrentUrl() - state = state || {} // ugh - - const debouncedSetSearch = _.debounce(search => go(url, { search }), 300) - - const campaign = useCampaign() - const [_search, _setSearch] = useState('') - - function setSearch(search) { - _setSearch(search) - debouncedSetSearch(search) - } - - useEffect(() => { - _setSearch(state.search) - }, [state.search]) - - async function searchAction() { - const { _id } = await Card.create({ - title: state.search, - campaignId: campaign._id, - }) - setSearch('') - go(`/${campaign._id}/${_id}`) - } - - return campaign ? ( - <> - {campaign.title} - - - - -
    - - - - - - New - -
    -
    - -
    - -
    - - - ) : null -} diff --git a/imports/ui/pages/card.js b/imports/ui/pages/card.js deleted file mode 100644 index b70975bb..00000000 --- a/imports/ui/pages/card.js +++ /dev/null @@ -1,225 +0,0 @@ -import React, { useState, useRef, useEffect } from 'react' -import { useTracker } from '../utils/hooks' -import styled from 'styled-components' -import Markdown from '../document/markdown' -import { useCard, useCards } from '../data/card' -import { useCampaignId } from '../data/campaign' -import { SplashBleed, Hero, HeroTitle } from '../visual/splash' -import ShowCard, { CardListItem } from '../document/card' -import { canEdit as canEditCard } from '../../lib/utils/validators/card' -import { useUser } from '../utils/logged-in' -import Icon from '../visual/icon' -import Title from '../utils/title' -import schema from '../../lib/schema' -import { Owner } from '../document/user' -import { - SplashToolbar, - MenuItem, - MenuLink, - Space, - Divider, - Center, -} from '../visual/menu' -import { useImage } from '../data/image' -import { CardHistoryList } from '../collection/card-history' -import Search from '../collection/card-search' -import { useCardSearch } from '../data/card-search' -import { addRelated, Card } from '../../lib/methods' -import _ from 'lodash' -import { ReactiveVar } from 'meteor/reactive-var' -import { FlexGrid } from '../visual/grid' -import { Dropdown } from '../visual/primitives' -import colours from '@quarterto/colours' - -export const CardSplash = ({ card }) => { - const { image, ready } = useImage(card.cover) - return ( - - - {card.title} - - - ) -} - -const CardBody = styled.article` - grid-column: main-left; -` - -const Right = styled.aside` - grid-column: right; -` - -const SearchWrapper = styled.div` - display: flex; - position: relative; -` - -const searchVar = new ReactiveVar('') -const debouncedSetSearch = _.debounce(searchVar.set.bind(searchVar), 300) - -const CardList = styled.ul` - list-style: none; - padding: 0; - margin: 0; - - li { - border-bottom: 1px solid ${colours.steel[4]}; - } -` - -const SearchContainer = ({ card, hideCardIds }) => { - const campaignId = useCampaignId() - const [_search, _setSearch] = useState('') - const containerRef = useRef() - const search = useTracker(() => searchVar.get()) - const [showDropdown, setShowDropdown] = useState(false) - const { cards, ready } = useCardSearch({ search, campaignId }) - - function setSearch(search) { - _setSearch(search) - debouncedSetSearch(search) - } - - async function searchAction() { - const relatedCard = await Card.create({ - title: search, - campaignId, - }) - - setSearch('') - addRelated(card, relatedCard) - } - - function handleOutsideClick(event) { - if (containerRef.current && !containerRef.current.contains(event.target)) { - setShowDropdown(false) - } - } - - useEffect(() => { - document.body.addEventListener('mousedown', handleOutsideClick) - return () => - document.body.removeEventListener('mousedown', handleOutsideClick) - }) - - return ( - - setShowDropdown(true)} - /> - {_search && showDropdown && ( - - {ready && search && ( - - {cards.map( - related => - !hideCardIds.has(related._id) && ( - { - event.preventDefault() - addRelated(card, related) - setSearch('') - }} - /> - ), - )} - - )} - - )} - - ) -} - -export default ({ cardId }) => { - const { card, ready } = useCard(cardId, [cardId]) - const { cards: relatedCards } = useCards( - { - _id: { $in: (card && card.related) || [] }, - }, - { deps: [ready] }, - ) - - const user = useUser() - - if (!ready) { - return 'Loading...' - } - - return ( - <> - - {card.title} - - -
    - {card.type && ( - <> - {schema[card.type].name} - - - )} - - {card.type && ( - <> - {_.map( - schema[card.type].fields, - ({ label, format = a => a }, key) => ( - - {label} - {format(card[key])} - - ), - )} - - - )} - - - - - - {canEditCard(card, user._id) && ( - <> - - - Edit - - - - card._id))} - /> - - )} -
    -
    - - - - - - - {relatedCards.length > 0 && ( - - {relatedCards.map(related => ( - - ))} - - )} - - - - - ) -} diff --git a/imports/ui/pages/control.js b/imports/ui/pages/control.js deleted file mode 100644 index 08392e73..00000000 --- a/imports/ui/pages/control.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react' -import BlockLayout from '../collection/block-layout' -import Icon from '../visual/icon' -import { useCampaign } from '../data/campaign' -import { useAssertAmOwner } from '../data/owner' -import { MenuLink } from '../visual/menu' - -export const LaunchDashboardLink = () => { - const campaign = useCampaign() - - if (!campaign) return null - - function launchDashboard(ev) { - ev.preventDefault() - - const dashboardWindow = window.open( - `/${campaign._id}/dashboard`, - campaign._id, - 'width=600,height=400', - ) - - dashboardWindow.document.body.addEventListener( - 'click', - () => { - dashboardWindow.document.body.requestFullscreen() - }, - { once: true }, - ) - } - - return ( - - - Launch Dashboard - - ) -} - -export default props => { - const campaign = useCampaign() - useAssertAmOwner(campaign) - - return -} diff --git a/imports/ui/pages/dashboard.js b/imports/ui/pages/dashboard.js deleted file mode 100644 index 023ea040..00000000 --- a/imports/ui/pages/dashboard.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' -import BlockLayout from '../collection/block-layout' -import { useCampaign } from '../data/campaign' -import { useAmOwner } from '../data/owner' -import { useHidesNav } from './layout' - -export default props => { - const campaign = useCampaign() - const { amOwner } = useAmOwner(campaign) - useHidesNav(amOwner) - - return -} diff --git a/imports/ui/pages/edit-card.js b/imports/ui/pages/edit-card.js deleted file mode 100644 index 9a230d93..00000000 --- a/imports/ui/pages/edit-card.js +++ /dev/null @@ -1,160 +0,0 @@ -import React from 'react' - -import styled from 'styled-components' -import { navigate as go } from 'use-history' - -import { useCard } from '../data/card' -import { useCampaignId } from '../data/campaign' -import { Form, useFormFields } from '../control/form' -import { useAssertAmOwner } from '../data/owner' -import TypeSelect from '../collection/type-select' -import preventingDefault from '../utils/preventing-default' -import { Card } from '../../lib/methods' -import { LabelledInput } from '../visual/primitives' -import { Input, Textarea, BonelessInput } from '../visual/form' -import AccessForm from '../control/privacy' -import Icon from '../visual/icon' -import schema from '../../lib/schema' -import { ImageSelectModal } from '../control/image-select' -import { Main } from '../visual/grid' -import { SplashBleed, Hero, HeroTitle, SplashAccessory } from '../visual/splash' -import { useImage } from '../data/image' -import { - SplashToolbar, - Center, - Space, - Divider, - MenuButton, - MenuItem, -} from '../visual/menu' -import _ from 'lodash' - -const FormCardSplash = props => { - const { cover } = useFormFields() - const { image } = useImage(cover) - - return -} - -const SchemaFields = () => { - const { type } = useFormFields() - return type ? ( - <> - {_.map(schema[type].fields, ({ label, format, ...field }, key) => ( - - -
    {label}
    - -
    -
    - ))} - - - ) : null -} - -const ContentsForm = props => ( -
    -) - -const FloatMenuItem = styled.div` - display: flex; - padding: 0 1rem; - align-items: center; - font-size: 0.9em; -` - -const EditCard = ({ card = {}, saveCard, back, deleteCard, isOwner }) => ( - - - - - - - - - - - - - - -
    - - - - - {(isOwner || !card._id) && ( - - )} - - - - {card._id ? : } - {card._id ? 'Save' : 'Create'} - - {back && ( - - Cancel - - )} - {deleteCard && card._id && ( - - Delete - - )} -
    -
    - -
    -