mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-08 03:18:36 +03:00
Cleaning technical debt (#192)
* abstract Space component to switch out Map * basic viewing possible * restructure components dir * all jsx --> js * App.jsx --> App.js * comment out 3d for now
This commit is contained in:
@@ -404,7 +404,7 @@ module.exports = function (webpackEnv) {
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
plugins: [
|
||||
[
|
||||
require.resolve('babel-plugin-named-asset-import'),
|
||||
@@ -449,7 +449,7 @@ module.exports = function (webpackEnv) {
|
||||
cacheDirectory: true,
|
||||
// See #6846 for context on why cacheCompression is disabled
|
||||
cacheCompression: false,
|
||||
|
||||
|
||||
// Babel sourcemaps are needed for debugging into node_modules
|
||||
// code. Without the options below, debuggers like VSCode
|
||||
// show incorrect code and set breakpoints on the wrong lines.
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
import copy from "../common/data/copy.json";
|
||||
import React from "react";
|
||||
|
||||
import CardCustomField from "./presentational/Card/CustomField";
|
||||
import CardTime from "./presentational/Card/Time";
|
||||
import CardLocation from "./presentational/Card/Location";
|
||||
import CardCaret from "./presentational/Card/Caret";
|
||||
import CardSummary from "./presentational/Card/Summary";
|
||||
import CardSource from "./presentational/Card/Source";
|
||||
import { makeNiceDate } from "../common/utilities";
|
||||
|
||||
class Card extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.setState({
|
||||
isOpen: !this.state.isOpen,
|
||||
});
|
||||
}
|
||||
|
||||
makeTimelabel(datetime) {
|
||||
return makeNiceDate(datetime);
|
||||
}
|
||||
|
||||
handleCardSelect(e) {
|
||||
if (!e.target.className.includes("arrow-down")) {
|
||||
const selectedEventFormat =
|
||||
this.props.idx > 0 ? [this.props.event] : this.props.event;
|
||||
this.props.onSelect(selectedEventFormat, this.props.idx);
|
||||
}
|
||||
}
|
||||
|
||||
renderSummary() {
|
||||
return (
|
||||
<CardSummary
|
||||
language={this.props.language}
|
||||
description={this.props.event.description}
|
||||
isOpen={this.state.isOpen}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderLocation() {
|
||||
return (
|
||||
<CardLocation
|
||||
language={this.props.language}
|
||||
location={this.props.event.location}
|
||||
isPrecise={
|
||||
!this.props.event.type || this.props.event.type === "Structure"
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderSources() {
|
||||
if (this.props.sourceError) {
|
||||
return <div>ERROR: something went wrong loading sources, TODO:</div>;
|
||||
}
|
||||
|
||||
const sourceLang = copy[this.props.language].cardstack.sources;
|
||||
return (
|
||||
<div className="card-col">
|
||||
<h4>{sourceLang}: </h4>
|
||||
{this.props.event.sources.map((source) => (
|
||||
<CardSource
|
||||
isLoading={this.props.isLoading}
|
||||
source={source}
|
||||
onClickHandler={(source) => this.props.onViewSource(source)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// NB: should be internaionalized.
|
||||
renderTime() {
|
||||
const timelabel = this.makeTimelabel(this.props.event.datetime);
|
||||
|
||||
// let precision = this.props.event.time_display
|
||||
// if (precision === '_date_only') {
|
||||
// precision = ''
|
||||
// timelabel = timelabel.substring(0, 11)
|
||||
// } else if (precision === '_approximate_date_only') {
|
||||
// precision = ' (Approximate date)'
|
||||
// timelabel = timelabel.substring(0, 11)
|
||||
// } else if (precision === '_approximate_datetime') {
|
||||
// precision = ' (Approximate datetime)'
|
||||
// } else {
|
||||
// timelabel = timelabel.substring(0, 11)
|
||||
// }
|
||||
|
||||
return (
|
||||
<CardTime
|
||||
makeTimelabel={timelabel}
|
||||
language={this.props.language}
|
||||
timelabel={timelabel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderCustomFields() {
|
||||
return this.props.features.CUSTOM_EVENT_FIELDS.map((field) => {
|
||||
const value = this.props.event[field.key];
|
||||
return value ? (
|
||||
<CardCustomField field={field} value={this.props.event[field.key]} />
|
||||
) : null;
|
||||
});
|
||||
}
|
||||
|
||||
renderMain() {
|
||||
return (
|
||||
<div className="card-container">
|
||||
<div className="card-row details">
|
||||
{this.renderTime()}
|
||||
{this.renderLocation()}
|
||||
</div>
|
||||
{this.renderSummary()}
|
||||
{this.renderCustomFields()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderExtra() {
|
||||
return <div className="card-bottomhalf">{this.renderSources()}</div>;
|
||||
}
|
||||
|
||||
renderCaret() {
|
||||
return this.props.features.USE_SOURCES ? (
|
||||
<CardCaret toggle={() => this.toggle()} isOpen={this.state.isOpen} />
|
||||
) : null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isSelected, idx } = this.props;
|
||||
return (
|
||||
<li
|
||||
className={`event-card ${isSelected ? "selected" : ""}`}
|
||||
id={`event-card-${idx}`}
|
||||
ref={this.props.innerRef}
|
||||
onClick={(e) => this.handleCardSelect(e)}
|
||||
>
|
||||
{this.renderMain()}
|
||||
{this.state.isOpen ? this.renderExtra() : null}
|
||||
{this.renderCaret()}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The ref to each card will be used in CardStack for programmatic scrolling
|
||||
export default React.forwardRef((props, ref) => (
|
||||
<Card innerRef={ref} {...props} />
|
||||
));
|
||||
@@ -1,145 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const Icon = ({ iconType }) => {
|
||||
if (iconType === "personas") {
|
||||
return (
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="40px"
|
||||
height="40px"
|
||||
viewBox="0 0 40 40"
|
||||
enableBackground="new 0 0 40 40"
|
||||
>
|
||||
<path d="M15.464,17.713" />
|
||||
<path d="M5.526,17.713c-1.537,0.595-3,1.472-4.314,2.637l1.114,17.081h16.338" />
|
||||
<path d="M12.283,15.522c-1.707,0.661-3.332,1.636-4.792,2.93l1.238,18.979h18.153" />
|
||||
<circle cx="27.432" cy="8.876" r="6.877" />
|
||||
<path d="M21.297,13.088c-1.896,0.733-3.702,1.817-5.326,3.256l1.375,21.087h20.17l1.376-21.087c-1.624-1.438-3.43-2.522-5.326-3.256" />
|
||||
<path d="M20.968,6.547c-0.926-0.554-2.006-0.877-3.163-0.877c-3.418,0-6.188,2.771-6.188,6.188c0,2.811,1.875,5.18,4.441,5.935" />
|
||||
<path d="M12.38,8.881c-0.738-0.361-1.564-0.57-2.441-0.57c-3.076,0-5.57,2.494-5.57,5.57c0,1.983,1.04,3.72,2.601,4.707" />
|
||||
</svg>
|
||||
);
|
||||
} else if (iconType === "tipos") {
|
||||
return (
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="40px"
|
||||
height="40px"
|
||||
viewBox="0 0 40 40"
|
||||
enableBackground="new 0 0 40 40"
|
||||
>
|
||||
<path
|
||||
strokeDasharray="3, 4"
|
||||
d="M22.326,5.346
|
||||
c-2.154-2.081-5.082-3.367-8.314-3.367c-6.614,0-11.976,5.361-11.976,11.974c0,6.613,5.361,11.977,11.976,11.977
|
||||
c0.228,0,0.449-0.021,0.674-0.034"
|
||||
/>
|
||||
<circle cx="23" cy="17.288" r="11.975" />
|
||||
<circle strokeDasharray="3, 4" cx="25.987" cy="26.926" r="11.976" />
|
||||
</svg>
|
||||
);
|
||||
} else if (iconType === "hardware") {
|
||||
return (
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="40px"
|
||||
height="40px"
|
||||
viewBox="0 0 40 40"
|
||||
enableBackground="new 0 0 40 40"
|
||||
>
|
||||
<path
|
||||
d="M20,1.695C12.571,1.696,6.286,2.019,5.272,2.452C5.253,2.458,5.233,2.466,5.215,2.474
|
||||
c-0.01,0.004-0.019,0.008-0.027,0.012C4.38,2.831,3.803,4.256,3.802,5.907v3.502H2.926H1.175c-0.241,0-0.438,0.196-0.438,0.438
|
||||
v0.875v5.254c0,0.242,0.196,0.438,0.438,0.438h1.751c0.242,0,0.438-0.195,0.438-0.438V11.16h0.438v15.324h5.691
|
||||
c0.242,0,0.438,0.195,0.438,0.438v1.751c0,0.241-0.195,0.438-0.438,0.438H3.802v3.063c0,0.626,0.167,1.203,0.438,1.515v3.74
|
||||
c0,0.482,0.393,0.875,0.876,0.875h2.627c0.483,0,0.875-0.393,0.875-0.875v-2.627h22.765v2.627c0,0.482,0.393,0.875,0.877,0.875
|
||||
h2.627c0.482,0,0.875-0.393,0.875-0.875v-3.74c0.271-0.312,0.438-0.889,0.438-1.515v-3.065h-5.691c-0.241,0-0.438-0.195-0.438-0.438
|
||||
v-1.751c0-0.241,0.197-0.438,0.438-0.438H36.2V11.161h0.438v4.816c0,0.242,0.195,0.438,0.438,0.438h1.752
|
||||
c0.24,0,0.438-0.195,0.438-0.438v-5.254V9.848c0-0.242-0.195-0.438-0.438-0.438h-1.752h-0.875V5.907
|
||||
c-0.001-1.703-0.614-3.159-1.453-3.448C33.79,2.023,27.479,1.696,20,1.695z M5.429,3.28h29.144c0.483,0,0.875,0.98,0.875,2.189l0,0
|
||||
V7.22c0,0.242-0.195,0.438-0.438,0.438H4.991c-0.242,0-0.438-0.196-0.438-0.438V5.469C4.553,4.261,4.945,3.28,5.429,3.28z
|
||||
M5.553,8.534h28.895c0.483,0,0.876,0.392,0.876,0.875v13.134c0,0.484-0.393,0.876-0.876,0.876h-3.466c0,0-0.863,0.613-0.912,0.613
|
||||
H9.931c-0.113,0-0.225-0.022-0.33-0.065l-0.778-0.548h-3.27c-0.483,0-0.875-0.392-0.875-0.876V9.409
|
||||
C4.678,8.926,5.069,8.534,5.553,8.534L5.553,8.534z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
} else if (iconType === "escenas") {
|
||||
return (
|
||||
<svg
|
||||
className="scenes"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="40px"
|
||||
height="40px"
|
||||
viewBox="0 0 40 40"
|
||||
enableBackground="new 0 0 40 40"
|
||||
>
|
||||
<path
|
||||
d="M36.729,14.743v13.15l-14.225,6.693V21.438L36.729,14.743 M38.732,11.045L20.5,19.625v18.662l18.232-8.58V11.045
|
||||
L38.732,11.045z"
|
||||
/>
|
||||
<path
|
||||
d="M4.271,14.743l14.225,6.695v13.148L4.271,27.894V14.743 M2.268,11.045v18.662l18.232,8.58V19.625L2.268,11.045L2.268,11.045
|
||||
z"
|
||||
/>
|
||||
<path
|
||||
d="M20.5,4.844l13.289,6.202L20.5,17.247L7.209,11.046L20.5,4.844 M20.5,2.537L2.268,11.045L20.5,19.554l18.232-8.509
|
||||
L20.5,2.537L20.5,2.537z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
} else if (iconType === "docs") {
|
||||
return (
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="40px"
|
||||
height="40px"
|
||||
viewBox="0 0 40 40"
|
||||
enableBackground="new 0 0 40 40"
|
||||
>
|
||||
<path
|
||||
d="M31.543,5.987V3.158
|
||||
c0-1.103-0.095-1.197-1.197-1.197H4.791c-1.103,0-1.198,0.095-1.198,1.197V32.84c0,1.103,0.095,1.197,1.198,1.197h2.829"
|
||||
/>
|
||||
<path
|
||||
d="M35.57,36.866
|
||||
c0,1.103-0.096,1.198-1.198,1.198H8.817c-1.103,0-1.198-0.096-1.198-1.198V7.185c0-1.103,0.095-1.197,1.198-1.197h25.555
|
||||
c1.103,0,1.198,0.095,1.198,1.197V36.866z"
|
||||
/>
|
||||
<path d="M58.755,29.633" />
|
||||
<path d="M21.86,40.072" />
|
||||
<path d="M-22.755,58.555" />
|
||||
<line x1="11.612" y1="11.977" x2="31.577" y2="11.977" />
|
||||
<line x1="11.612" y1="17.966" x2="31.577" y2="17.966" />
|
||||
<line x1="11.612" y1="29.945" x2="31.577" y2="29.945" />
|
||||
<line x1="11.612" y1="23.955" x2="31.577" y2="23.955" />
|
||||
</svg>
|
||||
);
|
||||
} else if (iconType === "search") {
|
||||
return (
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="40px"
|
||||
height="40px"
|
||||
viewBox="0 0 40 40"
|
||||
enableBackground="new 0 0 40 40"
|
||||
>
|
||||
<circle cx="18.306" cy="18.307" r="13.856" />
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M28.24,28.24
|
||||
l8.346,8.346L28.24,28.24z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Icon;
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import Popup from "./presentational/Popup";
|
||||
import Popup from "./atoms/Popup";
|
||||
import copy from "../common/data/copy.json";
|
||||
|
||||
const Infopopup = ({ isOpen, onClose, language, styles }) => (
|
||||
@@ -5,24 +5,25 @@ import { connect } from "react-redux";
|
||||
import * as actions from "../actions";
|
||||
import * as selectors from "../selectors";
|
||||
|
||||
import MediaOverlay from "./Overlay/Media";
|
||||
import LoadingOverlay from "./Overlay/Loading";
|
||||
import Map from "./Map.jsx";
|
||||
import Toolbar from "./Toolbar/Layout";
|
||||
import CardStack from "./CardStack.jsx";
|
||||
import NarrativeControls from "./presentational/Narrative/Controls.js";
|
||||
import InfoPopup from "./InfoPopup.jsx";
|
||||
import Popup from "./presentational/Popup";
|
||||
import Timeline from "./Timeline.jsx";
|
||||
import Notification from "./Notification.jsx";
|
||||
import StateOptions from "./StateOptions.jsx";
|
||||
import StaticPage from "./StaticPage";
|
||||
import Toolbar from "./Toolbar";
|
||||
import InfoPopup from "./InfoPopup";
|
||||
import Notification from "./Notification";
|
||||
import TemplateCover from "./TemplateCover";
|
||||
|
||||
import Popup from "./atoms/Popup";
|
||||
import StaticPage from "./atoms/StaticPage";
|
||||
import MediaOverlay from "./atoms/Media";
|
||||
import LoadingOverlay from "./atoms/Loading";
|
||||
|
||||
import Timeline from "./time/Timeline";
|
||||
import Space from "./space/Space";
|
||||
import Search from "./controls/Search";
|
||||
import CardStack from "./controls/CardStack";
|
||||
import NarrativeControls from "./controls/NarrativeControls.js";
|
||||
|
||||
import colors from "../common/global";
|
||||
import { binarySearch, insetSourceFrom } from "../common/utilities";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
import Search from "./Search.jsx";
|
||||
|
||||
class Dashboard extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -329,7 +330,8 @@ class Dashboard extends React.Component {
|
||||
onSelectNarrative: this.setNarrative,
|
||||
}}
|
||||
/>
|
||||
<Map
|
||||
<Space
|
||||
kind={"map" in app ? "map" : "space3d"}
|
||||
onKeyDown={this.onKeyDown}
|
||||
methods={{
|
||||
onSelectNarrative: this.setNarrative,
|
||||
@@ -359,16 +361,6 @@ class Dashboard extends React.Component {
|
||||
onToggleCardstack={() => actions.updateSelected([])}
|
||||
getCategoryColor={this.getCategoryColor}
|
||||
/>
|
||||
<StateOptions
|
||||
showing={
|
||||
this.props.narratives &&
|
||||
this.props.narratives.length !== 0 &&
|
||||
!app.associations.narrative &&
|
||||
app.associations.filters.length > 0
|
||||
}
|
||||
timelineDims={app.timeline.dimensions}
|
||||
onClickHandler={this.setNarrativeFromFilters}
|
||||
/>
|
||||
<NarrativeControls
|
||||
narrative={
|
||||
app.associations.narrative
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
const StateOptions = ({ showing, onClickHandler, timelineDims }) => {
|
||||
const [checked, setChecked] = useState(false);
|
||||
const handleCheck = () => setChecked(!checked);
|
||||
const onNarrativise = () => onClickHandler(checked);
|
||||
|
||||
if (!showing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="stateoptions-panel" style={{ bottom: timelineDims.height }}>
|
||||
<div>
|
||||
<div className="button" onClick={onNarrativise}>
|
||||
Narrativise
|
||||
</div>
|
||||
<label for="withlines">Connect by lines</label>
|
||||
<input
|
||||
name="withlines"
|
||||
onClick={handleCheck}
|
||||
checked={checked}
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StateOptions;
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Player } from "video-react";
|
||||
import marked from "marked";
|
||||
import MediaOverlay from "./Overlay/Media";
|
||||
import MediaOverlay from "./atoms/Media";
|
||||
import falogo from "../assets/fa-logo.png";
|
||||
import bcatlogo from "../assets/bellingcat-logo.png";
|
||||
const MEDIA_HIDDEN = -2;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { bindActionCreators } from "redux";
|
||||
import * as actions from "../../actions";
|
||||
import * as selectors from "../../selectors";
|
||||
import * as actions from "../actions";
|
||||
import * as selectors from "../selectors";
|
||||
|
||||
import { Tabs, TabPanel } from "react-tabs";
|
||||
import FilterListPanel from "./FilterListPanel";
|
||||
import CategoriesListPanel from "./CategoriesListPanel";
|
||||
import BottomActions from "./BottomActions";
|
||||
import copy from "../../common/data/copy.json";
|
||||
import FilterListPanel from "./controls/FilterListPanel";
|
||||
import CategoriesListPanel from "./controls/CategoriesListPanel";
|
||||
import BottomActions from "./controls/BottomActions";
|
||||
import copy from "../common/data/copy.json";
|
||||
import {
|
||||
trimAndEllipse,
|
||||
getImmediateFilterParent,
|
||||
getFilterSiblings,
|
||||
getFilterParents,
|
||||
} from "../../common/utilities.js";
|
||||
} from "../common/utilities.js";
|
||||
|
||||
class Toolbar extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -1,80 +0,0 @@
|
||||
import React from "react";
|
||||
import Checkbox from "../presentational/Checkbox";
|
||||
|
||||
function SelectFilter(props) {
|
||||
function isActive() {
|
||||
if (props.isCategory) {
|
||||
return props.categoryFilters.includes(props.filter.id);
|
||||
}
|
||||
return props.filterFilters.includes(props.filter.id);
|
||||
}
|
||||
|
||||
function onClickFilter() {
|
||||
if (isActive()) {
|
||||
props.filter({
|
||||
filters: props.filterFilters.filter(
|
||||
(element) => element !== props.filter.id
|
||||
),
|
||||
});
|
||||
} else {
|
||||
props.filter({
|
||||
filters: props.filterFilters.concat(props.filter.id),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onClickCategory() {
|
||||
if (isActive()) {
|
||||
props.filter({
|
||||
categories: props.categoryFilters.filter(
|
||||
(element) => element !== props.filter.id
|
||||
),
|
||||
});
|
||||
} else {
|
||||
props.filter({
|
||||
categories: props.categoryFilters.concat(props.filter.id),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function renderFilter() {
|
||||
const filter = props.filter;
|
||||
const classes = isActive() ? "filter-filter active" : "filter-filter";
|
||||
let label = `${filter.name} ( ${filter.mentions} )`;
|
||||
if (props.isShowTree) {
|
||||
label = `${filter.group} > ${filter.subgroup} > ${filter.name} ( ${filter.mentions} )`;
|
||||
}
|
||||
return (
|
||||
<li key={props.filter.id} className={classes}>
|
||||
<Checkbox
|
||||
isActive={isActive()}
|
||||
label={label}
|
||||
onClickCheckbox={() => onClickFilter()}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function renderCategory() {
|
||||
const category = props.categories[props.filter.id];
|
||||
const classes = isActive() ? "filter-filter active" : "filter-filter";
|
||||
|
||||
if (category) {
|
||||
return (
|
||||
<li key={props.filter.id} className={classes}>
|
||||
<Checkbox
|
||||
isActive={isActive()}
|
||||
label={`${category.name} ( ${category.counts} )`}
|
||||
onClickCheckbox={onClickCategory}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
return <div />;
|
||||
}
|
||||
|
||||
if (props.isCategory) return renderCategory();
|
||||
return renderFilter();
|
||||
}
|
||||
|
||||
export default SelectFilter;
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { getCoordinatesForPercent } from "../../../common/utilities";
|
||||
import { getCoordinatesForPercent } from "../../common/utilities";
|
||||
|
||||
function ColoredMarkers({ radius, colorPercentMap, styles, className }) {
|
||||
let cumulativeAngleSweep = 0;
|
||||
@@ -2,8 +2,8 @@ import React from "react";
|
||||
import { Player } from "video-react";
|
||||
import Img from "react-image";
|
||||
import Md from "./Md";
|
||||
import Spinner from "../presentational/Spinner";
|
||||
import NoSource from "../presentational/NoSource";
|
||||
import Spinner from "../atoms/Spinner";
|
||||
import NoSource from "../atoms/NoSource";
|
||||
|
||||
const Content = ({ media, viewIdx, translations, switchLanguage, langIdx }) => {
|
||||
const el = document.querySelector(".source-media-gallery");
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
|
||||
import SitesIcon from "../presentational/Icons/Sites";
|
||||
import CoverIcon from "../presentational/Icons/Cover";
|
||||
import InfoIcon from "../presentational/Icons/Info";
|
||||
import SitesIcon from "../atoms/SitesIcon";
|
||||
import CoverIcon from "../atoms/CoverIcon";
|
||||
import InfoIcon from "../atoms/InfoIcon";
|
||||
|
||||
function BottomActions(props) {
|
||||
function renderToggles() {
|
||||
@@ -5,10 +5,9 @@ import {
|
||||
generateCardLayout,
|
||||
} from "@forensic-architecture/design-system/dist/react";
|
||||
|
||||
import * as selectors from "../selectors";
|
||||
import { getFilterIdxFromColorSet } from "../common/utilities";
|
||||
// import Card from './Card.jsx'
|
||||
import copy from "../common/data/copy.json";
|
||||
import * as selectors from "../../selectors";
|
||||
import { getFilterIdxFromColorSet } from "../../common/utilities";
|
||||
import copy from "../../common/data/copy.json";
|
||||
|
||||
class CardStack extends React.Component {
|
||||
constructor() {
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import marked from "marked";
|
||||
import Checkbox from "../presentational/Checkbox";
|
||||
import Checkbox from "../atoms/Checkbox";
|
||||
import copy from "../../common/data/copy.json";
|
||||
|
||||
const CategoriesListPanel = ({
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import Checkbox from "../presentational/Checkbox";
|
||||
import Checkbox from "../atoms/Checkbox";
|
||||
import marked from "marked";
|
||||
import copy from "../../common/data/copy.json";
|
||||
import { getFilterIdxFromColorSet } from "../../common/utilities";
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import Card from "./Card";
|
||||
import Adjust from "./Adjust";
|
||||
import Close from "./Close";
|
||||
import Card from "./atoms/NarrativeCard";
|
||||
import Adjust from "./atoms/NarrativeAdjust";
|
||||
import Close from "./atoms/NarrativeClose";
|
||||
|
||||
const NarrativeControls = ({ narrative, methods }) => {
|
||||
if (!narrative) return null;
|
||||
@@ -2,11 +2,9 @@ import React from "react";
|
||||
|
||||
import { bindActionCreators } from "redux";
|
||||
import { connect } from "react-redux";
|
||||
import * as actions from "../actions";
|
||||
import * as actions from "../../actions";
|
||||
|
||||
import "../scss/search.scss";
|
||||
|
||||
import SearchRow from "./SearchRow.jsx";
|
||||
import SearchRow from "./atoms/SearchRow.jsx";
|
||||
|
||||
class Search extends React.Component {
|
||||
constructor(props) {
|
||||
BIN
src/components/presentational/.DS_Store
vendored
BIN
src/components/presentational/.DS_Store
vendored
Binary file not shown.
@@ -1,15 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const CardCaret = ({ isOpen, toggle }) => {
|
||||
const classes = isOpen ? "arrow-down" : "arrow-down folded";
|
||||
|
||||
return (
|
||||
<div className="card-toggle" onClick={toggle}>
|
||||
<p>
|
||||
<i className={classes} />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardCaret;
|
||||
@@ -1,15 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import { capitalize } from "../../../common/utilities.js";
|
||||
|
||||
const CardCategory = ({ categoryTitle, categoryLabel, color }) => (
|
||||
<div className="card-row card-cell category">
|
||||
<h4>{categoryTitle}</h4>
|
||||
<p>
|
||||
{capitalize(categoryLabel)}
|
||||
<span className="color-category" style={{ background: color }} />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default CardCategory;
|
||||
@@ -1,14 +0,0 @@
|
||||
import React from "react";
|
||||
import marked from "marked";
|
||||
|
||||
const CardCustomField = ({ field, value }) => (
|
||||
<div className="card-cell">
|
||||
<p>
|
||||
<i className="material-icons left">{field.icon}</i>
|
||||
<b>{field.title ? `${field.title}: ` : "- "}</b>
|
||||
{field.kind === "text" ? value : marked(`[${value}](${field.value})`)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default CardCustomField;
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import copy from "../../../common/data/copy.json";
|
||||
|
||||
const CardLocation = ({ language, location, isPrecise }) => {
|
||||
if (location !== "") {
|
||||
return (
|
||||
<div className="card-cell location">
|
||||
<p>
|
||||
<i className="material-icons left">location_on</i>
|
||||
{`${location}${isPrecise ? "" : " (Approximated)"}`}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const unknown = copy[language].cardstack.unknown_location;
|
||||
return (
|
||||
<div className="card-cell location">
|
||||
<p>
|
||||
<i className="material-icons left">location_on</i>
|
||||
{unknown}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default CardLocation;
|
||||
@@ -1,79 +0,0 @@
|
||||
import React from "react";
|
||||
import Img from "react-image";
|
||||
import Spinner from "../Spinner";
|
||||
import { typeForPath } from "../../../common/utilities";
|
||||
|
||||
const CardSource = ({ source, isLoading, onClickHandler }) => {
|
||||
function renderIconText(type) {
|
||||
switch (type) {
|
||||
case "Eyewitness Testimony":
|
||||
return "visibility";
|
||||
case "Government Data":
|
||||
return "public";
|
||||
case "Satellite Imagery":
|
||||
return "satellite";
|
||||
case "Second-Hand Testimony":
|
||||
return "visibility_off";
|
||||
case "Video":
|
||||
return "videocam";
|
||||
case "Photo":
|
||||
return "photo";
|
||||
case "Photobook":
|
||||
return "photo_album";
|
||||
case "Document":
|
||||
return "picture_as_pdf";
|
||||
default:
|
||||
return "help";
|
||||
}
|
||||
}
|
||||
|
||||
if (!source) {
|
||||
return (
|
||||
<div className="card-source">
|
||||
<div>Error: this source was not found</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isImgUrl = /\.(jpg|png)$/;
|
||||
let thumbnail = source.thumbnail;
|
||||
|
||||
if (!thumbnail || thumbnail === "" || !thumbnail.match(isImgUrl)) {
|
||||
// default to first image in paths, null if no images
|
||||
const imgs = source.paths.filter((p) => p.match(isImgUrl));
|
||||
thumbnail = imgs.length > 0 ? imgs[0] : null;
|
||||
}
|
||||
|
||||
if (source.type === "" && source.paths.length >= 1) {
|
||||
source.type = typeForPath(source.paths[0]);
|
||||
}
|
||||
const fallbackIcon = (
|
||||
<i className="material-icons source-icon">{renderIconText(source.type)}</i>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="card-source">
|
||||
{isLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<div className="source-row" onClick={() => onClickHandler(source)}>
|
||||
{thumbnail ? (
|
||||
<Img
|
||||
className="source-icon"
|
||||
src={thumbnail}
|
||||
loader={<Spinner small />}
|
||||
unloader={fallbackIcon}
|
||||
width={30}
|
||||
height={30}
|
||||
/>
|
||||
) : (
|
||||
fallbackIcon
|
||||
)}
|
||||
<p>{source.title ? source.title : source.id}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardSource;
|
||||
@@ -1,18 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import copy from "../../../common/data/copy.json";
|
||||
|
||||
const CardSummary = ({ language, description, isHighlighted }) => {
|
||||
const summary = copy[language].cardstack.description;
|
||||
|
||||
return (
|
||||
<div className="card-row summary">
|
||||
<div className="card-cell">
|
||||
<h4>{summary}</h4>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardSummary;
|
||||
@@ -1,33 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import copy from "../../../common/data/copy.json";
|
||||
import { isNotNullNorUndefined } from "../../../common/utilities";
|
||||
|
||||
const CardTime = ({ timelabel, language, precision }) => {
|
||||
// const daytimeLang = copy[language].cardstack.timestamp
|
||||
// const estimatedLang = copy[language].cardstack.estimated
|
||||
const unknownLang = copy[language].cardstack.unknown_time;
|
||||
|
||||
if (isNotNullNorUndefined(timelabel)) {
|
||||
return (
|
||||
<div className="card-cell timestamp">
|
||||
<p>
|
||||
<i className="material-icons left">today</i>
|
||||
{timelabel}
|
||||
{precision && precision !== "" ? ` - ${precision}` : ""}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="card-cell timestamp">
|
||||
<p>
|
||||
<i className="material-icons left">today</i>
|
||||
{unknownLang}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default CardTime;
|
||||
14
src/components/space/Space.js
Normal file
14
src/components/space/Space.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import MapCarto from "./carto/Map";
|
||||
// import Map3d from "./3d/Map";
|
||||
|
||||
const Space = (props) => {
|
||||
switch (props.kind) {
|
||||
// case "3d":
|
||||
// return <Map3d {...props} />;
|
||||
default:
|
||||
return <MapCarto {...props} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default Space;
|
||||
@@ -1,21 +1,20 @@
|
||||
/* global L */
|
||||
import "leaflet";
|
||||
import React from "react";
|
||||
import { Portal } from "react-portal";
|
||||
import Supercluster from "supercluster";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import * as selectors from "../selectors";
|
||||
import * as selectors from "../../../selectors";
|
||||
|
||||
import "leaflet";
|
||||
|
||||
import Sites from "./presentational/Map/Sites.jsx";
|
||||
import Shapes from "./presentational/Map/Shapes.jsx";
|
||||
import Events from "./presentational/Map/Events.jsx";
|
||||
import Clusters from "./presentational/Map/Clusters.jsx";
|
||||
import SelectedEvents from "./presentational/Map/SelectedEvents.jsx";
|
||||
import Narratives from "./presentational/Map/Narratives";
|
||||
import DefsMarkers from "./presentational/Map/DefsMarkers.jsx";
|
||||
import LoadingOverlay from "../components/Overlay/Loading";
|
||||
import Sites from "./atoms/Sites";
|
||||
import Shapes from "./atoms/Shapes";
|
||||
import Events from "./atoms/Events";
|
||||
import Clusters from "./atoms/Clusters";
|
||||
import SelectedEvents from "./atoms/SelectedEvents";
|
||||
import Narratives from "./atoms/Narratives";
|
||||
import DefsMarkers from "./atoms/DefsMarkers";
|
||||
import LoadingOverlay from "../../atoms/Loading";
|
||||
|
||||
import {
|
||||
mapClustersToLocations,
|
||||
@@ -24,7 +23,7 @@ import {
|
||||
isLongitude,
|
||||
calculateTotalClusterPoints,
|
||||
calcClusterSize,
|
||||
} from "../common/utilities";
|
||||
} from "../../../common/utilities";
|
||||
|
||||
// NB: important constants for map, TODO: make statics
|
||||
const supportedMapboxMap = ["streets", "satellite"];
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { Portal } from "react-portal";
|
||||
import colors from "../../../common/global.js";
|
||||
import ColoredMarkers from "./ColoredMarkers.jsx";
|
||||
import colors from "../../../../common/global";
|
||||
import ColoredMarkers from "../../../atoms/ColoredMarkers";
|
||||
import {
|
||||
calcClusterOpacity,
|
||||
calcClusterSize,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
calculateColorPercentages,
|
||||
zipColorsToPercentages,
|
||||
calculateTotalClusterPoints,
|
||||
} from "../../../common/utilities";
|
||||
} from "../../../../common/utilities";
|
||||
|
||||
const DefsClusters = () => (
|
||||
<defs>
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from "react";
|
||||
import { Portal } from "react-portal";
|
||||
import colors from "../../../common/global.js";
|
||||
import ColoredMarkers from "./ColoredMarkers.jsx";
|
||||
import colors from "../../../../common/global";
|
||||
import ColoredMarkers from "../../../atoms/ColoredMarkers";
|
||||
import {
|
||||
calcOpacity,
|
||||
getCoordinatesForPercent,
|
||||
calculateColorPercentages,
|
||||
zipColorsToPercentages,
|
||||
} from "../../../common/utilities";
|
||||
} from "../../../../common/utilities";
|
||||
|
||||
function MapEvents({
|
||||
getCategoryColor,
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { Portal } from "react-portal";
|
||||
import colors from "../../../common/global.js";
|
||||
import colors from "../../../../common/global";
|
||||
|
||||
class MapSelectedEvents extends React.Component {
|
||||
renderMarker(marker) {
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import * as d3 from "d3";
|
||||
import { setD3Locale } from "../common/utilities";
|
||||
import { setD3Locale } from "../../common/utilities";
|
||||
|
||||
const TEXT_HEIGHT = 15;
|
||||
setD3Locale(d3);
|
||||
@@ -2,19 +2,20 @@ import React from "react";
|
||||
import { bindActionCreators } from "redux";
|
||||
import { connect } from "react-redux";
|
||||
import * as d3 from "d3";
|
||||
import * as selectors from "../selectors";
|
||||
import { setLoading, setNotLoading } from "../actions";
|
||||
import hash from "object-hash";
|
||||
|
||||
import copy from "../common/data/copy.json";
|
||||
import Header from "./presentational/Timeline/Header";
|
||||
import Axis from "./TimelineAxis.jsx";
|
||||
import Clip from "./presentational/Timeline/Clip";
|
||||
import Handles from "./presentational/Timeline/Handles.js";
|
||||
import ZoomControls from "./presentational/Timeline/ZoomControls.js";
|
||||
import Markers from "./presentational/Timeline/Markers.js";
|
||||
import Events from "./presentational/Timeline/Events.js";
|
||||
import Categories from "./TimelineCategories.jsx";
|
||||
import { setLoading, setNotLoading } from "../../actions";
|
||||
import * as selectors from "../../selectors";
|
||||
import copy from "../../common/data/copy.json";
|
||||
|
||||
import Header from "./atoms/Header";
|
||||
import Axis from "./Axis";
|
||||
import Clip from "./atoms/Clip";
|
||||
import Handles from "./atoms/Handles.js";
|
||||
import ZoomControls from "./atoms/ZoomControls.js";
|
||||
import Markers from "./atoms/Markers.js";
|
||||
import Events from "./atoms/Events.js";
|
||||
import Categories from "./Categories";
|
||||
|
||||
class Timeline extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -3,7 +3,7 @@ import DatetimeBar from "./DatetimeBar";
|
||||
import DatetimeSquare from "./DatetimeSquare";
|
||||
import DatetimeStar from "./DatetimeStar";
|
||||
import Project from "./Project";
|
||||
import ColoredMarkers from "../Map/ColoredMarkers.jsx";
|
||||
import ColoredMarkers from "../../atoms/ColoredMarkers";
|
||||
import {
|
||||
calcOpacity,
|
||||
getEventCategories,
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import store from "./store/index.js";
|
||||
import App from "./components/App.jsx";
|
||||
import store from "./store";
|
||||
import App from "./components/App";
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
|
||||
@@ -21,13 +21,20 @@ function createEventSchema(custom) {
|
||||
date: Joi.string().allow(""),
|
||||
time: Joi.string().allow(""),
|
||||
time_precision: Joi.string().allow(""),
|
||||
|
||||
/* map */
|
||||
location: Joi.string().allow(""),
|
||||
latitude: Joi.string().allow(""),
|
||||
longitude: Joi.string().allow(""),
|
||||
/* space */
|
||||
x: Joi.string().allow(""),
|
||||
y: Joi.string().allow(""),
|
||||
z: Joi.string().allow(""),
|
||||
|
||||
type: Joi.string().allow(""),
|
||||
category: Joi.string().allow(""),
|
||||
category_full: Joi.string().allow(""),
|
||||
associations: Joi.array().required().default([]),
|
||||
associations: Joi.array().default([]),
|
||||
sources: Joi.array(),
|
||||
comments: Joi.string().allow(""),
|
||||
time_display: Joi.string().allow(""),
|
||||
|
||||
@@ -12,4 +12,4 @@
|
||||
@import "notification";
|
||||
@import "mediaplayer";
|
||||
@import "cover";
|
||||
@import "stateoptions";
|
||||
@import "search";
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
.stateoptions-panel {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
margin: 1px 0 0 0;
|
||||
padding: 15px;
|
||||
border: 1px solid $black;
|
||||
transition: 0.2 ease;
|
||||
background: $midwhite;
|
||||
color: $darkgrey;
|
||||
box-shadow: 0 19px 19px rgba(0, 0, 0, 0.3), 0 15px 12px rgba(0, 0, 0, 0.22);
|
||||
font-size: $large;
|
||||
line-height: $xxlarge;
|
||||
height: auto;
|
||||
opacity: 0.9;
|
||||
transition: background-color 0.4s;
|
||||
|
||||
.button {
|
||||
border: 1px solid black;
|
||||
padding: 0.3em;
|
||||
transition: all 0.3s ease;
|
||||
&:hover {
|
||||
background-color: black;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,19 @@ import copy from "../common/data/copy.json";
|
||||
import { language } from "../common/utilities";
|
||||
|
||||
const isSmallLaptop = window.innerHeight < 800;
|
||||
const mapIniital = {
|
||||
anchor: [31.356397, 34.784818],
|
||||
startZoom: 11,
|
||||
minZoom: 2,
|
||||
maxZoom: 16,
|
||||
bounds: null,
|
||||
maxBounds: [
|
||||
[180, -180],
|
||||
[-180, 180],
|
||||
],
|
||||
};
|
||||
const space3dInitial = {};
|
||||
|
||||
const initial = {
|
||||
/*
|
||||
* The Domain or 'domain' of this state refers to the tree of data
|
||||
@@ -51,17 +64,6 @@ const initial = {
|
||||
},
|
||||
isMobile: /Mobi/.test(navigator.userAgent),
|
||||
language: "en-US",
|
||||
map: {
|
||||
anchor: [31.356397, 34.784818],
|
||||
startZoom: 11,
|
||||
minZoom: 2,
|
||||
maxZoom: 16,
|
||||
bounds: null,
|
||||
maxBounds: [
|
||||
[180, -180],
|
||||
[-180, 180],
|
||||
],
|
||||
},
|
||||
cluster: {
|
||||
radius: 30,
|
||||
minZoom: 2,
|
||||
@@ -173,7 +175,14 @@ if (process.env.store) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
if ("space3d" in appStore.app) {
|
||||
appStore.app.space3d = mergeDeepLeft(appStore.app.space3d, space3dInitial);
|
||||
}
|
||||
|
||||
export default appStore;
|
||||
|
||||
Reference in New Issue
Block a user