├── .env.example
├── .github
└── FUNDING.yml
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .travis.yml
├── LICENSE
├── README.md
├── firebase.json
├── firestore.indexes.json
├── firestore.rules
├── functions
├── .gitignore
├── .prettierrc
├── index.js
├── package-lock.json
├── package.json
└── src
│ ├── delete-sector-objects.js
│ └── save-entities.js
├── jsconfig.json
├── package-lock.json
├── package.json
├── public
├── icons
│ ├── android-icon-144x144.png
│ ├── android-icon-192x192.png
│ ├── android-icon-36x36.png
│ ├── android-icon-48x48.png
│ ├── android-icon-72x72.png
│ ├── android-icon-96x96.png
│ ├── apple-icon-114x114.png
│ ├── apple-icon-120x120.png
│ ├── apple-icon-144x144.png
│ ├── apple-icon-152x152.png
│ ├── apple-icon-180x180.png
│ ├── apple-icon-57x57.png
│ ├── apple-icon-60x60.png
│ ├── apple-icon-72x72.png
│ ├── apple-icon-76x76.png
│ ├── apple-icon-precomposed.png
│ ├── apple-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── favicon.ico
│ ├── ms-icon-144x144.png
│ ├── ms-icon-150x150.png
│ ├── ms-icon-310x310.png
│ └── ms-icon-70x70.png
├── index.html
└── manifest.json
└── src
├── changelog.json
├── components
├── app-wrapper
│ ├── app-wrapper.js
│ └── index.js
├── changelog
│ ├── changelog.js
│ ├── index.js
│ └── style.scss
├── configure
│ ├── configure.js
│ ├── index.js
│ └── style.scss
├── custom-tag-modal
│ ├── custom-tag-modal.js
│ ├── index.js
│ ├── styles.module.scss
│ ├── tag-details.js
│ └── tag-form.js
├── empty-overview
│ ├── empty-overview.js
│ ├── index.js
│ └── style.scss
├── entity-tooltips
│ ├── entity-tooltips.js
│ ├── index.js
│ └── styles.module.scss
├── export-modal
│ ├── export-modal.js
│ ├── index.js
│ └── style.scss
├── faction-form
│ ├── faction-asset-form.js
│ ├── faction-form.js
│ ├── index.js
│ └── styles.module.scss
├── faction-sidebar
│ ├── faction-assets
│ │ ├── faction-assets.js
│ │ ├── index.js
│ │ └── style.scss
│ ├── faction-attributes
│ │ ├── faction-attributes.js
│ │ ├── index.js
│ │ └── style.scss
│ ├── faction-sidebar.js
│ ├── index.js
│ └── styles.module.scss
├── faction-table
│ ├── faction-not-saved
│ │ ├── faction-not-saved.js
│ │ └── index.js
│ ├── faction-table.js
│ ├── index.js
│ └── style.scss
├── floating-toolbar
│ ├── floating-toolbar.js
│ ├── index.js
│ └── style.scss
├── game-routes
│ ├── index.js
│ ├── routes.js
│ └── style.scss
├── hex-map
│ ├── hex-map.js
│ ├── index.js
│ └── style.scss
├── home
│ ├── featured
│ │ ├── index.js
│ │ └── style.scss
│ ├── home.js
│ ├── index.js
│ ├── saved
│ │ ├── index.js
│ │ └── style.scss
│ └── style.module.scss
├── login-modal
│ ├── index.js
│ ├── login-modal.js
│ └── style.scss
├── navigation
│ ├── index.js
│ ├── navigation.js
│ └── style.scss
├── overview-list
│ ├── index.js
│ ├── overview-list.js
│ └── styles.module.scss
├── overview-table
│ ├── index.js
│ ├── overview-table.js
│ └── style.scss
├── printables
│ ├── condensed-printable
│ │ ├── condensed-printable.js
│ │ ├── index.js
│ │ └── style.scss
│ ├── expanded-printable
│ │ ├── expanded-printable.js
│ │ ├── index.js
│ │ └── style.scss
│ ├── map-printable
│ │ ├── index.js
│ │ ├── map-hex.js
│ │ ├── map-printable.js
│ │ └── style.scss
│ └── style.scss
├── profile-modal
│ ├── index.js
│ ├── profile-modal.js
│ └── style.scss
├── sector-expansion-modal
│ ├── index.js
│ ├── sector-expansion-modal.js
│ └── style.module.scss
├── sector-map
│ ├── error
│ │ └── index.js
│ ├── index.js
│ ├── loading
│ │ └── index.js
│ ├── sector-map.js
│ └── style.scss
├── sidebar-actions
│ ├── action-layout
│ │ ├── action-layout.js
│ │ └── index.js
│ ├── default-actions
│ │ ├── default-actions.js
│ │ └── index.js
│ ├── entity-actions
│ │ ├── entity-actions.js
│ │ ├── index.js
│ │ └── style.scss
│ └── layer-actions
│ │ ├── index.js
│ │ ├── layer-actions.js
│ │ └── style.scss
├── sidebar-entities
│ ├── default-sidebar
│ │ ├── default-sidebar.js
│ │ ├── entity-attribute
│ │ │ ├── entity-attribute.js
│ │ │ └── index.js
│ │ ├── entity-attributes
│ │ │ ├── entity-attributes.js
│ │ │ ├── index.js
│ │ │ └── styles.module.scss
│ │ ├── entity-edit-row
│ │ │ ├── entity-edit-row.js
│ │ │ ├── index.js
│ │ │ └── style.scss
│ │ ├── entity-link-row
│ │ │ ├── entity-link-row.js
│ │ │ └── index.js
│ │ ├── entity-list
│ │ │ ├── entity-list.js
│ │ │ ├── index.js
│ │ │ └── style.scss
│ │ ├── entity-tags
│ │ │ ├── entity-tags.js
│ │ │ ├── index.js
│ │ │ └── styles.module.scss
│ │ ├── index.js
│ │ └── styles.module.scss
│ ├── layer-sidebar
│ │ ├── index.js
│ │ ├── layer-form
│ │ │ ├── index.js
│ │ │ ├── layer-form.js
│ │ │ └── style.scss
│ │ ├── layer-sidebar.js
│ │ ├── region-row
│ │ │ ├── index.js
│ │ │ ├── region-row.js
│ │ │ └── style.scss
│ │ └── style.scss
│ ├── navigation-sidebar
│ │ ├── index.js
│ │ ├── navigation-sidebar.js
│ │ └── style.scss
│ ├── note-sidebar
│ │ ├── index.js
│ │ ├── note-sidebar.js
│ │ └── style.scss
│ └── settings-sidebar
│ │ ├── index.js
│ │ ├── settings-sidebar.js
│ │ └── style.scss
├── sidebar
│ ├── index.js
│ └── sidebar.js
├── star-background
│ ├── index.js
│ ├── star-background.js
│ └── style.scss
└── top-level-entity-modal
│ ├── index.js
│ ├── style.scss
│ └── top-level-entity-modal.js
├── constants
├── asteroid-base
│ ├── occupation.js
│ └── situation.js
├── asteroid-belt
│ ├── occupation.js
│ └── situation.js
├── atmosphere.js
├── biosphere.js
├── defaults.js
├── elements.js
├── entities.js
├── export-types.js
├── faction.js
├── gas-giant-mine
│ ├── occupation.js
│ └── situation.js
├── language
│ ├── cosmic-names.json
│ ├── greek-letters.json
│ └── mars-craters.json
├── locale.js
├── lodash.js
├── moon-base
│ ├── occupation.js
│ └── situation.js
├── orbital-ruin
│ ├── occupation.js
│ └── situation.js
├── population.js
├── refueling-station
│ ├── occupation.js
│ └── situation.js
├── research-base
│ ├── occupation.js
│ └── situation.js
├── space-station
│ ├── occupation.js
│ └── situation.js
├── tech-level.js
├── temperature.js
└── world-tags.js
├── featured.json
├── index.js
├── lang
├── de.json
├── en.json
├── es.json
├── fr.json
├── he.json
├── index.js
├── pl.json
├── ru.json
├── sr.json
└── sv.json
├── primitives
├── __tests__
│ └── spinner.spec.js
├── container
│ ├── absolute-container
│ │ ├── index.js
│ │ └── style.scss
│ ├── content-container
│ │ ├── index.js
│ │ └── style.scss
│ ├── flex-container
│ │ ├── index.js
│ │ └── style.scss
│ ├── sidebar-container
│ │ ├── index.js
│ │ └── style.scss
│ └── sub-container
│ │ ├── index.js
│ │ └── style.scss
├── form
│ ├── checkbox
│ │ ├── index.js
│ │ └── style.scss
│ ├── color-picker
│ │ ├── index.js
│ │ └── style.scss
│ ├── deletable-row
│ │ ├── index.js
│ │ └── style.scss
│ ├── dropdown
│ │ ├── index.js
│ │ └── style.scss
│ ├── icon-input
│ │ ├── index.js
│ │ └── style.scss
│ ├── input
│ │ ├── index.js
│ │ └── style.scss
│ ├── label
│ │ ├── index.js
│ │ └── style.scss
│ └── labeled-input
│ │ ├── index.js
│ │ └── style.scss
├── icons
│ └── dice.js
├── modal
│ ├── confirm-modal
│ │ └── index.js
│ └── modal
│ │ ├── index.js
│ │ └── style.scss
├── other
│ ├── basic-link
│ │ ├── index.js
│ │ └── style.scss
│ ├── button-link
│ │ └── index.js
│ ├── button
│ │ ├── index.js
│ │ └── style.scss
│ ├── collapsible-table
│ │ ├── index.js
│ │ └── style.scss
│ ├── color-swatch
│ │ ├── index.js
│ │ └── styles.module.scss
│ ├── item-row
│ │ ├── index.js
│ │ └── style.scss
│ ├── labeled-item
│ │ ├── index.js
│ │ └── styles.module.scss
│ ├── link-icon
│ │ ├── index.js
│ │ └── style.scss
│ ├── link-row
│ │ ├── index.js
│ │ └── style.scss
│ ├── save-footer
│ │ ├── index.js
│ │ └── style.scss
│ ├── spinner
│ │ ├── index.js
│ │ └── style.scss
│ └── table
│ │ ├── index.js
│ │ └── style.scss
├── regions
│ ├── loading
│ │ └── index.js
│ └── star-field
│ │ ├── index.js
│ │ └── style.scss
└── text
│ ├── action-header
│ ├── index.js
│ └── styles.module.scss
│ ├── header
│ ├── index.js
│ └── style.scss
│ └── section-header
│ ├── index.js
│ ├── section-header.js
│ └── style.scss
├── serviceWorker.js
├── setupTests.js
├── store
├── actions
│ ├── combined.actions.js
│ ├── entity.actions.js
│ ├── faction.actions.js
│ ├── layer.actions.js
│ ├── navigation.actions.js
│ ├── sector.actions.js
│ ├── settings.actions.js
│ ├── sidebar.actions.js
│ ├── tag.actions.js
│ └── user.actions.js
├── api
│ ├── entity.js
│ ├── faction.js
│ ├── layer.js
│ ├── navigation.js
│ ├── tag.js
│ └── user.js
├── index.js
├── localStorage.js
├── reducers
│ ├── entity.reducers.js
│ ├── faction.reducers.js
│ ├── index.js
│ ├── layer.reducers.js
│ ├── navigation.reducers.js
│ ├── sector.reducers.js
│ ├── settings.reducers.js
│ ├── sidebar.reducers.js
│ ├── tag.reducers.js
│ └── user.reducers.js
└── selectors
│ ├── base.selectors.js
│ ├── entity.selectors.js
│ ├── faction.selectors.js
│ ├── layer.selectors.js
│ ├── navigation.selectors.js
│ ├── sector.selectors.js
│ └── tag.selectors.js
├── styles
├── _constants.scss
├── _fonts.scss
├── _theme.scss
├── _utilities.scss
├── fonts
│ ├── KillTheNoise.eot
│ ├── KillTheNoise.ttf
│ └── KillTheNoise.woff
└── global.scss
└── utils
├── __tests__
├── common.spec.js
├── export.spec.js
├── name-generator.spec.js
└── toasts.spec.js
├── canvas-helpers.js
├── common.js
├── convert-translations.js
├── effects.js
├── entity-generators
├── asteroid-base-generator.js
├── asteroid-belt-generator.js
├── black-hole-generator.js
├── common-generator.js
├── deep-space-station-generator.js
├── gas-giant-mine-generator.js
├── index.js
├── moon-base-generator.js
├── moon-generator.js
├── note-generator.js
├── orbital-ruin-generator.js
├── planet-generator.js
├── refueling-station-generator.js
├── research-base-generator.js
├── sector-generator.js
├── space-station-generator.js
└── system-generator.js
├── entity.js
├── export.js
├── faction.js
├── hex
├── canvas.js
├── common.js
└── generator.js
├── name-generator.js
└── toasts.js
/.env.example:
--------------------------------------------------------------------------------
1 | REACT_APP_API_KEY=
2 | REACT_APP_AUTH_DOMAIN=
3 | REACT_APP_DATABASE_URL=
4 | REACT_APP_PROJECT_ID=
5 | REACT_APP_STORAGE_BUCKET=
6 | REACT_APP_SENDER_ID=
7 | REACT_APP_APP_KEY=
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: sectorswithoutnumber
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | npm-debug.log*
15 | yarn-debug.log*
16 | yarn-error.log*
17 | .vscode
18 |
19 | # firebase
20 | .firebaserc
21 | .firebase
22 | .env*
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 16
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | package.json
2 | **/node_modules
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | cache:
3 | directories:
4 | - node_modules
5 | script:
6 | - cp .env.example .env
7 | - npm run build
8 | - npm test
9 | - npm run lint
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Mitchel Pigsley
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "firestore": {
3 | "rules": "firestore.rules",
4 | "indexes": "firestore.indexes.json"
5 | },
6 | "hosting": {
7 | "public": "build",
8 | "headers": [
9 | {
10 | "source": "/serviceWorker.js",
11 | "headers": [{ "key": "Cache-Control", "value": "no-cache" }]
12 | }
13 | ],
14 | "rewrites": [
15 | {
16 | "source": "**",
17 | "destination": "/index.html"
18 | }
19 | ]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/firestore.indexes.json:
--------------------------------------------------------------------------------
1 | {
2 | "indexes": []
3 | }
4 |
--------------------------------------------------------------------------------
/functions/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/functions/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
--------------------------------------------------------------------------------
/functions/index.js:
--------------------------------------------------------------------------------
1 | const admin = require('firebase-admin');
2 |
3 | const saveEntities = require('./src/save-entities');
4 | const deleteSectorObjects = require('./src/delete-sector-objects');
5 |
6 | admin.initializeApp();
7 |
8 | exports.saveEntities = saveEntities;
9 | exports.deleteSectorObjects = deleteSectorObjects;
10 |
--------------------------------------------------------------------------------
/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "description": "Cloud Functions for Firebase",
4 | "engines": {
5 | "node": "16"
6 | },
7 | "dependencies": {
8 | "firebase-admin": "^11.5.0",
9 | "firebase-functions": "^4.2.1",
10 | "lodash": "^4.17.21"
11 | }
12 | }
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src"
4 | },
5 | "include": ["src"]
6 | }
7 |
--------------------------------------------------------------------------------
/public/icons/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/android-icon-144x144.png
--------------------------------------------------------------------------------
/public/icons/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/android-icon-192x192.png
--------------------------------------------------------------------------------
/public/icons/android-icon-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/android-icon-36x36.png
--------------------------------------------------------------------------------
/public/icons/android-icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/android-icon-48x48.png
--------------------------------------------------------------------------------
/public/icons/android-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/android-icon-72x72.png
--------------------------------------------------------------------------------
/public/icons/android-icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/android-icon-96x96.png
--------------------------------------------------------------------------------
/public/icons/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/apple-icon-114x114.png
--------------------------------------------------------------------------------
/public/icons/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/apple-icon-120x120.png
--------------------------------------------------------------------------------
/public/icons/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/apple-icon-144x144.png
--------------------------------------------------------------------------------
/public/icons/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/apple-icon-152x152.png
--------------------------------------------------------------------------------
/public/icons/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/apple-icon-180x180.png
--------------------------------------------------------------------------------
/public/icons/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/apple-icon-57x57.png
--------------------------------------------------------------------------------
/public/icons/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/apple-icon-60x60.png
--------------------------------------------------------------------------------
/public/icons/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/apple-icon-72x72.png
--------------------------------------------------------------------------------
/public/icons/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/apple-icon-76x76.png
--------------------------------------------------------------------------------
/public/icons/apple-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/apple-icon-precomposed.png
--------------------------------------------------------------------------------
/public/icons/apple-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/apple-icon.png
--------------------------------------------------------------------------------
/public/icons/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 | #ffffff
--------------------------------------------------------------------------------
/public/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/icons/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/favicon-96x96.png
--------------------------------------------------------------------------------
/public/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/favicon.ico
--------------------------------------------------------------------------------
/public/icons/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/ms-icon-144x144.png
--------------------------------------------------------------------------------
/public/icons/ms-icon-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/ms-icon-150x150.png
--------------------------------------------------------------------------------
/public/icons/ms-icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/ms-icon-310x310.png
--------------------------------------------------------------------------------
/public/icons/ms-icon-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/public/icons/ms-icon-70x70.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Sectors Without Number",
3 | "short_name": "Sector Gen",
4 | "start_url": "./index.html",
5 | "display": "standalone",
6 | "theme_color": "#303E4F",
7 | "background_color": "#DBDBDB",
8 | "icons": [
9 | {
10 | "src": "icons/android-icon-36x36.png",
11 | "sizes": "36x36",
12 | "type": "image/png",
13 | "density": "0.75"
14 | },
15 | {
16 | "src": "icons/android-icon-48x48.png",
17 | "sizes": "48x48",
18 | "type": "image/png",
19 | "density": "1.0"
20 | },
21 | {
22 | "src": "icons/android-icon-72x72.png",
23 | "sizes": "72x72",
24 | "type": "image/png",
25 | "density": "1.5"
26 | },
27 | {
28 | "src": "icons/android-icon-96x96.png",
29 | "sizes": "96x96",
30 | "type": "image/png",
31 | "density": "2.0"
32 | },
33 | {
34 | "src": "icons/android-icon-144x144.png",
35 | "sizes": "144x144",
36 | "type": "image/png",
37 | "density": "3.0"
38 | },
39 | {
40 | "src": "icons/android-icon-192x192.png",
41 | "sizes": "192x192",
42 | "type": "image/png",
43 | "density": "4.0"
44 | }
45 | ]
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/app-wrapper/app-wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ReduxToastr from 'react-redux-toastr';
4 | import { IntlProvider } from 'react-intl';
5 |
6 | import LoginModal from 'components/login-modal';
7 | import CustomTagModal from 'components/custom-tag-modal';
8 |
9 | export default function AppWrapper({ children, userLocale, locale, location }) {
10 | return (
11 |
12 | <>
13 |
14 |
15 |
16 | {React.Children.map(children, child =>
17 | React.cloneElement(child, { location }),
18 | )}
19 | >
20 |
21 | );
22 | }
23 |
24 | AppWrapper.propTypes = {
25 | children: PropTypes.node.isRequired,
26 | userLocale: PropTypes.string.isRequired,
27 | locale: PropTypes.shape().isRequired,
28 | location: PropTypes.shape().isRequired,
29 | };
30 |
--------------------------------------------------------------------------------
/src/components/app-wrapper/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 |
3 | import {
4 | userLocaleSelector,
5 | userModelLocaleSelector,
6 | routerLocationSelector,
7 | } from 'store/selectors/base.selectors';
8 |
9 | import AppWrapper from './app-wrapper';
10 |
11 | const mapStateToProps = state => ({
12 | userLocale: userModelLocaleSelector(state),
13 | locale: userLocaleSelector(state),
14 | location: routerLocationSelector(state),
15 | });
16 |
17 | export default connect(mapStateToProps)(AppWrapper);
18 |
--------------------------------------------------------------------------------
/src/components/changelog/index.js:
--------------------------------------------------------------------------------
1 | import Changelog from './changelog';
2 |
3 | export default Changelog;
4 |
--------------------------------------------------------------------------------
/src/components/changelog/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .Changelog-Header {
4 | margin-top: 3rem;
5 | }
6 |
7 | .Changelog-Item {
8 | color: $light2;
9 | max-width: 36rem;
10 | }
11 |
12 | .Changelog-Updates {
13 | margin-bottom: 0;
14 | margin-top: 0.3rem;
15 | }
16 |
17 | .Changelog-List {
18 | margin-top: 0.3rem;
19 | }
20 |
21 | .Changelog-Change {
22 | margin-bottom: 0.2rem;
23 | }
24 |
25 | .Changelog-Date {
26 | color: $light1;
27 | font-size: 1rem;
28 | margin-left: 0.6rem;
29 | }
30 |
31 | .Changelog-Version {
32 | margin-bottom: 0.5rem;
33 | }
34 |
35 | .Changelog-ContributorLink {
36 | color: $light2;
37 | margin-left: 0.4rem;
38 | transition: color 0.2s linear;
39 |
40 | &:hover {
41 | color: $light3;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/configure/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 |
4 | import Entities from 'constants/entities';
5 | import {
6 | isLoggedInSelector,
7 | configurationSelector,
8 | } from 'store/selectors/base.selectors';
9 | import { generateEntity } from 'store/actions/entity.actions';
10 | import { updateConfiguration } from 'store/actions/sector.actions';
11 | import { openCustomTagModal } from 'store/actions/tag.actions';
12 | import Configure from './configure';
13 |
14 | const mapStateToProps = state => ({
15 | isLoggedIn: isLoggedInSelector(state),
16 | ...configurationSelector(state),
17 | });
18 |
19 | const mapDispatchToProps = (dispatch, props) => ({
20 | openCustomTagModal: () => dispatch(openCustomTagModal()),
21 | updateConfiguration: (key, value) =>
22 | dispatch(updateConfiguration(key, value)),
23 | generateSector: () =>
24 | dispatch(
25 | generateEntity({ entityType: Entities.sector.key }, {}, props.intl),
26 | ),
27 | });
28 |
29 | export default injectIntl(
30 | connect(mapStateToProps, mapDispatchToProps)(Configure),
31 | );
32 |
--------------------------------------------------------------------------------
/src/components/configure/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .Configure-PaddedButtons {
4 | margin-top: 2rem;
5 | }
6 |
7 | .Configure-Info {
8 | color: $light1;
9 | }
10 |
11 | .Configure-ManageContainer {
12 | width: 100%;
13 | }
14 |
15 | .Configure-ManageLink {
16 | color: $light1;
17 | margin-bottom: 0;
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/custom-tag-modal/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { createStructuredSelector } from 'reselect';
3 | import { injectIntl } from 'react-intl';
4 |
5 | import { isCustomTagModalOpenSelector } from 'store/selectors/base.selectors';
6 | import { getUsersCustomTags } from 'store/selectors/tag.selectors';
7 | import {
8 | createTag,
9 | editTag,
10 | deleteTag,
11 | closeCustomTagModal,
12 | } from 'store/actions/tag.actions';
13 |
14 | import CustomTagModal from './custom-tag-modal';
15 |
16 | const mapStateToProps = createStructuredSelector({
17 | isCustomTagModalOpen: isCustomTagModalOpenSelector,
18 | tags: getUsersCustomTags,
19 | });
20 |
21 | export default injectIntl(
22 | connect(mapStateToProps, {
23 | createTag,
24 | editTag,
25 | deleteTag,
26 | closeCustomTagModal,
27 | })(CustomTagModal),
28 | );
29 |
--------------------------------------------------------------------------------
/src/components/custom-tag-modal/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .modal {
4 | max-width: 900px;
5 | }
6 |
7 | .innerContainer {
8 | height: 500px;
9 | }
10 |
11 | .tagsContainer {
12 | background-color: $dark3;
13 | width: 35%;
14 | }
15 |
16 | .formBtn {
17 | margin-left: 1rem;
18 | }
19 |
20 | .btnContainer {
21 | margin-top: 0.8rem;
22 | }
23 |
24 | .scrollable {
25 | height: 100%;
26 | overflow-y: auto;
27 | }
28 |
29 | .selectedRow {
30 | background-color: $dark2;
31 | }
32 |
33 | .detailsContainer {
34 | @extend .scrollable;
35 | padding-left: 2rem;
36 | flex: 1;
37 |
38 | .formHeader {
39 | margin: 1rem 0;
40 | width: 100%;
41 | }
42 | }
43 |
44 | .rowIcon {
45 | color: $light1;
46 | }
47 |
48 | .entityTypes {
49 | margin-right: 0.5rem;
50 | }
51 |
52 | .empty * {
53 | color: $light1;
54 | }
55 |
56 | .deletableRow {
57 | margin-bottom: 0.5rem;
58 | }
59 |
60 | .contentList {
61 | margin-top: 0;
62 | }
63 |
--------------------------------------------------------------------------------
/src/components/empty-overview/empty-overview.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { FormattedMessage } from 'react-intl';
4 |
5 | import Header, { HeaderType } from 'primitives/text/header';
6 | import FlexContainer from 'primitives/container/flex-container';
7 | import Loading from 'primitives/regions/loading';
8 |
9 | import './style.scss';
10 |
11 | export default function EmptyOverview({ children, isInitialized }) {
12 | let body = ;
13 | if (isInitialized) {
14 | body = (
15 | <>
16 |
19 |
22 | >
23 | );
24 | }
25 | return (
26 |
33 | {body}
34 |
35 | );
36 | }
37 |
38 | EmptyOverview.propTypes = {
39 | children: PropTypes.node,
40 | isInitialized: PropTypes.bool.isRequired,
41 | };
42 |
43 | EmptyOverview.defaultProps = {
44 | children: undefined,
45 | };
46 |
--------------------------------------------------------------------------------
/src/components/empty-overview/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 |
3 | import { isInitializedSelector } from 'store/selectors/base.selectors';
4 | import EmptyOverview from './empty-overview';
5 |
6 | const mapStateToProps = state => ({
7 | isInitialized: isInitializedSelector(state),
8 | });
9 |
10 | export default connect(mapStateToProps)(EmptyOverview);
11 |
--------------------------------------------------------------------------------
/src/components/empty-overview/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .EmptyOverview {
4 | background-color: $dark3;
5 | position: relative;
6 | }
7 |
8 | .EmptyOverview-Header1 {
9 | color: $light1;
10 | font-size: 2.5rem;
11 | margin-bottom: 0;
12 | }
13 |
14 | .EmptyOverview-Header2 {
15 | color: $light1;
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/entity-tooltips/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { createStructuredSelector } from 'reselect';
3 |
4 | import {
5 | holdKeySelector,
6 | hoverKeySelector,
7 | } from 'store/selectors/base.selectors';
8 | import { hexLayerNameMapping } from 'store/selectors/layer.selectors';
9 | import EntityTooltips from './entity-tooltips';
10 |
11 | const mapStateToProps = createStructuredSelector({
12 | holdKey: holdKeySelector,
13 | hoverKey: hoverKeySelector,
14 | hexLayerNames: hexLayerNameMapping,
15 | });
16 |
17 | export default connect(mapStateToProps)(EntityTooltips);
18 |
--------------------------------------------------------------------------------
/src/components/entity-tooltips/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .tooltip {
4 | max-width: 15rem;
5 | opacity: 0;
6 | pointer-events: none;
7 | position: absolute;
8 | transition: opacity 0.5s;
9 | z-index: 1;
10 |
11 | &--hovered {
12 | opacity: 1;
13 | }
14 | }
15 |
16 | .text {
17 | background-color: $light4;
18 | padding: 0.6rem;
19 | border-radius: 6px;
20 | transform: translateX(-50%) translateY(-100%);
21 | z-index: 1;
22 |
23 | &:after {
24 | content: ' ';
25 | position: absolute;
26 | top: 100%;
27 | left: 50%;
28 | margin-left: -5px;
29 | border-width: 5px;
30 | border-style: solid;
31 | border-color: transparent;
32 | border-top-color: $light4;
33 | }
34 | }
35 |
36 | .name {
37 | color: $dark4;
38 | font-size: 1.1rem;
39 | margin: 0;
40 | white-space: nowrap;
41 | }
42 |
43 | .key {
44 | color: $light1;
45 | font-size: 0.8rem;
46 | margin: 0 0 0.1rem 0.5rem;
47 | }
48 |
49 | .regions {
50 | margin-top: 0.4rem;
51 | text-align: center;
52 | }
53 |
54 | .layer {
55 | font-size: 0.75rem;
56 | margin-bottom: 0.3rem;
57 |
58 | &:last-of-type {
59 | margin-bottom: 0;
60 | }
61 |
62 | b {
63 | color: $dark2;
64 | line-height: 0.85rem;
65 | margin: 0;
66 | }
67 |
68 | span {
69 | color: $dark1;
70 | line-height: 0.85rem;
71 | margin: 0 0 0 0.2rem;
72 | white-space: nowrap;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/components/export-modal/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 | import { createStructuredSelector } from 'reselect';
4 |
5 | import {
6 | customTagSelector,
7 | exportTypeSelector,
8 | isExportOpenSelector,
9 | } from 'store/selectors/base.selectors';
10 | import {
11 | getExportEntities,
12 | getCurrentSector,
13 | } from 'store/selectors/entity.selectors';
14 | import {
15 | setEntityExport,
16 | closeExport,
17 | startPrint,
18 | } from 'store/actions/sector.actions';
19 |
20 | import ExportModal from './export-modal';
21 |
22 | const mapStateToProps = createStructuredSelector({
23 | exportType: exportTypeSelector,
24 | isExportOpen: isExportOpenSelector,
25 | customTags: customTagSelector,
26 | sector: getCurrentSector,
27 | entities: getExportEntities,
28 | });
29 |
30 | export default injectIntl(
31 | connect(mapStateToProps, { setEntityExport, closeExport, startPrint })(
32 | ExportModal,
33 | ),
34 | );
35 |
--------------------------------------------------------------------------------
/src/components/export-modal/style.scss:
--------------------------------------------------------------------------------
1 | .ExportModal-Buttons {
2 | margin-bottom: 1rem;
3 | }
4 |
5 | .ExportModal-Description {
6 | text-align: center;
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/faction-form/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 | import { push } from 'connected-react-router';
4 | import { createStructuredSelector } from 'reselect';
5 |
6 | import {
7 | factionFormSelector,
8 | currentEntitySelector,
9 | factionIsCreatingSelector,
10 | } from 'store/selectors/base.selectors';
11 | import { getCurrentEntities } from 'store/selectors/entity.selectors';
12 | import {
13 | currentFormHitPoints,
14 | isValidFactionForm,
15 | } from 'store/selectors/faction.selectors';
16 | import {
17 | updateFaction,
18 | updateFactionAsset,
19 | createBlankAsset,
20 | submitForm,
21 | } from 'store/actions/faction.actions';
22 |
23 | import FactionForm from './faction-form';
24 |
25 | const mapStateToProps = createStructuredSelector({
26 | currentFaction: currentEntitySelector,
27 | isCreating: factionIsCreatingSelector,
28 | isValid: isValidFactionForm,
29 | form: factionFormSelector,
30 | currentEntities: getCurrentEntities,
31 | hitPoints: currentFormHitPoints,
32 | });
33 |
34 | const mapDispatchToProps = (dispatch, props) => ({
35 | updateFaction: update => dispatch(updateFaction(update)),
36 | createBlankAsset: () => dispatch(createBlankAsset()),
37 | toRoute: route => dispatch(push(route)),
38 | updateFactionAsset: (key, update) =>
39 | dispatch(updateFactionAsset(key, update)),
40 | submitForm: () => dispatch(submitForm(props.intl)),
41 | });
42 |
43 | export default injectIntl(
44 | connect(mapStateToProps, mapDispatchToProps)(FactionForm),
45 | );
46 |
--------------------------------------------------------------------------------
/src/components/faction-form/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | margin: 0 1rem 0.4rem;
3 | }
4 |
5 | .topRow {
6 | margin-top: 0;
7 | }
8 |
9 | .loneInput {
10 | padding-bottom: 0.5rem;
11 | }
12 |
13 | .iconInput {
14 | flex: 0;
15 | }
16 |
17 | .iconLabel {
18 | display: block;
19 | margin: 0.1rem 0 0 0.2rem;
20 | }
21 |
22 | .hitPoints {
23 | width: 5rem;
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/faction-sidebar/faction-assets/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { createStructuredSelector } from 'reselect';
3 |
4 | import { currentFactionAssets } from 'store/selectors/faction.selectors';
5 |
6 | import FactionAssets from './faction-assets';
7 |
8 | const mapStateToProps = createStructuredSelector({
9 | assets: currentFactionAssets,
10 | });
11 |
12 | export default connect(mapStateToProps)(FactionAssets);
13 |
--------------------------------------------------------------------------------
/src/components/faction-sidebar/faction-assets/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .FactionAssets-Asset {
4 | margin-bottom: 1.5rem;
5 | }
6 |
7 | .FactionAssets-Header {
8 | font-weight: 900;
9 | margin-right: 0.5rem;
10 | }
11 |
12 | .FactionAssets-Description {
13 | color: $light2;
14 | font-size: 0.9rem;
15 | }
16 |
17 | .FactionAssets-Item {
18 | margin-bottom: 0.1rem;
19 | padding: 0;
20 | }
21 |
22 | .FactionAssets-Icon {
23 | margin-right: 0.2rem;
24 | }
25 |
26 | .FactionAssets-Spacer {
27 | margin: 0 0.4rem;
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/faction-sidebar/faction-attributes/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { createStructuredSelector } from 'reselect';
3 |
4 | import { currentEntitySelector } from 'store/selectors/base.selectors';
5 | import { currentFactionAttributes } from 'store/selectors/faction.selectors';
6 |
7 | import FactionAttributes from './faction-attributes';
8 |
9 | const mapStateToProps = createStructuredSelector({
10 | currentFaction: currentEntitySelector,
11 | attributes: currentFactionAttributes,
12 | });
13 |
14 | export default connect(mapStateToProps)(FactionAttributes);
15 |
--------------------------------------------------------------------------------
/src/components/faction-sidebar/faction-attributes/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .FactionAttributes-Title {
4 | font-style: italic;
5 | margin-right: 0.3rem;
6 | }
7 |
8 | .FactionAttributes-TagEffect {
9 | margin-bottom: 1rem;
10 | }
11 |
12 | .FactionAttributes-Effect {
13 | font-style: italic;
14 | margin-right: 0.8rem;
15 | }
16 |
17 | .FactionAttributes-AttributeContainer {
18 | margin-bottom: 1rem;
19 | }
20 |
21 | .FactionAttributes-Attribute {
22 | align-items: center;
23 | }
24 |
25 | .FactionAttributes-Value {
26 | color: $light4;
27 | font-size: 3.5rem;
28 | line-height: 3.2rem;
29 | }
30 |
31 | .FactionAttributes-Superscript {
32 | font-size: 1.2rem;
33 | margin-left: 0.2rem;
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/faction-sidebar/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { createStructuredSelector } from 'reselect';
3 | import { injectIntl } from 'react-intl';
4 |
5 | import {
6 | currentSectorSelector,
7 | currentEntitySelector,
8 | } from 'store/selectors/base.selectors';
9 | import { currentFaction } from 'store/selectors/faction.selectors';
10 | import { removeFaction } from 'store/actions/faction.actions';
11 |
12 | import FactionSidebar from './faction-sidebar';
13 |
14 | const mapStateToProps = createStructuredSelector({
15 | faction: currentFaction,
16 | currentSector: currentSectorSelector,
17 | currentFaction: currentEntitySelector,
18 | });
19 |
20 | const mapDispatchToProps = (dispatch, props) => ({
21 | removeFaction: () => dispatch(removeFaction(props.intl)),
22 | });
23 |
24 | export default injectIntl(
25 | connect(mapStateToProps, mapDispatchToProps)(FactionSidebar),
26 | );
27 |
--------------------------------------------------------------------------------
/src/components/faction-sidebar/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .content {
4 | margin: 0 1rem;
5 | }
6 |
7 | .image {
8 | margin: 1rem;
9 | width: calc(100% - 2rem);
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/faction-table/faction-not-saved/faction-not-saved.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { FormattedMessage } from 'react-intl';
4 | import { Save, LogIn } from 'react-feather';
5 |
6 | import ContentContainer from 'primitives/container/content-container';
7 | import Header, { HeaderType } from 'primitives/text/header';
8 | import LinkIcon from 'primitives/other/link-icon';
9 | import Button from 'primitives/other/button';
10 |
11 | export default function FactionNotSaved({
12 | saveSector,
13 | openLoginModal,
14 | isLoggedIn,
15 | }) {
16 | let action;
17 | if (isLoggedIn) {
18 | action = (
19 |
23 | );
24 | } else {
25 | action = (
26 |
30 | );
31 | }
32 | return (
33 |
34 |
37 |
40 | {action}
41 |
42 | );
43 | }
44 |
45 | FactionNotSaved.propTypes = {
46 | saveSector: PropTypes.func.isRequired,
47 | openLoginModal: PropTypes.func.isRequired,
48 | isLoggedIn: PropTypes.bool.isRequired,
49 | };
50 |
--------------------------------------------------------------------------------
/src/components/faction-table/faction-not-saved/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 | import { createStructuredSelector } from 'reselect';
4 |
5 | import { openLoginModal } from 'store/actions/user.actions';
6 | import { isLoggedInSelector } from 'store/selectors/base.selectors';
7 | import { saveSector } from 'store/actions/entity.actions';
8 |
9 | import FactionNotSaved from './faction-not-saved';
10 |
11 | const mapStateToProps = createStructuredSelector({
12 | isLoggedIn: isLoggedInSelector,
13 | });
14 |
15 | const mapDispatchToProps = (dispatch, props) => ({
16 | saveSector: () => dispatch(saveSector(props.intl)),
17 | openLoginModal: () => dispatch(openLoginModal()),
18 | });
19 |
20 | export default injectIntl(
21 | connect(mapStateToProps, mapDispatchToProps)(FactionNotSaved),
22 | );
23 |
--------------------------------------------------------------------------------
/src/components/faction-table/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { withRouter } from 'react-router-dom';
3 | import { push } from 'connected-react-router';
4 | import { createStructuredSelector } from 'reselect';
5 | import { injectIntl } from 'react-intl';
6 |
7 | import {
8 | isLoggedInSelector,
9 | isInitializedSelector,
10 | currentSectorSelector,
11 | currentEntitySelector,
12 | } from 'store/selectors/base.selectors';
13 | import {
14 | currentSectorFactionTable,
15 | currentFaction,
16 | } from 'store/selectors/faction.selectors';
17 | import {
18 | isCurrentSectorSaved,
19 | isViewingSharedSector,
20 | currentSectorIsLoading,
21 | } from 'store/selectors/sector.selectors';
22 | import { sectorDoesNotExist } from 'store/selectors/entity.selectors';
23 |
24 | import FactionTable from './faction-table';
25 |
26 | const mapStateToProps = createStructuredSelector({
27 | isInitialized: isInitializedSelector,
28 | isLoggedIn: isLoggedInSelector,
29 | isSaved: isCurrentSectorSaved,
30 | isShared: isViewingSharedSector,
31 | doesNotExist: sectorDoesNotExist,
32 | table: currentSectorFactionTable,
33 | isLoading: currentSectorIsLoading,
34 | currentSector: currentSectorSelector,
35 | currentElement: currentEntitySelector,
36 | currentFaction,
37 | });
38 |
39 | const mapDispatchToProps = dispatch => ({
40 | toSafeRoute: () => dispatch(push('/')),
41 | });
42 |
43 | export default injectIntl(
44 | withRouter(connect(mapStateToProps, mapDispatchToProps)(FactionTable)),
45 | );
46 |
--------------------------------------------------------------------------------
/src/components/faction-table/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 | @import '~styles/constants';
3 |
4 | .FactionTable {
5 | display: grid;
6 | grid-template-columns: [table] auto [sidebar] 0px [end];
7 | grid-template-rows: [header] 4rem [table] auto [end];
8 | overflow: hidden;
9 |
10 | &--sidebarOpen {
11 | grid-template-columns: [table] auto [sidebar] $large-sidebar-width [end];
12 | }
13 | }
14 |
15 | .FactionTable-Header {
16 | grid-column: table / sidebar;
17 | grid-row: header / table;
18 | padding: 0 2rem;
19 | }
20 |
21 | .FactionTable-Table {
22 | grid-column: table / sidebar;
23 | grid-row: table / end;
24 | overflow: auto;
25 | padding: 2rem;
26 | }
27 |
28 | .FactionTable-Sidebar {
29 | grid-column: sidebar / end;
30 | grid-row: header / end;
31 | overflow: auto;
32 | }
33 |
34 | .FactionTable-Empty {
35 | color: $light2;
36 | font-size: 1rem;
37 | margin-top: 0.5rem;
38 | }
39 |
40 | .FactionTable-ExportOption {
41 | margin-left: 1rem;
42 | }
43 |
44 | .FactionTable-Income {
45 | margin-left: 0.3rem;
46 | margin-right: 0.1rem;
47 | }
48 |
49 | .FactionTable-Relationship {
50 | margin-left: 0.5rem;
51 |
52 | &--friendly {
53 | color: $cyber;
54 | }
55 |
56 | &--hostile {
57 | color: $error;
58 | }
59 | }
60 |
61 | .FactionTable-Stealthed {
62 | margin-left: 0.5rem;
63 | }
64 |
65 | .FactionTable-Color {
66 | margin: 0 0.5rem 0 0 !important;
67 | }
68 |
69 | @media (max-width: 1200px) {
70 | .FactionTable--sidebarOpen {
71 | grid-template-columns: [table] auto [sidebar] $small-sidebar-width [end];
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/components/floating-toolbar/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { withRouter } from 'react-router-dom';
3 | import { push } from 'connected-react-router';
4 | import { createStructuredSelector } from 'reselect';
5 | import { injectIntl } from 'react-intl';
6 |
7 | import {
8 | playerViewSelector,
9 | currentSectorSelector,
10 | isSharedSectorSelector,
11 | } from 'store/selectors/base.selectors';
12 | import {
13 | isCurrentSectorSaved,
14 | isViewingSharedSector,
15 | } from 'store/selectors/sector.selectors';
16 | import { currentSectorLayers } from 'store/selectors/layer.selectors';
17 | import { getMapLock, getSectorLayers } from 'store/selectors/entity.selectors';
18 | import { toggleMapLock, toggleLayer } from 'store/actions/entity.actions';
19 | import { togglePlayerView } from 'store/actions/sector.actions';
20 |
21 | import FloatingToolbar from './floating-toolbar';
22 |
23 | const mapStateToProps = createStructuredSelector({
24 | sectorId: currentSectorSelector,
25 | mapLocked: getMapLock,
26 | layers: currentSectorLayers,
27 | sectorLayers: getSectorLayers,
28 | isSharedSector: isSharedSectorSelector,
29 | isShared: isViewingSharedSector,
30 | isSaved: isCurrentSectorSaved,
31 | playerView: playerViewSelector,
32 | });
33 |
34 | const mapDispatchToProps = dispatch => ({
35 | toggleMapLock: () => dispatch(toggleMapLock()),
36 | togglePlayerView: () => dispatch(togglePlayerView()),
37 | toggleLayer: layer => dispatch(toggleLayer(layer)),
38 | redirectToHome: sectorId => dispatch(push(`/sector/${sectorId}`)),
39 | });
40 |
41 | export default injectIntl(
42 | connect(mapStateToProps, mapDispatchToProps)(withRouter(FloatingToolbar)),
43 | );
44 |
--------------------------------------------------------------------------------
/src/components/game-routes/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Loadable from 'react-loadable';
3 |
4 | const LoadableGameRoutes = Loadable.Map({
5 | delay: 400,
6 | loading: () => <>>,
7 | loader: {
8 | Component: () =>
9 | import(
10 | /* webpackChunkName: "game-routes" */ 'components/game-routes/routes'
11 | ),
12 | },
13 | render: (loaded, props) => {
14 | const Routes = loaded.Component.default;
15 | return ;
16 | },
17 | });
18 |
19 | export default function GameRoutes() {
20 | return ;
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/game-routes/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 | @import '~styles/constants';
3 |
4 | .GameRoutes {
5 | display: grid;
6 | grid-template-columns: [navigation] $large-navigation-width [game] auto [end];
7 | height: 100vh;
8 | width: 100vw;
9 | }
10 |
11 | .GameRoutes-Navigation {
12 | grid-column: navigation / game;
13 | }
14 |
15 | @media (max-width: 1500px) {
16 | .GameRoutes {
17 | grid-template-columns: [navigation] $small-navigation-width [game] auto [end];
18 | }
19 | }
20 |
21 | @media (max-width: 700px) {
22 | .GameRoutes {
23 | grid-template-columns: auto;
24 | }
25 | }
26 |
27 | @media print {
28 | .GameRoutes {
29 | display: block;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/hex-map/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .HexMap-SVG {
4 | display: flex;
5 |
6 | &--drag {
7 | cursor: move;
8 | }
9 | }
10 |
11 | .HexMap-Container {
12 | display: flex;
13 | overflow: hidden;
14 | position: relative;
15 | }
16 |
17 | .HexMap-Message {
18 | color: $light3;
19 | max-width: 200px;
20 | }
21 |
22 | @media print {
23 | .HexMap-Container {
24 | display: none;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/home/featured/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | $border: 1px solid $dark1;
4 |
5 | .HomeFeatured {
6 | border: $border;
7 | border-radius: 5px;
8 | }
9 |
10 | .HomeFeatured-Title {
11 | flex: 1;
12 | font-weight: bold;
13 | line-height: initial;
14 | padding: 1rem;
15 | }
16 |
17 | .HomeFeatured-Sector,
18 | .HomeFeatured-Website,
19 | .HomeFeatured-Patreon {
20 | color: $light2;
21 | flex: 1;
22 | padding: 1rem;
23 | text-align: center;
24 | text-decoration: none;
25 |
26 | &:hover {
27 | background-color: $transparentDark;
28 | color: $light2;
29 | }
30 | }
31 |
32 | .HomeFeatured-Website {
33 | border-top: $border;
34 | }
35 |
36 | .HomeFeatured-Sector {
37 | border-right: $border;
38 | border-top: $border;
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/home/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 |
4 | import Entities from 'constants/entities';
5 | import { generateEntity } from 'store/actions/entity.actions';
6 | import { getUserSectors } from 'store/selectors/sector.selectors';
7 |
8 | import Home from './home';
9 |
10 | const mapStateToProps = state => ({
11 | saved: getUserSectors(state),
12 | });
13 |
14 | const mapDispatchToProps = (dispatch, props) => ({
15 | generateSector: () =>
16 | dispatch(
17 | generateEntity({ entityType: Entities.sector.key }, {}, props.intl),
18 | ),
19 | });
20 |
21 | export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(Home));
22 |
--------------------------------------------------------------------------------
/src/components/home/saved/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import dayjs from 'dayjs';
3 | import PropTypes from 'prop-types';
4 | import { Link } from 'react-router-dom';
5 | import { FormattedMessage } from 'react-intl';
6 |
7 | import FlexContainer from 'primitives/container/flex-container';
8 | import Header, { HeaderType } from 'primitives/text/header';
9 |
10 | import './style.scss';
11 |
12 | export default function HomeSaved({ name, sector, rows, columns, created }) {
13 | let date = new Date(created);
14 | if (!created) {
15 | date = undefined;
16 | } else if (created.toDate) {
17 | date = created.toDate();
18 | }
19 | return (
20 |
25 |
26 |
29 |
30 |
31 | :
32 | {' '}
33 | {columns}, {rows}
34 |
35 |
36 |
37 | :
38 | {' '}
39 | {dayjs(date).format('MMMM D, YYYY')}
40 |
41 |
42 |
43 | );
44 | }
45 |
46 | HomeSaved.propTypes = {
47 | name: PropTypes.string.isRequired,
48 | sector: PropTypes.string.isRequired,
49 | rows: PropTypes.number.isRequired,
50 | columns: PropTypes.number.isRequired,
51 | created: PropTypes.shape(),
52 | };
53 |
54 | HomeSaved.defaultProps = {
55 | created: null,
56 | };
57 |
--------------------------------------------------------------------------------
/src/components/home/saved/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | $border: 1px solid $dark1;
4 |
5 | .HomeSaved {
6 | border: $border;
7 | border-radius: 5px;
8 | }
9 |
10 | .HomeSaved-Title {
11 | font-weight: bold;
12 | line-height: initial;
13 | padding: 0 0 1rem;
14 | }
15 |
16 | .HomeSaved-Link {
17 | color: $light3;
18 | display: flex;
19 | flex: 1;
20 | flex-direction: column;
21 | padding: 0.2rem 1rem;
22 | justify-content: center;
23 | text-align: center;
24 | text-decoration: none;
25 |
26 | &:hover {
27 | background-color: $transparentDark;
28 | }
29 | }
30 |
31 | .HomeSaved-Supporting {
32 | color: $light2;
33 | margin: 0;
34 | padding-bottom: 0.5rem;
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/login-modal/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 |
4 | import {
5 | closeLoginModal,
6 | updateUserForm,
7 | googleLogin,
8 | signup,
9 | login,
10 | passwordReset,
11 | } from 'store/actions/user.actions';
12 | import LoginModal from './login-modal';
13 |
14 | const mapStateToProps = ({ user }) => ({
15 | ...user.form,
16 | isLoginModalOpen: user.isLoginModalOpen,
17 | error: user.error,
18 | });
19 |
20 | const mapDispatchToProps = (dispatch, props) => ({
21 | googleLogin: () => dispatch(googleLogin()),
22 | signup: () => dispatch(signup(props.intl)),
23 | login: () => dispatch(login(props.intl)),
24 | closeLoginModal: () => dispatch(closeLoginModal()),
25 | updateUserForm: (key, value) => dispatch(updateUserForm(key, value)),
26 | passwordReset: () => dispatch(passwordReset(props.intl)),
27 | });
28 |
29 | export default injectIntl(
30 | connect(mapStateToProps, mapDispatchToProps)(LoginModal),
31 | );
32 |
--------------------------------------------------------------------------------
/src/components/navigation/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { withRouter } from 'react-router-dom';
3 | import { createStructuredSelector } from 'reselect';
4 | import { injectIntl } from 'react-intl';
5 |
6 | import {
7 | openLoginModal,
8 | openEditModal,
9 | logout,
10 | } from 'store/actions/user.actions';
11 | import {
12 | isLoggedInSelector,
13 | currentSectorSelector,
14 | lastOverviewEntitySelector,
15 | } from 'store/selectors/base.selectors';
16 | import { isViewingSharedSector } from 'store/selectors/sector.selectors';
17 |
18 | import Navigation from './navigation';
19 |
20 | const mapStateToProps = createStructuredSelector({
21 | isLoggedIn: isLoggedInSelector,
22 | currentSector: currentSectorSelector,
23 | isSharedSector: isViewingSharedSector,
24 | lastOverviewEntity: lastOverviewEntitySelector,
25 | });
26 |
27 | const mapDispatchTopProps = (dispatch, props) => ({
28 | openLoginModal: () => dispatch(openLoginModal()),
29 | openEditModal: () => dispatch(openEditModal()),
30 | logout: () => dispatch(logout(props.intl)),
31 | });
32 |
33 | export default injectIntl(
34 | withRouter(connect(mapStateToProps, mapDispatchTopProps)(Navigation)),
35 | );
36 |
--------------------------------------------------------------------------------
/src/components/navigation/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | $margin: 1.55rem;
4 |
5 | .Navigation {
6 | background-color: $dark2;
7 | height: 100vh;
8 | width: 185px;
9 | }
10 |
11 | .Navigation-LinkContainer {
12 | width: 100%;
13 | }
14 |
15 | .Navigation-Icon {
16 | display: inline-block;
17 | margin: 0 $margin;
18 | }
19 |
20 | .Navigation-Title {
21 | display: inline-block;
22 | font-size: 16px;
23 | margin: 0 $margin 0 0;
24 | text-align: left;
25 | width: calc(100% - (1.55rem * 3) - 25px);
26 | }
27 |
28 | .Navigation-Link {
29 | color: $light3;
30 | cursor: pointer;
31 | background-color: $dark3;
32 | padding: 1.1rem 0;
33 | text-align: center;
34 | text-decoration: none;
35 | transition: color, background-color linear 0.2s;
36 | width: 100%;
37 |
38 | &:hover,
39 | &--active {
40 | background-color: $dark4;
41 | color: $light4;
42 | }
43 |
44 | &.Navigation-Login {
45 | background-color: $primary;
46 |
47 | &:hover {
48 | background-color: $primary-alt;
49 | }
50 | }
51 | }
52 |
53 | @media (max-width: 1500px) {
54 | .Navigation {
55 | width: 75px;
56 | }
57 |
58 | .Navigation-Icon {
59 | margin: auto;
60 | }
61 |
62 | .Navigation-Title {
63 | display: none;
64 | }
65 | }
66 |
67 | @media print {
68 | .Navigation {
69 | display: none;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/components/overview-list/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { push } from 'connected-react-router';
3 | import { injectIntl } from 'react-intl';
4 | import { withRouter } from 'react-router-dom';
5 | import { createStructuredSelector } from 'reselect';
6 |
7 | import {
8 | getCurrentSector,
9 | getPrintableEntities,
10 | sectorDoesNotExist,
11 | } from 'store/selectors/entity.selectors';
12 | import {
13 | customTagSelector,
14 | currentSectorSelector,
15 | isInitializedSelector,
16 | } from 'store/selectors/base.selectors';
17 | import { openCustomTagModal } from 'store/actions/tag.actions';
18 |
19 | import OverviewList from './overview-list';
20 |
21 | const mapStateToProps = createStructuredSelector({
22 | currentSectorId: currentSectorSelector,
23 | currentSector: getCurrentSector,
24 | entities: getPrintableEntities,
25 | isInitialized: isInitializedSelector,
26 | doesNotExist: sectorDoesNotExist,
27 | customTags: customTagSelector,
28 | });
29 |
30 | const mapDispatchToProps = dispatch => ({
31 | toSafeRoute: () => dispatch(push('/')),
32 | openCustomTagModal: () => dispatch(openCustomTagModal()),
33 | });
34 |
35 | export default injectIntl(
36 | withRouter(connect(mapStateToProps, mapDispatchToProps)(OverviewList)),
37 | );
38 |
--------------------------------------------------------------------------------
/src/components/overview-list/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .container {
4 | flex: 1;
5 | max-height: 100vh;
6 | max-width: 400px;
7 | min-width: 300px;
8 | }
9 |
10 | .list {
11 | overflow: auto;
12 | width: 100%;
13 | }
14 |
15 | .selectedItem {
16 | background-color: $dark3;
17 | }
18 |
19 | .arrow {
20 | color: $dark4;
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/overview-table/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 | import { createStructuredSelector } from 'reselect';
4 |
5 | import {
6 | getPrintableEntities,
7 | getCurrentSector,
8 | } from 'store/selectors/entity.selectors';
9 | import { customTagSelector } from 'store/selectors/base.selectors';
10 | import OverviewTable from './overview-table';
11 |
12 | const mapStateToProps = createStructuredSelector({
13 | entities: getPrintableEntities,
14 | currentSector: getCurrentSector,
15 | customTags: customTagSelector,
16 | });
17 |
18 | export default injectIntl(connect(mapStateToProps)(OverviewTable));
19 |
--------------------------------------------------------------------------------
/src/components/overview-table/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .OverviewTable {
4 | background-color: $dark3;
5 | max-height: calc(100vh - 1.8rem);
6 | overflow: auto;
7 | padding: 0.3rem 1.5rem 1.5rem;
8 | }
9 |
10 | .OverviewTable-Link {
11 | color: $light2;
12 | transition: color linear 0.2s;
13 |
14 | &:hover {
15 | color: $light1;
16 | }
17 | }
18 |
19 | .OverviewTable-Table {
20 | font-size: 0.75rem;
21 | }
22 |
23 | .OverviewTable-FlexWrap {
24 | width: 100%;
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/printables/condensed-printable/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 |
4 | import { endPrint } from 'store/actions/sector.actions';
5 | import { getPrintableEntities } from 'store/selectors/entity.selectors';
6 | import { customTagSelector } from 'store/selectors/base.selectors';
7 | import CondensedPrintable from './condensed-printable';
8 |
9 | const mapStateToProps = state => ({
10 | entities: getPrintableEntities(state),
11 | customTags: customTagSelector(state),
12 | });
13 |
14 | export default injectIntl(
15 | connect(mapStateToProps, { endPrint })(CondensedPrintable),
16 | );
17 |
--------------------------------------------------------------------------------
/src/components/printables/condensed-printable/style.scss:
--------------------------------------------------------------------------------
1 | .CondensedPrintable-Entity {
2 | page-break-after: always;
3 | width: 100vw;
4 | }
5 |
6 | .CondensedPrintable-EntityTitle {
7 | margin-bottom: 1rem;
8 | }
9 |
10 | .CondensedPrintable-Table {
11 | font-size: 0.8rem;
12 | width: 100%;
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/printables/expanded-printable/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 |
4 | import { endPrint } from 'store/actions/sector.actions';
5 | import { getPrintableEntities } from 'store/selectors/entity.selectors';
6 | import { customTagSelector } from 'store/selectors/base.selectors';
7 |
8 | import ExpendedPrintable from './expanded-printable';
9 |
10 | const mapStateToProps = state => ({
11 | entities: getPrintableEntities(state),
12 | customTags: customTagSelector(state),
13 | });
14 |
15 | export default injectIntl(
16 | connect(mapStateToProps, { endPrint })(ExpendedPrintable),
17 | );
18 |
--------------------------------------------------------------------------------
/src/components/printables/expanded-printable/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .ExpandedPrintable-Entity {
4 | page-break-after: always;
5 | width: 100%;
6 | }
7 |
8 | .ExpandedPrintable-Header {
9 | border-bottom: 1px solid $light3;
10 | width: 100%;
11 |
12 | * {
13 | margin-bottom: 0.5rem;
14 | margin-left: 0.4rem;
15 | }
16 | }
17 |
18 | .ExpandedPrintable-Type {
19 | color: $light1;
20 | margin-left: 1rem;
21 |
22 | span {
23 | margin: 0;
24 | }
25 | }
26 |
27 | .ExpandedPrintable-Block {
28 | background-color: $light4;
29 | color: $dark4;
30 | margin-top: 1rem;
31 | max-width: 350px;
32 | padding: 1rem;
33 | }
34 |
35 | .ExpandedPrintable-Name {
36 | margin-right: 0.4rem;
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/printables/map-printable/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 |
4 | import { getCurrentTopLevelEntities } from 'store/selectors/entity.selectors';
5 |
6 | import MapPrintable from './map-printable';
7 |
8 | const mapStateToProps = state => ({
9 | topLevelEntities: getCurrentTopLevelEntities(state),
10 | });
11 |
12 | export default injectIntl(connect(mapStateToProps)(MapPrintable));
13 |
--------------------------------------------------------------------------------
/src/components/printables/map-printable/map-printable.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import MapHex from './map-hex';
5 | import './style.scss';
6 |
7 | export default function MapPrintable({ viewbox, hexes, topLevelEntities }) {
8 | return (
9 |
10 |
23 |
24 | );
25 | }
26 |
27 | MapPrintable.propTypes = {
28 | viewbox: PropTypes.string,
29 | topLevelEntities: PropTypes.shape().isRequired,
30 | hexes: PropTypes.arrayOf(
31 | PropTypes.shape({
32 | hexKey: PropTypes.string.isRequired,
33 | }),
34 | ),
35 | };
36 |
37 | MapPrintable.defaultProps = {
38 | viewbox: null,
39 | hexes: [],
40 | };
41 |
--------------------------------------------------------------------------------
/src/components/printables/map-printable/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .MapPrintable-SVG {
4 | position: absolute;
5 | top: 0;
6 | left: 0;
7 | width: 100%;
8 | height: 100%;
9 | }
10 |
11 | .MapPrintable-Container {
12 | background-color: white;
13 | width: 100%;
14 | height: 100%;
15 | overflow: hidden;
16 | position: relative;
17 | }
18 |
19 | .MapPrintable-BlackHole {
20 | fill: $dark4;
21 | stroke: $dark4;
22 | font-size: 25px;
23 | }
24 |
25 | .MapPrintable-System {
26 | stroke: $dark4;
27 | fill: white;
28 | font-size: 25px;
29 | stroke-width: 6px;
30 | }
31 |
32 | .MapPrintable-Polygon {
33 | fill: white;
34 | stroke-width: 3px;
35 | stroke: $dark4;
36 | transition: all 0.2s;
37 | }
38 |
39 | .MapPrintable-Text {
40 | fill: $dark4;
41 | font-size: 25px;
42 | font-weight: 100;
43 | stroke: $dark4;
44 | text-anchor: middle;
45 | stroke-width: 0.6px;
46 | user-select: none;
47 | }
48 |
49 | .MapPrintable-Children {
50 | transform: translateY(25px);
51 | }
52 |
53 | .MapPrintable-Key {
54 | transform: translateY(-8px);
55 | }
56 |
57 | .MapPrintable-Name {
58 | transform: translateY(-24px);
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/printables/style.scss:
--------------------------------------------------------------------------------
1 | .Printable * {
2 | font-family: 'Helvetica', sans-serif !important;
3 | -webkit-print-color-adjust: exact;
4 | }
5 |
6 | .Printable-Container {
7 | display: none;
8 | width: 100vw;
9 | height: 100vh;
10 | }
11 |
12 | .Printable-EntityContainer {
13 | display: none;
14 | width: 100vw;
15 | }
16 |
17 | @media print {
18 | .Printable-Container,
19 | .Printable-EntityContainer {
20 | display: inherit;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/profile-modal/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 |
4 | import {
5 | updateUserForm,
6 | updateUser,
7 | closeEditModal,
8 | } from 'store/actions/user.actions';
9 |
10 | import ProfileModal from './profile-modal';
11 | import {
12 | userFormSelector,
13 | isUserEditModalOpenSelector,
14 | } from '../../store/selectors/base.selectors';
15 |
16 | const mapStateToProps = state => ({
17 | form: userFormSelector(state),
18 | isEditModalOpen: isUserEditModalOpenSelector(state),
19 | });
20 |
21 | const mapDispatchToProps = (dispatch, props) => ({
22 | updateUserForm: (key, value) => dispatch(updateUserForm(key, value)),
23 | updateUser: () => dispatch(updateUser(props.intl)),
24 | closeEditModal: () => dispatch(closeEditModal()),
25 | });
26 |
27 | export default injectIntl(
28 | connect(mapStateToProps, mapDispatchToProps)(ProfileModal),
29 | );
30 |
--------------------------------------------------------------------------------
/src/components/profile-modal/style.scss:
--------------------------------------------------------------------------------
1 | .ProfileModel-Content {
2 | padding: 1rem 0;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/sector-expansion-modal/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { createStructuredSelector } from 'reselect';
3 | import { injectIntl } from 'react-intl';
4 |
5 | import { closeSectorExpansion } from 'store/actions/sector.actions';
6 | import { expandSector } from 'store/actions/combined.actions';
7 | import { getCurrentSector } from 'store/selectors/entity.selectors';
8 | import { isSectorExpansionOpenSelector } from 'store/selectors/base.selectors';
9 |
10 | import SectorExpansionModal from './sector-expansion-modal';
11 |
12 | const mapStateToProps = createStructuredSelector({
13 | isOpen: isSectorExpansionOpenSelector,
14 | sector: getCurrentSector,
15 | });
16 |
17 | const mapDispatchToProps = (dispatch, props) => ({
18 | closeSectorExpansion: () => dispatch(closeSectorExpansion()),
19 | expandSector: state => dispatch(expandSector(state, props.intl)),
20 | });
21 |
22 | export default injectIntl(
23 | connect(mapStateToProps, mapDispatchToProps)(SectorExpansionModal),
24 | );
25 |
--------------------------------------------------------------------------------
/src/components/sector-expansion-modal/style.module.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 | @import '~styles/constants';
3 |
4 | .square {
5 | border: 1px solid $light2;
6 | height: 125px;
7 | font-size: 1.4rem;
8 | width: 125px;
9 | }
10 |
11 | .recommendation {
12 | margin-top: 0;
13 | margin-bottom: 2rem;
14 | text-align: center;
15 | }
16 |
17 | %input {
18 | text-align: center;
19 | width: 75px;
20 | }
21 |
22 | .top {
23 | @extend %input;
24 | margin-bottom: 1rem;
25 | }
26 |
27 | .bottom {
28 | @extend %input;
29 | margin-top: 1rem;
30 | }
31 |
32 | .left {
33 | @extend %input;
34 | margin-right: 1rem;
35 | }
36 |
37 | .right {
38 | @extend %input;
39 | margin-left: 1rem;
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/sector-map/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { push } from 'connected-react-router';
3 | import { injectIntl } from 'react-intl';
4 | import { withRouter } from 'react-router-dom';
5 | import { createStructuredSelector } from 'reselect';
6 |
7 | import Entities from 'constants/entities';
8 | import { fetchSector } from 'store/actions/combined.actions';
9 | import { generateEntity } from 'store/actions/entity.actions';
10 | import { currentSectorIsLoading } from 'store/selectors/sector.selectors';
11 | import {
12 | getCurrentTopLevelEntities,
13 | getCurrentSector,
14 | sectorDoesNotExist,
15 | } from 'store/selectors/entity.selectors';
16 | import {
17 | renderSectorSelector,
18 | exportTypeSelector,
19 | isPrintingSelector,
20 | fetchedSectorSelector,
21 | } from 'store/selectors/base.selectors';
22 |
23 | import SectorMap from './sector-map';
24 |
25 | const mapStateToProps = createStructuredSelector({
26 | doesNotExist: sectorDoesNotExist,
27 | renderSector: renderSectorSelector,
28 | sector: getCurrentSector,
29 | isLoading: currentSectorIsLoading,
30 | topLevelEntities: getCurrentTopLevelEntities,
31 | exportType: exportTypeSelector,
32 | isPrinting: isPrintingSelector,
33 | fetchedSectors: fetchedSectorSelector,
34 | });
35 |
36 | const mapDispatchToProps = (dispatch, props) => ({
37 | toSafeRoute: sector => dispatch(push(sector ? `/sector/${sector}` : '/')),
38 | fetchSector: () => dispatch(fetchSector()),
39 | generateSector: () =>
40 | dispatch(
41 | generateEntity({ entityType: Entities.sector.key }, {}, props.intl),
42 | ),
43 | });
44 |
45 | export default injectIntl(
46 | withRouter(connect(mapStateToProps, mapDispatchToProps)(SectorMap)),
47 | );
48 |
--------------------------------------------------------------------------------
/src/components/sector-map/loading/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Loading from 'primitives/regions/loading';
4 | import HexMap from 'components/hex-map';
5 |
6 | import hexGenerator from 'utils/hex/generator';
7 |
8 | export default function SectorLoading() {
9 | const { hexes } = hexGenerator({
10 | renderSector: false,
11 | height: window.innerHeight,
12 | width: window.innerWidth,
13 | });
14 |
15 | return (
16 | <>
17 |
22 |
23 | >
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/sector-map/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 | @import '~styles/constants';
3 |
4 | .SectorMap {
5 | display: grid;
6 | grid-template-columns: [map] auto [sidebar] $large-sidebar-width [end];
7 | overflow: hidden;
8 | }
9 |
10 | .SectorMap-Map {
11 | position: relative;
12 | grid-column: map / sidebar;
13 | width: calc(100vw - #{$large-sidebar-width} - #{$large-navigation-width});
14 | }
15 |
16 | .SectorMap-Sidebar {
17 | grid-column: sidebar / end;
18 | overflow: auto;
19 | }
20 |
21 | @media (max-width: 700px) {
22 | .SectorMap-Sidebar {
23 | display: none;
24 | }
25 | }
26 |
27 | @media (max-width: 1200px) {
28 | .SectorMap {
29 | grid-template-columns: [map] auto [sidebar] $small-sidebar-width [end];
30 | }
31 |
32 | .SectorMap-Map {
33 | width: calc(100vw - #{$small-sidebar-width} - #{$small-navigation-width});
34 | }
35 | }
36 |
37 | @media (min-width: 1200px) and (max-width: 1500px) {
38 | .SectorMap-Map {
39 | width: calc(100vw - #{$large-sidebar-width} - #{$small-navigation-width});
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/sidebar-actions/action-layout/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 |
4 | import { currentSectorSelector } from 'store/selectors/base.selectors';
5 | import {
6 | isCurrentSectorSaved,
7 | isViewingSharedSector,
8 | } from 'store/selectors/sector.selectors';
9 | import { getCurrentEntityType } from 'store/selectors/entity.selectors';
10 |
11 | import { openExport } from 'store/actions/sector.actions';
12 | import ActionLayout from './action-layout';
13 |
14 | const mapStateToProps = state => ({
15 | isSaved: isCurrentSectorSaved(state),
16 | isShared: isViewingSharedSector(state),
17 | currentSector: currentSectorSelector(state),
18 | entityType: getCurrentEntityType(state),
19 | });
20 |
21 | export default injectIntl(
22 | connect(mapStateToProps, { openExport })(ActionLayout),
23 | );
24 |
--------------------------------------------------------------------------------
/src/components/sidebar-actions/default-actions/default-actions.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { intlShape } from 'react-intl';
4 |
5 | import ActionLayout from 'components/sidebar-actions/action-layout';
6 | import Entities from 'constants/entities';
7 |
8 | export default function DefaultActions({ intl, entityType, children }) {
9 | return (
10 |
11 | {children}
12 |
13 | );
14 | }
15 |
16 | DefaultActions.propTypes = {
17 | intl: intlShape.isRequired,
18 | entityType: PropTypes.string,
19 | children: PropTypes.node.isRequired,
20 | };
21 |
22 | DefaultActions.defaultProps = {
23 | entityType: undefined,
24 | };
25 |
--------------------------------------------------------------------------------
/src/components/sidebar-actions/default-actions/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 |
4 | import { currentSectorSelector } from 'store/selectors/base.selectors';
5 | import {
6 | isCurrentSectorSaved,
7 | isViewingSharedSector,
8 | } from 'store/selectors/sector.selectors';
9 | import { getCurrentEntityType } from 'store/selectors/entity.selectors';
10 |
11 | import { openExport } from 'store/actions/sector.actions';
12 | import DefaultActions from './default-actions';
13 |
14 | const mapStateToProps = state => ({
15 | isSaved: isCurrentSectorSaved(state),
16 | isShared: isViewingSharedSector(state),
17 | currentSector: currentSectorSelector(state),
18 | entityType: getCurrentEntityType(state),
19 | });
20 |
21 | export default injectIntl(
22 | connect(mapStateToProps, { openExport })(DefaultActions),
23 | );
24 |
--------------------------------------------------------------------------------
/src/components/sidebar-actions/entity-actions/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .EntityActions-TypeHeader {
4 | color: $light1;
5 | margin-left: 0.8rem;
6 | }
7 |
8 | .EntityActions-Welcome {
9 | padding-top: 3rem;
10 | }
11 |
12 | .EntityActions-WelcomeList {
13 | color: $light1;
14 | margin: 0 1rem 0 0.5rem;
15 | }
16 |
17 | .EntityActions-WelcomeItem {
18 | margin-top: 1rem;
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/sidebar-actions/layer-actions/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 | import { push } from 'connected-react-router';
4 | import { createStructuredSelector } from 'reselect';
5 |
6 | import {
7 | isCurrentSectorSaved,
8 | isViewingSharedSector,
9 | } from 'store/selectors/sector.selectors';
10 | import {
11 | currentSectorSelector,
12 | currentEntitySelector,
13 | layerIsEditingSelector,
14 | } from 'store/selectors/base.selectors';
15 | import {
16 | currentLayer,
17 | isValidLayerForm,
18 | } from 'store/selectors/layer.selectors';
19 | import {
20 | initializeLayerEdit,
21 | submitForm,
22 | resetForms,
23 | } from 'store/actions/layer.actions';
24 | import { removeLayer } from 'store/actions/combined.actions';
25 | import LayerActions from './layer-actions';
26 |
27 | const mapStateToProps = createStructuredSelector({
28 | isSaved: isCurrentSectorSaved,
29 | isShared: isViewingSharedSector,
30 | isValid: isValidLayerForm,
31 | layer: currentLayer,
32 | layerId: currentEntitySelector,
33 | isEditing: layerIsEditingSelector,
34 | sector: currentSectorSelector,
35 | });
36 |
37 | const mapDispatchToProps = (dispatch, props) => ({
38 | removeLayer: () => dispatch(removeLayer(props.intl)),
39 | initializeLayerEdit: () => dispatch(initializeLayerEdit()),
40 | submitForm: () => dispatch(submitForm(props.intl)),
41 | cancelForm: () => dispatch(resetForms()),
42 | route: to => dispatch(push(to)),
43 | });
44 |
45 | export default injectIntl(
46 | connect(mapStateToProps, mapDispatchToProps)(LayerActions),
47 | );
48 |
--------------------------------------------------------------------------------
/src/components/sidebar-actions/layer-actions/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .LayerActions-TypeHeader {
4 | color: $light1;
5 | margin-left: 0.8rem;
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/default-sidebar/entity-attribute/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 | import { createStructuredSelector } from 'reselect';
4 |
5 | import { getCurrentEntity } from 'store/selectors/entity.selectors';
6 | import { isSidebarEditActiveSelector } from 'store/selectors/base.selectors';
7 | import { updateEntityInEdit } from 'store/actions/sidebar.actions';
8 | import { isViewingSharedSector } from 'store/selectors/sector.selectors';
9 |
10 | import EntityAttribute from './entity-attribute';
11 |
12 | const mapStateToProps = createStructuredSelector({
13 | entity: getCurrentEntity,
14 | isSidebarEditActive: isSidebarEditActiveSelector,
15 | isShared: isViewingSharedSector,
16 | });
17 |
18 | export default injectIntl(
19 | connect(mapStateToProps, { updateEntityInEdit })(EntityAttribute),
20 | );
21 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/default-sidebar/entity-attributes/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 | import { createStructuredSelector } from 'reselect';
4 |
5 | import {
6 | getCurrentEntity,
7 | getCurrentEntityId,
8 | getCurrentEntityType,
9 | getEntityAttributes,
10 | isAncestorHidden,
11 | } from 'store/selectors/entity.selectors';
12 | import { isSidebarEditActiveSelector } from 'store/selectors/base.selectors';
13 | import { updateEntityInEdit } from 'store/actions/sidebar.actions';
14 |
15 | import EntityAttributes from './entity-attributes';
16 |
17 | const mapStateToProps = createStructuredSelector({
18 | entity: getCurrentEntity,
19 | entityId: getCurrentEntityId,
20 | entityType: getCurrentEntityType,
21 | entityAttributes: getEntityAttributes,
22 | isSidebarEditActive: isSidebarEditActiveSelector,
23 | isAncestorHidden,
24 | });
25 |
26 | export default injectIntl(
27 | connect(mapStateToProps, { updateEntityInEdit })(EntityAttributes),
28 | );
29 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/default-sidebar/entity-attributes/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .attributes {
4 | margin: 0 1rem 1rem 1rem;
5 | width: calc(100% - 2rem);
6 | }
7 |
8 | .subHeader {
9 | color: $light2;
10 | margin: 0 1rem;
11 | }
12 |
13 | .subHeaderHidden {
14 | margin: 0rem 0.1rem 0 0.5rem;
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/default-sidebar/entity-edit-row/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 |
4 | import {
5 | deleteChildInEdit,
6 | undoDeleteChildInEdit,
7 | updateChildInEdit,
8 | } from 'store/actions/sidebar.actions';
9 | import {
10 | isCurrentOrAncestorHidden,
11 | getEmptyHexKeys,
12 | } from 'store/selectors/entity.selectors';
13 | import EntityEditRow from './entity-edit-row';
14 |
15 | const mapStateToProps = state => ({
16 | emptyHexKeys: getEmptyHexKeys(state),
17 | isCurrentOrAncestorHidden: isCurrentOrAncestorHidden(state),
18 | });
19 |
20 | const mapDispatchToProps = (dispatch, props) => ({
21 | deleteChildInEdit: () =>
22 | dispatch(deleteChildInEdit(props.entityType, props.entity.entityId)),
23 | undoDeleteChildInEdit: () =>
24 | dispatch(undoDeleteChildInEdit(props.entityType, props.entity.entityId)),
25 | updateChildInEdit: updates =>
26 | dispatch(
27 | updateChildInEdit(props.entityType, props.entity.entityId, updates),
28 | ),
29 | });
30 |
31 | export default injectIntl(
32 | connect(mapStateToProps, mapDispatchToProps)(EntityEditRow),
33 | );
34 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/default-sidebar/entity-edit-row/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .EntityEditRow {
4 | margin: 0 1rem 0.5rem;
5 | }
6 |
7 | .EntityEditRow-Action {
8 | color: $light2;
9 | cursor: pointer;
10 | margin-right: 0.5rem;
11 | transition: all 0.2s;
12 |
13 | &:hover {
14 | color: $light3;
15 | }
16 | }
17 |
18 | .EntityEditRow-Checkbox {
19 | margin-left: 0.5rem;
20 | margin-right: 0;
21 | }
22 |
23 | .EntityEditRow-Dropdown {
24 | width: 68px;
25 | margin-right: 0.5rem;
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/default-sidebar/entity-link-row/entity-link-row.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { EyeOff } from 'react-feather';
4 |
5 | import LinkRow from 'primitives/other/link-row';
6 |
7 | export default function EntityLinkRow({
8 | entity,
9 | entityType,
10 | currentSector,
11 | isCurrentOrAncestorHidden,
12 | }) {
13 | return (
14 |
22 | );
23 | }
24 |
25 | EntityLinkRow.propTypes = {
26 | entity: PropTypes.shape({
27 | entityId: PropTypes.string.isRequired,
28 | name: PropTypes.string.isRequired,
29 | additional: PropTypes.string,
30 | isHidden: PropTypes.bool,
31 | }).isRequired,
32 | entityType: PropTypes.string.isRequired,
33 | currentSector: PropTypes.string.isRequired,
34 | isCurrentOrAncestorHidden: PropTypes.bool.isRequired,
35 | };
36 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/default-sidebar/entity-link-row/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 |
3 | import { currentSectorSelector } from 'store/selectors/base.selectors';
4 | import { isCurrentOrAncestorHidden } from 'store/selectors/entity.selectors';
5 |
6 | import EntityLinkRow from './entity-link-row';
7 |
8 | const mapStateToProps = state => ({
9 | currentSector: currentSectorSelector(state),
10 | isCurrentOrAncestorHidden: isCurrentOrAncestorHidden(state),
11 | });
12 |
13 | export default connect(mapStateToProps)(EntityLinkRow);
14 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/default-sidebar/entity-list/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 |
4 | import {
5 | customTagSelector,
6 | isSidebarEditActiveSelector,
7 | sidebarEditChildrenSelector,
8 | } from 'store/selectors/base.selectors';
9 | import { createChildInEdit } from 'store/actions/sidebar.actions';
10 | import { isViewingSharedSector } from 'store/selectors/sector.selectors';
11 |
12 | import EntityList from './entity-list';
13 |
14 | const mapStateToProps = (state, props) => ({
15 | isSidebarEditActive: isSidebarEditActiveSelector(state),
16 | editableEntities: sidebarEditChildrenSelector(state)[props.entityType],
17 | isShared: isViewingSharedSector(state),
18 | customTags: customTagSelector(state),
19 | });
20 |
21 | const mapDispatchToProps = (dispatch, props) => ({
22 | createChildInEdit: () => dispatch(createChildInEdit(props.entityType)),
23 | });
24 |
25 | export default injectIntl(
26 | connect(mapStateToProps, mapDispatchToProps)(EntityList),
27 | );
28 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/default-sidebar/entity-list/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .EntityList-SubHeader {
4 | color: $light2;
5 | margin: 0 1rem;
6 | }
7 |
8 | .EntityList-SubHeaderHidden {
9 | margin: 0rem 0.1rem 0 0.5rem;
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/default-sidebar/entity-tags/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 | import { createStructuredSelector } from 'reselect';
4 |
5 | import {
6 | getCurrentEntity,
7 | getCurrentEntityId,
8 | getCurrentEntityType,
9 | } from 'store/selectors/entity.selectors';
10 | import { isSidebarEditActiveSelector } from 'store/selectors/base.selectors';
11 | import { updateEntityInEdit } from 'store/actions/sidebar.actions';
12 | import { openCustomTagModal } from 'store/actions/tag.actions';
13 | import { isViewingSharedSector } from 'store/selectors/sector.selectors';
14 | import { getTagsForCurrentEntity } from 'store/selectors/tag.selectors';
15 |
16 | import EntityTags from './entity-tags';
17 |
18 | const mapStateToProps = createStructuredSelector({
19 | customTags: getTagsForCurrentEntity,
20 | entity: getCurrentEntity,
21 | entityId: getCurrentEntityId,
22 | entityType: getCurrentEntityType,
23 | isSidebarEditActive: isSidebarEditActiveSelector,
24 | isShared: isViewingSharedSector,
25 | });
26 |
27 | export default injectIntl(
28 | connect(mapStateToProps, { updateEntityInEdit, openCustomTagModal })(
29 | EntityTags,
30 | ),
31 | );
32 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/default-sidebar/entity-tags/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .section {
4 | margin-bottom: 1rem;
5 | }
6 |
7 | .tag {
8 | margin: 0 1rem 2rem 1rem;
9 | text-align: left;
10 | }
11 |
12 | .content {
13 | font-size: 0.9rem;
14 | font-weight: 200;
15 | color: $light2;
16 | }
17 |
18 | .contentList {
19 | margin-top: 0.1rem;
20 | }
21 |
22 | .entityTagEdit {
23 | margin: 0 1rem 0.5rem;
24 | }
25 |
26 | .dropdown {
27 | flex: 1;
28 | }
29 |
30 | .checkbox {
31 | margin-left: 0.5rem;
32 | margin-right: 0;
33 | }
34 |
35 | .subHeader {
36 | color: $light2;
37 | margin: 0 1rem;
38 | }
39 |
40 | .subHeaderHidden {
41 | margin: 0rem 0.1rem 0 0.5rem;
42 | }
43 |
44 | .customTagBtn {
45 | cursor: pointer;
46 | transition: color 0.2s linear;
47 |
48 | &:hover {
49 | color: $light2;
50 | }
51 | }
52 |
53 | .emptyTags {
54 | color: $light1;
55 | font-size: 0.9rem;
56 | margin-bottom: 1rem;
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/default-sidebar/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { createStructuredSelector } from 'reselect';
3 |
4 | import {
5 | getCurrentEntity,
6 | getCurrentEntityType,
7 | getCurrentEntityChildren,
8 | } from 'store/selectors/entity.selectors';
9 | import { isViewingSharedSector } from 'store/selectors/sector.selectors';
10 | import { isSidebarEditActiveSelector } from 'store/selectors/base.selectors';
11 |
12 | import DefaultSidebar from './default-sidebar';
13 |
14 | const mapStateToProps = createStructuredSelector({
15 | isShared: isViewingSharedSector,
16 | isSidebarEditActive: isSidebarEditActiveSelector,
17 | entity: getCurrentEntity,
18 | entityType: getCurrentEntityType,
19 | entityChildren: getCurrentEntityChildren,
20 | });
21 |
22 | export default connect(mapStateToProps)(DefaultSidebar);
23 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/default-sidebar/styles.module.scss:
--------------------------------------------------------------------------------
1 | .entityImg {
2 | margin: 1rem;
3 | width: calc(100% - 2rem);
4 | }
5 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/layer-sidebar/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 | import { push } from 'connected-react-router';
4 | import { createStructuredSelector } from 'reselect';
5 |
6 | import {
7 | currentSectorSelector,
8 | currentEntitySelector,
9 | layerIsEditingSelector,
10 | layerRegionFormSelector,
11 | layerColorPickerSelector,
12 | } from 'store/selectors/base.selectors';
13 | import { currentLayer } from 'store/selectors/layer.selectors';
14 | import { isViewingSharedSector } from 'store/selectors/sector.selectors';
15 | import {
16 | initializeRegionForm,
17 | updateRegion,
18 | removeRegion,
19 | } from 'store/actions/layer.actions';
20 |
21 | import LayerSidebar from './layer-sidebar';
22 |
23 | const mapStateToProps = createStructuredSelector({
24 | colorPicker: layerColorPickerSelector,
25 | regionForm: layerRegionFormSelector,
26 | isEditing: layerIsEditingSelector,
27 | isShared: isViewingSharedSector,
28 | sectorId: currentSectorSelector,
29 | layerId: currentEntitySelector,
30 | currentLayer,
31 | });
32 |
33 | const mapDispatchToProps = (dispatch, props) => ({
34 | initializeRegionForm: regionId => dispatch(initializeRegionForm(regionId)),
35 | updateRegion: (regionId, update) => dispatch(updateRegion(regionId, update)),
36 | removeRegion: regionId => dispatch(removeRegion(regionId, props.intl)),
37 | toSafeRoute: sectorId => dispatch(push(`/sector/${sectorId}`)),
38 | });
39 |
40 | export default injectIntl(
41 | connect(mapStateToProps, mapDispatchToProps)(LayerSidebar),
42 | );
43 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/layer-sidebar/layer-form/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 | import { createStructuredSelector } from 'reselect';
4 |
5 | import { layerFormSelector } from 'store/selectors/base.selectors';
6 | import { updateLayer } from 'store/actions/layer.actions';
7 |
8 | import LayerForm from './layer-form';
9 |
10 | const mapStateToProps = createStructuredSelector({
11 | layerForm: layerFormSelector,
12 | });
13 |
14 | export default injectIntl(connect(mapStateToProps, { updateLayer })(LayerForm));
15 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/layer-sidebar/layer-form/style.scss:
--------------------------------------------------------------------------------
1 | .LayerForm {
2 | margin: 1rem;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/layer-sidebar/region-row/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { createStructuredSelector } from 'reselect';
3 | import { injectIntl } from 'react-intl';
4 |
5 | import {
6 | layerRegionFormSelector,
7 | layerColorPickerSelector,
8 | layerRegionPaintSelector,
9 | } from 'store/selectors/base.selectors';
10 | import { isViewingSharedSector } from 'store/selectors/sector.selectors';
11 | import {
12 | updateRegionForm,
13 | initializeRegionForm,
14 | cancelRegionForm,
15 | submitRegionForm,
16 | openColorPicker,
17 | updateRegion,
18 | beginRegionPaint,
19 | closeRegionPaint,
20 | } from 'store/actions/layer.actions';
21 | import RegionRow from './region-row';
22 |
23 | const mapStateToProps = createStructuredSelector({
24 | regionForm: layerRegionFormSelector,
25 | colorPicker: layerColorPickerSelector,
26 | regionPaint: layerRegionPaintSelector,
27 | isShared: isViewingSharedSector,
28 | });
29 |
30 | const mapDispatchToProps = (dispatch, props) => ({
31 | updateRegionForm: update => dispatch(updateRegionForm(update)),
32 | cancelRegionForm: () => dispatch(cancelRegionForm()),
33 | submitRegionForm: () => dispatch(submitRegionForm(props.intl)),
34 | openColorPicker: regionId => dispatch(openColorPicker(regionId)),
35 | initializeRegionForm: regionId => dispatch(initializeRegionForm(regionId)),
36 | updateRegion: (regionId, update) => dispatch(updateRegion(regionId, update)),
37 | beginRegionPaint: regionId => dispatch(beginRegionPaint(regionId)),
38 | closeRegionPaint: () => dispatch(closeRegionPaint()),
39 | });
40 |
41 | export default injectIntl(
42 | connect(mapStateToProps, mapDispatchToProps)(RegionRow),
43 | );
44 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/layer-sidebar/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .LayerSidebar {
4 | font-size: 0.9rem;
5 | margin: 1rem 0 2rem;
6 | }
7 |
8 | .LayerSidebar-Hidden {
9 | color: $light1;
10 | margin: 0 1rem 1rem;
11 |
12 | span {
13 | margin-left: 0.5rem;
14 | }
15 | }
16 |
17 | .LayerSidebar-DescriptionContainer {
18 | color: $light3;
19 | margin: 0 1rem;
20 | }
21 |
22 | .LayerSidebar-Label {
23 | font-weight: bold;
24 | }
25 |
26 | .LayerSidebar-Description {
27 | color: $light3;
28 | font-weight: 100;
29 | margin-top: 0.3rem;
30 | }
31 |
32 | .LayerSidebar-ColorHint {
33 | position: relative;
34 |
35 | &::after {
36 | border: 5px solid transparent;
37 | border-left: none;
38 | border-right-color: $light4;
39 | bottom: 0;
40 | content: '';
41 | display: block;
42 | left: 0;
43 | margin: auto;
44 | position: absolute;
45 | height: 0;
46 | right: auto;
47 | top: 0;
48 | width: 0;
49 | }
50 |
51 | &--content {
52 | background-color: $light4;
53 | left: 5px;
54 | position: absolute;
55 | top: -16px;
56 | }
57 |
58 | &--picker {
59 | background-color: $light4;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/navigation-sidebar/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 | import { createStructuredSelector } from 'reselect';
4 |
5 | import {
6 | navigationSettingsSelector,
7 | isHelpOpenSelector,
8 | } from 'store/selectors/base.selectors';
9 | import { getCurrentSectorNavigation } from 'store/selectors/navigation.selectors';
10 | import {
11 | resetNavSettings,
12 | cancelNavigation,
13 | updateNavSettings,
14 | completeRoute,
15 | removeRoute,
16 | toggleVisibility,
17 | locateRoute,
18 | } from 'store/actions/navigation.actions';
19 |
20 | import NavigationSidebar from './navigation-sidebar';
21 |
22 | const mapStateToProps = createStructuredSelector({
23 | settings: navigationSettingsSelector,
24 | isHelpOpen: isHelpOpenSelector,
25 | routes: getCurrentSectorNavigation,
26 | });
27 |
28 | const mapDispatchToProps = (dispatch, props) => ({
29 | resetNavSettings: () => dispatch(resetNavSettings()),
30 | cancelNavigation: () => dispatch(cancelNavigation()),
31 | updateNavSettings: (key, value) => dispatch(updateNavSettings(key, value)),
32 | completeRoute: () => dispatch(completeRoute(props.intl)),
33 | removeRoute: routeId => dispatch(removeRoute(routeId, props.intl)),
34 | toggleVisibility: routeId => dispatch(toggleVisibility(routeId, props.intl)),
35 | locateRoute: routeId => dispatch(locateRoute(routeId)),
36 | });
37 |
38 | export default injectIntl(
39 | connect(mapStateToProps, mapDispatchToProps)(NavigationSidebar),
40 | );
41 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/navigation-sidebar/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .NavigationSidebar-Options {
4 | margin-top: 1rem;
5 | }
6 |
7 | .NavigationSidebar {
8 | width: calc(100% - 2rem);
9 | }
10 |
11 | .NavigationSidebar-FormRow {
12 | margin: 0 1rem 1rem;
13 | width: calc(100% - 2rem);
14 |
15 | button {
16 | margin: 0;
17 | }
18 | }
19 |
20 | .NavigationSidebar-Cancel {
21 | margin-left: 0.5rem;
22 | }
23 |
24 | .NavigationSidebar-FormColumn {
25 | margin: 0.2rem 0;
26 | }
27 |
28 | .NavigationSidebar-NavItem {
29 | padding: 0 1rem;
30 | }
31 |
32 | .NavigationSidebar-SubHeader {
33 | color: $light2;
34 | margin: 0 1rem;
35 | }
36 |
37 | .NavigationSidebar-Delete {
38 | color: $light2;
39 | cursor: pointer;
40 | margin-right: 0.5rem;
41 | transition: all 0.2s;
42 |
43 | &:hover {
44 | color: $light3;
45 | }
46 | }
47 |
48 | .NavigationSidebar-Checkbox {
49 | margin-left: 0.5rem;
50 | margin-right: 0;
51 | }
52 |
53 | .NavigationSidebar-SubHeaderHidden {
54 | margin: 0rem 0.1rem 0 0.5rem;
55 | }
56 |
57 | .NavigationSidebar-Additional {
58 | color: $light1;
59 | font-size: 0.8rem;
60 | margin: 0 0.5rem;
61 | }
62 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/note-sidebar/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 |
4 | import {
5 | getCurrentEntity,
6 | isAncestorHidden,
7 | } from 'store/selectors/entity.selectors';
8 | import { isSidebarEditActiveSelector } from 'store/selectors/base.selectors';
9 | import { updateEntityInEdit } from 'store/actions/sidebar.actions';
10 |
11 | import NoteSidebar from './note-sidebar';
12 |
13 | const mapStateToProps = state => ({
14 | note: getCurrentEntity(state),
15 | isSidebarEditActive: isSidebarEditActiveSelector(state),
16 | isAncestorHidden: isAncestorHidden(state),
17 | });
18 |
19 | export default injectIntl(
20 | connect(mapStateToProps, { updateEntityInEdit })(NoteSidebar),
21 | );
22 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/note-sidebar/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .NoteSidebar-Display {
4 | color: $light2;
5 | padding: 1rem;
6 |
7 | a {
8 | color: $light1;
9 | transition: linear color 0.2s;
10 |
11 | &:hover {
12 | color: $light4;
13 | }
14 | }
15 |
16 | img {
17 | max-width: 100%;
18 | }
19 |
20 | code {
21 | background: $dark1;
22 | }
23 |
24 | table {
25 | max-width: 100%;
26 | }
27 |
28 | blockquote {
29 | border-left: 3px solid $light1;
30 | color: $light1;
31 | margin-left: 1rem;
32 | padding-left: 1rem;
33 | }
34 | }
35 |
36 | .NoteSidebar-Attributes {
37 | margin: 1rem;
38 | width: calc(100% - 2rem);
39 | }
40 |
41 | .NoteSidebar-Attribute {
42 | color: $light2;
43 | font-size: 0.9rem;
44 | padding: 0.2rem 0;
45 | }
46 |
47 | .NoteSidebar-Header {
48 | padding-right: 0.5rem;
49 | width: 7rem;
50 | }
51 |
52 | .NoteSidebar-Item {
53 | flex: 1;
54 | width: 100%;
55 |
56 | &--multiline {
57 | white-space: pre-line;
58 | }
59 | }
60 |
61 | .NoteSidebar-Textarea {
62 | height: 100%;
63 | resize: none;
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/settings-sidebar/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 |
4 | import { updateSettings } from 'store/actions/settings.actions';
5 | import { settingsSelector } from 'store/selectors/base.selectors';
6 |
7 | import SettingsSidebar from './settings-sidebar';
8 |
9 | const mapStateToProps = state => ({
10 | settings: settingsSelector(state),
11 | });
12 |
13 | const mapDispatchToProps = { updateSettings };
14 |
15 | export default injectIntl(
16 | connect(mapStateToProps, mapDispatchToProps)(SettingsSidebar),
17 | );
18 |
--------------------------------------------------------------------------------
/src/components/sidebar-entities/settings-sidebar/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .SettingsSidebar-Content {
4 | margin: 0 1rem;
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/sidebar/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { createStructuredSelector } from 'reselect';
3 |
4 | import { getCurrentEntityType } from 'store/selectors/entity.selectors';
5 |
6 | import Sidebar from './sidebar';
7 |
8 | const mapStateToProps = createStructuredSelector({
9 | entityType: getCurrentEntityType,
10 | });
11 |
12 | export default connect(mapStateToProps)(Sidebar);
13 |
--------------------------------------------------------------------------------
/src/components/sidebar/sidebar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Entities from 'constants/entities';
5 |
6 | import DefaultSidebar from 'components/sidebar-entities/default-sidebar';
7 | import NoteSidebar from 'components/sidebar-entities/note-sidebar';
8 | import NavigationSidebar from 'components/sidebar-entities/navigation-sidebar';
9 | import LayerSidebar from 'components/sidebar-entities/layer-sidebar';
10 | import SettingsSidebar from 'components/sidebar-entities/settings-sidebar';
11 |
12 | import DefaultActions from 'components/sidebar-actions/default-actions';
13 | import EntityActions from 'components/sidebar-actions/entity-actions';
14 | import LayerActions from 'components/sidebar-actions/layer-actions';
15 |
16 | const SIDEBAR_TYPE = {
17 | default: DefaultSidebar,
18 | note: NoteSidebar,
19 | navigation: NavigationSidebar,
20 | layer: LayerSidebar,
21 | settings: SettingsSidebar,
22 | };
23 |
24 | const ACTION_TYPE = {
25 | default: DefaultActions,
26 | entity: EntityActions,
27 | layer: LayerActions,
28 | };
29 |
30 | export default function Sidebar({ entityType }) {
31 | const SidebarContent =
32 | SIDEBAR_TYPE[Entities[entityType].sidebar] || SIDEBAR_TYPE.default;
33 | const Actions =
34 | ACTION_TYPE[Entities[entityType].action] || ACTION_TYPE.default;
35 | return (
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | Sidebar.propTypes = {
43 | entityType: PropTypes.string,
44 | };
45 |
46 | Sidebar.defaultProps = {
47 | entityType: undefined,
48 | };
49 |
--------------------------------------------------------------------------------
/src/components/star-background/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { createStructuredSelector } from 'reselect';
3 |
4 | import {
5 | userUidSelector,
6 | isInitializedSelector,
7 | } from 'store/selectors/base.selectors';
8 |
9 | import { openLoginModal } from 'store/actions/user.actions';
10 |
11 | import StarBackground from './star-background';
12 |
13 | const mapStateToProps = createStructuredSelector({
14 | isInitialized: isInitializedSelector,
15 | uid: userUidSelector,
16 | });
17 |
18 | export default connect(mapStateToProps, { openLoginModal })(StarBackground);
19 |
--------------------------------------------------------------------------------
/src/components/star-background/star-background.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { FormattedMessage } from 'react-intl';
4 |
5 | import AbsoluteContainer from 'primitives/container/absolute-container';
6 | import StarField from 'primitives/regions/star-field';
7 | import Button from 'primitives/other/button';
8 |
9 | import './style.scss';
10 |
11 | export default function StarBackground({
12 | children,
13 | openLoginModal,
14 | isInitialized,
15 | uid,
16 | ...props
17 | }) {
18 | let loginButton = null;
19 | if (isInitialized && !uid) {
20 | loginButton = (
21 |
24 | );
25 | }
26 |
27 | return (
28 | <>
29 |
30 |
31 | {children}
32 | {loginButton}
33 |
34 | >
35 | );
36 | }
37 |
38 | StarBackground.propTypes = {
39 | children: PropTypes.node.isRequired,
40 | openLoginModal: PropTypes.func.isRequired,
41 | isInitialized: PropTypes.bool.isRequired,
42 | uid: PropTypes.string,
43 | };
44 |
45 | StarBackground.defaultProps = {
46 | uid: null,
47 | };
48 |
--------------------------------------------------------------------------------
/src/components/star-background/style.scss:
--------------------------------------------------------------------------------
1 | .StarBackground-Login {
2 | position: absolute;
3 | right: 1rem;
4 | top: 1rem;
5 | }
6 |
7 | .StarBackground-Container {
8 | overflow: auto;
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/top-level-entity-modal/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { injectIntl } from 'react-intl';
3 | import { createStructuredSelector } from 'reselect';
4 |
5 | import { cancelTopLevelEntityCreate } from 'store/actions/sector.actions';
6 | import { generateEntity } from 'store/actions/entity.actions';
7 | import { topLevelEntityModalOpen } from 'store/selectors/entity.selectors';
8 | import {
9 | customTagSelector,
10 | topLevelKeySelector,
11 | currentSectorSelector,
12 | } from 'store/selectors/base.selectors';
13 |
14 | import TopLevelEntityModal from './top-level-entity-modal';
15 |
16 | const mapStateToProps = createStructuredSelector({
17 | topLevelKey: topLevelKeySelector,
18 | currentSector: currentSectorSelector,
19 | isOpen: topLevelEntityModalOpen,
20 | customTags: customTagSelector,
21 | });
22 |
23 | const mapDispatchToProps = (dispatch, props) => ({
24 | cancelTopLevelEntityCreate: () => dispatch(cancelTopLevelEntityCreate()),
25 | generateEntity: (entity, parameters) =>
26 | dispatch(generateEntity(entity, parameters, props.intl)),
27 | });
28 |
29 | export default injectIntl(
30 | connect(mapStateToProps, mapDispatchToProps)(TopLevelEntityModal),
31 | );
32 |
--------------------------------------------------------------------------------
/src/components/top-level-entity-modal/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .TopLevelEntityModal-Type {
4 | flex: 1;
5 | margin-right: 0.25rem;
6 | }
7 |
8 | .TopLevelEntityModal-Name {
9 | flex: 1;
10 | margin-left: 0.25rem;
11 | }
12 |
13 | .TopLevelEntityModal-Planet {
14 | margin-bottom: 0.3rem;
15 | }
16 |
17 | .TopLevelEntityModal-Help {
18 | margin-bottom: 0.1rem;
19 | }
20 |
21 | .TopLevelEntityModal-Delete {
22 | cursor: pointer;
23 | margin-right: 0.5rem;
24 | transition: all 0.2s;
25 |
26 | &:hover {
27 | color: $light3;
28 | }
29 | }
30 |
31 | .TopLevelEntityModal-Add {
32 | cursor: pointer;
33 | margin-top: 0.5rem;
34 | transition: all 0.2s;
35 | font-size: 0.9rem;
36 |
37 | &:hover {
38 | color: $light3;
39 | }
40 | }
41 |
42 | .TopLevelEntityModal-Generate {
43 | margin-left: 0.5rem;
44 | margin-right: 0;
45 | }
46 |
47 | .TopLevelEntityModal-Plus {
48 | margin-right: 0.6rem;
49 | }
50 |
51 | .TopLevelEntityModal-Input {
52 | flex: 1;
53 |
54 | &--padded {
55 | margin-right: 28px;
56 | }
57 | }
58 |
59 | .TopLevelEntityModal-Error {
60 | color: $error;
61 | margin-top: 1rem;
62 | font-size: 0.9rem;
63 | text-align: center;
64 | }
65 |
66 | .TopLevelEntityModal-EmptyText {
67 | text-align: center;
68 | }
69 |
--------------------------------------------------------------------------------
/src/constants/asteroid-base/occupation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.occupation',
3 | key: 'occupation',
4 | attributes: {
5 | sectarians: {
6 | key: 'sectarians',
7 | name: 'entity.asteroidBase.occupation.sectarians',
8 | },
9 | rebels: {
10 | key: 'rebels',
11 | name: 'entity.asteroidBase.occupation.rebels',
12 | },
13 | miners: {
14 | key: 'miners',
15 | name: 'entity.asteroidBase.occupation.miners',
16 | },
17 | prospectors: {
18 | key: 'prospectors',
19 | name: 'entity.asteroidBase.occupation.prospectors',
20 | },
21 | pirates: {
22 | key: 'pirates',
23 | name: 'entity.asteroidBase.occupation.pirates',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/asteroid-base/situation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.situation',
3 | key: 'situation',
4 | attributes: {
5 | life: {
6 | key: 'life',
7 | name: 'entity.asteroidBase.situation.life',
8 | },
9 | asteroid: {
10 | key: 'asteroid',
11 | name: 'entity.asteroidBase.situation.asteroid',
12 | },
13 | nasty: {
14 | key: 'nasty',
15 | name: 'entity.asteroidBase.situation.nasty',
16 | },
17 | fighting: {
18 | key: 'fighting',
19 | name: 'entity.asteroidBase.situation.fighting',
20 | },
21 | priceless: {
22 | key: 'priceless',
23 | name: 'entity.asteroidBase.situation.priceless',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/asteroid-belt/occupation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.occupation',
3 | key: 'occupation',
4 | attributes: {
5 | mine: {
6 | key: 'mine',
7 | name: 'entity.asteroidBelt.occupation.mine',
8 | },
9 | drones: {
10 | key: 'drones',
11 | name: 'entity.asteroidBelt.occupation.drones',
12 | },
13 | base: {
14 | key: 'base',
15 | name: 'entity.asteroidBelt.occupation.base',
16 | },
17 | pirates: {
18 | key: 'pirates',
19 | name: 'entity.asteroidBelt.occupation.pirates',
20 | },
21 | patrol: {
22 | key: 'patrol',
23 | name: 'entity.asteroidBelt.occupation.patrol',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/asteroid-belt/situation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.situation',
3 | key: 'situation',
4 | attributes: {
5 | rock: {
6 | key: 'rock',
7 | name: 'entity.asteroidBelt.situation.rock',
8 | },
9 | spy: {
10 | key: 'spy',
11 | name: 'entity.asteroidBelt.situation.spy',
12 | },
13 | minerals: {
14 | key: 'minerals',
15 | name: 'entity.asteroidBelt.situation.minerals',
16 | },
17 | ruins: {
18 | key: 'ruins',
19 | name: 'entity.asteroidBelt.situation.ruins',
20 | },
21 | war: {
22 | key: 'war',
23 | name: 'entity.asteroidBelt.situation.war',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/atmosphere.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'atmosphere',
3 | key: 'atmosphere',
4 | attributes: {
5 | corrosive: {
6 | key: 'corrosive',
7 | name: 'atmosphere.corrosive',
8 | },
9 | inert: {
10 | key: 'inert',
11 | name: 'atmosphere.inert',
12 | },
13 | airlessThin: {
14 | key: 'airlessThin',
15 | name: 'atmosphere.airlessThin',
16 | },
17 | breathable: {
18 | key: 'breathable',
19 | name: 'atmosphere.breathable',
20 | },
21 | thick: {
22 | key: 'thick',
23 | name: 'atmosphere.thick',
24 | },
25 | invasive: {
26 | key: 'invasive',
27 | name: 'atmosphere.invasive',
28 | },
29 | corrosiveInvasive: {
30 | key: 'corrosiveInvasive',
31 | name: 'atmosphere.corrosiveInvasive',
32 | },
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/src/constants/biosphere.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'biosphere',
3 | key: 'biosphere',
4 | attributes: {
5 | remnant: {
6 | key: 'remnant',
7 | name: 'biosphere.remnant',
8 | },
9 | microbial: {
10 | key: 'microbial',
11 | name: 'biosphere.microbial',
12 | },
13 | none: {
14 | key: 'none',
15 | name: 'biosphere.none',
16 | },
17 | humanMiscible: {
18 | key: 'humanMiscible',
19 | name: 'biosphere.humanMiscible',
20 | },
21 | immiscible: {
22 | key: 'immiscible',
23 | name: 'biosphere.immiscible',
24 | },
25 | hybrid: {
26 | key: 'hybrid',
27 | name: 'biosphere.hybrid',
28 | },
29 | engineered: {
30 | key: 'engineered',
31 | name: 'biosphere.engineered',
32 | },
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/src/constants/defaults.js:
--------------------------------------------------------------------------------
1 | export const ROWS = 10;
2 | export const COLUMNS = 8;
3 | export const SECTOR_LIMIT = 10;
4 | export const MIN_DIMENSION = 5;
5 | export const MAX_DIMENSION = 20;
6 | export const LAYER_NAME_LENGTH = 40;
7 | export const DEFAULT_HEX_WIDTH = 50; // Hex width when not rendering sector
8 | export const HEX_PADDING = 2; // Pixels between hexes
9 | export const EXTRA_HEXES = 1; // Extra hexes around canvas edges
10 | export const PIXEL_BUFFER = 75; // Pixel buffer between the sector and window
11 | export const MAX_HEXES = 1250; // Number of hexagons to render before this generator is short circuited
12 | export const TARGET_COLOR_WIDTH = 11;
13 |
--------------------------------------------------------------------------------
/src/constants/elements.js:
--------------------------------------------------------------------------------
1 | const faction = {
2 | key: 'faction',
3 | };
4 |
5 | export default {
6 | faction,
7 | };
8 |
--------------------------------------------------------------------------------
/src/constants/export-types.js:
--------------------------------------------------------------------------------
1 | const ExportTypes = {
2 | condensed: {
3 | key: 'condensed',
4 | description: 'misc.printEntitiesTable',
5 | },
6 | expanded: {
7 | key: 'expanded',
8 | description: 'misc.oneEntity',
9 | },
10 | image: {
11 | key: 'image',
12 | description: 'misc.imageExport',
13 | },
14 | json: {
15 | key: 'json',
16 | description: 'misc.jsonDescription',
17 | },
18 | };
19 |
20 | export default ExportTypes;
21 |
--------------------------------------------------------------------------------
/src/constants/gas-giant-mine/occupation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.occupation',
3 | key: 'occupation',
4 | attributes: {
5 | serfs: {
6 | key: 'serfs',
7 | name: 'entity.gasGiantMine.occupation.serfs',
8 | },
9 | robots: {
10 | key: 'robots',
11 | name: 'entity.gasGiantMine.occupation.robots',
12 | },
13 | alien: {
14 | key: 'alien',
15 | name: 'entity.gasGiantMine.occupation.alien',
16 | },
17 | mine: {
18 | key: 'mine',
19 | name: 'entity.gasGiantMine.occupation.mine',
20 | },
21 | group: {
22 | key: 'group',
23 | name: 'entity.gasGiantMine.occupation.group',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/gas-giant-mine/situation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.situation',
3 | key: 'situation',
4 | attributes: {
5 | things: {
6 | key: 'things',
7 | name: 'entity.gasGiantMine.situation.things',
8 | },
9 | supplies: {
10 | key: 'supplies',
11 | name: 'entity.gasGiantMine.situation.supplies',
12 | },
13 | revolt: {
14 | key: 'revolt',
15 | name: 'entity.gasGiantMine.situation.revolt',
16 | },
17 | pirates: {
18 | key: 'pirates',
19 | name: 'entity.gasGiantMine.situation.pirates',
20 | },
21 | remnants: {
22 | key: 'remnants',
23 | name: 'entity.gasGiantMine.situation.remnants',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/language/greek-letters.json:
--------------------------------------------------------------------------------
1 | [
2 | "Alpha",
3 | "Beta",
4 | "Gamma",
5 | "Delta",
6 | "Epsilon",
7 | "Zeta",
8 | "Eta",
9 | "Theta",
10 | "Iota",
11 | "Kappa",
12 | "Lambda",
13 | "Mu",
14 | "Nu",
15 | "Xi",
16 | "Omicron",
17 | "Pi",
18 | "Rho",
19 | "Sigma",
20 | "Tau",
21 | "Upsilon",
22 | "Phi",
23 | "Chi",
24 | "Psi",
25 | "Omega"
26 | ]
27 |
--------------------------------------------------------------------------------
/src/constants/moon-base/occupation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.occupation',
3 | key: 'occupation',
4 | attributes: {
5 | researchers: {
6 | key: 'researchers',
7 | name: 'entity.moonBase.occupation.researchers',
8 | },
9 | genius: {
10 | key: 'genius',
11 | name: 'entity.moonBase.occupation.genius',
12 | },
13 | colony: {
14 | key: 'colony',
15 | name: 'entity.moonBase.occupation.colony',
16 | },
17 | military: {
18 | key: 'military',
19 | name: 'entity.moonBase.occupation.military',
20 | },
21 | miners: {
22 | key: 'miners',
23 | name: 'entity.moonBase.occupation.miners',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/moon-base/situation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.situation',
3 | key: 'situation',
4 | attributes: {
5 | dark: {
6 | key: 'dark',
7 | name: 'entity.moonBase.situation.dark',
8 | },
9 | criminals: {
10 | key: 'criminals',
11 | name: 'entity.moonBase.situation.criminals',
12 | },
13 | plague: {
14 | key: 'plague',
15 | name: 'entity.moonBase.situation.plague',
16 | },
17 | supplies: {
18 | key: 'supplies',
19 | name: 'entity.moonBase.situation.supplies',
20 | },
21 | rich: {
22 | key: 'rich',
23 | name: 'entity.moonBase.situation.rich',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/orbital-ruin/occupation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.occupation',
3 | key: 'occupation',
4 | attributes: {
5 | robots: {
6 | key: 'robots',
7 | name: 'entity.orbitalRuin.occupation.robots',
8 | },
9 | scavengers: {
10 | key: 'scavengers',
11 | name: 'entity.orbitalRuin.occupation.scavengers',
12 | },
13 | researchers: {
14 | key: 'researchers',
15 | name: 'entity.orbitalRuin.occupation.researchers',
16 | },
17 | military: {
18 | key: 'military',
19 | name: 'entity.orbitalRuin.occupation.military',
20 | },
21 | heirs: {
22 | key: 'heirs',
23 | name: 'entity.orbitalRuin.occupation.heirs',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/orbital-ruin/situation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.situation',
3 | key: 'situation',
4 | attributes: {
5 | awakening: {
6 | key: 'awakening',
7 | name: 'entity.orbitalRuin.situation.awakening',
8 | },
9 | tech: {
10 | key: 'tech',
11 | name: 'entity.orbitalRuin.situation.tech',
12 | },
13 | calamity: {
14 | key: 'calamity',
15 | name: 'entity.orbitalRuin.situation.calamity',
16 | },
17 | secret: {
18 | key: 'secret',
19 | name: 'entity.orbitalRuin.situation.secret',
20 | },
21 | interlopers: {
22 | key: 'interlopers',
23 | name: 'entity.orbitalRuin.situation.interlopers',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/population.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'population',
3 | key: 'population',
4 | attributes: {
5 | failed: {
6 | key: 'failed',
7 | name: 'population.failed',
8 | },
9 | outpost: {
10 | key: 'outpost',
11 | name: 'population.outpost',
12 | },
13 | lessThanMillion: {
14 | key: 'lessThanMillion',
15 | name: 'population.lessThanMillion',
16 | },
17 | severalMillion: {
18 | key: 'severalMillion',
19 | name: 'population.severalMillion',
20 | },
21 | hundredsOfMillions: {
22 | key: 'hundredsOfMillions',
23 | name: 'population.hundredsOfMillions',
24 | },
25 | billions: {
26 | key: 'billions',
27 | name: 'population.billions',
28 | },
29 | alien: {
30 | key: 'alien',
31 | name: 'population.alien',
32 | },
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/src/constants/refueling-station/occupation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.occupation',
3 | key: 'occupation',
4 | attributes: {
5 | hermit: {
6 | key: 'hermit',
7 | name: 'entity.refuelingStation.occupation.hermit',
8 | },
9 | fun: {
10 | key: 'fun',
11 | name: 'entity.refuelingStation.occupation.fun',
12 | },
13 | minions: {
14 | key: 'minions',
15 | name: 'entity.refuelingStation.occupation.minions',
16 | },
17 | religious: {
18 | key: 'religious',
19 | name: 'entity.refuelingStation.occupation.religious',
20 | },
21 | vendors: {
22 | key: 'vendors',
23 | name: 'entity.refuelingStation.occupation.vendors',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/refueling-station/situation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.situation',
3 | key: 'situation',
4 | attributes: {
5 | distress: {
6 | key: 'distress',
7 | name: 'entity.refuelingStation.situation.distress',
8 | },
9 | pirates: {
10 | key: 'pirates',
11 | name: 'entity.refuelingStation.situation.pirates',
12 | },
13 | agents: {
14 | key: 'agents',
15 | name: 'entity.refuelingStation.situation.agents',
16 | },
17 | saboteurs: {
18 | key: 'saboteurs',
19 | name: 'entity.refuelingStation.situation.saboteurs',
20 | },
21 | alien: {
22 | key: 'alien',
23 | name: 'entity.refuelingStation.situation.alien',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/research-base/occupation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.occupation',
3 | key: 'occupation',
4 | attributes: {
5 | experminents: {
6 | key: 'experminents',
7 | name: 'entity.researchBase.occupation.experminents',
8 | },
9 | scientists: {
10 | key: 'scientists',
11 | name: 'entity.researchBase.occupation.scientists',
12 | },
13 | researchers: {
14 | key: 'researchers',
15 | name: 'entity.researchBase.occupation.researchers',
16 | },
17 | secret: {
18 | key: 'secret',
19 | name: 'entity.researchBase.occupation.secret',
20 | },
21 | alien: {
22 | key: 'alien',
23 | name: 'entity.researchBase.occupation.alien',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/research-base/situation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.situation',
3 | key: 'situation',
4 | attributes: {
5 | research: {
6 | key: 'research',
7 | name: 'entity.researchBase.situation.research',
8 | },
9 | immoral: {
10 | key: 'immoral',
11 | name: 'entity.researchBase.situation.immoral',
12 | },
13 | outsiders: {
14 | key: 'outsiders',
15 | name: 'entity.researchBase.situation.outsiders',
16 | },
17 | monsters: {
18 | key: 'monsters',
19 | name: 'entity.researchBase.situation.monsters',
20 | },
21 | tech: {
22 | key: 'tech',
23 | name: 'entity.researchBase.situation.tech',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/space-station/occupation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.occupation',
3 | key: 'occupation',
4 | attributes: {
5 | odd: {
6 | key: 'odd',
7 | name: 'entity.spaceStation.occupation.odd',
8 | },
9 | corpses: {
10 | key: 'corpses',
11 | name: 'entity.spaceStation.occupation.corpses',
12 | },
13 | observers: {
14 | key: 'observers',
15 | name: 'entity.spaceStation.occupation.observers',
16 | },
17 | minions: {
18 | key: 'minions',
19 | name: 'entity.spaceStation.occupation.minions',
20 | },
21 | scientist: {
22 | key: 'scientist',
23 | name: 'entity.spaceStation.occupation.scientist',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/space-station/situation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'misc.situation',
3 | key: 'situation',
4 | attributes: {
5 | systems: {
6 | key: 'systems',
7 | name: 'entity.spaceStation.situation.systems',
8 | },
9 | sabotage: {
10 | key: 'sabotage',
11 | name: 'entity.spaceStation.situation.sabotage',
12 | },
13 | market: {
14 | key: 'market',
15 | name: 'entity.spaceStation.situation.market',
16 | },
17 | pretech: {
18 | key: 'pretech',
19 | name: 'entity.spaceStation.situation.pretech',
20 | },
21 | pirates: {
22 | key: 'pirates',
23 | name: 'entity.spaceStation.situation.pirates',
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants/tech-level.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'techLevel',
3 | key: 'techLevel',
4 | attributes: {
5 | TL0: { key: 'TL0', name: 'techLevel.0' },
6 | TL1: { key: 'TL1', name: 'techLevel.1' },
7 | TL2: { key: 'TL2', name: 'techLevel.2' },
8 | TL3: { key: 'TL3', name: 'techLevel.3' },
9 | TL4: { key: 'TL4', name: 'techLevel.4' },
10 | 'TL4+': { key: 'TL4+', name: 'techLevel.4+' },
11 | TL5: { key: 'TL5', name: 'techLevel.5' },
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/src/constants/temperature.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'temperature',
3 | key: 'temperature',
4 | attributes: {
5 | frozen: {
6 | key: 'frozen',
7 | name: 'temperature.frozen',
8 | },
9 | cold: {
10 | key: 'cold',
11 | name: 'temperature.cold',
12 | },
13 | variableCold: {
14 | key: 'variableCold',
15 | name: 'temperature.variableCold',
16 | },
17 | temperate: {
18 | key: 'temperate',
19 | name: 'temperature.temperate',
20 | },
21 | variableWarm: {
22 | key: 'variableWarm',
23 | name: 'temperature.variableWarm',
24 | },
25 | warm: {
26 | key: 'warm',
27 | name: 'temperature.warm',
28 | },
29 | burning: {
30 | key: 'burning',
31 | name: 'temperature.burning',
32 | },
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/src/featured.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Far Verona's Acheron Rho",
4 | "sector": "m11ZXBOt6xiJGo21EKio",
5 | "website": "http://far-verona.wikia.com/wiki/Far_Verona_Wiki",
6 | "username": "@OutrO"
7 | }
8 | ]
9 |
--------------------------------------------------------------------------------
/src/lang/index.js:
--------------------------------------------------------------------------------
1 | import de from './de';
2 | import en from './en';
3 | import fr from './fr';
4 | import he from './he';
5 | import sv from './sv';
6 |
7 | export default { de, en, fr, he, sv };
8 |
--------------------------------------------------------------------------------
/src/lang/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "atmosphere": "Атмосфера",
3 | "atmosphere.corrosive": "Едкая",
4 | "atmosphere.inert": "Инертный газ",
5 | "atmosphere.airlessThin": "Разряженная или отсутствует",
6 | "atmosphere.breathable": "Пригодная для дыхания",
7 | "atmosphere.thick": "Плотная, позволяет дышать с респиратором",
8 | "atmosphere.invasive": "Инвазивная, токсичная",
9 | "atmosphere.corrosiveInvasive": "Едкая и инвазивная",
10 | "temperature": "Климат",
11 | "temperature.frozen": "Экстремально холодный",
12 | "temperature.variableCold": "Переменный, от холодного до умеренного",
13 | "temperature.cold": "Холодный",
14 | "temperature.temperate": "Умеренный",
15 | "temperature.warm": "Теплый",
16 | "temperature.variableWarm": "Переменный, от теплого до умеренного",
17 | "temperature.burning": "Экстремально жаркий",
18 | "population": "Население",
19 | "population.failed": "Неудачная колонизация",
20 | "population.outpost": "Аванпост",
21 | "population.lessThanMillion": "Менее миллиона",
22 | "population.severalMillion": "Несколько миллионов",
23 | "population.hundredsOfMillions": "Сотни миллионов",
24 | "population.billions": "Миллиарды",
25 | "population.alien": "Ксеноцивилизация",
26 | "biosphere": "Биосфера",
27 | "biosphere.remnant": "Остаточная",
28 | "biosphere.microbial": "Микроорганизмы",
29 | "biosphere.none": "Отсутствует",
30 | "biosphere.humanMiscible": "Совместимая с земной",
31 | "biosphere.immiscible": "Несовместимая с земной",
32 | "biosphere.hybrid": "Гибридная",
33 | "biosphere.engineered": "Искусственнная",
34 | "techLevel": "Технологический уровень",
35 | "techLevel.0": "ТУ0",
36 | "techLevel.1": "ТУ1",
37 | "techLevel.2": "ТУ2",
38 | "techLevel.3": "ТУ3",
39 | "techLevel.4": "ТУ4",
40 | "techLevel.4+": "ТУ5",
41 | "techLevel.5": "ТУ6"
42 | }
43 |
--------------------------------------------------------------------------------
/src/lang/sr.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/src/primitives/__tests__/spinner.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Spinner from '../other/spinner';
5 |
6 | describe('Spinner', () => {
7 | it('should render the 12 spinning dots', () => {
8 | const spinner = shallow();
9 | expect(spinner.hasClass('Spinner')).toBeTruthy();
10 | expect(spinner.children()).toHaveLength(12);
11 | });
12 |
13 | it('should render with a default size of 30', () => {
14 | const spinner = shallow();
15 | expect(spinner.prop('style')).toMatchObject({ width: 30, height: 30 });
16 | });
17 |
18 | it('should render with a custom size', () => {
19 | const size = 50;
20 | const spinner = shallow();
21 | expect(spinner.prop('style')).toMatchObject({ width: size, height: size });
22 | });
23 |
24 | it('should default to light theme', () => {
25 | const spinner = shallow();
26 | expect(spinner.hasClass('Spinner--dark')).toBeFalsy();
27 | });
28 |
29 | it('should set dark theme appropriately', () => {
30 | const spinner = shallow();
31 | expect(spinner.hasClass('Spinner--dark')).toBeTruthy();
32 | });
33 |
34 | it('should pass through custom class name', () => {
35 | const className = 'test';
36 | const spinner = shallow();
37 | expect(spinner.hasClass(className)).toBeTruthy();
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/src/primitives/container/absolute-container/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import './style.scss';
6 |
7 | function AbsoluteContainer({ forwardedRef, children, className, ...props }) {
8 | return (
9 |
14 | {children}
15 |
16 | );
17 | }
18 |
19 | AbsoluteContainer.propTypes = {
20 | forwardedRef: PropTypes.func,
21 | children: PropTypes.node.isRequired,
22 | className: PropTypes.string,
23 | };
24 |
25 | AbsoluteContainer.defaultProps = {
26 | forwardedRef: null,
27 | className: null,
28 | };
29 |
30 | export default React.forwardRef((props, ref) => (
31 |
32 | ));
33 |
--------------------------------------------------------------------------------
/src/primitives/container/absolute-container/style.scss:
--------------------------------------------------------------------------------
1 | .AbsoluteContainer {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | right: 0;
6 | bottom: 0;
7 | }
8 |
--------------------------------------------------------------------------------
/src/primitives/container/content-container/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import FlexContainer from '../flex-container';
6 |
7 | import './style.scss';
8 |
9 | export default function ContentContainer(props) {
10 | const { children, className, ...rest } = props;
11 | return (
12 |
16 | {children}
17 |
18 | );
19 | }
20 |
21 | ContentContainer.propTypes = {
22 | children: PropTypes.node.isRequired,
23 | className: PropTypes.string,
24 | };
25 |
26 | ContentContainer.defaultProps = {
27 | className: null,
28 | };
29 |
--------------------------------------------------------------------------------
/src/primitives/container/content-container/style.scss:
--------------------------------------------------------------------------------
1 | .ContentContainer {
2 | height: 100%;
3 | overflow-y: auto;
4 | }
5 |
--------------------------------------------------------------------------------
/src/primitives/container/flex-container/style.scss:
--------------------------------------------------------------------------------
1 | .FlexContainer {
2 | display: flex;
3 | }
4 |
5 | .FlexContainer-Align {
6 | &--stretch {
7 | align-items: stretch;
8 | }
9 | &--center {
10 | align-items: center;
11 | }
12 | &--flexStart {
13 | align-items: flex-start;
14 | }
15 | &--flexEnd {
16 | align-items: flex-end;
17 | }
18 | &--baseline {
19 | align-items: baseline;
20 | }
21 | &--initial {
22 | align-items: initial;
23 | }
24 | &--inherit {
25 | align-items: inherit;
26 | }
27 | }
28 |
29 | .FlexContainer-Direction {
30 | &--row {
31 | flex-direction: row;
32 | }
33 | &--column {
34 | flex-direction: column;
35 | }
36 | }
37 |
38 | .FlexContainer-Justify {
39 | &--flexStart {
40 | justify-content: flex-start;
41 | }
42 | &--flexEnd {
43 | justify-content: flex-end;
44 | }
45 | &--center {
46 | justify-content: center;
47 | }
48 | &--spaceBetween {
49 | justify-content: space-between;
50 | }
51 | &--spaceAround {
52 | justify-content: space-around;
53 | }
54 | &--spaceEvenly {
55 | justify-content: space-evenly;
56 | }
57 | &--initial {
58 | justify-content: initial;
59 | }
60 | &--inherit {
61 | justify-content: inherit;
62 | }
63 | }
64 |
65 | .FlexContainer-Shrink {
66 | &--0 {
67 | flex-shrink: 0;
68 | }
69 | &--1 {
70 | flex-shrink: 1;
71 | }
72 | }
73 |
74 | .FlexContainer-Wrap {
75 | flex-wrap: wrap;
76 | }
77 |
78 | .FlexContainer-Scroll {
79 | overflow-y: auto;
80 | }
81 |
--------------------------------------------------------------------------------
/src/primitives/container/sidebar-container/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { FormattedMessage } from 'react-intl';
4 |
5 | import FlexContainer from 'primitives/container/flex-container';
6 | import ActionHeader from 'primitives/text/action-header';
7 | import ButtonLink from 'primitives/other/button-link';
8 |
9 | import './style.scss';
10 |
11 | export default function SidebarContainer({ title, actions, footer, children }) {
12 | return (
13 |
14 |
15 |
16 | {children}
17 |
18 | {footer || (
19 |
20 |
21 |
27 |
28 |
29 |
30 |
31 | )}
32 |
33 | );
34 | }
35 |
36 | SidebarContainer.propTypes = {
37 | title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
38 | actions: PropTypes.arrayOf(
39 | PropTypes.shape({
40 | key: PropTypes.string.isRequired,
41 | to: PropTypes.string,
42 | }),
43 | ),
44 | children: PropTypes.node.isRequired,
45 | footer: PropTypes.node,
46 | };
47 |
48 | SidebarContainer.defaultProps = {
49 | actions: [],
50 | footer: undefined,
51 | };
52 |
--------------------------------------------------------------------------------
/src/primitives/container/sidebar-container/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 | @import '~styles/constants';
3 |
4 | .SidebarContainer {
5 | background-color: $dark3;
6 | height: 100vh;
7 | width: $large-sidebar-width;
8 | }
9 |
10 | .SidebarContainer-Footer {
11 | padding: 0.6rem 0;
12 | border-top: 1px solid $dark2;
13 | }
14 |
15 | .SidebarContainer-Patreon {
16 | color: $cyber;
17 | }
18 |
19 | @media (max-width: 1200px) {
20 | .SidebarContainer {
21 | width: $small-sidebar-width;
22 | }
23 | }
24 |
25 | @media (max-width: 700px) {
26 | .SidebarContainer {
27 | width: 100%;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/primitives/container/sub-container/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import FlexContainer from '../flex-container';
6 |
7 | import './style.scss';
8 |
9 | export default function SubContainer(props) {
10 | const { children, className, noMargin, fullWidth, ...rest } = props;
11 | return (
12 |
19 | {children}
20 |
21 | );
22 | }
23 |
24 | SubContainer.propTypes = {
25 | children: PropTypes.node.isRequired,
26 | className: PropTypes.string,
27 | noMargin: PropTypes.bool,
28 | fullWidth: PropTypes.bool,
29 | };
30 |
31 | SubContainer.defaultProps = {
32 | className: null,
33 | noMargin: false,
34 | fullWidth: false,
35 | };
36 |
--------------------------------------------------------------------------------
/src/primitives/container/sub-container/style.scss:
--------------------------------------------------------------------------------
1 | .SubContainer {
2 | text-align: center;
3 |
4 | &--fullWidth { width: calc(100% - 1rem) }
5 | &--margin { margin: 0 1rem; }
6 | }
--------------------------------------------------------------------------------
/src/primitives/form/checkbox/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import FlexContainer from 'primitives/container/flex-container';
5 | import Input from 'primitives/form/input';
6 | import Label from 'primitives/form/label';
7 |
8 | import './style.scss';
9 |
10 | export default function Checkbox({ label, value, onChange, ...rest }) {
11 | return (
12 |
13 |
20 |
23 |
24 | );
25 | }
26 |
27 | Checkbox.propTypes = {
28 | label: PropTypes.string.isRequired,
29 | value: PropTypes.bool.isRequired,
30 | onChange: PropTypes.func.isRequired,
31 | };
32 |
--------------------------------------------------------------------------------
/src/primitives/form/checkbox/style.scss:
--------------------------------------------------------------------------------
1 | .Checkbox {
2 | margin-top: 1.2rem;
3 |
4 | & * {
5 | margin: 0;
6 | }
7 |
8 | & input {
9 | height: 1.3rem;
10 | margin-right: 1rem;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/primitives/form/color-picker/style.scss:
--------------------------------------------------------------------------------
1 | .ColorPicker-InputContainer {
2 | padding: 1px;
3 | padding-left: 15px;
4 | border-radius: 4px;
5 | margin-right: 0.2rem;
6 | }
7 |
8 | .ColorPicker-Input {
9 | border: none;
10 | border-top-right-radius: 4px;
11 | border-bottom-right-radius: 4px;
12 | margin: 0;
13 | padding: 9.5px 6px;
14 | width: 100%;
15 |
16 | &:focus {
17 | border: none;
18 | outline: none;
19 | }
20 | }
21 |
22 | .ColorPicker-Swatch {
23 | margin: 0 0.2rem;
24 |
25 | &:last-of-type {
26 | margin-right: 0;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/primitives/form/deletable-row/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { RotateCcw, X } from 'react-feather';
4 |
5 | import FlexContainer from 'primitives/container/flex-container';
6 |
7 | import './style.scss';
8 |
9 | export default function DeletableRow({
10 | onAction,
11 | undoDelete,
12 | children,
13 | ...props
14 | }) {
15 | const Icon = undoDelete ? RotateCcw : X;
16 | return (
17 |
18 |
19 | {children}
20 |
21 | );
22 | }
23 |
24 | DeletableRow.propTypes = {
25 | onAction: PropTypes.func.isRequired,
26 | undoDelete: PropTypes.bool,
27 | children: PropTypes.node.isRequired,
28 | };
29 |
30 | DeletableRow.defaultProps = {
31 | undoDelete: false,
32 | };
33 |
--------------------------------------------------------------------------------
/src/primitives/form/deletable-row/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .DeletableRow-Icon {
4 | color: $light2;
5 | cursor: pointer;
6 | margin-right: 0.5rem;
7 | transition: all 0.2s;
8 |
9 | &:hover {
10 | color: $light3;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/primitives/form/dropdown/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 | import Select, { Creatable } from 'react-select';
5 |
6 | import './style.scss';
7 |
8 | export default function Dropdown({
9 | onItemClick,
10 | icon,
11 | dropUp,
12 | allowCreate,
13 | wrapperClassName,
14 | ...rest
15 | }) {
16 | const newProps = {
17 | promptTextCreator: label => label,
18 | ...rest,
19 | };
20 | const DropdownComponent = allowCreate ? Creatable : Select;
21 | const Icon = icon;
22 | return (
23 |
24 |
32 | {icon && (
33 |
34 |
35 |
36 | )}
37 |
38 | );
39 | }
40 |
41 | Dropdown.propTypes = {
42 | ...Select.propTypes,
43 | dropUp: PropTypes.bool,
44 | allowCreate: PropTypes.bool,
45 | wrapperClassName: PropTypes.string,
46 | onItemClick: PropTypes.func,
47 | };
48 |
49 | Dropdown.defaultProps = {
50 | dropUp: false,
51 | allowCreate: false,
52 | wrapperClassName: null,
53 | onItemClick: () => {},
54 | };
55 |
--------------------------------------------------------------------------------
/src/primitives/form/icon-input/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import Input from 'primitives/form/input';
6 |
7 | import './style.scss';
8 |
9 | const defaultFunc = () => {};
10 |
11 | export default function IconInput({
12 | icon,
13 | onIconClick,
14 | wrapperClassName,
15 | className,
16 | ...rest
17 | }) {
18 | const Icon = icon;
19 | return (
20 |
21 |
28 |
29 |
30 | );
31 | }
32 |
33 | IconInput.propTypes = {
34 | className: PropTypes.string,
35 | wrapperClassName: PropTypes.string,
36 | icon: PropTypes.func.isRequired,
37 | onIconClick: PropTypes.func,
38 | };
39 |
40 | IconInput.defaultProps = {
41 | className: null,
42 | wrapperClassName: null,
43 | onIconClick: null,
44 | };
45 |
--------------------------------------------------------------------------------
/src/primitives/form/icon-input/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .IconInput {
4 | flex: 1;
5 | position: relative;
6 | width: 100%;
7 | }
8 |
9 | .IconInput-Input {
10 | padding-left: 40px;
11 | }
12 |
13 | .IconInput-Icon {
14 | color: $dark1;
15 | position: absolute;
16 | left: 12px;
17 | top: 11px;
18 | transition: all 0.2s;
19 | cursor: pointer;
20 |
21 | &:hover {
22 | color: $dark3;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/primitives/form/input/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import ColorPicker from 'primitives/form/color-picker';
6 | import Dropdown from 'primitives/form/dropdown';
7 |
8 | import './style.scss';
9 |
10 | export default function Input({ className, width, error, type, ...rest }) {
11 | let style = {};
12 | if (width) {
13 | style = { width };
14 | }
15 | if (type === 'textarea') {
16 | return (
17 |
24 | );
25 | }
26 | if (type === 'dropdown') {
27 | return (
28 |
33 | );
34 | }
35 | if (type === 'color') {
36 | return ;
37 | }
38 | return (
39 |
47 | );
48 | }
49 |
50 | Input.propTypes = {
51 | className: PropTypes.string,
52 | type: PropTypes.string,
53 | width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
54 | error: PropTypes.bool,
55 | };
56 |
57 | Input.defaultProps = {
58 | className: null,
59 | type: 'text',
60 | width: null,
61 | error: false,
62 | };
63 |
--------------------------------------------------------------------------------
/src/primitives/form/input/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .Input-Dropdown {
4 | width: 100%;
5 | }
6 |
7 | .Input-Textarea {
8 | display: inline-block;
9 | border: solid 1px transparent;
10 | padding: 10px;
11 | color: $dark4;
12 | background-color: $light4;
13 | font-size: 11px;
14 | font-weight: 600;
15 | line-height: 1rem;
16 | letter-spacing: 0.1rem;
17 | text-decoration: none;
18 | border-radius: 4px;
19 | box-sizing: border-box;
20 | font-weight: 400;
21 | resize: vertical;
22 | transition: border-color 0.2s;
23 | outline: none;
24 | width: 100%;
25 |
26 | &--error {
27 | border-color: red;
28 | }
29 |
30 | &:disabled {
31 | background-color: $light2;
32 | }
33 | }
34 |
35 | .Input {
36 | display: inline-block;
37 | border: solid 1px transparent;
38 | height: 38px;
39 | padding: 0 10px;
40 | color: $dark4;
41 | background-color: $light4;
42 | font-size: 11px;
43 | font-weight: 600;
44 | line-height: 38px;
45 | letter-spacing: 0.1rem;
46 | text-decoration: none;
47 | white-space: nowrap;
48 | border-radius: 4px;
49 | box-sizing: border-box;
50 | font-weight: 400;
51 | transition: all 0.2s;
52 | outline: none;
53 | width: 100%;
54 |
55 | &[type='checkbox'] {
56 | width: 20px;
57 | }
58 |
59 | &--error {
60 | border-color: red;
61 | }
62 |
63 | &:disabled {
64 | background-color: $light2;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/primitives/form/label/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import './style.scss';
6 |
7 | export default function Label(props) {
8 | const { htmlFor, className, noPadding, ...rest } = props;
9 | return (
10 |
17 | );
18 | }
19 |
20 | Label.propTypes = {
21 | className: PropTypes.string,
22 | noPadding: PropTypes.bool,
23 | htmlFor: PropTypes.string,
24 | };
25 |
26 | Label.defaultProps = {
27 | className: null,
28 | noPadding: false,
29 | htmlFor: 'input',
30 | };
31 |
--------------------------------------------------------------------------------
/src/primitives/form/label/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .Label {
4 | color: $light4;
5 | display: block;
6 | font-weight: 600;
7 | margin-top: 1.2rem;
8 | margin-bottom: 0.3rem;
9 |
10 | &--noPadding {
11 | margin-top: 0rem;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/primitives/form/labeled-input/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import LabeledItem from 'primitives/other/labeled-item';
6 | import IconInput from 'primitives/form/icon-input';
7 | import Input from 'primitives/form/input';
8 |
9 | import './style.scss';
10 |
11 | export default function LabeledInput({
12 | label,
13 | labelWidth,
14 | type,
15 | icon,
16 | checkboxes,
17 | isVertical,
18 | isRequired,
19 | className,
20 | ...rest
21 | }) {
22 | const InputComponent = icon && type !== 'dropdown' ? IconInput : Input;
23 | return (
24 |
31 |
32 | {React.Children.map(checkboxes, checkbox =>
33 | React.cloneElement(checkbox, {
34 | className: classNames(
35 | checkbox.props.className,
36 | 'LabeledInput-Checkbox',
37 | ),
38 | }),
39 | )}
40 |
41 | );
42 | }
43 |
44 | LabeledInput.propTypes = {
45 | label: PropTypes.node.isRequired,
46 | labelWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
47 | type: PropTypes.string,
48 | icon: PropTypes.func,
49 | checkboxes: PropTypes.arrayOf(PropTypes.node),
50 | isVertical: PropTypes.bool,
51 | isRequired: PropTypes.bool,
52 | className: PropTypes.string,
53 | };
54 |
55 | LabeledInput.defaultProps = {
56 | labelWidth: undefined,
57 | type: 'text',
58 | icon: undefined,
59 | checkboxes: [],
60 | isVertical: false,
61 | isRequired: false,
62 | className: undefined,
63 | };
64 |
--------------------------------------------------------------------------------
/src/primitives/form/labeled-input/style.scss:
--------------------------------------------------------------------------------
1 | .LabeledInput-Checkbox {
2 | margin: 0 0 0 0.5rem;
3 | }
4 |
--------------------------------------------------------------------------------
/src/primitives/modal/confirm-modal/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
4 |
5 | import Modal from 'primitives/modal/modal';
6 | import Button from 'primitives/other/button';
7 |
8 | function ConfirmModal({ isOpen, onConfirm, onCancel, children, intl }) {
9 | return (
10 |
17 |
18 | ,
19 | ]}
20 | >
21 | {children || }
22 |
23 | );
24 | }
25 |
26 | ConfirmModal.propTypes = {
27 | isOpen: PropTypes.bool.isRequired,
28 | onConfirm: PropTypes.func,
29 | onCancel: PropTypes.func,
30 | children: PropTypes.node,
31 | intl: intlShape.isRequired,
32 | };
33 |
34 | ConfirmModal.defaultProps = {
35 | onConfirm: () => {},
36 | onCancel: () => {},
37 | children: undefined,
38 | };
39 |
40 | export default injectIntl(ConfirmModal);
41 |
--------------------------------------------------------------------------------
/src/primitives/modal/modal/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .Modal {
4 | background: $dark3;
5 | border-radius: 0.2rem;
6 | color: $light2;
7 | outline: none;
8 | overflow: auto;
9 | -webkit-overflow-scrolling: touch;
10 | }
11 |
12 | .Modal-Overlay {
13 | align-items: center;
14 | background-color: rgba(200, 200, 200, 0.3);
15 | bottom: 0;
16 | display: flex;
17 | justify-content: center;
18 | left: 0;
19 | position: fixed;
20 | right: 0;
21 | top: 0;
22 | z-index: 1000;
23 | }
24 |
25 | .Modal-InnerContainer {
26 | height: 100%;
27 | }
28 |
29 | .Modal-Title {
30 | margin: 0;
31 | }
32 |
33 | .Modal-Close {
34 | cursor: pointer;
35 | transition: all 0.2s;
36 |
37 | &:hover {
38 | color: $light3;
39 | }
40 | }
41 |
42 | .Modal-Section {
43 | padding: 1rem;
44 | }
45 |
46 | .Modal-Header {
47 | }
48 |
49 | .Modal-Content {
50 | background-color: $dark2;
51 | flex: 1;
52 | padding: 1.5rem 1rem;
53 | }
54 |
55 | .Modal-Footer {
56 | }
57 |
58 | .Model-FooterButton {
59 | margin-right: 0;
60 | }
61 |
--------------------------------------------------------------------------------
/src/primitives/other/basic-link/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import ButtonLink from 'primitives/other/button-link';
6 |
7 | import './style.scss';
8 |
9 | export default function BasicLink({ className, ...rest }) {
10 | return (
11 |
16 | );
17 | }
18 |
19 | BasicLink.propTypes = {
20 | className: PropTypes.string,
21 | };
22 |
23 | BasicLink.defaultProps = {
24 | className: null,
25 | };
26 |
--------------------------------------------------------------------------------
/src/primitives/other/basic-link/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .BasicLink {
4 | border-bottom-color: $dark1;
5 | color: inherit;
6 | font-size: 0.9rem;
7 | height: auto;
8 | letter-spacing: 0;
9 | line-height: normal;
10 | margin: 0;
11 | padding: 0;
12 | }
13 |
--------------------------------------------------------------------------------
/src/primitives/other/button-link/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Link as RouterLink } from 'react-router-dom';
4 | import isExternal from 'is-url-external';
5 | import classNames from 'classnames';
6 |
7 | export default function ButtonLink(props) {
8 | const { className, minimal, to, ...rest } = props;
9 | const linkClass = classNames(className, {
10 | Button: !minimal, // Uses Button CSS
11 | 'Button-Minimal': minimal,
12 | });
13 | if (isExternal(to)) {
14 | return (
15 |
20 | );
21 | }
22 | return ;
23 | }
24 |
25 | ButtonLink.propTypes = {
26 | to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
27 | className: PropTypes.string,
28 | minimal: PropTypes.bool,
29 | };
30 |
31 | ButtonLink.defaultProps = {
32 | className: null,
33 | minimal: false,
34 | };
35 |
--------------------------------------------------------------------------------
/src/primitives/other/button/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import Spinner from 'primitives/other/spinner';
6 |
7 | import './style.scss';
8 |
9 | export default function Button({
10 | children,
11 | className,
12 | minimal,
13 | primary,
14 | skinny,
15 | loading,
16 | noMargin,
17 | ...rest
18 | }) {
19 | let loadingSpinner = null;
20 | if (loading) {
21 | loadingSpinner = (
22 |
23 | );
24 | }
25 | return (
26 |
44 | );
45 | }
46 |
47 | Button.propTypes = {
48 | children: PropTypes.node.isRequired,
49 | className: PropTypes.string,
50 | minimal: PropTypes.bool,
51 | primary: PropTypes.bool,
52 | skinny: PropTypes.bool,
53 | loading: PropTypes.bool,
54 | noMargin: PropTypes.bool,
55 | };
56 |
57 | Button.defaultProps = {
58 | className: null,
59 | minimal: false,
60 | primary: false,
61 | skinny: false,
62 | loading: false,
63 | noMargin: false,
64 | };
65 |
--------------------------------------------------------------------------------
/src/primitives/other/collapsible-table/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .CollapsibleTable-Child {
4 | color: $light1;
5 |
6 | .CollapsibleTable-Icon {
7 | cursor: default;
8 | }
9 | }
10 |
11 | tbody .CollapsibleTable-Icon {
12 | border-bottom-width: 0;
13 | }
14 |
15 | .CollapsibleTable-Icon {
16 | cursor: pointer;
17 | margin: auto;
18 | text-align: center;
19 | transition: color 0.2s linear;
20 | width: 1.5rem;
21 |
22 | svg {
23 | margin-bottom: 0.2rem;
24 | vertical-align: middle;
25 | }
26 |
27 | &--open {
28 | color: $primary;
29 |
30 | &:hover {
31 | color: $primary-alt;
32 | }
33 | }
34 |
35 | &--closed {
36 | color: $dark1;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/primitives/other/color-swatch/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import styles from './styles.module.scss';
6 |
7 | export default function ColorSwatch({
8 | className,
9 | color,
10 | size,
11 | hoverable,
12 | ...rest
13 | }) {
14 | return (
15 |
27 | );
28 | }
29 |
30 | ColorSwatch.propTypes = {
31 | className: PropTypes.string,
32 | color: PropTypes.string.isRequired,
33 | size: PropTypes.number,
34 | hoverable: PropTypes.bool,
35 | };
36 |
37 | ColorSwatch.defaultProps = {
38 | className: undefined,
39 | size: 36,
40 | hoverable: false,
41 | };
42 |
--------------------------------------------------------------------------------
/src/primitives/other/color-swatch/styles.module.scss:
--------------------------------------------------------------------------------
1 | .swatch--hoverable {
2 | transition: box-shadow 0.2s linear;
3 |
4 | &:hover {
5 | box-shadow: 1px 1px 3px 0 rgba(0, 0, 0, 0.9);
6 | cursor: pointer;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/primitives/other/item-row/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import FlexContainer from 'primitives/container/flex-container';
6 |
7 | import './style.scss';
8 |
9 | export default function ItemRow({ children, className, ...rest }) {
10 | return (
11 |
16 | {React.Children.map(
17 | children,
18 | item =>
19 | item &&
20 | React.cloneElement(item, {
21 | className: classNames(item.props.className, 'ItemRow-Item'),
22 | }),
23 | )}
24 |
25 | );
26 | }
27 |
28 | ItemRow.propTypes = {
29 | children: PropTypes.arrayOf(PropTypes.node).isRequired,
30 | className: PropTypes.string,
31 | };
32 |
33 | ItemRow.defaultProps = {
34 | className: undefined,
35 | };
36 |
--------------------------------------------------------------------------------
/src/primitives/other/item-row/style.scss:
--------------------------------------------------------------------------------
1 | .ItemRow {
2 | width: 100%;
3 | }
4 |
5 | .ItemRow-Item {
6 | margin-left: 0.5rem;
7 | margin-right: 0.5rem;
8 |
9 | &:first-of-type {
10 | margin-left: 0;
11 | }
12 |
13 | &:last-of-type {
14 | margin-right: 0;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/primitives/other/labeled-item/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 | import { FormattedMessage } from 'react-intl';
5 |
6 | import FlexContainer from 'primitives/container/flex-container';
7 | import Label from 'primitives/form/label';
8 |
9 | import styles from './styles.module.scss';
10 |
11 | export default function LabeledItem({
12 | isVertical,
13 | isRequired,
14 | label,
15 | labelWidth,
16 | children,
17 | className,
18 | }) {
19 | const requiredFlag = isRequired ? ' *' : '';
20 | let labelItem = label;
21 | if (typeof label === 'string') {
22 | labelItem = ;
23 | }
24 | return (
25 |
31 |
39 | {children}
40 |
41 | );
42 | }
43 |
44 | LabeledItem.propTypes = {
45 | isVertical: PropTypes.bool,
46 | isRequired: PropTypes.bool,
47 | label: PropTypes.node.isRequired,
48 | labelWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
49 | children: PropTypes.node.isRequired,
50 | className: PropTypes.string,
51 | };
52 |
53 | LabeledItem.defaultProps = {
54 | isVertical: false,
55 | isRequired: false,
56 | labelWidth: '7rem',
57 | className: undefined,
58 | };
59 |
--------------------------------------------------------------------------------
/src/primitives/other/labeled-item/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .container {
4 | color: $light2;
5 | font-size: 0.9rem;
6 | padding: 0.2rem 0 0.5rem;
7 |
8 | &--vertical {
9 | align-items: flex-start;
10 | flex-direction: column;
11 | width: 100%;
12 |
13 | .LabeledItem-Label {
14 | padding: 0;
15 | }
16 | }
17 | }
18 |
19 | .label {
20 | margin: 0;
21 | padding-right: 0.5rem;
22 | text-align: left;
23 | }
24 |
25 | .item {
26 | flex: 1;
27 | white-space: pre-line;
28 | width: 100%;
29 | }
30 |
--------------------------------------------------------------------------------
/src/primitives/other/link-icon/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import './style.scss';
6 |
7 | export default function LinkIcon({ icon, className, ...rest }) {
8 | const Icon = icon;
9 | return ;
10 | }
11 |
12 | LinkIcon.propTypes = {
13 | icon: PropTypes.func.isRequired,
14 | className: PropTypes.string,
15 | };
16 |
17 | LinkIcon.defaultProps = {
18 | className: null,
19 | };
20 |
--------------------------------------------------------------------------------
/src/primitives/other/link-icon/style.scss:
--------------------------------------------------------------------------------
1 | .LinkIcon {
2 | vertical-align: middle;
3 | margin-right: 0.6rem;
4 | margin-top: -0.3rem;
5 | }
6 |
--------------------------------------------------------------------------------
/src/primitives/other/link-row/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .LinkRow {
4 | color: $dark3;
5 | cursor: pointer;
6 | display: flex;
7 | padding: 0.8rem 1.5rem;
8 | text-decoration: none;
9 | transition: background-color 0.5s;
10 |
11 | &:hover {
12 | background-color: $primary;
13 |
14 | .LinkRow-RightArrow {
15 | color: $light4;
16 | }
17 | }
18 | }
19 |
20 | .LinkRow-Name {
21 | margin: 0;
22 | text-align: left;
23 | }
24 |
25 | .LinkRow-RightArrow {
26 | color: transparent;
27 | pointer-events: none;
28 | transition: color 0.5s;
29 | }
30 |
31 | .LinkRow-Additional {
32 | color: $light1;
33 | font-size: 0.8rem;
34 | margin-left: 0.5rem;
35 | }
36 |
37 | .LinkRow-AdditionalIcon {
38 | color: $light2;
39 | }
40 |
--------------------------------------------------------------------------------
/src/primitives/other/save-footer/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { FormattedMessage } from 'react-intl';
4 |
5 | import FlexContainer from 'primitives/container/flex-container';
6 |
7 | import './style.scss';
8 |
9 | export default function SaveFooter({
10 | onCancel,
11 | onSave,
12 | cancelText,
13 | saveText,
14 | disabled,
15 | }) {
16 | return (
17 |
18 |
25 |
33 |
34 | );
35 | }
36 |
37 | SaveFooter.propTypes = {
38 | onCancel: PropTypes.func.isRequired,
39 | onSave: PropTypes.func.isRequired,
40 | cancelText: PropTypes.string,
41 | saveText: PropTypes.string,
42 | disabled: PropTypes.bool,
43 | };
44 |
45 | SaveFooter.defaultProps = {
46 | cancelText: 'misc.cancel',
47 | saveText: 'misc.save',
48 | disabled: false,
49 | };
50 |
--------------------------------------------------------------------------------
/src/primitives/other/save-footer/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .SaveFooter-Button {
4 | border: 0;
5 | border-radius: 0;
6 | box-sizing: border-box;
7 | color: $light2;
8 | cursor: pointer;
9 | display: inline-block;
10 | font-size: 0.9rem;
11 | letter-spacing: 0.1rem;
12 | outline: none;
13 | line-height: 56px;
14 | padding: 0;
15 | text-align: center;
16 | text-decoration: none;
17 | white-space: nowrap;
18 | transition: all 0.2s;
19 | font-weight: 300;
20 | flex: 1;
21 |
22 | &:hover {
23 | color: $light4;
24 | outline: 0;
25 | }
26 | }
27 |
28 | .SaveFooter-Cancel {
29 | background-color: $dark1;
30 |
31 | &:hover {
32 | background-color: $light1;
33 | }
34 | }
35 |
36 | .SaveFooter-Save {
37 | background-color: $primary;
38 |
39 | &:hover {
40 | background-color: $primary-alt;
41 | }
42 |
43 | &:disabled,
44 | &:disabled:hover {
45 | background-color: $light1;
46 | color: $light2;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/primitives/other/spinner/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import './style.scss';
6 |
7 | export default function Spinner({ className, isDark, size }) {
8 | return (
9 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | }
30 |
31 | Spinner.propTypes = {
32 | className: PropTypes.string,
33 | isDark: PropTypes.bool,
34 | size: PropTypes.number,
35 | };
36 |
37 | Spinner.defaultProps = {
38 | className: null,
39 | isDark: false,
40 | size: 30,
41 | };
42 |
--------------------------------------------------------------------------------
/src/primitives/other/table/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .Table {
4 | border-collapse: collapse;
5 | font-size: 0.9rem;
6 | margin-top: 0.5rem;
7 | width: 100%;
8 |
9 | &--centered.Table-Header,
10 | &--centered.Table-Element {
11 | text-align: center;
12 | }
13 | }
14 |
15 | .Table-Row {
16 | color: $light2;
17 |
18 | &:last-of-type .Table-Element {
19 | border: none;
20 | }
21 | }
22 |
23 | .Table-Header {
24 | border-bottom: 2px solid $dark1;
25 | border-collapse: collapse;
26 | color: $light4;
27 | font-weight: 700;
28 | padding: 6px;
29 | text-align: left;
30 | vertical-align: bottom;
31 | white-space: nowrap;
32 |
33 | &--light {
34 | border-bottom-color: $light3;
35 | color: $dark4;
36 | }
37 |
38 | &--condensed {
39 | padding: 0px 4px 4px;
40 | }
41 |
42 | &--sortable {
43 | cursor: pointer;
44 | }
45 | }
46 |
47 | .Table-Element {
48 | font-weight: 200;
49 | padding: 6px;
50 | border-bottom: 1px solid $dark1;
51 |
52 | &--light {
53 | border-bottom-color: $light2;
54 | color: $dark4;
55 | }
56 |
57 | &--condensed {
58 | padding: 0px 4px 4px;
59 | }
60 | }
61 |
62 | .Table-SortIcon {
63 | margin-left: 0.2rem;
64 | }
65 |
--------------------------------------------------------------------------------
/src/primitives/regions/loading/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FormattedMessage } from 'react-intl';
3 |
4 | import ContentContainer from 'primitives/container/content-container';
5 | import AbsoluteContainer from 'primitives/container/absolute-container';
6 | import Header, { HeaderType } from 'primitives/text/header';
7 | import Spinner from 'primitives/other/spinner';
8 |
9 | export default function Loading() {
10 | return (
11 |
12 |
13 |
14 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/src/primitives/regions/star-field/style.scss:
--------------------------------------------------------------------------------
1 | .StarField-Container {
2 | display: flex;
3 |
4 | &:before {
5 | position: absolute;
6 | content: '';
7 | top: 0;
8 | right: 0;
9 | bottom: 0;
10 | left: 0;
11 | animation: bg-move 2s linear infinite;
12 | background-size: 100% 8px;
13 | background-image: linear-gradient(
14 | 0,
15 | rgba(255, 255, 255, 0.05) 10%,
16 | transparent 10%,
17 | transparent 50%,
18 | rgba(255, 255, 255, 0.05) 50%,
19 | rgba(255, 255, 255, 0.05) 60%,
20 | transparent 60%,
21 | transparent
22 | );
23 | }
24 | }
25 |
26 | @keyframes bg-move {
27 | 0% {
28 | background-position: 0 0;
29 | }
30 | 100% {
31 | background-position: 0 -32px;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/primitives/text/action-header/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .container {
4 | border-bottom: 1px solid $dark2;
5 | overflow: hidden;
6 | width: 100%;
7 | }
8 |
9 | .subHeader {
10 | padding-bottom: 0.6rem;
11 | }
12 |
13 | .spacer {
14 | background-color: $dark1;
15 | width: 1px;
16 | margin: 0.2rem;
17 | }
18 |
--------------------------------------------------------------------------------
/src/primitives/text/header/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import './style.scss';
6 |
7 | export const HeaderType = {
8 | header1: 'header1',
9 | header2: 'header2',
10 | header3: 'header3',
11 | header4: 'header4',
12 | };
13 |
14 | export default function Header({
15 | type,
16 | className,
17 | dark,
18 | noMargin,
19 | children,
20 | ...rest
21 | }) {
22 | const headerNum = type.split('header').pop();
23 | const Component = `h${headerNum}`;
24 | return (
25 |
32 | {children}
33 |
34 | );
35 | }
36 |
37 | Header.propTypes = {
38 | type: PropTypes.oneOf(Object.keys(HeaderType)),
39 | children: PropTypes.node.isRequired,
40 | dark: PropTypes.bool,
41 | className: PropTypes.string,
42 | noMargin: PropTypes.bool,
43 | };
44 |
45 | Header.defaultProps = {
46 | type: HeaderType.header1,
47 | dark: false,
48 | className: null,
49 | noMargin: false,
50 | };
51 |
--------------------------------------------------------------------------------
/src/primitives/text/header/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .Header {
4 | color: $light4;
5 | margin: 1rem 0;
6 | text-align: center;
7 |
8 | &--dark {
9 | color: $dark4;
10 | }
11 |
12 | &--noMargin {
13 | margin: 0;
14 | }
15 | }
16 | .Header-1 {
17 | font-size: 5rem;
18 | font-weight: 600;
19 | line-height: 5rem;
20 | }
21 | .Header-2 {
22 | font-size: 2rem;
23 | font-weight: 600;
24 | line-height: 2rem;
25 | }
26 | .Header-3 {
27 | font-size: 1.5rem;
28 | font-weight: 400;
29 | line-height: 1.6rem;
30 | }
31 | .Header-4 {
32 | font-size: 1.1rem;
33 | font-weight: 200;
34 | line-height: 1.2rem;
35 | }
36 |
--------------------------------------------------------------------------------
/src/primitives/text/section-header/index.js:
--------------------------------------------------------------------------------
1 | import { injectIntl } from 'react-intl';
2 |
3 | import SectionHeader from './section-header';
4 |
5 | export default injectIntl(SectionHeader);
6 |
--------------------------------------------------------------------------------
/src/primitives/text/section-header/style.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/theme';
2 |
3 | .SectionHeader {
4 | color: $light4;
5 | border-bottom: 1px solid $light1;
6 | margin: 1rem;
7 | padding: 0.5rem 0.3rem;
8 | width: calc(100% - 2.6rem);
9 | }
10 |
11 | .SectionHeader-Inner {
12 | flex: 1;
13 | font-size: 1.3rem;
14 | font-weight: 200;
15 | margin: 0 0 0 0.2rem;
16 | text-align: left;
17 | }
18 |
19 | .SectionHeader-Icon {
20 | cursor: pointer;
21 | }
22 |
23 | .SectionHeader-AddButton {
24 | margin: 0;
25 | }
26 |
27 | .SectionHeader-Additional {
28 | color: $light1;
29 | font-size: 1rem;
30 | }
31 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | import { configure } from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 |
4 | configure({ adapter: new Adapter() });
5 |
--------------------------------------------------------------------------------
/src/store/actions/settings.actions.js:
--------------------------------------------------------------------------------
1 | const ACTION_PREFIX = '@@settings';
2 | export const UPDATE_SETTINGS = `${ACTION_PREFIX}/UPDATE_SETTINGS`;
3 |
4 | export const updateSettings = (key, value) => ({
5 | type: UPDATE_SETTINGS,
6 | key,
7 | value,
8 | });
9 |
--------------------------------------------------------------------------------
/src/store/api/faction.js:
--------------------------------------------------------------------------------
1 | import Firebase from 'firebase/app';
2 |
3 | export const createFaction = (sectorId, faction) =>
4 | Firebase.firestore()
5 | .collection('factions')
6 | .doc(sectorId)
7 | .collection('faction')
8 | .add(faction)
9 | .then(doc => ({ factionId: doc.id, faction }));
10 |
11 | export const editFaction = (sectorId, factionId, faction) =>
12 | Firebase.firestore()
13 | .collection('factions')
14 | .doc(sectorId)
15 | .collection('faction')
16 | .doc(factionId)
17 | .set(faction)
18 | .then(() => ({ factionId, faction }));
19 |
20 | export const deleteFaction = (sectorId, factionId) =>
21 | Firebase.firestore()
22 | .collection('factions')
23 | .doc(sectorId)
24 | .collection('faction')
25 | .doc(factionId)
26 | .delete();
27 |
28 | export const getFactionData = sectorId =>
29 | Firebase.firestore()
30 | .collection('factions')
31 | .doc(sectorId)
32 | .collection('faction')
33 | .get()
34 | .then(snapshot => {
35 | const factions = {};
36 | snapshot.forEach(doc => {
37 | factions[doc.id] = doc.data();
38 | });
39 | return factions;
40 | });
41 |
--------------------------------------------------------------------------------
/src/store/api/layer.js:
--------------------------------------------------------------------------------
1 | import Firebase from 'firebase/app';
2 | import { forEach } from 'constants/lodash';
3 |
4 | export const createLayer = (sectorId, layer) =>
5 | Firebase.firestore()
6 | .collection('layers')
7 | .doc(sectorId)
8 | .collection('layer')
9 | .add(layer)
10 | .then(doc => ({ layerId: doc.id, layer }));
11 |
12 | export const updateLayer = (sectorId, layerId, layer) =>
13 | Firebase.firestore()
14 | .collection('layers')
15 | .doc(sectorId)
16 | .collection('layer')
17 | .doc(layerId)
18 | .set(layer, { merge: true })
19 | .then(() => ({ layerId, layer }));
20 |
21 | export const updateLayers = (sectorId, layers) => {
22 | const batch = Firebase.firestore().batch();
23 | forEach(layers, (update, layerId) =>
24 | batch.update(
25 | Firebase.firestore()
26 | .collection('layers')
27 | .doc(sectorId)
28 | .collection('layer')
29 | .doc(layerId),
30 | update,
31 | ),
32 | );
33 | return batch.commit();
34 | };
35 |
36 | export const deleteLayer = (sectorId, layerId) =>
37 | Firebase.firestore()
38 | .collection('layers')
39 | .doc(sectorId)
40 | .collection('layer')
41 | .doc(layerId)
42 | .delete();
43 |
44 | export const getLayerData = sectorId =>
45 | Firebase.firestore()
46 | .collection('layers')
47 | .doc(sectorId)
48 | .collection('layer')
49 | .get()
50 | .then(snapshot => {
51 | const layers = {};
52 | snapshot.forEach(doc => {
53 | layers[doc.id] = doc.data();
54 | });
55 | return layers;
56 | });
57 |
--------------------------------------------------------------------------------
/src/store/api/navigation.js:
--------------------------------------------------------------------------------
1 | import Firebase from 'firebase/app';
2 | import { forEach } from 'constants/lodash';
3 |
4 | export const createRoute = (sectorId, route) =>
5 | Firebase.firestore()
6 | .collection('navigation')
7 | .doc(sectorId)
8 | .collection('routes')
9 | .add(route)
10 | .then(doc => ({ key: doc.id, route }));
11 |
12 | export const deleteRoute = (sectorId, routeId) =>
13 | Firebase.firestore()
14 | .collection('navigation')
15 | .doc(sectorId)
16 | .collection('routes')
17 | .doc(routeId)
18 | .delete();
19 |
20 | export const setVisibility = (sectorId, routeId, isHidden) =>
21 | Firebase.firestore()
22 | .collection('navigation')
23 | .doc(sectorId)
24 | .collection('routes')
25 | .doc(routeId)
26 | .set({ isHidden }, { merge: true });
27 |
28 | export const updateRoutes = (sectorId, routes) => {
29 | const batch = Firebase.firestore().batch();
30 | forEach(routes, (update, routeId) =>
31 | batch.update(
32 | Firebase.firestore()
33 | .collection('navigation')
34 | .doc(sectorId)
35 | .collection('routes')
36 | .doc(routeId),
37 | update,
38 | ),
39 | );
40 | return batch.commit();
41 | };
42 |
43 | export const getNavigationData = sectorId =>
44 | Firebase.firestore()
45 | .collection('navigation')
46 | .doc(sectorId)
47 | .collection('routes')
48 | .get()
49 | .then(snapshot => {
50 | const routes = {};
51 | snapshot.forEach(doc => {
52 | routes[doc.id] = doc.data();
53 | });
54 | return routes;
55 | });
56 |
--------------------------------------------------------------------------------
/src/store/api/tag.js:
--------------------------------------------------------------------------------
1 | import Firebase from 'firebase/app';
2 |
3 | export const createCustomTag = (uid, tag) => {
4 | if (!uid) {
5 | throw new Error('User ID must exist to create a custom tag.');
6 | }
7 | const newTag = { ...tag, creator: uid };
8 | return Firebase.firestore()
9 | .collection('tags')
10 | .add(newTag)
11 | .then(doc => ({ tagId: doc.id, tag: newTag }));
12 | };
13 |
14 | export const updateCustomTag = (tagId, tag) =>
15 | Firebase.firestore()
16 | .collection('tags')
17 | .doc(tagId)
18 | .set(tag, { merge: true })
19 | .then(() => ({ tagId, tag }));
20 |
21 | export const deleteCustomTag = tagId =>
22 | Firebase.firestore()
23 | .collection('tags')
24 | .doc(tagId)
25 | .delete();
26 |
27 | export const getCustomTags = uid =>
28 | Firebase.firestore()
29 | .collection('tags')
30 | .where('creator', '==', uid)
31 | .get()
32 | .then(snapshot => {
33 | let tags = {};
34 | snapshot.forEach(doc => {
35 | tags = { ...tags, [doc.id]: doc.data() };
36 | });
37 | return tags;
38 | });
39 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore, combineReducers, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { reducer as toastrReducer } from 'react-redux-toastr';
4 | import { createBrowserHistory } from 'history';
5 | import { composeWithDevTools } from 'redux-devtools-extension';
6 | import { connectRouter, routerMiddleware } from 'connected-react-router';
7 | import { throttle } from 'constants/lodash';
8 |
9 | import { loadState, saveState } from './localStorage';
10 |
11 | import reducers from './reducers';
12 |
13 | export const history = createBrowserHistory();
14 | const middleware = [thunk, routerMiddleware(history)];
15 |
16 | const store = createStore(
17 | combineReducers({
18 | ...reducers,
19 | toastr: toastrReducer,
20 | router: connectRouter(history),
21 | }),
22 | loadState(),
23 | composeWithDevTools(applyMiddleware(...middleware)),
24 | );
25 |
26 | // have at least a second delay between the local storage saves
27 | store.subscribe(throttle(() => saveState(store.getState()), 1000));
28 |
29 | export default store;
30 |
--------------------------------------------------------------------------------
/src/store/localStorage.js:
--------------------------------------------------------------------------------
1 | function extractDataToSave(state) {
2 | return {
3 | settings: { ...state.settings },
4 | };
5 | }
6 |
7 | export const loadState = () => {
8 | try {
9 | const serializedState = localStorage.getItem('state');
10 | if (serializedState === null) {
11 | return undefined;
12 | }
13 | return JSON.parse(serializedState);
14 | } catch (err) {
15 | return undefined;
16 | }
17 | };
18 |
19 | export const saveState = state => {
20 | try {
21 | const serializedState = JSON.stringify(extractDataToSave(state));
22 | localStorage.setItem('state', serializedState);
23 | } catch (err) {
24 | // Ignore errors for the moment
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/src/store/reducers/index.js:
--------------------------------------------------------------------------------
1 | import sector from './sector.reducers';
2 | import user from './user.reducers';
3 | import entity from './entity.reducers';
4 | import sidebar from './sidebar.reducers';
5 | import navigation from './navigation.reducers';
6 | import layer from './layer.reducers';
7 | import faction from './faction.reducers';
8 | import settings from './settings.reducers';
9 | import tag from './tag.reducers';
10 |
11 | export default {
12 | sector,
13 | user,
14 | entity,
15 | sidebar,
16 | navigation,
17 | layer,
18 | faction,
19 | settings,
20 | tag,
21 | };
22 |
--------------------------------------------------------------------------------
/src/store/reducers/settings.reducers.js:
--------------------------------------------------------------------------------
1 | import { UPDATE_SETTINGS } from 'store/actions/settings.actions';
2 |
3 | const initialState = {
4 | showNumberOfChildren: true,
5 | showEntityName: false,
6 | showCoordinates: true,
7 | };
8 |
9 | export default function settings(state = initialState, action) {
10 | switch (action.type) {
11 | case UPDATE_SETTINGS:
12 | return {
13 | ...state,
14 | [action.key]: action.value,
15 | };
16 | default:
17 | return state;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/store/reducers/tag.reducers.js:
--------------------------------------------------------------------------------
1 | import { FETCHED_SECTOR, INITIALIZED } from 'store/actions/combined.actions';
2 | import {
3 | OPEN_MODAL,
4 | CLOSE_MODAL,
5 | ITEM_ADDED,
6 | ITEM_DELETED,
7 | } from 'store/actions/tag.actions';
8 | import { omit } from 'constants/lodash';
9 |
10 | export const initialState = {
11 | models: {},
12 | isOpen: false,
13 | };
14 |
15 | export default function tag(state = initialState, action) {
16 | switch (action.type) {
17 | case INITIALIZED:
18 | case FETCHED_SECTOR:
19 | return {
20 | ...state,
21 | models: {
22 | ...state.models,
23 | ...(action.tags || {}),
24 | },
25 | };
26 | case OPEN_MODAL:
27 | return { ...state, isOpen: true };
28 | case CLOSE_MODAL:
29 | return { ...state, isOpen: false };
30 | case ITEM_ADDED:
31 | return { ...state, models: { ...state.models, ...action.item } };
32 | case ITEM_DELETED:
33 | return { ...state, models: omit(state.models, action.item) };
34 | default:
35 | return state;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/store/selectors/navigation.selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | import { find, mapValues } from 'constants/lodash';
4 | import { coordinateKey } from 'utils/common';
5 |
6 | import {
7 | currentSectorSelector,
8 | navigationRoutesSelector,
9 | navigationSettingsSelector,
10 | } from 'store/selectors/base.selectors';
11 | import { getAllTopLevelEntities } from 'store/selectors/entity.selectors';
12 |
13 | export const getCurrentSectorNavigation = createSelector(
14 | [currentSectorSelector, navigationRoutesSelector, getAllTopLevelEntities],
15 | (sector, routes, entities) =>
16 | mapValues(routes[sector], route => {
17 | const firstKey = route.route[0];
18 | const lastKey = route.route[route.route.length - 1];
19 | const firstEntity = find(
20 | entities,
21 | ({ x, y }) => coordinateKey(x, y) === firstKey,
22 | );
23 | const lastEntity = find(
24 | entities,
25 | ({ x, y }) => coordinateKey(x, y) === lastKey,
26 | );
27 | const firstHidden = firstEntity ? !!firstEntity.isHidden : true;
28 | const lastHidden = lastEntity ? !!lastEntity.isHidden : true;
29 | const hiddenByEntity = firstHidden || lastHidden;
30 | return {
31 | ...route,
32 | hiddenByEntity,
33 | isHidden: route.isHidden || hiddenByEntity,
34 | from: firstEntity ? firstEntity.name : firstKey,
35 | to: lastEntity ? lastEntity.name : lastKey,
36 | };
37 | }),
38 | );
39 |
40 | export const getCurrentNavigationWithSettings = createSelector(
41 | [getCurrentSectorNavigation, navigationSettingsSelector],
42 | (routes, settings) =>
43 | settings.isCreatingRoute ? { ...routes, settings } : routes,
44 | );
45 |
--------------------------------------------------------------------------------
/src/store/selectors/sector.selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | import {
4 | isInitializedSelector,
5 | currentSectorSelector,
6 | sectorSelector,
7 | savedSectorSelector,
8 | shareSectorSelector,
9 | fetchedSectorSelector,
10 | playerViewSelector,
11 | } from 'store/selectors/base.selectors';
12 | import { omitBy, includes } from 'constants/lodash';
13 |
14 | export const getUserSectors = createSelector(
15 | [sectorSelector, savedSectorSelector],
16 | (sectors, saved) => omitBy(sectors, (sector, key) => !includes(saved, key)),
17 | );
18 |
19 | export const isCurrentSectorSaved = createSelector(
20 | [currentSectorSelector, savedSectorSelector],
21 | (currentSector, saved) => includes(saved, currentSector),
22 | );
23 |
24 | export const isCurrentSectorFetched = createSelector(
25 | [currentSectorSelector, fetchedSectorSelector],
26 | (currentSector, fetched) => includes(fetched, currentSector),
27 | );
28 |
29 | export const isSharedSector = createSelector(
30 | [currentSectorSelector, shareSectorSelector],
31 | (currentSector, share) => currentSector === share,
32 | );
33 |
34 | export const isViewingSharedSector = createSelector(
35 | [isSharedSector, playerViewSelector],
36 | (isShared, isPlayerView) => isShared || isPlayerView,
37 | );
38 |
39 | export const currentSectorIsLoading = createSelector(
40 | [isInitializedSelector, isCurrentSectorFetched],
41 | (isInitialized, isCurrentFetched) => !isInitialized || !isCurrentFetched,
42 | );
43 |
--------------------------------------------------------------------------------
/src/store/selectors/tag.selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | import {
4 | userUidSelector,
5 | customTagSelector,
6 | currentEntityTypeSelector,
7 | } from 'store/selectors/base.selectors';
8 | import { pickBy, includes, mapValues, omit } from 'constants/lodash';
9 |
10 | export const getUsersCustomTags = createSelector(
11 | [customTagSelector, userUidSelector],
12 | (tags, uid) => (!uid ? tags : pickBy(tags, tag => tag.creator === uid)),
13 | );
14 |
15 | const getNormalizedTags = createSelector([getUsersCustomTags], tags =>
16 | mapValues(tags, (tag, key) => ({ ...omit(tag, 'creator'), key })),
17 | );
18 |
19 | export const getTagsForCurrentEntity = createSelector(
20 | [getNormalizedTags, currentEntityTypeSelector],
21 | (tags, entityType) =>
22 | mapValues(
23 | pickBy(tags, tag => includes(tag.types, entityType)),
24 | tag => omit(tag, 'types'),
25 | ),
26 | );
27 |
--------------------------------------------------------------------------------
/src/styles/_constants.scss:
--------------------------------------------------------------------------------
1 | $small-sidebar-width: 374px;
2 | $large-sidebar-width: 465px;
3 |
4 | $small-navigation-width: 75px;
5 | $large-navigation-width: 185px;
6 |
--------------------------------------------------------------------------------
/src/styles/_fonts.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'KillTheNoise';
3 | src: url('./fonts/KillTheNoise.eot');
4 | src: url('./fonts/KillTheNoise.woff') format('woff'),
5 | url('./fonts/KillTheNoise.ttf') format('truetype'),
6 | url('./fonts/KillTheNoise.eot?#iefix') format('embedded-opentype');
7 | font-weight: normal;
8 | font-style: normal;
9 | }
10 |
--------------------------------------------------------------------------------
/src/styles/_theme.scss:
--------------------------------------------------------------------------------
1 | $dark4: #091833;
2 | $dark3: #11203b;
3 | $dark2: #182742;
4 | $dark1: #3c4b66;
5 |
6 | $cyber: #d600ff;
7 | $error: #fc3c3c;
8 | $transparentDark: rgba(60, 75, 102, 0.5);
9 | $primary: #863c4e;
10 | $primary-alt: #792f41;
11 |
12 | $light1: #8f8f8f;
13 | $light2: #b2b2b2;
14 | $light3: #dbdbdb;
15 | $light4: #e3e3e3;
16 |
17 | $google: #d34836;
18 |
--------------------------------------------------------------------------------
/src/styles/_utilities.scss:
--------------------------------------------------------------------------------
1 | @function randomVal($val) {
2 | @return floor(random($val));
3 | }
4 |
5 | $minWidth: 400;
6 | $maxWidth: 1440;
7 | @function variableSize($min, $max) {
8 | @return calc(
9 | #{$min}px +
10 | ((100vw - #{$minWidth}px) / #{(($maxWidth - $minWidth) / ($max - $min))})
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/src/styles/fonts/KillTheNoise.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/src/styles/fonts/KillTheNoise.eot
--------------------------------------------------------------------------------
/src/styles/fonts/KillTheNoise.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/src/styles/fonts/KillTheNoise.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KillTheNoise.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpigsley/sectors-without-number/2c70c55ed72b538d8390668a9076b7ae78da2d61/src/styles/fonts/KillTheNoise.woff
--------------------------------------------------------------------------------
/src/styles/global.scss:
--------------------------------------------------------------------------------
1 | @import '~react-redux-toastr/src/styles/index';
2 | @import '~react-select/scss/default';
3 | @import '~styles/theme';
4 | @import '~styles/fonts';
5 |
6 | body {
7 | margin: 0;
8 | padding: 0;
9 | background-color: $dark4;
10 | }
11 |
12 | * {
13 | font-family: 'Titillium Web', sans-serif !important;
14 | }
15 |
16 | .DefaultIcon g {
17 | fill: $light2;
18 | }
19 |
20 | @media print {
21 | body {
22 | background-color: white;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/utils/__tests__/export.spec.js:
--------------------------------------------------------------------------------
1 | import { generateCSV } from '../export';
2 |
3 | describe('Export Util', () => {
4 | describe('generateCSV', () => {
5 | it('should return a string given an array', () => {
6 | const csv = generateCSV([[]]);
7 | expect(typeof csv).toEqual('string');
8 | });
9 |
10 | it('should return a string that starts with CSV front-matter', () => {
11 | const csv = generateCSV([[]]);
12 | expect(csv.indexOf('data:text/csv;charset=utf-8,')).toEqual(0);
13 | });
14 |
15 | it("shouldn't error if the table is an empty array", () => {
16 | const csv = generateCSV([]);
17 | expect(csv).toEqual('data:text/csv;charset=utf-8,');
18 | });
19 |
20 | it("shouldn't error if the table is undefined", () => {
21 | const csv = generateCSV();
22 | expect(csv).toEqual('data:text/csv;charset=utf-8,');
23 | });
24 |
25 | it('should surround data in double quotes to escape commas in content', () => {
26 | const test1 = 'test';
27 | const test2 = 'test,test';
28 | const test3 = 'test,test,test';
29 | const csv = generateCSV([[test1, test2, test3]]);
30 | const contentArray = csv.split('"');
31 | expect(contentArray[1]).toEqual(test1);
32 | expect(contentArray[3]).toEqual(test2);
33 | expect(contentArray[5]).toEqual(test3);
34 | });
35 |
36 | it('should break table onto multiple lines', () => {
37 | const csv = generateCSV([['test'], ['test'], ['test']]);
38 | const splitString = csv.split(/\r?\n/);
39 | expect(splitString.length).toEqual(4);
40 | });
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/src/utils/__tests__/name-generator.spec.js:
--------------------------------------------------------------------------------
1 | import Chance from 'chance';
2 |
3 | import GreekLetters from 'constants/language/greek-letters';
4 | import { generateName, generateSectorName } from '../name-generator';
5 |
6 | describe('generateName', () => {
7 | let chance;
8 | beforeEach(() => {
9 | chance = new Chance();
10 | });
11 |
12 | it('uppercases the first letter of the name', () => {
13 | const firstLetter = generateName(chance)[0];
14 | expect(firstLetter).toEqual(firstLetter.toUpperCase());
15 | });
16 |
17 | it("doesn't generate the same name twice", () => {
18 | const name = generateName(chance);
19 | expect(generateName(chance)).not.toEqual(name);
20 | });
21 | });
22 |
23 | describe('generateSectorName', () => {
24 | let chance;
25 | beforeEach(() => {
26 | chance = new Chance();
27 | });
28 |
29 | it('contains a greek letter', () => {
30 | const name = generateSectorName(chance);
31 | expect(
32 | GreekLetters.filter(letter => name.includes(letter)).length,
33 | ).toBeGreaterThanOrEqual(1);
34 | });
35 |
36 | it('is two or three words', () => {
37 | expect(
38 | [2, 3].indexOf(generateSectorName(chance).split(' ').length) >= 0,
39 | ).toEqual(true);
40 | });
41 |
42 | it("doesn't generate the same name twice", () => {
43 | const name = generateSectorName(chance);
44 | expect(generateSectorName(chance)).not.toEqual(name);
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/src/utils/canvas-helpers.js:
--------------------------------------------------------------------------------
1 | import { HEX_PADDING } from 'constants/defaults';
2 |
3 | const getHexBoundingBox = ({ height, width, xOffset, yOffset }) => ({
4 | x1: xOffset - (width + HEX_PADDING) / 2,
5 | x2: xOffset + (width + HEX_PADDING) / 2,
6 | y1: yOffset - (height + HEX_PADDING) / 2,
7 | y2: yOffset + (height + HEX_PADDING) / 2,
8 | });
9 | export const distanceBetween = (a, b) =>
10 | Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
11 | const isWithin = ({ x, y }, { x1, x2, y1, y2 }) =>
12 | x >= x1 && x <= x2 && y >= y1 && y <= y2;
13 |
14 | export const getHoveredHex = ({ x, y, hexes }) => {
15 | const containedInBoundingBox = hexes.filter(
16 | hex => isWithin({ x, y }, getHexBoundingBox(hex)) && hex.highlighted,
17 | );
18 | if (!containedInBoundingBox.length) {
19 | return undefined;
20 | }
21 | if (containedInBoundingBox.length === 1) {
22 | return containedInBoundingBox[0].hexKey;
23 | }
24 | return containedInBoundingBox.reduce(
25 | (minVal, hex) => {
26 | const distance = distanceBetween(
27 | { x, y },
28 | { x: hex.xOffset, y: hex.yOffset },
29 | );
30 | return distance < minVal.distance
31 | ? { hexKey: hex.hexKey, distance }
32 | : minVal;
33 | },
34 | { distance: Number.MAX_SAFE_INTEGER },
35 | ).hexKey;
36 | };
37 |
38 | export const getPixelRatio = () => {
39 | const canvas = document.createElement('canvas');
40 | const ctx = canvas.getContext('2d');
41 | const dpr = window.devicePixelRatio || 1;
42 | const bsr =
43 | ctx.webkitBackingStorePixelRatio ||
44 | ctx.mozBackingStorePixelRatio ||
45 | ctx.msBackingStorePixelRatio ||
46 | ctx.oBackingStorePixelRatio ||
47 | ctx.backingStorePixelRatio ||
48 | 1;
49 | return dpr / bsr;
50 | };
51 |
--------------------------------------------------------------------------------
/src/utils/effects.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState, useRef } from 'react';
2 | import { debounce } from 'lodash';
3 |
4 | export const useResize = cb =>
5 | useEffect(() => {
6 | window.addEventListener('resize', cb);
7 | return () => window.removeEventListener('resize', cb);
8 | }, [cb]);
9 |
10 | export const useDimensions = () => {
11 | const [dimensions, setDimensions] = useState({
12 | width: window.innerWidth,
13 | height: window.innerHeight,
14 | });
15 | useResize(
16 | debounce(
17 | () =>
18 | setDimensions({
19 | width: window.innerWidth,
20 | height: window.innerHeight,
21 | }),
22 | 50,
23 | ),
24 | );
25 | return dimensions;
26 | };
27 |
28 | export const useIsMobile = () => {
29 | const { width } = useDimensions();
30 | return width <= 700;
31 | };
32 |
33 | export const usePrevious = value => {
34 | const ref = useRef();
35 | useEffect(() => {
36 | ref.current = value;
37 | });
38 | return ref.current;
39 | };
40 |
--------------------------------------------------------------------------------
/src/utils/entity-generators/asteroid-base-generator.js:
--------------------------------------------------------------------------------
1 | import Chance from 'chance';
2 |
3 | import Occupation from 'constants/asteroid-base/occupation';
4 | import Situation from 'constants/asteroid-base/situation';
5 | import Entities from 'constants/entities';
6 | import commonGenerator from './common-generator';
7 |
8 | export const generateAsteroidBase = commonGenerator(
9 | (entity, { hideOccAndSit = false, generate = true }) => {
10 | if (!generate) {
11 | return entity;
12 | }
13 | let visibility = {};
14 | if (hideOccAndSit) {
15 | visibility = {
16 | 'attr.occupation': false,
17 | 'attr.situation': false,
18 | };
19 | }
20 | const chance = new Chance();
21 | return {
22 | ...entity,
23 | visibility: {
24 | ...entity.visibility,
25 | ...visibility,
26 | },
27 | attributes: {
28 | ...entity.attributes,
29 | occupation: chance.pickone(Object.keys(Occupation.attributes)),
30 | situation: chance.pickone(Object.keys(Situation.attributes)),
31 | },
32 | };
33 | },
34 | );
35 |
36 | export const generateAsteroidBases = ({
37 | children = [...Array(new Chance().weighted([0, 1, 2], [1, 3, 2]))],
38 | additionalPointsOfInterest = true,
39 | ...config
40 | }) => {
41 | if (!additionalPointsOfInterest) {
42 | return { children: [] };
43 | }
44 | return {
45 | children: children.map(({ name, generate } = {}) =>
46 | generateAsteroidBase(Entities.asteroidBase.key, {
47 | ...config,
48 | name,
49 | generate,
50 | }),
51 | ),
52 | };
53 | };
54 |
--------------------------------------------------------------------------------
/src/utils/entity-generators/asteroid-belt-generator.js:
--------------------------------------------------------------------------------
1 | import Chance from 'chance';
2 |
3 | import Occupation from 'constants/asteroid-belt/occupation';
4 | import Situation from 'constants/asteroid-belt/situation';
5 | import Entities from 'constants/entities';
6 | import commonGenerator from './common-generator';
7 |
8 | export const generateAsteroidBelt = commonGenerator(
9 | (entity, { hideOccAndSit = false, generate = true }) => {
10 | if (!generate) {
11 | return entity;
12 | }
13 | let visibility;
14 | if (hideOccAndSit) {
15 | visibility = {
16 | 'attr.occupation': false,
17 | 'attr.situation': false,
18 | };
19 | }
20 | const chance = new Chance();
21 | return {
22 | ...entity,
23 | visibility: {
24 | ...entity.visiblity,
25 | ...visibility,
26 | },
27 | attributes: {
28 | ...entity.attributes,
29 | occupation: chance.pickone(Object.keys(Occupation.attributes)),
30 | situation: chance.pickone(Object.keys(Situation.attributes)),
31 | },
32 | };
33 | },
34 | );
35 |
36 | export const generateAsteroidBelts = ({
37 | children = [...Array(new Chance().weighted([0, 1], [4, 1]))],
38 | additionalPointsOfInterest = true,
39 | ...config
40 | }) => {
41 | if (!additionalPointsOfInterest) {
42 | return { children: [] };
43 | }
44 | return {
45 | children: children.map(({ name, generate } = {}) =>
46 | generateAsteroidBelt(Entities.asteroidBelt.key, {
47 | name,
48 | generate,
49 | ...config,
50 | }),
51 | ),
52 | };
53 | };
54 |
--------------------------------------------------------------------------------
/src/utils/entity-generators/black-hole-generator.js:
--------------------------------------------------------------------------------
1 | import Chance from 'chance';
2 |
3 | import { xor } from 'constants/lodash';
4 | import Entities from 'constants/entities';
5 | import commonGenerator from './common-generator';
6 |
7 | export const generateBlackHole = commonGenerator((entity, { x, y }) => {
8 | if (!x || !y) {
9 | throw new Error(
10 | 'Sector coordinate must be provided to generate a black hole',
11 | );
12 | }
13 | return { x, y, ...entity };
14 | });
15 |
16 | export const generateBlackHoles = ({
17 | additionalPointsOfInterest = true,
18 | ...config
19 | }) => {
20 | if (!additionalPointsOfInterest) {
21 | return { children: [] };
22 | }
23 |
24 | const chance = new Chance();
25 | const numHexes = config.rows * config.columns;
26 | const blackHoleNum =
27 | chance.integer({ min: 1, max: Math.max(1, Math.floor(numHexes / 40)) }) +
28 | Math.floor(numHexes / 45);
29 | const chosenCoordinates = chance.pickset(config.coordinates, blackHoleNum);
30 |
31 | return {
32 | coordinates: xor(config.coordinates, chosenCoordinates),
33 | children: chosenCoordinates.map(coordinate =>
34 | generateBlackHole(Entities.blackHole.key, { ...coordinate, ...config }),
35 | ),
36 | };
37 | };
38 |
--------------------------------------------------------------------------------
/src/utils/entity-generators/deep-space-station-generator.js:
--------------------------------------------------------------------------------
1 | import Chance from 'chance';
2 |
3 | import Occupation from 'constants/space-station/occupation';
4 | import Situation from 'constants/space-station/situation';
5 | import Entities from 'constants/entities';
6 | import commonGenerator from './common-generator';
7 |
8 | export const generateDeepSpaceStation = commonGenerator(
9 | (entity, { hideOccAndSit = false, generate = true }) => {
10 | if (!generate) {
11 | return entity;
12 | }
13 | let visibility = {};
14 | if (hideOccAndSit) {
15 | visibility = {
16 | 'attr.occupation': false,
17 | 'attr.situation': false,
18 | };
19 | }
20 | const chance = new Chance();
21 | return {
22 | ...entity,
23 | visibility: {
24 | ...entity.visibility,
25 | ...visibility,
26 | },
27 | attributes: {
28 | ...entity.attributes,
29 | occupation: chance.pickone(Object.keys(Occupation.attributes)),
30 | situation: chance.pickone(Object.keys(Situation.attributes)),
31 | },
32 | };
33 | },
34 | );
35 |
36 | export const generateDeepSpaceStations = ({
37 | children = [...Array(new Chance().weighted([0, 1], [3, 1]))],
38 | additionalPointsOfInterest = true,
39 | ...config
40 | }) => {
41 | if (!additionalPointsOfInterest) {
42 | return { children: [] };
43 | }
44 | return {
45 | children: children.map(({ name, generate } = {}) =>
46 | generateDeepSpaceStation(Entities.deepSpaceStation.key, {
47 | name,
48 | generate,
49 | ...config,
50 | }),
51 | ),
52 | };
53 | };
54 |
--------------------------------------------------------------------------------
/src/utils/entity-generators/gas-giant-mine-generator.js:
--------------------------------------------------------------------------------
1 | import Chance from 'chance';
2 |
3 | import Occupation from 'constants/gas-giant-mine/occupation';
4 | import Situation from 'constants/gas-giant-mine/situation';
5 | import Entities from 'constants/entities';
6 | import commonGenerator from './common-generator';
7 |
8 | export const generateGasGiantMine = commonGenerator(
9 | (entity, { hideOccAndSit = false, generate = true }) => {
10 | if (!generate) {
11 | return entity;
12 | }
13 | let visibility = {};
14 | if (hideOccAndSit) {
15 | visibility = {
16 | 'attr.occupation': false,
17 | 'attr.situation': false,
18 | };
19 | }
20 | const chance = new Chance();
21 | return {
22 | ...entity,
23 | visibility: {
24 | ...entity.visibility,
25 | ...visibility,
26 | },
27 | attributes: {
28 | ...entity.attributes,
29 | occupation: chance.pickone(Object.keys(Occupation.attributes)),
30 | situation: chance.pickone(Object.keys(Situation.attributes)),
31 | },
32 | };
33 | },
34 | );
35 |
36 | export const generateGasGiantMines = ({
37 | children = [...Array(new Chance().weighted([0, 1, 2], [8, 3, 1]))],
38 | additionalPointsOfInterest = true,
39 | ...config
40 | }) => {
41 | if (!additionalPointsOfInterest) {
42 | return { children: [] };
43 | }
44 | return {
45 | children: children.map(({ name, generate } = {}) =>
46 | generateGasGiantMine(Entities.gasGiantMine.key, {
47 | name,
48 | generate,
49 | ...config,
50 | }),
51 | ),
52 | };
53 | };
54 |
--------------------------------------------------------------------------------
/src/utils/entity-generators/moon-base-generator.js:
--------------------------------------------------------------------------------
1 | import Chance from 'chance';
2 |
3 | import Occupation from 'constants/moon-base/occupation';
4 | import Situation from 'constants/moon-base/situation';
5 | import Entities from 'constants/entities';
6 | import commonGenerator from './common-generator';
7 |
8 | export const generateMoonBase = commonGenerator(
9 | (entity, { hideOccAndSit = false, generate = true }) => {
10 | if (!generate) {
11 | return entity;
12 | }
13 | let visibility = {};
14 | if (hideOccAndSit) {
15 | visibility = {
16 | 'attr.occupation': false,
17 | 'attr.situation': false,
18 | };
19 | }
20 | const chance = new Chance();
21 | return {
22 | ...entity,
23 | visibility: {
24 | ...entity.visibility,
25 | ...visibility,
26 | },
27 | attributes: {
28 | ...entity.attributes,
29 | occupation: chance.pickone(Object.keys(Occupation.attributes)),
30 | situation: chance.pickone(Object.keys(Situation.attributes)),
31 | },
32 | };
33 | },
34 | );
35 |
36 | export const generateMoonBases = ({
37 | children = [...Array(new Chance().weighted([0, 1], [5, 2]))],
38 | additionalPointsOfInterest = true,
39 | ...config
40 | }) => {
41 | if (!additionalPointsOfInterest) {
42 | return { children: [] };
43 | }
44 | return {
45 | children: children.map(({ name, generate } = {}) =>
46 | generateMoonBase(Entities.gasGiantMine.key, {
47 | name,
48 | generate,
49 | ...config,
50 | }),
51 | ),
52 | };
53 | };
54 |
--------------------------------------------------------------------------------
/src/utils/entity-generators/moon-generator.js:
--------------------------------------------------------------------------------
1 | import Chance from 'chance';
2 |
3 | import Entities from 'constants/entities';
4 | import commonGenerator from './common-generator';
5 |
6 | export const generateMoon = commonGenerator(entity => entity);
7 |
8 | export const generateMoons = ({
9 | children = [...Array(new Chance().weighted([0, 1, 2, 3], [20, 5, 2, 1]))],
10 | additionalPointsOfInterest = true,
11 | ...config
12 | }) => {
13 | if (!additionalPointsOfInterest) {
14 | return { children: [] };
15 | }
16 | return {
17 | children: children.map(({ name, generate } = {}) =>
18 | generateMoon(Entities.moon.key, { name, generate, ...config }),
19 | ),
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/src/utils/entity-generators/note-generator.js:
--------------------------------------------------------------------------------
1 | import commonGenerator from './common-generator';
2 |
3 | // eslint-disable-next-line
4 | export const generateNote = commonGenerator(entity => entity);
5 |
--------------------------------------------------------------------------------
/src/utils/entity-generators/orbital-ruin-generator.js:
--------------------------------------------------------------------------------
1 | import Chance from 'chance';
2 |
3 | import Occupation from 'constants/orbital-ruin/occupation';
4 | import Situation from 'constants/orbital-ruin/situation';
5 | import Entities from 'constants/entities';
6 | import commonGenerator from './common-generator';
7 |
8 | export const generateOrbitalRuin = commonGenerator(
9 | (entity, { hideOccAndSit = false, generate = true }) => {
10 | if (!generate) {
11 | return entity;
12 | }
13 | let visibility = {};
14 | if (hideOccAndSit) {
15 | visibility = {
16 | 'attr.occupation': false,
17 | 'attr.situation': false,
18 | };
19 | }
20 | const chance = new Chance();
21 | return {
22 | ...entity,
23 | visibility: {
24 | ...entity.visibility,
25 | ...visibility,
26 | },
27 | attributes: {
28 | ...entity.attributes,
29 | occupation: chance.pickone(Object.keys(Occupation.attributes)),
30 | situation: chance.pickone(Object.keys(Situation.attributes)),
31 | },
32 | };
33 | },
34 | );
35 |
36 | export const generateOrbitalRuins = ({
37 | children = [...Array(new Chance().weighted([0, 1], [3, 1]))],
38 | additionalPointsOfInterest = true,
39 | ...config
40 | }) => {
41 | if (!additionalPointsOfInterest) {
42 | return { children: [] };
43 | }
44 | return {
45 | children: children.map(({ name, generate } = {}) =>
46 | generateOrbitalRuin(Entities.orbitalRuin.key, {
47 | name,
48 | generate,
49 | ...config,
50 | }),
51 | ),
52 | };
53 | };
54 |
--------------------------------------------------------------------------------
/src/utils/entity-generators/refueling-station-generator.js:
--------------------------------------------------------------------------------
1 | import Chance from 'chance';
2 |
3 | import Occupation from 'constants/refueling-station/occupation';
4 | import Situation from 'constants/refueling-station/situation';
5 | import Entities from 'constants/entities';
6 | import commonGenerator from './common-generator';
7 |
8 | export const generateRefuelingStation = commonGenerator(
9 | (entity, { hideOccAndSit = false, generate = true }) => {
10 | if (!generate) {
11 | return entity;
12 | }
13 | let visibility = {};
14 | if (hideOccAndSit) {
15 | visibility = {
16 | 'attr.occupation': false,
17 | 'attr.situation': false,
18 | };
19 | }
20 | const chance = new Chance();
21 | return {
22 | ...entity,
23 | visibility: {
24 | ...entity.visibility,
25 | ...visibility,
26 | },
27 | attributes: {
28 | ...entity.attributes,
29 | occupation: chance.pickone(Object.keys(Occupation.attributes)),
30 | situation: chance.pickone(Object.keys(Situation.attributes)),
31 | },
32 | };
33 | },
34 | );
35 |
36 | export const generateRefuelingStations = ({
37 | children = [...Array(new Chance().weighted([0, 1], [3, 1]))],
38 | additionalPointsOfInterest = true,
39 | ...config
40 | }) => {
41 | if (!additionalPointsOfInterest) {
42 | return { children: [] };
43 | }
44 | return {
45 | children: children.map(({ name, generate } = {}) =>
46 | generateRefuelingStation(Entities.refuelingStation.key, {
47 | name,
48 | generate,
49 | ...config,
50 | }),
51 | ),
52 | };
53 | };
54 |
--------------------------------------------------------------------------------
/src/utils/entity-generators/research-base-generator.js:
--------------------------------------------------------------------------------
1 | import Chance from 'chance';
2 |
3 | import Occupation from 'constants/research-base/occupation';
4 | import Situation from 'constants/research-base/situation';
5 | import Entities from 'constants/entities';
6 | import commonGenerator from './common-generator';
7 |
8 | export const generateResearchBase = commonGenerator(
9 | (entity, { hideOccAndSit = false, generate = true }) => {
10 | if (!generate) {
11 | return entity;
12 | }
13 | let visibility = {};
14 | if (hideOccAndSit) {
15 | visibility = {
16 | 'attr.occupation': false,
17 | 'attr.situation': false,
18 | };
19 | }
20 | const chance = new Chance();
21 | return {
22 | ...entity,
23 | visibility: {
24 | ...entity.visibility,
25 | ...visibility,
26 | },
27 | attributes: {
28 | ...entity.attributes,
29 | occupation: chance.pickone(Object.keys(Occupation.attributes)),
30 | situation: chance.pickone(Object.keys(Situation.attributes)),
31 | },
32 | };
33 | },
34 | );
35 |
36 | export const generateResearchBases = ({
37 | children = [...Array(new Chance().weighted([0, 1], [3, 1]))],
38 | additionalPointsOfInterest = true,
39 | ...config
40 | }) => {
41 | if (!additionalPointsOfInterest) {
42 | return { children: [] };
43 | }
44 | return {
45 | children: children.map(({ name, generate } = {}) =>
46 | generateResearchBase(Entities.researchBase.key, {
47 | name,
48 | generate,
49 | ...config,
50 | }),
51 | ),
52 | };
53 | };
54 |
--------------------------------------------------------------------------------
/src/utils/entity-generators/sector-generator.js:
--------------------------------------------------------------------------------
1 | import { ROWS, COLUMNS } from 'constants/defaults';
2 | import { generateSectorName } from 'utils/name-generator';
3 |
4 | export default ({
5 | sectorName = generateSectorName(),
6 | rows = ROWS,
7 | columns = COLUMNS,
8 | } = {}) => ({
9 | name: sectorName,
10 | rows,
11 | columns,
12 | });
13 |
--------------------------------------------------------------------------------
/src/utils/entity-generators/space-station-generator.js:
--------------------------------------------------------------------------------
1 | import Chance from 'chance';
2 |
3 | import Occupation from 'constants/space-station/occupation';
4 | import Situation from 'constants/space-station/situation';
5 | import Entities from 'constants/entities';
6 | import commonGenerator from './common-generator';
7 |
8 | export const generateSpaceStation = commonGenerator(
9 | (entity, { hideOccAndSit = false, generate = true }) => {
10 | if (!generate) {
11 | return entity;
12 | }
13 | let visibility = {};
14 | if (hideOccAndSit) {
15 | visibility = {
16 | 'attr.occupation': false,
17 | 'attr.situation': false,
18 | };
19 | }
20 | const chance = new Chance();
21 | return {
22 | ...entity,
23 | visibility: {
24 | ...entity.visibility,
25 | ...visibility,
26 | },
27 | attributes: {
28 | ...entity.attributes,
29 | occupation: chance.pickone(Object.keys(Occupation.attributes)),
30 | situation: chance.pickone(Object.keys(Situation.attributes)),
31 | },
32 | };
33 | },
34 | );
35 |
36 | export const generateSpaceStations = ({
37 | children = [...Array(new Chance().weighted([0, 1, 2], [3, 1, 1]))],
38 | additionalPointsOfInterest = true,
39 | ...config
40 | }) => {
41 | if (!additionalPointsOfInterest) {
42 | return { children: [] };
43 | }
44 | return {
45 | children: children.map(({ name, generate } = {}) =>
46 | generateSpaceStation(Entities.spaceStation.key, {
47 | name,
48 | generate,
49 | ...config,
50 | }),
51 | ),
52 | };
53 | };
54 |
--------------------------------------------------------------------------------
/src/utils/entity-generators/system-generator.js:
--------------------------------------------------------------------------------
1 | import Chance from 'chance';
2 |
3 | import { xor } from 'constants/lodash';
4 | import Entities from 'constants/entities';
5 | import commonGenerator from './common-generator';
6 |
7 | export const generateSystem = commonGenerator((entity, { x, y }) => {
8 | if (!x || !y) {
9 | throw new Error('Sector coordinate must be provided to generate a system');
10 | }
11 | return { x, y, ...entity };
12 | });
13 |
14 | export const generateSystems = ({
15 | additionalPointsOfInterest = true,
16 | ...config
17 | }) => {
18 | const chance = new Chance();
19 | const numHexes = config.rows * config.columns;
20 | const apoiModifier = additionalPointsOfInterest ? 10 : 4;
21 | const systemNum =
22 | chance.integer({ min: 0, max: Math.floor(numHexes / apoiModifier) }) +
23 | Math.floor(numHexes / 5);
24 | const chosenCoordinates = chance.pickset(config.coordinates, systemNum);
25 |
26 | return {
27 | coordinates: xor(config.coordinates, chosenCoordinates),
28 | children: chosenCoordinates.map(coordinate =>
29 | generateSystem(Entities.system.key, { ...coordinate, ...config }),
30 | ),
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/src/utils/export.js:
--------------------------------------------------------------------------------
1 | export const generateCSV = (table = []) =>
2 | table.reduce(
3 | (csvString, infoArray) =>
4 | `${csvString}${infoArray.map(item => `"${item}"`).join(',')}\n`,
5 | 'data:text/csv;charset=utf-8,',
6 | );
7 |
8 | export const createCSVDownload = (csvContent, fileName = 'data') => {
9 | const encodedUri = encodeURI(csvContent);
10 | const link = document.createElement('a');
11 | link.setAttribute('href', encodedUri);
12 | link.setAttribute('download', `${fileName}.csv`);
13 | document.body.appendChild(link); // Required for FF
14 | link.click();
15 | };
16 |
17 | export const createImageDownlaod = canvasId => {
18 | const canvas = document.getElementById(canvasId);
19 | const image = canvas
20 | .toDataURL('image/jpg')
21 | .replace('image/jpg', 'image/octet-stream');
22 | const link = document.createElement('a');
23 | link.setAttribute('href', image);
24 | link.setAttribute('download', 'sector-map.jpg');
25 | document.body.appendChild(link); // Required for FF
26 | link.click();
27 | };
28 |
29 | export const createJSONDownload = (jsonContent, fileName = 'data') => {
30 | const str = JSON.stringify(jsonContent, null, 2);
31 | const dataUri = `data:application/json;charset=utf-8,${encodeURIComponent(
32 | str,
33 | )}`;
34 | const link = document.createElement('a');
35 | link.setAttribute('href', dataUri);
36 | link.setAttribute('download', `${fileName}.json`);
37 | document.body.appendChild(link); // Required for FF
38 | link.click();
39 | };
40 |
--------------------------------------------------------------------------------
/src/utils/hex/common.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Assumes “odd-q” vertical layout
3 | * See https://www.redblobgames.com/grids/hexagons
4 | */
5 |
6 | const sizeDiff = Math.sqrt(3) / 2;
7 | export const toWidth = height => height / sizeDiff;
8 | export const toHeight = width => width * sizeDiff;
9 |
10 | // Height/Vertical Calculations
11 | export const getTotalHeight = (hexHeight, rows) => (rows + 1 / 2) * hexHeight;
12 | export const getHexHeight = (totalHeight, rows) => totalHeight / (rows + 1 / 2);
13 | export const getRows = (totalHeight, hexHeight) =>
14 | (totalHeight - 2 / hexHeight) / hexHeight;
15 |
16 | // Width/Horizontal Calculations
17 | export const getTotalWidth = (hexWidth, columns) =>
18 | (hexWidth / 4) * (3 * columns + 1);
19 | export const getHexWidth = (totalWidth, columns) =>
20 | (4 * totalWidth) / (3 * columns + 1);
21 | export const getColumns = (totalWidth, hexWidth) =>
22 | (4 * totalWidth) / (3 * hexWidth) - 1 / 3;
23 |
24 | // Other Helpers
25 | export const areNeighbors = (a, b) => {
26 | if (a.x % 2 === 1) {
27 | return (
28 | (Math.abs(a.x - b.x) <= 1 && [0, 1].indexOf(a.y - b.y) >= 0) ||
29 | (a.x === b.x && a.y === b.y - 1)
30 | );
31 | }
32 | return (
33 | (Math.abs(a.x - b.x) <= 1 && [0, 1].indexOf(b.y - a.y) >= 0) ||
34 | (a.x === b.x && a.y === b.y + 1)
35 | );
36 | };
37 |
38 | export const getHexPoints = ({ width, xOffset, yOffset }) => {
39 | const radius = width / 2;
40 | const hexagon = [];
41 |
42 | for (let i = 0; i < 6; i += 1) {
43 | const pointOnCircle = (i * Math.PI) / 3;
44 | const x = radius * Math.cos(pointOnCircle);
45 | const y = radius * Math.sin(pointOnCircle);
46 | hexagon.push({ x: x + xOffset, y: y + yOffset });
47 | }
48 |
49 | return hexagon;
50 | };
51 |
--------------------------------------------------------------------------------
/src/utils/toasts.js:
--------------------------------------------------------------------------------
1 | import { actions as ReduxToastrActions } from 'react-redux-toastr';
2 |
3 | export const SuccessToast = ({ title = '', message = '', config } = {}) =>
4 | ReduxToastrActions.add({
5 | options: {
6 | removeOnHover: true,
7 | showCloseButton: true,
8 | },
9 | position: 'bottom-left',
10 | type: 'success',
11 | ...(config || {}),
12 | message,
13 | title,
14 | });
15 |
16 | export const InfoToast = ({ title = '', message = '', config } = {}) =>
17 | ReduxToastrActions.add({
18 | options: {
19 | removeOnHover: true,
20 | showCloseButton: true,
21 | },
22 | position: 'bottom-left',
23 | type: 'info',
24 | ...(config || {}),
25 | message,
26 | title,
27 | });
28 |
29 | export const WarningToast = ({ title = '', message = '', config } = {}) =>
30 | ReduxToastrActions.add({
31 | options: {
32 | removeOnHover: true,
33 | showCloseButton: true,
34 | },
35 | position: 'bottom-left',
36 | type: 'warning',
37 | ...(config || {}),
38 | message,
39 | title,
40 | });
41 |
42 | export const ErrorToast = ({ title = '', message = '', config } = {}) =>
43 | ReduxToastrActions.add({
44 | options: {
45 | removeOnHover: true,
46 | showCloseButton: true,
47 | },
48 | position: 'bottom-left',
49 | type: 'error',
50 | ...(config || {}),
51 | title,
52 | message,
53 | });
54 |
55 | export const removeToastById = ReduxToastrActions.remove;
56 |
--------------------------------------------------------------------------------