Pickup user's preferred language from navigator.languages (#30)

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
Co-authored-by: msramalho <19508417+msramalho@users.noreply.github.com>
Co-authored-by: Felix Spöttel <1682504+fspoettel@users.noreply.github.com>
This commit is contained in:
wattroll
2022-04-07 16:17:30 +03:00
committed by GitHub
parent f024d2195a
commit 0d1968a110
23 changed files with 689 additions and 346 deletions

View File

@@ -150,6 +150,7 @@ module.exports = {
COLOR_BY_ASSOCIATION: true,
USE_ASSOCIATIONS: true,
USE_FULLSCREEN: true,
USE_DOWNLOAD: true,
USE_SOURCES: true,
USE_SPOTLIGHTS: false,
USE_SHAPES: false,

99
package-lock.json generated
View File

@@ -47,6 +47,7 @@
"jest-resolve": "26.6.0",
"jest-watch-typeahead": "0.6.1",
"joi": "^14.0.1",
"json2csv": "^5.0.7",
"leaflet": "^1.0.3",
"lint-staged": "^10.5.3",
"marked": "^0.7.0",
@@ -83,7 +84,7 @@
"screenfull": "^5.2.0",
"semver": "7.3.2",
"style-loader": "1.3.0",
"supercluster": "^7.1.0",
"supercluster": "^7.1.5",
"terser-webpack-plugin": "4.2.3",
"ts-pnp": "1.2.0",
"url-loader": "4.1.1",
@@ -15261,6 +15262,31 @@
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
"devOptional": true
},
"node_modules/json2csv": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/json2csv/-/json2csv-5.0.7.tgz",
"integrity": "sha512-YRZbUnyaJZLZUJSRi2G/MqahCyRv9n/ds+4oIetjDF3jWQA7AG7iSeKTiZiCNqtMZM7HDyt0e/W6lEnoGEmMGA==",
"dependencies": {
"commander": "^6.1.0",
"jsonparse": "^1.3.1",
"lodash.get": "^4.4.2"
},
"bin": {
"json2csv": "bin/json2csv.js"
},
"engines": {
"node": ">= 10",
"npm": ">= 6.13.0"
}
},
"node_modules/json2csv/node_modules/commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
"engines": {
"node": ">= 6"
}
},
"node_modules/json3": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz",
@@ -15288,6 +15314,14 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsonparse": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
"engines": [
"node >= 0.2.0"
]
},
"node_modules/jsprim": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
@@ -15845,6 +15879,11 @@
"integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
"dev": true
},
"node_modules/lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -23256,9 +23295,9 @@
}
},
"node_modules/supercluster": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.4.tgz",
"integrity": "sha512-GhKkRM1jMR6WUwGPw05fs66pOFWhf59lXq+Q3J3SxPvhNcmgOtLRV6aVQPMRsmXdpaeFJGivt+t7QXUPL3ff4g==",
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz",
"integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==",
"dependencies": {
"kdbush": "^3.0.0"
}
@@ -24434,19 +24473,6 @@
"is-typedarray": "^1.0.0"
}
},
"node_modules/typescript": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz",
"integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/ua-parser-js": {
"version": "0.7.31",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
@@ -37962,6 +37988,23 @@
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
"devOptional": true
},
"json2csv": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/json2csv/-/json2csv-5.0.7.tgz",
"integrity": "sha512-YRZbUnyaJZLZUJSRi2G/MqahCyRv9n/ds+4oIetjDF3jWQA7AG7iSeKTiZiCNqtMZM7HDyt0e/W6lEnoGEmMGA==",
"requires": {
"commander": "^6.1.0",
"jsonparse": "^1.3.1",
"lodash.get": "^4.4.2"
},
"dependencies": {
"commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="
}
}
},
"json3": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz",
@@ -37981,6 +38024,11 @@
"universalify": "^2.0.0"
}
},
"jsonparse": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA="
},
"jsprim": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
@@ -38407,6 +38455,11 @@
"integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
"dev": true
},
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -44233,9 +44286,9 @@
}
},
"supercluster": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.4.tgz",
"integrity": "sha512-GhKkRM1jMR6WUwGPw05fs66pOFWhf59lXq+Q3J3SxPvhNcmgOtLRV6aVQPMRsmXdpaeFJGivt+t7QXUPL3ff4g==",
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz",
"integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==",
"requires": {
"kdbush": "^3.0.0"
}
@@ -45128,12 +45181,6 @@
"is-typedarray": "^1.0.0"
}
},
"typescript": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz",
"integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==",
"peer": true
},
"ua-parser-js": {
"version": "0.7.31",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",

View File

@@ -54,6 +54,7 @@
"jest-resolve": "26.6.0",
"jest-watch-typeahead": "0.6.1",
"joi": "^14.0.1",
"json2csv": "^5.0.7",
"leaflet": "^1.0.3",
"lint-staged": "^10.5.3",
"marked": "^0.7.0",
@@ -90,7 +91,7 @@
"screenfull": "^5.2.0",
"semver": "7.3.2",
"style-loader": "1.3.0",
"supercluster": "^7.1.0",
"supercluster": "^7.1.5",
"terser-webpack-plugin": "4.2.3",
"ts-pnp": "1.2.0",
"url-loader": "4.1.1",

View File

@@ -341,6 +341,14 @@ export function toggleLanguage(language) {
};
}
export const CHANGE_NAVIGATOR_LANGUAGES = "CHANGE_NAVIGATOR_LANGUAGES";
export function changeNavigatorLanguages(languages) {
return {
type: CHANGE_NAVIGATOR_LANGUAGES,
languages,
};
}
export const CLOSE_TOOLBAR = "CLOSE_TOOLBAR";
export function closeToolbar() {
return {

View File

@@ -11,6 +11,7 @@ export const DEFAULT_TAB_ICONS = {
NARRATIVE: "timeline",
FILTER: "filter_list",
SHAPE: "change_history",
DOWNLOAD: "download",
};
export const AVAILABLE_SHAPES = {
@@ -24,8 +25,7 @@ export const AVAILABLE_SHAPES = {
};
export const POLYGON_CLIP_PATH = {
STAR:
"polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)",
STAR: "polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)",
DIAMOND: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)",
PENTAGON: "polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%)",
TRIANGLE: "polygon(50% 0%, 0% 100%, 100% 100%)",

View File

@@ -160,7 +160,24 @@
"explore_by_shapes__title": "Explore events by shape breakdown",
"explore_by_shape__description": "Shapes map to a given type of event that appears on the timeline.<br><br>Select the shape marker to toggle this type of event on / off",
"fullscreen_enter": "Fullscreen",
"fullscreen_exit": "Exit Fullscreen"
"fullscreen_exit": "Exit Fullscreen",
"download": {
"button": "Download",
"panel": {
"title": "Download events",
"description": "Export the most recent available events in different formats.",
"formats": {
"csv": {
"label": "CSV",
"description": "CSV file where sources and filters are concatenated into a single column due to data structure limitations."
},
"json": {
"label": "JSON",
"description": "JSON file where each event is a structured object containing nested arrays of sources and filters."
}
}
}
}
},
"timeline": {
"labels_title": "Testimonies",

35
src/common/language.js Normal file
View File

@@ -0,0 +1,35 @@
/**
* Picks the most preferred of the available languages. Comparison only on
* the primary language tag. Region, variant, and script tags are ignored.
*
* @param {readonly string[]} availableLanguages: Languages to pick from.
* @param {readonly string[]} preferredLanguages: Languages to prefer.
* @return {string|undefined} matching language, if any.
* @example
* pickPreferredLanguage(['en', 'ru', 'uk'], ['be-BY', 'ru-BY', 'en-UK']) // => 'ru'
*/
export function pickPreferredLanguage(availableLanguages, preferredLanguages) {
for (const preferredLanguage of preferredLanguages) {
const preferredLanguageCode = languageTagCode(preferredLanguage);
for (const availableLanguage of availableLanguages) {
const availableLanguageCode = languageTagCode(availableLanguage);
if (availableLanguageCode === preferredLanguageCode)
return availableLanguage;
}
}
}
/**
* Takes the language tag as per RFC5646 ("uk", "uk-UA", "ru-BY", en-GB").
* Returns the primary language-code subtag (lower-case, ISO 639).
*
* @param {string} languageTag language tag with one or more subtags
* @return {string|undefined} first subtag, two/three lowercase letters
* @example languageTagCode('en-US') // => 'en'
* @example languageTagCode('uk') // => 'uk'
* @see https://tools.ietf.org/html/rfc5646
*/
export function languageTagCode(languageTag) {
const matches = languageTag.toLowerCase().match(/^[a-z]{2,3}/);
return (matches ?? [])[0];
}

View File

@@ -0,0 +1,31 @@
import { languageTagCode, pickPreferredLanguage } from "./language.js";
describe("language tag matching", () => {
test("languageTagCode", () => {
expect(languageTagCode("en-US")).toEqual("en");
expect(languageTagCode("uk")).toEqual("uk");
expect(languageTagCode("UK")).toEqual("uk");
expect(languageTagCode("i-")).toBeUndefined();
expect(languageTagCode("-")).toBeUndefined();
expect(languageTagCode("")).toBeUndefined();
});
test("pickPreferredLanguage", () => {
expect(pickPreferredLanguage(["en", "ru", "uk"], ["en-GB"])).toBe("en");
expect(pickPreferredLanguage(["en-US", "uk-UA"], ["en-GB"])).toBe("en-US");
expect(
pickPreferredLanguage(
["en", "ru", "uk"],
["pl-PL", "uk-UA", "ru-RU", "en-GB"]
)
).toBe("uk");
expect(
pickPreferredLanguage(
["en-US", "ru-RU", "uk-UA"],
["pl-PL", "ru-BY", "en-GB"]
)
).toBe("ru-RU");
expect(
pickPreferredLanguage(["en", "uk"], ["sv-SE", "fr-FR"])
).toBeUndefined();
});
});

View File

@@ -576,4 +576,20 @@ export function getFilterIdx(
else return 0;
}
export function downloadAsFile(filename, content) {
let element = document.createElement("a");
element.setAttribute(
"href",
`data:application/octet-stream;charset=utf-8,${encodeURIComponent(content)}`
);
element.setAttribute("download", filename);
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
export const isEmptyString = (s) => s.length === 0;

View File

@@ -307,6 +307,7 @@ class Dashboard extends React.Component {
{checkMobile ? null : (
<Toolbar
isNarrative={!!app.associations.narrative}
domain={domain}
methods={{
onTitle: actions.toggleCover,
onSelectFilter: (filters) =>

View File

@@ -24,6 +24,7 @@ import {
import { ToolbarButton } from "./controls/atoms/ToolbarButton";
import { FullscreenToggle } from "./controls/FullScreenToggle";
import { LanguageSwitch } from "./controls/LanguageSwitch";
import DownloadPanel from "./controls/DownloadPanel";
class Toolbar extends React.Component {
constructor(props) {
@@ -181,6 +182,21 @@ class Toolbar extends React.Component {
}
}
renderToolbarDownloadPanel() {
const { panels } = this.props.toolbarCopy;
return (
<TabPanel>
<DownloadPanel
language={this.props.language}
title={panels.download.label}
description={panels.download.description}
domain={this.props.domain}
/>
</TabPanel>
);
}
renderToolbarTab(_selected, label, iconKey, key) {
return (
<ToolbarButton
@@ -224,6 +240,7 @@ class Toolbar extends React.Component {
{features.USE_CATEGORIES ? this.renderToolbarCategoriesPanel() : null}
{features.USE_ASSOCIATIONS ? this.renderToolbarFilterPanel() : null}
{features.USE_SHAPES ? this.renderToolbarShapePanel() : null}
{features.USE_DOWNLOAD ? this.renderToolbarDownloadPanel() : null}
</div>
);
}
@@ -268,7 +285,8 @@ class Toolbar extends React.Component {
features.USE_CATEGORIES,
numCategoryPanels || 0
);
const shapesIdx = filtersIdx + 1;
const shapesIdx = filtersIdx + features.USE_SHAPES;
const downloadIdx = shapesIdx + features.USE_DOWNLOAD;
return (
<div className="toolbar">
@@ -310,6 +328,13 @@ class Toolbar extends React.Component {
panels.shapes.icon
)
: null}
{features.USE_DOWNLOAD
? this.renderToolbarTab(
downloadIdx,
panels.download.label,
panels.download.icon
)
: null}
{features.USE_FULLSCREEN && (
<FullscreenToggle language={this.props.language} />
)}

View File

@@ -29,7 +29,13 @@ function BottomActions(props) {
<CoverIcon onClickHandler={props.cover.toggle} />
) : null}
</div>
<div style={{ fontSize: 9, paddingTop: 10 }}>Made with <a href="https://github.com/forensic-architecture/timemap">TimeMap</a><br />Free software from <br /> <a href="https://forensic-architecture.org">Forensic Architecture</a></div>
<div style={{ fontSize: 9, paddingTop: 10 }}>
Made with{" "}
<a href="https://github.com/forensic-architecture/timemap">TimeMap</a>
<br />
Free software from <br />{" "}
<a href="https://forensic-architecture.org">Forensic Architecture</a>
</div>
</>
);
}

View File

@@ -0,0 +1,86 @@
import React from "react";
import copy from "../../common/data/copy.json";
import { parse } from "json2csv";
import { downloadAsFile } from "../../common/utilities";
export class DownloadButton extends React.Component {
onDownload(format, domain) {
let filename = `ukr-civharm-${this.datetimeToDateString(new Date())}`;
if (format === "csv") {
let outputData = this.getCsvData(domain);
downloadAsFile(`${filename}.csv`, outputData);
} else if (format === "json") {
let outputData = this.getJsonData(domain);
downloadAsFile(`${filename}.json`, outputData);
}
}
getCsvData(domain) {
const { events, sources } = domain;
const exportEvents = events.map((e) => {
return {
id: e.civId,
date: this.datetimeToDateString(e.datetime),
latitude: e.latitude,
longitude: e.longitude,
location: e.location,
description: e.description,
sources: e.sources.map((s) => sources[s].paths[0]).join(","),
associations: e.associations
.map((a) => a.filter_paths.join("="))
.join(","),
};
});
return parse(exportEvents, { flatten: true });
}
getJsonData(domain) {
const { events, sources } = domain;
const exportEvents = events.map((e) => {
return {
id: e.civId,
date: this.datetimeToDateString(e.datetime),
latitude: e.latitude,
longitude: e.longitude,
location: e.location,
description: e.description,
sources: e.sources.map((id) => {
const s = sources[id];
return {
id,
path: s.paths[0],
description: s.description,
};
}),
filters: e.associations.map((a) => {
return {
key: a.filter_paths[0],
value: a.filter_paths[1],
};
}),
};
});
return JSON.stringify(exportEvents);
}
datetimeToDateString(datetime) {
try {
return datetime.toISOString().split("T")[0];
} catch (_) {}
return "";
}
render() {
const { language, domain, format } = this.props;
const textByFormat = copy[language].toolbar.download.panel.formats[format];
return (
<div className="download-row">
<span
className="download-button"
key={`download-${format}`}
onClick={() => this.onDownload(format, domain)}
>
<i className="material-icons">{"download"}</i>
<span className="tab-caption">{textByFormat.label}</span>
</span>
<span className="download-description">{textByFormat.description}</span>
</div>
);
}
}

View File

@@ -0,0 +1,16 @@
import React from "react";
import { DownloadButton } from "./DownloadButton";
const DownloadPanel = ({ language, title, description, domain }) => {
return (
<div className="react-innertabpanel">
<h2>{title}</h2>
<p>{description}</p>
<hr />
<DownloadButton language={language} domain={domain} format="csv" />
<DownloadButton language={language} domain={domain} format="json" />
</div>
);
};
export default DownloadPanel;

View File

@@ -74,7 +74,10 @@ const Media = ({ src, title }) => {
return (
<div className="card-cell media embedded">
<TwitterTweetEmbed tweetId={tweetId} options={{ conversation: "none" }} />
<TwitterTweetEmbed
tweetId={tweetId}
options={{ conversation: "none" }}
/>
</div>
);
default:

View File

@@ -2,6 +2,7 @@
import { bindActionCreators } from "redux";
import "leaflet";
import React from "react";
import { flushSync } from "react-dom";
import { Portal } from "react-portal";
import Supercluster from "supercluster";
import { isMobileOnly } from "react-device-detect";
@@ -173,8 +174,10 @@ class Map extends React.Component {
this.map.dragging.enable();
this.map.doubleClickZoom.enable();
this.map.scrollWheelZoom.enable();
this.alignLayers();
this.updateClusters();
flushSync(() => {
this.alignLayers();
this.updateClusters();
});
});
map.on("zoomstart", () => {
if (this.svgRef.current !== null)

View File

@@ -1,7 +1,7 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import store from "./store";
import store, { dispatchNavigatorLanguagesChange } from "./store";
import App from "./components/App";
import copy from "./common/data/copy.json";
@@ -19,6 +19,10 @@ root.render(
</Provider>
);
// pick up user's preferred language on startup and whenever it changes
dispatchNavigatorLanguagesChange();
window.addEventListener("languagechange", dispatchNavigatorLanguagesChange);
store.subscribe(() => {
const { app } = store.getState();
renderAppLanguage(app);

View File

@@ -1,6 +1,7 @@
import initial from "../store/initial.js";
import { ASSOCIATION_MODES } from "../common/constants";
import { toggleFlagAC } from "../common/utilities";
import { pickPreferredLanguage } from "../common/language.js";
import * as selectors from "../selectors";
import {
@@ -31,6 +32,7 @@ import {
SET_INITIAL_CATEGORIES,
SET_INITIAL_SHAPES,
UPDATE_SEARCH_QUERY,
CHANGE_NAVIGATOR_LANGUAGES,
} from "../actions";
function updateHighlighted(appState, action) {
@@ -238,6 +240,13 @@ function toggleLanguage(appState, action) {
}
}
function changeNavigatorLanguages(appState, action) {
const preferred = action.languages;
const available = appState.languages || [appState.language];
const language = pickPreferredLanguage(available, preferred);
return language ? { ...appState, language } : appState;
}
function updateSource(appState, action) {
return {
...appState,
@@ -341,6 +350,8 @@ function app(appState = initial.app, action) {
return updateNarrativeStepIdx(appState, action);
case UPDATE_SOURCE:
return updateSource(appState, action);
case CHANGE_NAVIGATOR_LANGUAGES:
return changeNavigatorLanguages(appState, action);
/* toggles */
case TOGGLE_LANGUAGE:
return toggleLanguage(appState, action);

View File

@@ -1,343 +1,344 @@
.cover-container {
position: absolute;
top: -100%;
left: 0;
height: 100vh;
background-color: black;
width: 100%;
opacity: 1;
transition: top 0.4s ease;
z-index: $loading-overlay + 1;
overflow-y: auto;
overflow-x: hidden;
color: $offwhite;
position: absolute;
top: -100%;
left: 0;
height: 100vh;
background-color: black;
width: 100%;
opacity: 1;
transition: top 0.4s ease;
z-index: $loading-overlay + 1;
overflow-y: auto;
overflow-x: hidden;
color: $offwhite;
&.showing {
top: 0;
left: 0;
}
&.showing {
top: 0;
left: 0;
}
}
.cover-header {
position: fixed;
bottom: 20px;
left: 0;
position: fixed;
bottom: 20px;
left: 0;
display: flex;
width: 100vw;
@media only screen and (max-width: 1200px) {
position: inherit;
}
.cover-logo-container {
padding: 20px 0 0 20px;
display: flex;
width: 100vw;
@media only screen and (max-width: 1200px) {
position: inherit;
}
.cover-logo-container {
padding: 20px 0 0 20px;
display: flex;
&.minimized {}
.cover-logo {
transition: all 1s;
width: 60px;
height: 60px;
}
}
&.minimized {
bottom: 150px;
max-width: $toolbar-width;
max-height: 30px;
justify-content: center;
align-items: center;
flex-direction: column;
.cover-logo-container {
padding: 5px;
}
.cover-logo {
width: 60px;
height: 60px;
}
}
.cover-logo {
transition: all 1s;
width: 60px;
height: 60px;
}
}
&.minimized {
bottom: 150px;
max-width: $toolbar-width;
max-height: 30px;
justify-content: center;
align-items: center;
flex-direction: column;
.cover-logo-container {
padding: 5px;
}
.cover-logo {
width: 60px;
height: 60px;
}
}
}
.fullscreen-bg {
&.hidden {
top: -100%;
}
&.hidden {
top: -100%;
}
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
// overflow: hidden;
z-index: -100;
background: #000000;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
// overflow: hidden;
z-index: -100;
background: #000000;
}
.fullscreen-bg__video {
position: relative;
top: 0;
left: -25vw;
width: 150vw;
height: 100vh;
-webkit-filter: contrast(70%) brightness(70%) grayscale(100%);
filter: contrast(70%) brightness(70%) grayscale(100%);
position: relative;
top: 0;
left: -25vw;
width: 150vw;
height: 100vh;
-webkit-filter: contrast(70%) brightness(70%) grayscale(100%);
filter: contrast(70%) brightness(70%) grayscale(100%);
@media only screen and (max-width: 992px) {
display: none;
}
@media only screen and (max-width: 992px) {
display: none;
}
}
.default-cover-container {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.cover-container {
font-size: 12pt;
font-size: 12pt;
display: flex;
flex-direction: column;
max-height: 100%;
hr,
br {
width: 100%;
}
.sidebar {
display: flex;
flex-direction: column;
max-height: 100%;
hr,
br {
width: 100%;
}
.sidebar {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: space-around;
position: fixed;
left: 0;
background-color: $offwhite;
margin-top: 60px;
min-height: calc(100% - 280px);
max-height: calc(100% - 280px);
min-width: 19%;
max-width: 19%;
color: black;
.il-video-pill {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
flex: 1;
background-color: transparent;
border-bottom: 5px solid black;
transition: all 0.4s ease;
&.explore {
background-color: $yellow;
}
&.videos {
background-color: blue;
}
&:hover {
cursor: pointer;
background-color: $darkwhite;
color: white;
}
}
}
.hero {
min-width: 100%;
min-height: 80px;
margin: auto;
display: flex;
flex-direction: column;
margin-bottom: 20px;
margin-top: 60px;
@media only screen and (max-width: 1200px) {
min-height: 250px;
}
.row {
display: flex;
flex: 1;
flex-direction: row;
@media only screen and (max-width: 1200px) {
flex-direction: column;
}
justify-content: space-around;
&.vertical {
flex-direction: column;
}
.cell {
border: 1px solid white;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
flex: 1;
background-color: $darkgrey;
padding: 10px 0;
transition: all 0.4s ease;
letter-spacing: 2px;
min-height: 40px;
&.small {
letter-spacing: inherit;
font-size: 10pt;
}
&.plain {
min-height: 10px;
background-color: black;
letter-spacing: 1px;
@media only screen and (max-width: 1200px) {
min-height: 100px;
}
}
&.yellow {
color: black !important;
background-color: $yellow;
}
&:hover {
cursor: pointer;
background-color: $darkwhite;
color: white;
}
@media only screen and (max-width: 1200px) {
min-height: 100px;
}
}
}
}
.cover-content {
display: flex;
flex-direction: column;
max-width: 600px;
overflow-y: auto;
overflow-x: hidden;
padding-bottom: 10em;
h1,
h2,
h3,
h4,
h5 {
text-align: center;
}
h1 {
margin-bottom: -15px;
margin-top: 30px;
}
h5 {
margin-top: -15px;
}
.md-container {
width: 100%;
overflow-wrap: break-word;
// white-space: pre-line;
ul {
list-style: none;
}
li::before {
content: "* ";
}
p {
text-align: justify;
}
}
// mobile styles, remove overlay buttons
@media only screen and (max-width: 1200px) {
font-size: 22pt !important;
max-width: 100vw;
padding: 0 40px 80px 40px;
margin-bottom: 0;
}
.verify-tabs {
background-color: $yellow;
color: black;
display: flex;
flex-direction: column;
.v-tab {
display: flex;
margin: auto;
justify-content: center;
align-content: center;
flex: 1;
}
}
.il-cover-verification-container {
display: flex;
flex-direction: column;
.il-cover-verification {
.il-video {
border-radius: 1em;
background-color: rgba(240, 240, 240, 0.5);
}
}
}
}
_::-webkit-full-page-media,
_:future,
:root .cover-content {
max-width: auto;
}
}
.cover-footer {
&.disabled {
display: none;
}
justify-content: space-around;
align-items: space-around;
position: fixed;
bottom: 0;
min-height: 150px;
min-width: 100%;
padding: 10px;
background-color: black;
display: flex;
justify-content: center;
left: 0;
background-color: $offwhite;
margin-top: 60px;
min-height: calc(100% - 280px);
max-height: calc(100% - 280px);
min-width: 19%;
max-width: 19%;
color: black;
.il-cover-button {
.il-video-pill {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
flex: 1;
background-color: transparent;
border-bottom: 5px solid black;
transition: all 0.4s ease;
&.explore {
background-color: $yellow;
}
&.videos {
background-color: blue;
}
&:hover {
cursor: pointer;
background-color: $darkwhite;
color: white;
}
}
}
.hero {
min-width: 100%;
min-height: 80px;
margin: auto;
display: flex;
flex-direction: column;
margin-bottom: 20px;
margin-top: 60px;
@media only screen and (max-width: 1200px) {
min-height: 250px;
}
.row {
display: flex;
flex: 1;
flex-direction: row;
@media only screen and (max-width: 1200px) {
flex-direction: column;
}
justify-content: space-around;
&.vertical {
flex-direction: column;
}
.cell {
border: 1px solid white;
display: flex;
justify-content: center;
align-items: center;
min-width: 300px;
max-height: 80px;
margin-top: 30px;
background-color: $offwhite;
color: black;
transition: all 0.3s ease;
text-align: center;
flex: 1;
background-color: $darkgrey;
padding: 10px 0;
transition: all 0.4s ease;
letter-spacing: 2px;
min-height: 40px;
&.small {
letter-spacing: inherit;
font-size: 10pt;
}
&.plain {
min-height: 10px;
background-color: black;
letter-spacing: 1px;
@media only screen and (max-width: 1200px) {
min-height: 100px;
}
}
&.yellow {
color: black !important;
background-color: $yellow;
}
&:hover {
cursor: pointer;
background-color: darken($offwhite, 30%);
color: black;
cursor: pointer;
background-color: $darkwhite;
color: white;
}
@media only screen and (max-width: 1200px) {
min-height: 100px;
}
}
}
}
}
.cover-content {
display: flex;
flex-direction: column;
max-width: 600px;
overflow-y: auto;
overflow-x: hidden;
padding-bottom: 10em;
h1,
h2,
h3,
h4,
h5 {
text-align: center;
}
h1 {
margin-bottom: -15px;
margin-top: 30px;
}
h5 {
margin-top: -15px;
}
.md-container {
width: 100%;
overflow-wrap: break-word;
// white-space: pre-line;
ul {
list-style: none;
}
li::before {
content: "* ";
}
p {
text-align: justify;
}
}
// mobile styles, remove overlay buttons
@media only screen and (max-width: 1200px) {
font-size: 22pt !important;
max-width: 100vw;
padding: 0 40px 80px 40px;
margin-bottom: 0;
}
.verify-tabs {
background-color: $yellow;
color: black;
display: flex;
flex-direction: column;
.v-tab {
display: flex;
margin: auto;
justify-content: center;
align-content: center;
flex: 1;
}
}
.il-cover-verification-container {
display: flex;
flex-direction: column;
.il-cover-verification {
.il-video {
border-radius: 1em;
background-color: rgba(240, 240, 240, 0.5);
}
}
}
}
_::-webkit-full-page-media,
_:future,
:root .cover-content {
max-width: auto;
}
}
.cover-footer {
&.disabled {
display: none;
}
position: fixed;
bottom: 0;
min-height: 150px;
min-width: 100%;
padding: 10px;
background-color: black;
display: flex;
justify-content: center;
.il-cover-button {
display: flex;
justify-content: center;
align-items: center;
min-width: 300px;
max-height: 80px;
margin-top: 30px;
background-color: $offwhite;
color: black;
transition: all 0.3s ease;
&:hover {
cursor: pointer;
background-color: darken($offwhite, 30%);
color: black;
}
}
}

View File

@@ -117,6 +117,10 @@
&.hide {
display: none;
}
&:focus {
outline: none;
}
}
.leaflet-popup {

View File

@@ -186,7 +186,21 @@
}
}
.toolbar-tab {
.download-row {
display: flex;
flex-direction: row;
}
.download-button {
flex: 1 1 auto;
}
.download-description {
flex: 1 5 auto;
text-align: justify;
margin: auto;
}
.toolbar-tab,
.download-button {
display: flex;
align-items: center;
justify-content: center;

View File

@@ -2,6 +2,8 @@ import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "../reducers";
import { changeNavigatorLanguages } from "../actions";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
@@ -9,4 +11,9 @@ const store = createStore(
composeEnhancers(applyMiddleware(thunk))
);
export function dispatchNavigatorLanguagesChange() {
const languages = navigator.languages || [navigator.language];
return store.dispatch(changeNavigatorLanguages(languages));
}
export default store;

View File

@@ -140,6 +140,12 @@ const initial = {
title: copy[language].toolbar.explore_by_shape__title,
description: copy[language].toolbar.explore_by_shape__description,
},
download: {
icon: DEFAULT_TAB_ICONS.DOWNLOAD,
label: copy[language].toolbar.download.button,
title: copy[language].toolbar.download.panel.title,
description: copy[language].toolbar.download.panel.description,
},
},
},
loading: false,