mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-08 03:18:36 +03:00
Merge branch 'main' into fix-warnings
This commit is contained in:
21
config.js
21
config.js
@@ -38,8 +38,25 @@ module.exports = {
|
||||
{ label: "Zoom to 1 month", duration: 31 * one_day },
|
||||
{ label: "Zoom to 3 months", duration: 3 * 31 * one_day },
|
||||
],
|
||||
range: [new Date(Date.now() - 31 * (60 * 60 * 1000 * 24)), new Date()],
|
||||
// rangeLimits: []
|
||||
range: {
|
||||
/**
|
||||
* Initial date range shown on map load.
|
||||
* Use [start, end] (strings in ISO 8601 format) for a fixed range.
|
||||
* Use undefined for a dynamic initial range based on the browser time.
|
||||
*/
|
||||
initial: undefined,
|
||||
/** The number of days to show when using a dynamic initial range */
|
||||
initialDaysShown: 31,
|
||||
limits: {
|
||||
/** Required. The lower bound of the range that can be accessed on the map. (ISO 8601) */
|
||||
lower: "2022-02-01T00:00:00.000Z",
|
||||
/**
|
||||
* The upper bound of the range that can be accessed on the map.
|
||||
* Defaults to current browser time if undefined.
|
||||
*/
|
||||
upper: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
intro: [
|
||||
'<div style="display:flex; flex-direction: row; width: 100%; min-width: calc(100% - 20px); max-width: 25vw; margin-top: 20px; gap: 20px; justify-content: space-between;"><img style="max-width:35vw; width:50%;" src="https://bellingcat-embeds.ams3.cdn.digitaloceanspaces.com/ukraine-timemap/cover01-s.jpg" frameborder="0"></img><img style="max-width:35vw; width:50%;" src="https://bellingcat-embeds.ams3.cdn.digitaloceanspaces.com/ukraine-timemap/cover02-s.jpg" frameborder="0"></img></div>',
|
||||
|
||||
13
package-lock.json
generated
13
package-lock.json
generated
@@ -96,6 +96,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/highlight": "^7.14.5",
|
||||
"ava": "1.0.0-beta.8",
|
||||
"jest-date-mock": "^1.0.8",
|
||||
"mocha": "^5.2.0",
|
||||
"node-sass": "^6.0",
|
||||
"redux-devtools": "^3.4.0"
|
||||
@@ -13365,6 +13366,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-date-mock": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/jest-date-mock/-/jest-date-mock-1.0.8.tgz",
|
||||
"integrity": "sha512-0Lyp+z9xvuNmLbK+5N6FOhSiBeux05Lp5bbveFBmYo40Aggl2wwxFoIrZ+rOWC8nDNcLeBoDd2miQdEDSf3iQw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jest-diff": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz",
|
||||
@@ -36604,6 +36611,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"jest-date-mock": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/jest-date-mock/-/jest-date-mock-1.0.8.tgz",
|
||||
"integrity": "sha512-0Lyp+z9xvuNmLbK+5N6FOhSiBeux05Lp5bbveFBmYo40Aggl2wwxFoIrZ+rOWC8nDNcLeBoDd2miQdEDSf3iQw==",
|
||||
"dev": true
|
||||
},
|
||||
"jest-diff": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz",
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/highlight": "^7.14.5",
|
||||
"ava": "1.0.0-beta.8",
|
||||
"jest-date-mock": "^1.0.8",
|
||||
"mocha": "^5.2.0",
|
||||
"node-sass": "^6.0",
|
||||
"redux-devtools": "^3.4.0"
|
||||
|
||||
@@ -85,7 +85,6 @@ class Dashboard extends React.Component {
|
||||
matchedEvents.push(events[idx]);
|
||||
}
|
||||
|
||||
|
||||
// check events before
|
||||
let ptr = idx - 1;
|
||||
while (
|
||||
@@ -98,7 +97,6 @@ class Dashboard extends React.Component {
|
||||
ptr -= 1;
|
||||
}
|
||||
|
||||
|
||||
// check events after
|
||||
ptr = idx + 1;
|
||||
while (
|
||||
@@ -279,7 +277,7 @@ class Dashboard extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { actions, app, domain, features } = this.props;
|
||||
const { actions, app, domain, timeline, features } = this.props;
|
||||
const dateHeight = 80;
|
||||
const padding = 2;
|
||||
const checkMobile = isMobileOnly || window.innerWidth < 600;
|
||||
@@ -290,14 +288,14 @@ class Dashboard extends React.Component {
|
||||
width: checkMobile
|
||||
? "100vw"
|
||||
: window.innerWidth > 768
|
||||
? "60vw"
|
||||
: "calc(100vw - var(--toolbar-width))",
|
||||
? "60vw"
|
||||
: "calc(100vw - var(--toolbar-width))",
|
||||
maxWidth: checkMobile ? "100vw" : 600,
|
||||
maxHeight: checkMobile
|
||||
? "100vh"
|
||||
: window.innerHeight > 768
|
||||
? `calc(100vh - ${app.timeline.dimensions.height}px - ${dateHeight}px)`
|
||||
: "100vh",
|
||||
? `calc(100vh - ${timeline.dimensions.height}px - ${dateHeight}px)`
|
||||
: "100vh",
|
||||
left: checkMobile ? padding : "var(--toolbar-width)",
|
||||
top: 0,
|
||||
overflowY: "scroll",
|
||||
@@ -344,7 +342,7 @@ class Dashboard extends React.Component {
|
||||
/>
|
||||
)}
|
||||
<CardStack
|
||||
timelineDims={app.timeline.dimensions}
|
||||
timelineDims={timeline.dimensions}
|
||||
onViewSource={this.handleViewSource}
|
||||
onSelect={
|
||||
app.associations.narrative ? this.selectNarrativeStep : () => null
|
||||
@@ -357,9 +355,9 @@ class Dashboard extends React.Component {
|
||||
narrative={
|
||||
app.associations.narrative
|
||||
? {
|
||||
...app.associations.narrative,
|
||||
current: this.props.narrativeIdx,
|
||||
}
|
||||
...app.associations.narrative,
|
||||
current: this.props.narrativeIdx,
|
||||
}
|
||||
: null
|
||||
}
|
||||
methods={{
|
||||
@@ -427,6 +425,9 @@ function mapDispatchToProps(dispatch) {
|
||||
export default connect(
|
||||
(state) => ({
|
||||
...state,
|
||||
timeline: {
|
||||
dimensions: selectors.selectDimensions(state),
|
||||
},
|
||||
narrativeIdx: selectors.selectNarrativeIdx(state),
|
||||
narratives: selectors.selectNarratives(state),
|
||||
selected: selectors.selectSelected(state),
|
||||
|
||||
@@ -34,7 +34,7 @@ class Timeline extends React.Component {
|
||||
dims: props.dimensions,
|
||||
scaleX: null,
|
||||
scaleY: null,
|
||||
timerange: [null, null], // two datetimes
|
||||
timerange: [null, null], // two Dates
|
||||
dragPos0: null,
|
||||
transitionDuration: 300,
|
||||
};
|
||||
@@ -47,7 +47,7 @@ class Timeline extends React.Component {
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
if (hash(nextProps) !== hash(this.props)) {
|
||||
this.setState({
|
||||
timerange: nextProps.app.timeline.range,
|
||||
timerange: nextProps.timeline.range,
|
||||
scaleX: this.makeScaleX(),
|
||||
});
|
||||
}
|
||||
@@ -219,7 +219,7 @@ class Timeline extends React.Component {
|
||||
this.state.scaleX.domain()[0],
|
||||
extent / 2
|
||||
);
|
||||
const { rangeLimits } = this.props.app.timeline;
|
||||
const { rangeLimits } = this.props.timeline;
|
||||
|
||||
let newDomain0 = d3.timeMinute.offset(newCentralTime, -zoom.duration / 2);
|
||||
let newDomainF = d3.timeMinute.offset(newCentralTime, zoom.duration / 2);
|
||||
@@ -278,7 +278,7 @@ class Timeline extends React.Component {
|
||||
const dragNow = this.state.scaleX.invert(d3.event.x).getTime();
|
||||
const timeShift = (drag0 - dragNow) / 1000;
|
||||
|
||||
const { range, rangeLimits } = this.props.app.timeline;
|
||||
const { range, rangeLimits } = this.props.timeline;
|
||||
let newDomain0 = d3.timeSecond.offset(range[0], timeShift);
|
||||
let newDomainF = d3.timeSecond.offset(range[1], timeShift);
|
||||
|
||||
@@ -361,7 +361,7 @@ class Timeline extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isNarrative, app, domain } = this.props;
|
||||
const { isNarrative, app, timeline, domain } = this.props;
|
||||
|
||||
let classes = `timeline-wrapper ${this.state.isFolded ? " folded" : ""}`;
|
||||
classes += app.narrative !== null ? " narrative-mode" : "";
|
||||
@@ -405,7 +405,7 @@ class Timeline extends React.Component {
|
||||
<svg ref={this.svgRef} width={dims.width} style={contentHeight}>
|
||||
<Clip dims={dims} />
|
||||
<Axis
|
||||
ticks={app.timeline.dimensions.ticks}
|
||||
ticks={timeline.dimensions.ticks}
|
||||
dims={dims}
|
||||
extent={this.getTimeScaleExtent()}
|
||||
transitionDuration={this.state.transitionDuration}
|
||||
@@ -432,7 +432,7 @@ class Timeline extends React.Component {
|
||||
.default_categories_label
|
||||
}
|
||||
/>
|
||||
{app.timeline.dimensions.ticks === 1 && (
|
||||
{timeline.dimensions.ticks === 1 && (
|
||||
<Handles
|
||||
dims={dims}
|
||||
onMoveTime={(dir) => {
|
||||
@@ -442,7 +442,7 @@ class Timeline extends React.Component {
|
||||
)}
|
||||
<ZoomControls
|
||||
extent={this.getTimeScaleExtent()}
|
||||
zoomLevels={this.props.app.timeline.zoomLevels}
|
||||
zoomLevels={timeline.zoomLevels}
|
||||
dims={dims}
|
||||
onApplyZoom={this.onApplyZoom}
|
||||
/>
|
||||
@@ -504,10 +504,16 @@ function mapStateToProps(state) {
|
||||
app: {
|
||||
selected: state.app.selected,
|
||||
language: state.app.language,
|
||||
timeline: state.app.timeline,
|
||||
narrative: state.app.associations.narrative,
|
||||
coloringSet: state.app.associations.coloringSet,
|
||||
},
|
||||
timeline: {
|
||||
zoomLevels: state.app.timeline.zoomLevels,
|
||||
dimensions: selectors.selectDimensions(state),
|
||||
ticks: state.app.timeline.ticks,
|
||||
range: selectors.selectTimeRange(state),
|
||||
rangeLimits: selectors.selectTimeRangeLimits(state),
|
||||
},
|
||||
ui: {
|
||||
dom: state.ui.dom,
|
||||
styles: state.ui.style.selectedEvents,
|
||||
|
||||
16
src/reducers/__tests__/index.spec.js
Normal file
16
src/reducers/__tests__/index.spec.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { updateTimeRange } from "../../actions";
|
||||
import initial from "../../store/initial.js";
|
||||
import reduce from "../app";
|
||||
|
||||
describe("app reducer", () => {
|
||||
it("can update the selected time range", () => {
|
||||
const result = reduce(
|
||||
initial.app,
|
||||
updateTimeRange(["2022-01-01T00:00:00.000Z", "2022-03-01T00:30:00.000Z"])
|
||||
);
|
||||
expect(result.timeline.range.current).toEqual([
|
||||
"2022-01-01T00:00:00.000Z",
|
||||
"2022-03-01T00:30:00.000Z",
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import initial from "../store/initial.js";
|
||||
import { ASSOCIATION_MODES } from "../common/constants";
|
||||
import { toggleFlagAC } from "../common/utilities";
|
||||
import * as selectors from "../selectors";
|
||||
|
||||
import {
|
||||
UPDATE_HIGHLIGHTED,
|
||||
@@ -68,17 +69,14 @@ function updateColoringSet(appState, action) {
|
||||
}
|
||||
|
||||
function updateNarrative(appState, action) {
|
||||
let minTime = appState.timeline.range[0];
|
||||
let maxTime = appState.timeline.range[1];
|
||||
let [minTime, maxTime] = selectors.selectTimeRange(appState);
|
||||
|
||||
const cornerBound0 = [180, 180];
|
||||
const cornerBound1 = [-180, -180];
|
||||
|
||||
// Compute narrative time range and map bounds
|
||||
if (action.narrative) {
|
||||
// Forced to comment out min and max time changes, not sure why?
|
||||
minTime = appState.timeline.rangeLimits[0];
|
||||
maxTime = appState.timeline.rangeLimits[1];
|
||||
[minTime, maxTime] = selectors.selectTimeRangeLimits(appState);
|
||||
|
||||
// Find max and mins coordinates of narrative events
|
||||
action.narrative.steps.forEach((step) => {
|
||||
@@ -119,6 +117,7 @@ function updateNarrative(appState, action) {
|
||||
minTime = minTime - Math.abs((maxTime - minTime) / 10);
|
||||
maxTime = maxTime + Math.abs((maxTime - minTime) / 10);
|
||||
}
|
||||
|
||||
return {
|
||||
...appState,
|
||||
associations: {
|
||||
@@ -131,7 +130,10 @@ function updateNarrative(appState, action) {
|
||||
},
|
||||
timeline: {
|
||||
...appState.timeline,
|
||||
range: [minTime, maxTime],
|
||||
range: {
|
||||
...appState.timeline.range,
|
||||
current: [minTime, maxTime],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -200,7 +202,13 @@ function updateTimeRange(appState, action) {
|
||||
...appState,
|
||||
timeline: {
|
||||
...appState.timeline,
|
||||
range: action.timerange,
|
||||
range: {
|
||||
...appState.timeline.range,
|
||||
current: [
|
||||
new Date(action.timerange[0]).toISOString(),
|
||||
new Date(action.timerange[1]).toISOString(),
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
142
src/selectors/__tests__/timeline.spec.js
Normal file
142
src/selectors/__tests__/timeline.spec.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import initial from "../../store/initial";
|
||||
import { advanceTo, clear } from "jest-date-mock";
|
||||
import * as selectors from "../";
|
||||
|
||||
describe("timeline selectors", () => {
|
||||
beforeAll(() => {
|
||||
advanceTo(new Date("2022-02-01T00:00:00.000Z"));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
clear();
|
||||
});
|
||||
|
||||
const state = (range) => ({
|
||||
...initial,
|
||||
app: {
|
||||
...initial.app,
|
||||
timeline: {
|
||||
...initial.app.timeline,
|
||||
range,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe("selectTimeRange", () => {
|
||||
it("returns the currently selected time range", () => {
|
||||
expect(
|
||||
selectors.selectTimeRange(
|
||||
state({
|
||||
initial: ["2020-03-03T00:00:00.000Z", "2024-01-04T00:00:00.000Z"],
|
||||
current: ["2021-03-03T00:00:00.000Z", "2023-01-04T00:00:00.000Z"],
|
||||
initialDaysShown: 31,
|
||||
limits: {
|
||||
lower: "2022-02-01T00:00:00.000Z",
|
||||
upper: undefined,
|
||||
},
|
||||
})
|
||||
)
|
||||
).toEqual([
|
||||
new Date("2021-03-03T00:00:00.000Z"),
|
||||
new Date("2023-01-04T00:00:00.000Z"),
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back to a fixed default time range when no current range is set", () => {
|
||||
expect(
|
||||
selectors.selectTimeRange(
|
||||
state({
|
||||
current: undefined,
|
||||
initial: ["2020-03-03T00:00:00.000Z", "2024-01-04T00:00:00.000Z"],
|
||||
initialDaysShown: 31,
|
||||
limits: {
|
||||
lower: "2022-02-01T00:00:00.000Z",
|
||||
upper: undefined,
|
||||
},
|
||||
})
|
||||
)
|
||||
).toEqual([
|
||||
new Date("2020-03-03T00:00:00.000Z"),
|
||||
new Date("2024-01-04T00:00:00.000Z"),
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back to a dynamic default time range when no fixed default range or current range is set", () => {
|
||||
expect(
|
||||
selectors.selectTimeRange(
|
||||
state({
|
||||
current: undefined,
|
||||
initial: undefined,
|
||||
initialDaysShown: 31,
|
||||
limits: {
|
||||
lower: "2022-02-01T00:00:00.000Z",
|
||||
upper: undefined,
|
||||
},
|
||||
})
|
||||
)
|
||||
).toEqual([
|
||||
new Date("2022-01-01T00:00:00.000Z"),
|
||||
new Date("2022-02-01T00:00:00.000Z"),
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back to a dynamic default if an invalid default range is passed in", () => {
|
||||
expect(
|
||||
selectors.selectTimeRange(
|
||||
state({
|
||||
current: undefined,
|
||||
initial: "some garbage data",
|
||||
initialDaysShown: 31,
|
||||
limits: {
|
||||
lower: "2022-02-01T00:00:00.000Z",
|
||||
upper: undefined,
|
||||
},
|
||||
})
|
||||
)
|
||||
).toEqual([
|
||||
new Date("2022-01-01T00:00:00.000Z"),
|
||||
new Date("2022-02-01T00:00:00.000Z"),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("selectTimeRangeLimits", () => {
|
||||
it("returns fixed time range limits", () => {
|
||||
expect(
|
||||
selectors.selectTimeRangeLimits(
|
||||
state({
|
||||
current: undefined,
|
||||
initial: undefined,
|
||||
initialDaysShown: 31,
|
||||
limits: {
|
||||
lower: "2021-02-01T00:00:00.000Z",
|
||||
upper: "2023-03-03T00:00:00.000Z",
|
||||
},
|
||||
})
|
||||
)
|
||||
).toEqual([
|
||||
new Date("2021-02-01T00:00:00.000Z"),
|
||||
new Date("2023-03-03T00:00:00.000Z"),
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns limits from a given lower bound to the current date, when no upper bound is passed in", () => {
|
||||
expect(
|
||||
selectors.selectTimeRangeLimits(
|
||||
state({
|
||||
current: undefined,
|
||||
initial: undefined,
|
||||
initialDaysShown: 31,
|
||||
limits: {
|
||||
lower: "2021-02-01T00:00:00.000Z",
|
||||
upper: undefined,
|
||||
},
|
||||
})
|
||||
)
|
||||
).toEqual([
|
||||
new Date("2021-02-01T00:00:00.000Z"),
|
||||
new Date("2022-02-01T00:00:00.000Z"),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -34,8 +34,6 @@ export const getNotifications = (state) => state.domain.notifications;
|
||||
export const getActiveFilters = (state) => state.app.associations.filters;
|
||||
export const getActiveCategories = (state) => state.app.associations.categories;
|
||||
export const getActiveShapes = (state) => state.app.shapes;
|
||||
export const getTimeRange = (state) => state.app.timeline.range;
|
||||
export const getTimelineDimensions = (state) => state.app.timeline.dimensions;
|
||||
export const selectNarrative = (state) => state.app.associations.narrative;
|
||||
export const getFeatures = (state) => state.features;
|
||||
export const getEventRadius = (state) => state.ui.eventRadius;
|
||||
@@ -67,6 +65,47 @@ export const selectRegions = createSelector(
|
||||
}
|
||||
);
|
||||
|
||||
const getTimeRange = (state) => state.app.timeline.range.current;
|
||||
const getInitialTimeRange = (state) => state.app.timeline.range.initial;
|
||||
const getInitialDaysShown = (state) =>
|
||||
state.app.timeline.range.initialDaysShown;
|
||||
export const selectTimeRange = createSelector(
|
||||
[getTimeRange, getInitialTimeRange, getInitialDaysShown],
|
||||
(range, initialRange, initialDaysShown) => {
|
||||
let start, end;
|
||||
|
||||
if (Array.isArray(range) && range.length === 2) {
|
||||
[start, end] = range;
|
||||
} else if (Array.isArray(initialRange) && initialRange.length === 2) {
|
||||
[start, end] = initialRange;
|
||||
} else {
|
||||
end = new Date();
|
||||
start = new Date(end.getTime() - initialDaysShown * 24 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
return [new Date(start), new Date(end)];
|
||||
}
|
||||
);
|
||||
|
||||
const getTimeRangeLimits = (state) => state.app.timeline.range.limits;
|
||||
export const selectTimeRangeLimits = createSelector(
|
||||
getTimeRangeLimits,
|
||||
(limits) => {
|
||||
return [new Date(limits.lower), new Date(limits.upper || Date.now())];
|
||||
}
|
||||
);
|
||||
|
||||
const getTimelineDimensions = (state) => state.app.timeline.dimensions;
|
||||
export const selectDimensions = createSelector(
|
||||
getTimelineDimensions,
|
||||
(dimensions) => {
|
||||
return {
|
||||
...dimensions,
|
||||
trackHeight: dimensions.contentHeight - 50, // height of time labels
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Of all available events, selects those that
|
||||
* 1. fall in time range
|
||||
@@ -79,7 +118,7 @@ export const selectEvents = createSelector(
|
||||
getActiveFilters,
|
||||
getActiveCategories,
|
||||
getActiveShapes,
|
||||
getTimeRange,
|
||||
selectTimeRange,
|
||||
getFeatures,
|
||||
],
|
||||
(
|
||||
@@ -353,13 +392,3 @@ export const selectSelected = createSelector(
|
||||
return selected.map(insetSourceFrom(sources));
|
||||
}
|
||||
);
|
||||
|
||||
export const selectDimensions = createSelector(
|
||||
[getTimelineDimensions],
|
||||
(dimensions) => {
|
||||
return {
|
||||
...dimensions,
|
||||
trackHeight: dimensions.contentHeight - 50, // height of time labels
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { language } from "../common/utilities";
|
||||
import { DEFAULT_TAB_ICONS } from "../common/constants";
|
||||
|
||||
const isSmallLaptop = window.innerHeight < 800;
|
||||
const mapIniital = {
|
||||
const mapInitial = {
|
||||
anchor: [31.356397, 34.784818],
|
||||
startZoom: 11,
|
||||
minZoom: 2,
|
||||
@@ -83,8 +83,9 @@ const initial = {
|
||||
contentHeight: isSmallLaptop ? 160 : 200,
|
||||
width_controls: 100,
|
||||
},
|
||||
range: [new Date(2001, 2, 23, 12), new Date(2021, 2, 23, 12)],
|
||||
rangeLimits: [new Date(1, 1, 1, 1), new Date()],
|
||||
range: {
|
||||
current: null,
|
||||
},
|
||||
zoomLevels: copy[language].timeline.zoomLevels || [
|
||||
{ label: "20 years", duration: 10512000 },
|
||||
{ label: "2 years", duration: 1051200 },
|
||||
@@ -210,13 +211,10 @@ if (process.env.store) {
|
||||
appStore = initial;
|
||||
}
|
||||
|
||||
// NB: config.js dates get implicitly converted to strings in mergeDeepLeft
|
||||
appStore.app.timeline.range[0] = new Date(appStore.app.timeline.range[0]);
|
||||
appStore.app.timeline.range[1] = new Date(appStore.app.timeline.range[1]);
|
||||
appStore.app.flags.isIntropopup = !!appStore.app.intro;
|
||||
|
||||
if ("map" in appStore.app) {
|
||||
appStore.app.map = mergeDeepLeft(appStore.app.map, mapIniital);
|
||||
appStore.app.map = mergeDeepLeft(appStore.app.map, mapInitial);
|
||||
}
|
||||
|
||||
if ("space3d" in appStore.app) {
|
||||
|
||||
Reference in New Issue
Block a user