Skip to content

Commit

Permalink
Merge pull request #3 from OSINT-for-Ukraine/artillery-range
Browse files Browse the repository at this point in the history
Add circle with artillery range on click on artillery list
  • Loading branch information
popovvasile committed Apr 26, 2024
2 parents e8f0920 + 633145c commit b1e8ce1
Show file tree
Hide file tree
Showing 17 changed files with 337 additions and 44 deletions.
Binary file added public/artillery-grey.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/artillery-white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/artillery.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 32 additions & 18 deletions src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import { urlFromEnv } from "../common/utilities";

// TODO: relegate these URLs entirely to environment variables
// const CONFIG_URL = urlFromEnv('CONFIG_EXT')
const MILITARY_DATA_URL = urlFromEnv("MILITARY_EXT"); // TODO data here the data is fetched from API
const MILITARY_DATA_URL = urlFromEnv("MILITARY_EXT"); // TODO data here the data is fetched from API
const EVENT_DATA_URL = urlFromEnv("EVENTS_EXT");
const ASSOCIATIONS_URL = urlFromEnv("ASSOCIATIONS_EXT");
const SOURCES_URL = urlFromEnv("SOURCES_EXT");
const SITES_URL = urlFromEnv("SITES_EXT");
const REGIONS_URL = urlFromEnv("REGIONS_EXT");
const SHAPES_URL = urlFromEnv("SHAPES_EXT");
const LOGIN_URL = urlFromEnv("LOGIN_EXT")
const LOGIN_URL = urlFromEnv("LOGIN_EXT");
// In order for this way of authorization to work I had to modify json-server-auth module
// to disable token expiration jwt.verify(token, constants_1.JWT_SECRET_KEY, {ignoreExpiration:true});
const token = urlFromEnv("JWT_SECRET_KEY")
const token = urlFromEnv("JWT_SECRET_KEY");

const domainMsg = (domainType) =>
`Something went wrong fetching ${domainType}. Check the URL or try disabling them in the config file.`;
Expand Down Expand Up @@ -55,12 +55,11 @@ export function fetchDomain() {
const eventPromise = Promise.all(
EVENT_DATA_URL.map((url) =>
fetch(url, {
method: 'GET',
method: "GET",
headers: {
'Authorization': 'Bearer ' + token
}
}
)
Authorization: "Bearer " + token,
},
})
.then((response) => response.json())
.catch(() => handleError("events"))
)
Expand All @@ -76,12 +75,11 @@ export function fetchDomain() {
);
} else {
associationsPromise = fetch(ASSOCIATIONS_URL, {
method: 'GET',
method: "GET",
headers: {
'Authorization': 'Bearer ' + token
}
}
)
Authorization: "Bearer " + token,
},
})
.then((response) => response.json())
.catch(() => handleError(domainMsg("associations")));
}
Expand All @@ -97,12 +95,11 @@ export function fetchDomain() {
);
} else {
sourcesPromise = fetch(SOURCES_URL, {
method: 'GET',
method: "GET",
headers: {
'Authorization': 'Bearer ' + token
}
}
)
Authorization: "Bearer " + token,
},
})
.then((response) => response.json())
.catch(() => handleError(domainMsg("sources")));
}
Expand Down Expand Up @@ -456,3 +453,20 @@ export function rehydrateState() {
type: REHYDRATE_STATE,
};
}

export const UPDATE_CURRENT_ARTILLERY = "UPDATE_CURRENT_ARTILLERY";
export function updateCurrentArtillery(currentArtillery) {
return {
type: UPDATE_CURRENT_ARTILLERY,
currentArtillery: currentArtillery,
};
}

export const UPDATE_CURRENT_MILITARY_POSITIONS =
"UPDATE_CURRENT_MILITARY_POSITIONS";
export function updateCurrentMilitaryPositions(updateCurrentMilitaryPositions) {
return {
type: UPDATE_CURRENT_MILITARY_POSITIONS,
currentMilitaryPositions: updateCurrentMilitaryPositions,
};
}
Binary file added src/assets/artillery.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/common/data/copy.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@
}
}
}
},
"artillery": {
"button": "Artillery",
"title": "Artillery range",
"description": "Select an item from the list and click on the map to see the range."
}
},
"timeline": {
Expand Down
5 changes: 5 additions & 0 deletions src/common/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -591,3 +591,8 @@ export const isOdd = (num) => num % 2 !== 0;
export function isEmptyObject(o) {
return o == null || (typeof o === "object" && !Object.keys(o).length);
}

export const displayRangeInKm = (range) => {
const rangeInKm = (range / 1000).toFixed(2);
return rangeInKm + "km";
};
1 change: 0 additions & 1 deletion src/components/Layout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ class Dashboard extends Component {

handleMilitarySelect(selected) {
// this.props.actions.updateSelected(matchedEvents);

}

handleSelect(selected, axis) {
Expand Down
69 changes: 64 additions & 5 deletions src/components/Toolbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { ToolbarButton } from "./controls/atoms/ToolbarButton";
import { FullscreenToggle } from "./controls/FullScreenToggle";
import DownloadPanel from "./controls/DownloadPanel";
import { displayRangeInKm } from "../common/utilities";

class Toolbar extends Component {
constructor(props) {
Expand Down Expand Up @@ -105,7 +106,7 @@ class Toolbar extends Component {
<p>{panels.narratives.description}</p>
{this.props.narratives.map((narr) => {
return (
<div className="panel-action action">
<div key={narr.id} className="panel-action action">
<button
onClick={() => {
this.goToNarrative(narr);
Expand Down Expand Up @@ -203,6 +204,41 @@ class Toolbar extends Component {
);
}

renderToolbarArtilleryPanel() {
const { panels } = this.props.toolbarCopy;

return (
<TabPanel>
<hgroup>
<h3> {panels.artillery.label} </h3>
<p> {panels.artillery.description} </p>
</hgroup>
<ul>
{this.props.artilleries.map((artillery) => {
return (
<li
onClick={() => {
this.props.actions.updateCurrentArtillery(artillery);
}}
key={artillery.id}
>
<div className="artillery-item">
<p className="title"> {artillery.title} </p>
<p className="range"> {displayRangeInKm(artillery.range)}</p>
{this.props?.currentArtillery?.id === artillery.id ? (
<div>
<span className="selected">selected</span>
</div>
) : null}
</div>
</li>
);
})}
</ul>
</TabPanel>
);
}

renderToolbarTab(_selected, label, iconKey, key) {
return (
<ToolbarButton
Expand Down Expand Up @@ -235,6 +271,22 @@ class Toolbar extends Component {
);
}

renderToolbarArtilleryTab(id, label) {
return (
<ToolbarButton
isActive={this.state._selected === id && this.state._active === true}
iconKey="Artillery"
onClick={() => {
this.selectTab(id);
}}
label={label}
>
<div className="artillery-icon" role="img"></div>
<div className="tab-caption">Artillery</div>
</ToolbarButton>
);
}

renderToolbarPanels() {
const { features, narratives } = this.props;
const classes =
Expand All @@ -249,6 +301,7 @@ class Toolbar extends Component {
{features.USE_ASSOCIATIONS ? this.renderToolbarFilterPanel() : null}
{features.USE_SHAPES ? this.renderToolbarShapePanel() : null}
{features.USE_DOWNLOAD ? this.renderToolbarDownloadPanel() : null}
{this.renderToolbarArtilleryPanel()}
</div>
);
}
Expand All @@ -263,6 +316,7 @@ class Toolbar extends Component {

return (
<div
key={idx}
className={classes}
onClick={() => {
this.selectTab(idx);
Expand Down Expand Up @@ -335,6 +389,10 @@ class Toolbar extends Component {
panels.download.icon
)
: null}
{this.renderToolbarArtilleryTab(
filtersIdx + 1,
panels.artillery.label
)}
{features.USE_FULLSCREEN && (
<FullscreenToggle language={this.props.language} />
)}
Expand All @@ -361,10 +419,9 @@ class Toolbar extends Component {
<br />
Free software made by <br />{" "}
<a href="https://forensic-architecture.org">Forensic Architecture </a>
<br />
and
<a href="https://www.bellingcat.com/"> Bellingcat</a>

<br />
and
<a href="https://www.bellingcat.com/"> Bellingcat</a>
</div>
</div>
);
Expand All @@ -390,6 +447,8 @@ class Toolbar extends Component {
function mapStateToProps(state) {
return {
filters: selectors.getFilters(state),
artilleries: state.domain.artilleries,
currentArtillery: state.app.currentArtillery,
categories: selectors.getCategories(state),
narratives: selectors.selectNarratives(state),
shapes: selectors.getShapes(state),
Expand Down
12 changes: 9 additions & 3 deletions src/components/controls/atoms/ToolbarButton.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
export function ToolbarButton({ isActive, iconKey, onClick, label }) {
export function ToolbarButton({ isActive, iconKey, onClick, label, children }) {
return (
<div
className={isActive ? "toolbar-tab active" : "toolbar-tab"}
key={iconKey}
onClick={onClick}
>
<i className="material-icons">{iconKey}</i>
<div className="tab-caption">{label}</div>
{children ? (
children
) : (
<>
<i className="material-icons">{iconKey}</i>
<div className="tab-caption">{label}</div>
</>
)}
</div>
);
}
Expand Down
61 changes: 61 additions & 0 deletions src/components/space/carto/Map.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
isLongitude,
calculateTotalClusterPoints,
calcClusterSize,
displayRangeInKm,
} from "../../../common/utilities";

// NB: important constants for map, TODO: make statics
Expand All @@ -54,6 +55,8 @@ class Map extends Component {
clusters: [],
};
this.styleLocation = this.styleLocation.bind(this);
this.circle = null;
this.circleCenter = null;
}

componentDidMount() {
Expand Down Expand Up @@ -164,6 +167,32 @@ class Map extends Component {
map.keyboard.disable();
map.zoomControl.remove();

map.on("click", (e) => {
if (this.circle) {
map.removeLayer(this.circle);
map.removeLayer(this.circleCenter);
}
if (this.props.app.currentArtillery) {
const { range } = this.props.app.currentArtillery;
const center = Object.values(e.latlng);
this.circle = L.circle(Object.values(center), {
color: "red",
fillColor: "transparent",
radius: range || 0,
}).addTo(map);

const circleIcon = L.icon({
iconUrl: "artillery.png",
iconSize: [20, 20],
});

this.circleCenter = L.marker(center, { icon: circleIcon }).addTo(map);
} else {
this.circle = null;
this.circleCenter = null;
}
});

map.on("moveend", () => {
this.alignLayers();
this.updateClusters();
Expand All @@ -186,6 +215,7 @@ class Map extends Component {
if (this.svgRef.current !== null)
this.svgRef.current.classList.remove("hide");
});

window.addEventListener("resize", () => {
this.alignLayers();
});
Expand Down Expand Up @@ -446,6 +476,10 @@ class Map extends Component {
coloringSet={this.props.app.coloringSet}
filterColors={this.props.ui.filterColors}
features={this.props.features}
currentMilitaryPositions={this.props.app.currentMilitaryPositions}
setCurrentMilitaryPositions={(militaryPositions) =>
this.props.actions.updateCurrentMilitaryPositions(militaryPositions)
}
/>
);
}
Expand Down Expand Up @@ -517,6 +551,30 @@ class Map extends Component {
);
}

renderCurrentArtillery() {
const handleClick = () => {
this.props.actions.updateCurrentArtillery(null);
if (this.circle) {
this.map.removeLayer(this.circle);
}
};
return (
<Portal node={document.getElementById("timeline-wrapper")}>
<div className="current-artillery-presentation">
<p>
Showing &nbsp; <span>{this.props.app.currentArtillery.title} </span>
&nbsp; artillery of &nbsp;
<span>
{displayRangeInKm(this.props.app.currentArtillery.range)}
</span>
&nbsp; range
</p>
<button onClick={handleClick}>X</button>
</div>
</Portal>
);
}

render() {
const { isShowingSites, isFetchingDomain } = this.props.app.flags;
const checkMobile = isMobileOnly || window.innerWidth < 600;
Expand All @@ -535,6 +593,7 @@ class Map extends Component {
{this.renderEvents()}
{this.renderClusters()}
{this.renderSelected()}
{this.props.app.currentArtillery ? this.renderCurrentArtillery() : null}
</>
) : null;

Expand Down Expand Up @@ -569,6 +628,8 @@ function mapStateToProps(state) {
},
app: {
views: state.app.associations.views,
currentArtillery: state.app.currentArtillery,
currentMilitaryPositions: state.app.currentMilitaryPositions,
selected: selectors.selectSelected(state),
highlighted: state.app.highlighted,
map: state.app.map,
Expand Down
Loading

0 comments on commit b1e8ce1

Please sign in to comment.