Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(arcgis): support arcgis content #335

Merged
merged 11 commits into from
Jan 16, 2024
28 changes: 28 additions & 0 deletions __mocks__/@esri/arcgis-rest-portal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const mockContent = {
items: [
{
id: "new-york",
name: "NewYork.slpk",
url: "https://123.com",
created: 123456,
type: "Scene Service",
typeKeywords: "This is a Hosted Service",
title: "New York",
token: "token-https://123.com",
},
{
id: "turanga-library",
name: "TurangaLibrary.slpk",
url: "https://456.com",
created: 123457,
type: "Scene Service",
typeKeywords: "This is a Hosted Service",
title: "Turanga Library",
token: "token-https://456.com",
},
],
};

export const getUserContent = async (authentication) => {
return mockContent;
};
27 changes: 16 additions & 11 deletions __mocks__/@esri/arcgis-rest-request.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
const mockEmailExpected = "[email protected]";
const mockSessionExpected = '{"usermail": "usermail"}';
const mockTokenExpectedPrefix = "token-";

const session = {
usermail: mockEmailExpected,
serialize: () => {
return mockSessionExpected;
},
getUser: async () => {
return { email: mockEmailExpected };
},
getToken: async (url: string) => {
return mockTokenExpectedPrefix + url;
},
};

export class ArcGISIdentityManager {
static beginOAuth2 = async () => {
const session = {
usermail: mockEmailExpected,
serialize: () => {
return mockSessionExpected;
},
getUser: async () => {
return { email: mockEmailExpected };
},
};
return session;
};
static completeOAuth2 = async () => {
Expand All @@ -20,7 +25,7 @@ export class ArcGISIdentityManager {
static destroy = async () => {
return;
};
static deserialize = (session) => {
return { usermail: "usermail" };
static deserialize = () => {
return session;
};
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"dependencies": {
"@esri/arcgis-rest-auth": "^3.7.0",
"@esri/arcgis-rest-request": "^4.2.0",
"@esri/arcgis-rest-portal": "^4.4.0",
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/react-fontawesome": "^0.1.17",
Expand Down
3 changes: 3 additions & 0 deletions public/icons/arrow-down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion src/components/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ export const PanelContent = styled.div`
export const PanelHorizontalLine = styled.div<{
top?: number;
bottom?: number;
left?: number;
right?: number;
}>`
margin: ${({ top = 24, bottom = 16 }) => `${top}px 16px ${bottom}px 16px`};
margin: ${({ top = 24, bottom = 16, left = 16, right = 16 }) => `${top}px ${right}px ${bottom}px ${left}px`};
belom88 marked this conversation as resolved.
Show resolved Hide resolved
border: 1px solid ${({ theme }) => theme.colors.mainHiglightColorInverted};
border-radius: 1px;
background: ${({ theme }) => theme.colors.mainHiglightColorInverted};
Expand Down
23 changes: 22 additions & 1 deletion src/components/comparison/comparison-side/comparison-side.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,27 @@ export const ComparisonSide = ({
);
};

const onLayerInsertHandler = (
// TODO: Do we need it? Why don't we use onLayerInsertHandler?
const onArcGisInsertHandler = (
newLayer: LayerExample,
bookmarks?: Bookmark[]
) => {
const newExamples = [...examples, newLayer];
setExamples(newExamples);
const newActiveLayers = handleSelectAllLeafsInGroup(newLayer);
const newActiveLayersIds = newActiveLayers.map((layer) => layer.id);
setActiveLayers(newActiveLayers);
onChangeLayers && onChangeLayers(newExamples, newActiveLayersIds);

/**
* There is no sense to use webscene bookmarks in across layers mode.
*/
if (bookmarks?.length) {
onInsertBookmarks && onInsertBookmarks(bookmarks);
}
};

const onLayerInsertHandler = (
newLayer: LayerExample,
bookmarks?: Bookmark[]
) => {
Expand Down Expand Up @@ -514,6 +534,7 @@ export const ComparisonSide = ({
? ComparisonSideMode.left
: side
}
onArcGisImport={onArcGisInsertHandler}
onLayerInsert={onLayerInsertHandler}
onLayerSelect={onLayerSelectHandler}
onLayerDelete={(id) => onLayerDeleteHandler(id)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import styled, { css, useTheme } from "styled-components";
belom88 marked this conversation as resolved.
Show resolved Hide resolved
import { RadioButton } from "../../radio-button/radio-button";
import { Fragment, useEffect, useState } from "react";

Check warning on line 3 in src/components/layers-panel/arcgis-import-panel/arcgis-import-panel.tsx

View workflow job for this annotation

GitHub Actions / Linter

'useState' is defined but never used
belom88 marked this conversation as resolved.
Show resolved Hide resolved
import { PanelHorizontalLine } from "../../common";
import SortDownIcon from "../../../../public/icons/arrow-down.svg";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks";
import { ModalDialog } from "../../modal-dialog/modal-dialog";
import {
selectArcGisContent,
selectArcGisContentSelected,
selectSortAscending,
selectSortColumn,
selectStatus,
setSortAscending,
setSortColumn,
setArcGisContentSelected,
resetArcGisContentSelected,
} from "../../../redux/slices/arcgis-content-slice";
import { LoadingSpinner } from "../../loading-spinner/loading-spinner";

type InsertLayerProps = {
onImport: (object: { name: string; url: string; token?: string }) => void;
onCancel: () => void;
};

const SpinnerContainer = styled.div<{ visible: boolean }>`
position: absolute;
left: calc(50% - 22px);
top: calc(50% - 22px);
opacity: ${({ visible }) => (visible ? 1 : 0)};
`;

const Table = styled.div`
belom88 marked this conversation as resolved.
Show resolved Hide resolved
width: 584px;
display: flex;
flex-direction: column;
justify-content: start;
row-gap: 16px;
`;

const TableHeader = styled.div`
font-style: normal;
font-weight: 500;
font-size: 16px;
line-height: 19px;
overflow: hidden;
display: flex;
flex-direction: row;
justify-content: start;
column-gap: 24px;
`;

const TableHeaderItem2 = styled.div`
margin-left: 68px;
width: 343px;
`;
const TableHeaderItem3 = styled.div`
width: 149px;
`;

const TableRowItem1 = styled.div`
width: 44px;
`;
const TableRowItem2 = styled.div`
font-weight: 700;
width: 343px;
`;
const TableRowItem3 = styled.div`
width: 149px;
`;

const TableContent = styled.div`
display: flex;
flex-direction: column;
justify-content: start;
row-gap: 8px;
font-style: normal;
font-weight: 500;
font-size: 16px;
line-height: 19px;
overflow: auto;
max-height: 300px;
`;

const TableRow = styled.div<{ checked: boolean }>`
display: flex;
flex-direction: row;
justify-content: start;
align-items: center;
column-gap: 24px;

background: transparent;
cursor: pointer;
${({ checked }) =>
checked &&
css`
background: ${({ theme }) => theme.colors.mainHiglightColor};
box-shadow: 0px 17px 80px rgba(0, 0, 0, 0.1);
border-radius: 8px;
`}
&:hover {
background: ${({ theme }) => theme.colors.mainDimColor};
box-shadow: 0px 17px 80px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
`;

const Radio = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
width: 44px;
height: 44px;
`;

const TitleCellContainer = styled.div`
display: flex;
flex-direction: row;
justify-content: start;
align-items: center;
gap: 4px;
cursor: pointer;
`;

const IconContainer = styled.div<{ enabled: boolean }>`
display: flex;
justify-content: center;
align-items: center;
padding-top: 2px;
width: 16px;
height: 16px;
fill: ${({ theme }) => theme.colors.buttonDimIconColor};
visibility: ${({ enabled }) => (enabled ? "visible" : "hidden")};
`;

export const ArcGisImportPanel = ({ onImport, onCancel }: InsertLayerProps) => {
const dispatch = useAppDispatch();
const arcGisContentArray = useAppSelector(selectArcGisContent);
const arcGisContentSelected = useAppSelector(selectArcGisContentSelected);
const sortAscending = useAppSelector(selectSortAscending);
const sortColumn = useAppSelector(selectSortColumn);
const loadingStatus = useAppSelector(selectStatus);

const isLoading = loadingStatus === "loading";

useEffect(() => {
dispatch(resetArcGisContentSelected());
}, []);

const handleImport = () => {
const arcGisItem = arcGisContentArray.find(
(item) => item.id === arcGisContentSelected
);
if (arcGisItem) {
onImport(arcGisItem);
}
};

const onSort = (dataColumnName: string) => {
if (sortColumn === dataColumnName) {
dispatch(setSortAscending(!sortAscending));
} else {
dispatch(setSortColumn(dataColumnName));
}
};

const formatDate = (date: number) => {
belom88 marked this conversation as resolved.
Show resolved Hide resolved
const formatter = new Intl.DateTimeFormat("en-US", {
month: "long",
day: "2-digit",
year: "numeric",
});
return formatter.format(date);
};

const theme = useTheme();
return (
<ModalDialog
title={"Select map to import"}
okButtonText={"Import Selected"}
onConfirm={handleImport}
onCancel={onCancel}
>
<Table>
<TableHeader>
<TableHeaderItem2>
<TitleCellContainer onClick={() => onSort("title")}>
Title
<IconContainer enabled={sortColumn === "title"}>
<SortDownIcon
fill={theme.colors.buttonDimIconColor}
transform={sortAscending ? "" : "rotate(180)"}
/>
</IconContainer>
</TitleCellContainer>
</TableHeaderItem2>

<TableHeaderItem3>
<TitleCellContainer onClick={() => onSort("created")}>
Date
<IconContainer enabled={sortColumn === "created"}>
<SortDownIcon
belom88 marked this conversation as resolved.
Show resolved Hide resolved
fill={theme.colors.buttonDimIconColor}
transform={sortAscending ? "" : "rotate(180)"}
/>
</IconContainer>
</TitleCellContainer>
</TableHeaderItem3>
</TableHeader>
<TableContent key={`${sortColumn}-${sortAscending}`}>
<SpinnerContainer visible={isLoading}>
<LoadingSpinner />
</SpinnerContainer>

{arcGisContentArray.map((contentItem) => {
const isMapSelected = arcGisContentSelected === contentItem.id;

return (
<Fragment key={contentItem.id}>
belom88 marked this conversation as resolved.
Show resolved Hide resolved
<TableRow
checked={isMapSelected}
onClick={() => {
dispatch(setArcGisContentSelected(contentItem.id));
}}
>
<TableRowItem1>
<Radio>
<RadioButton
id={contentItem.id}
checked={isMapSelected}
onChange={() => {
dispatch(setArcGisContentSelected(contentItem.id));
}}
/>
</Radio>
</TableRowItem1>
<TableRowItem2>{contentItem.title}</TableRowItem2>
<TableRowItem3>
{formatDate(contentItem.created)}
</TableRowItem3>
</TableRow>
<PanelHorizontalLine top={0} bottom={0} left={0} right={0} />
belom88 marked this conversation as resolved.
Show resolved Hide resolved
</Fragment>
);
})}
</TableContent>
</Table>
</ModalDialog>
);
};
Loading