diff --git a/.gitignore b/.gitignore index 5fad7bdf..5f914c5a 100644 --- a/.gitignore +++ b/.gitignore @@ -16,8 +16,13 @@ docker-compose.yaml !.yarn/sdks !.yarn/versions +# IntelliJ +.idea/workspace.xml +.idea/tasks.xml + + # For ignoring static files *.png -*.jpg +*.jpg -.vite \ No newline at end of file +.vite diff --git a/.idea/prettier.xml b/.idea/prettier.xml new file mode 100644 index 00000000..727b8b53 --- /dev/null +++ b/.idea/prettier.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/watcherTasks.xml b/.idea/watcherTasks.xml new file mode 100644 index 00000000..1389c394 --- /dev/null +++ b/.idea/watcherTasks.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 77483ffd..00000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - { - "associatedIndex": 8 -} - - - - - - - - - - - - - - - - - - - - - - - 1718136713081 - - - - - - - - - \ No newline at end of file diff --git a/deps/web-components b/deps/web-components index 53dd60b3..5c409881 160000 --- a/deps/web-components +++ b/deps/web-components @@ -1 +1 @@ -Subproject commit 53dd60b3bcb37486e2a186f4ef5a055748c19276 +Subproject commit 5c409881df9a53c68d5e7696d45f89baac3ddf06 diff --git a/src/pages/map/map-interface/app-state/handlers/fetch.ts b/src/pages/map/map-interface/app-state/handlers/fetch.ts index c97d4f2a..14e7288a 100644 --- a/src/pages/map/map-interface/app-state/handlers/fetch.ts +++ b/src/pages/map/map-interface/app-state/handlers/fetch.ts @@ -1,8 +1,8 @@ import { SETTINGS, apiV2Prefix } from "@macrostrat-web/settings"; import axios from "axios"; import { joinURL } from "~/pages/map/map-interface/utils"; -import { ColumnGeoJSONRecord } from "../reducers"; -import { UPDATE_FILTERED_COLUMNS } from "../reducers/filtered-columns"; +import { ColumnGeoJSONRecord } from "./columns"; +import { UPDATE_COLUMN_FILTERS } from "../reducers/core/types"; import { XDDSnippet } from "~/types"; export const base = apiV2Prefix; @@ -54,7 +54,7 @@ function buildColumnQueryParams(filters) { export async function fetchFilteredColumns( providedFilters -): Promise { +): Promise { let queryString = buildColumnQueryParams(providedFilters); let url = `${base}/columns`; if (Object.keys(queryString).length === 0) { diff --git a/src/pages/map/map-interface/app-state/handlers/filters.ts b/src/pages/map/map-interface/app-state/handlers/filters.ts index 6b659fb0..d6b76d26 100644 --- a/src/pages/map/map-interface/app-state/handlers/filters.ts +++ b/src/pages/map/map-interface/app-state/handlers/filters.ts @@ -22,7 +22,7 @@ export async function runFilter(filter: Filter): Promise { return { category: "lithology", id: filter.name ?? filter.id, - name: filter.name ?? filter.id, + name: filter.name ?? filter.id.toString(), type: filter.type, legend_ids: [], }; @@ -169,11 +169,13 @@ export const fetchIntervalFilter = async ( type LithologyClassFilter = { type: FilterType.LithologyClasses; name: string; + id: number; }; type LithologyTypeFilter = { type: FilterType.LithologyTypes; name: string; + id: number; }; type LithologyFilter = { @@ -285,7 +287,8 @@ async function fetchAllLithTypes( return { category: "lithology", id, - name: id, + // TODO: revisit name/id differences + name: id.toString(), type, legend_ids, }; diff --git a/src/pages/map/map-interface/app-state/handlers/index.ts b/src/pages/map/map-interface/app-state/handlers/index.ts index 72822e2e..afbccafd 100644 --- a/src/pages/map/map-interface/app-state/handlers/index.ts +++ b/src/pages/map/map-interface/app-state/handlers/index.ts @@ -1,7 +1,12 @@ import { push } from "@lagunovsky/redux-react-router"; import { mapPagePrefix, routerBasename } from "@macrostrat-web/settings"; import axios from "axios"; -import { AppAction, AppState } from "../reducers"; +import { + AppAction, + AppState, + MenuPage, + setInfoMarkerPosition, +} from "../reducers"; import { base, fetchAllColumns, @@ -13,29 +18,25 @@ import { } from "./fetch"; import { runFilter } from "./filters"; -import { formatCoordForZoomLevel } from "@macrostrat/mapbox-utils"; import { LineString } from "geojson"; -import { matchPath } from "react-router"; import { currentPageForPathName, isDetailPanelRoute } from "../nav-hooks"; -import { MenuPage, setInfoMarkerPosition } from "../reducers"; import { MapLayer } from "../reducers/core"; import { getInitialStateFromHash } from "../reducers/hash-string"; -import { ColumnGeoJSONRecord, findColumnsForLocation } from "./columns"; - -function routeForActivePage(page: MenuPage) { - let newPathname = routerBasename; - if (page != null) { - newPathname += "/" + page; - } - return newPathname; -} +import { + ColumnGeoJSONRecord, + ColumnSummary, + ColumnProperties, + findColumnsForLocation, +} from "./columns"; +import { matchPath } from "react-router"; -async function actionRunner( +export default async function actionRunner( state: AppState, action: AppAction, dispatch = null ): Promise { const coreState = state.core; + switch (action.type) { case "get-initial-map-state": { const { pathname } = state.router.location; @@ -50,6 +51,15 @@ async function actionRunner( state.router.location.hash ); + const newState = { + ...state, + core: { + ...coreState1, + initialLoadComplete: true, + }, + menu: { activePage }, + }; + // If we are on the column route, the column layer must be enabled // const colMatch = matchPath( // mapPagePrefix + "/loc/:lng/:lat/column", @@ -62,22 +72,40 @@ async function actionRunner( // Fill out the remainder with defaults // We always get all columns on initial load, which might be - // a bit unnecessary - let allColumns: ColumnGeoJSONRecord[] | null = await fetchAllColumns(); + // a bit unnecessary and slow. + //let allColumns: ColumnGeoJSONRecord[] | null = await fetchAllColumns(); + + fetchAllColumns().then((res) => { + runAsyncAction( + newState, + { + type: "set-all-columns", + columns: res, + }, + dispatch + ); + }); dispatch({ type: "replace-state", - state: { - ...state, - core: { - ...coreState1, - allColumns, - initialLoadComplete: true, - }, - menu: { activePage }, - }, + state: newState, }); + // Set info marker position if it is defined + if (newState.core.infoMarkerPosition != null) { + runAsyncAction( + newState, + { + type: "map-query", + z: state.core.mapPosition.target?.zoom ?? 7, + ...state.core.infoMarkerPosition, + map_id: null, + columns: null, + }, + dispatch + ); + } + // Apply all filters in parallel const newFilters = await Promise.all( filters.map((f) => { @@ -102,6 +130,20 @@ async function actionRunner( return null; } } + case "set-all-columns": + if (state.core.infoMarkerPosition != null) { + fetchColumnInfo( + { + lng: state.core.infoMarkerPosition.lng, + lat: state.core.infoMarkerPosition.lat, + columns: [], + }, + action.columns, + state.core.columnInfo, + dispatch + ); + } + return action; case "toggle-menu": { // Push the menu onto the history stack let activePage = state.menu.activePage; @@ -125,56 +167,17 @@ async function actionRunner( dispatch ); } - case "set-menu-page": { - const { pathname, hash } = state.router.location; - if (!isDetailPanelRoute(pathname)) { - const newPathname = routeForActivePage(action.page); - await dispatch(push({ pathname: newPathname, hash })); - } - return { type: "set-menu-page", page: action.page }; - } - case "close-infodrawer": - // If we are showing a cross-section, we need to go there - await dispatch( - push({ - pathname: - state.core.crossSectionLine == null - ? routeForActivePage(state.menu.activePage) - : buildCrossSectionPath(state.core.crossSectionLine), - hash: state.router.location.hash, - }) - ); - return action; case "toggle-cross-section": { - let line: GeoJSON.LineString | null = null; + let line: LineString | null = null; if (state.core.crossSectionLine == null) { line = { type: "LineString", coordinates: [] }; } - const action = { + const action: AppAction = { type: "update-cross-section", line, }; return actionRunner(state, action, dispatch); } - case "update-cross-section": - if (state.core.crossSectionLine != null) { - // Return to the base route - let nextPathname = ""; - const pos = state.core.infoMarkerPosition; - if (pos != null) { - const z = state.core.mapPosition.target?.zoom ?? 7; - nextPathname = buildLocationPath(pos.lng, pos.lat, z); - } else { - nextPathname = routeForActivePage(state.menu.activePage); - } - await dispatch( - push({ - pathname: nextPathname, - hash: state.router.location.hash, - }) - ); - } - return action; case "fetch-search-query": const { term } = action; let CancelToken = axios.CancelToken; @@ -218,38 +221,7 @@ async function actionRunner( return { type: "add-filter", filter: await runFilter(action.filter) }; case "get-filtered-columns": return await fetchFilteredColumns(coreState.filters); - case "set-cross-section-line": { - const { line } = action; - - if (state.core.infoMarkerPosition == null) { - // If we are showing a marker, that route takes precedence - const pathname = buildCrossSectionPath(line); - await dispatch(push({ pathname, hash: location.hash })); - } - - return { type: "did-set-cross-section-line", line }; - } - case "map-query": { - const { lng, lat, z } = action; - // Check if matches column detail route - const { pathname } = state.router.location; - - let newPath = buildLocationPath(lng, lat, Number(z)); - if ( - pathname.startsWith(mapPagePrefix + "/loc") && - pathname.endsWith("/column") - ) { - // If so, we want to append columns to the end of the URL - newPath += "/column"; - } - - return push({ - pathname: newPath, - hash: location.hash, - }); - //return { ...action, type: "run-map-query" }; - } - case "run-map-query": + case "map-query": const { lng, lat, z, map_id } = action; // Get column data from the map action if it is provided. // This saves us from having to filter the columns more inefficiently @@ -266,58 +238,19 @@ async function actionRunner( lat, cancelToken: sourceMapQuery, }); - let mapData = await runMapQuery( - lng, - lat, - z, - map_id, - sourceMapQuery.token - ); - - let { columns } = action; - // If no columns are provided, try to find them from the active column dataset - if ( - (columns == null || columns.length == 0) && - state.core.allColumns != null - ) { - columns = findColumnsForLocation(state.core.allColumns, { - lng, - lat, - }).map((c) => c.properties); - } - const firstColumn = columns?.[0]; - const { columnInfo } = state.core; - if (firstColumn != null && columnInfo?.col_id != firstColumn.col_id) { - // Get the column units if we don't have them already - actionRunner( - state, - { type: "get-column-units", column: firstColumn }, - dispatch - ).then(dispatch); - } else if (firstColumn == null && columnInfo != null) { - // Clear the column info if we don't have any columns - dispatch({ type: "clear-column-info", data: null, column: null }); - } - coreState.infoMarkerPosition = { lng, lat }; - return { - type: "received-map-query", - data: mapData, - }; - case "get-column-units": - let CancelTokenGetColumn = axios.CancelToken; - let sourceGetColumn = CancelTokenGetColumn.source(); - dispatch({ type: "start-column-query", cancelToken: sourceGetColumn }); + // Run a bunch of async queries in ~parallel + runMapQuery(lng, lat, z, map_id, sourceMapQuery.token).then((res) => { + dispatch({ type: "received-map-query", data: res }); + }); - let columnData = await runColumnQuery( - action.column, - sourceGetColumn.token + fetchColumnInfo( + { lng, lat, columns: action.columns }, + state.core.allColumns, + state.core.columnInfo, + dispatch ); - return { - type: "received-column-query", - data: columnData, - column: action.column, - }; + return; case "get-pbdb": let collection_nos = action.collection_nos; dispatch({ type: "start-pdbd-query" }); @@ -330,18 +263,56 @@ async function actionRunner( } } -function buildCrossSectionPath(line: LineString) { - const pts = line.coordinates - .map((p) => `${p[0].toFixed(4)},${p[1].toFixed(4)}`) - .join("/"); - - return mapPagePrefix + "/cross-section/" + pts; +async function runAsyncAction( + state: AppState, + action: AppAction, + dispatch: any +) { + const res = await actionRunner(state, action, dispatch); + if (res != null) dispatch(res); } -function buildLocationPath(lng: number, lat: number, z: number) { - const ln = formatCoordForZoomLevel(lng, Number(z)); - const lt = formatCoordForZoomLevel(lat, Number(z)); - return mapPagePrefix + `/loc/${ln}/${lt}`; +async function getColumnUnits(column: ColumnProperties, dispatch: any) { + let CancelTokenGetColumn = axios.CancelToken; + let sourceGetColumn = CancelTokenGetColumn.source(); + dispatch({ type: "start-column-query", cancelToken: sourceGetColumn }); + + let columnData = await runColumnQuery(column, sourceGetColumn.token); + dispatch({ + type: "received-column-query", + data: columnData, + column: column, + }); } -export default actionRunner; +type ColumnFetchParams = { + lng: number; + lat: number; + columns: ColumnProperties[]; +}; + +function fetchColumnInfo( + params: ColumnFetchParams, + allColumns: ColumnGeoJSONRecord[] | null, + currentColumn: ColumnSummary | null, + dispatch: any +): AppAction | void { + const { lng, lat, columns } = params; + let providedColumns = columns ?? []; + + if (providedColumns.length == 0) { + // We could also just fire off a query using a lat/lon here + providedColumns = findColumnsForLocation(allColumns ?? [], { + lng, + lat, + }).map((c) => c.properties); + } + const nextColumn = providedColumns?.[0]; + if (nextColumn != null && currentColumn?.col_id != nextColumn.col_id) { + // Get the column units if we don't have them already + getColumnUnits(nextColumn, dispatch); + } else if (nextColumn == null && currentColumn != null) { + // Clear the column info if we don't have any columns + dispatch({ type: "clear-column-info" }); + } +} diff --git a/src/pages/map/map-interface/app-state/handlers/pathname.ts b/src/pages/map/map-interface/app-state/handlers/pathname.ts new file mode 100644 index 00000000..192de812 --- /dev/null +++ b/src/pages/map/map-interface/app-state/handlers/pathname.ts @@ -0,0 +1,64 @@ +import { + AppState, + AppAction, + MenuPage, +} from "~/pages/map/map-interface/app-state"; +import { push, UpdateLocationAction } from "@lagunovsky/redux-react-router"; +import { LineString } from "geojson"; +import { mapPagePrefix, routerBasename } from "@macrostrat-web/settings"; +import { formatCoordForZoomLevel } from "@macrostrat/mapbox-utils"; + +export function pathNameAction( + state: AppState +): UpdateLocationAction<"push"> | null { + /** Set the pathname based on the current state. Only one of a location, cross-section line, + * or active page can be selected at a time. + * The following priority is applied: + * 1. If a location is selected, show that location + * 2. If a cross-section line is selected, set the cross-section path + * 3. If an active page is selected, show that page + */ + + const pos = state.core.infoMarkerPosition; + let nextPathname: string = state.router.location.pathname; + if (pos != null) { + const z = state.core.mapPosition.target?.zoom ?? 7; + nextPathname = buildLocationPath(pos.lng, pos.lat, z); + // TODO: we could probably assign column page based on a flag in the state + if (state.router.location.pathname.endsWith("/column")) { + nextPathname += "/column"; + } + } else if (state.core.crossSectionLine != null) { + nextPathname = buildCrossSectionPath(state.core.crossSectionLine); + } else if (state.menu.activePage != null) { + nextPathname = routeForActivePage(state.menu.activePage); + } else { + nextPathname = routerBasename; + } + if (nextPathname == state.router.location.pathname) { + return null; + } + return push({ pathname: nextPathname, hash: state.router.location.hash }); +} + +function buildCrossSectionPath(line: LineString) { + const pts = line.coordinates + .map((p) => `${p[0].toFixed(4)},${p[1].toFixed(4)}`) + .join("/"); + + return mapPagePrefix + "/cross-section/" + pts; +} + +export function buildLocationPath(lng: number, lat: number, z: number) { + const ln = formatCoordForZoomLevel(lng, Number(z)); + const lt = formatCoordForZoomLevel(lat, Number(z)); + return mapPagePrefix + `/loc/${ln}/${lt}`; +} + +function routeForActivePage(page: MenuPage) { + let newPathname = routerBasename; + if (page != null) { + newPathname += "/" + page; + } + return newPathname; +} diff --git a/src/pages/map/map-interface/app-state/hooks.ts b/src/pages/map/map-interface/app-state/hooks.ts index 86656376..639b53ae 100644 --- a/src/pages/map/map-interface/app-state/hooks.ts +++ b/src/pages/map/map-interface/app-state/hooks.ts @@ -13,9 +13,13 @@ function useAppActions(): (action: AppAction) => Promise { const store = useStore(); return async (action) => { const appState = store.getState(); - const newAction = await actionRunner(appState, action, dispatch); - if (newAction == undefined || newAction == null) return; - dispatch(newAction as AppAction); + try { + const newAction = await actionRunner(appState, action, dispatch); + if (newAction == undefined || newAction == null) return; + dispatch(newAction as AppAction); + } catch (err) { + console.error(err); + } }; } diff --git a/src/pages/map/map-interface/app-state/reducers/core/index.ts b/src/pages/map/map-interface/app-state/reducers/core/index.ts index 3789d692..03800382 100644 --- a/src/pages/map/map-interface/app-state/reducers/core/index.ts +++ b/src/pages/map/map-interface/app-state/reducers/core/index.ts @@ -253,10 +253,6 @@ export function coreReducer( return { ...state, allColumns: action.columns }; case "received-column-query": - // summarize units - if (state.allColumns == null || state.allColumns.length == 0) { - return state; - } return { ...state, fetchingColumnInfo: false, diff --git a/src/pages/map/map-interface/app-state/reducers/core/types.ts b/src/pages/map/map-interface/app-state/reducers/core/types.ts index 5fa7375c..119009dc 100644 --- a/src/pages/map/map-interface/app-state/reducers/core/types.ts +++ b/src/pages/map/map-interface/app-state/reducers/core/types.ts @@ -22,7 +22,7 @@ type ASYNC_ADD_FILTER = { type: "async-add-filter"; filter: any }; type GET_FILTERED_COLUMNS = { type: "get-filtered-columns" }; type FETCH_XDD = { type: "fetch-xdd" }; type MAP_QUERY = { - type: "map-query" | "run-map-query"; + type: "map-query"; z: string | number; map_id: any; columns: ColumnProperties[] | null | undefined; @@ -41,7 +41,7 @@ type CLOSE_INFODRAWER = { type: "close-infodrawer" }; type TOGGLE_FILTERS = { type: "toggle-filters" }; type REMOVE_FILTER = { type: "remove-filter"; filter: any }; -type UPDATE_COLUMN_FILTERS = { +export type UPDATE_COLUMN_FILTERS = { type: "update-column-filters"; columns: ColumnGeoJSONRecord[]; }; diff --git a/src/pages/map/map-interface/app-state/reducers/index.ts b/src/pages/map/map-interface/app-state/reducers/index.ts index 70e1f3d9..f22305a5 100644 --- a/src/pages/map/map-interface/app-state/reducers/index.ts +++ b/src/pages/map/map-interface/app-state/reducers/index.ts @@ -10,6 +10,8 @@ import { contextPanelIsInitiallyOpen } from "../nav-hooks"; import { CoreAction, coreReducer } from "./core"; import { hashStringReducer } from "./hash-string"; import { AppAction, AppState, MenuAction, MenuState } from "./types"; +import { pathNameAction } from "../handlers/pathname"; + export const browserHistory = createBrowserHistory(); const routerReducer = createRouterReducer(browserHistory); @@ -30,6 +32,7 @@ const defaultState: AppState = { core: coreReducer(undefined, { type: "init" }), router: routerReducer(undefined, { type: "init" }), menu: menuReducer(undefined, { type: "init" }), + nextRouterAction: null, }; function mainReducer( @@ -92,15 +95,38 @@ function mainReducer( core: coreReducer(state.core, action as CoreAction), menu: menuReducer(state.menu, action as MenuAction), performance: performanceReducer(state.performance, action), + nextRouterAction: state.nextRouterAction, }; } } -const appReducer = (state: AppState, action: AppAction) => { +export default function appReducer(state: AppState, action: AppAction) { // This might not be the right way to do hash management, but it // centralizes the logic in one place. - return hashStringReducer(mainReducer(state, action), action); -}; + return applyNextPath( + hashStringReducer(mainReducer(state, action), action), + action + ); +} + +const pathChangingActions: AppAction["type"][] = [ + "set-menu-page", + "update-cross-section", + "update-state", + "start-map-query", + "close-infodrawer", +]; + +function applyNextPath(state: AppState, action: AppAction): AppState { + if (!pathChangingActions.includes(action.type)) return state; + + const nextRouterAction = pathNameAction(state); + if (nextRouterAction == null) return state; + return { + ...state, + nextRouterAction, + }; +} export function setInfoMarkerPosition( state: AppState, @@ -114,27 +140,6 @@ export function setInfoMarkerPosition( let s1 = state; - // //If we are on the column route, the column layer must be enabled - // let s1 = state; - // const colMatch = matchPath( - // mapPagePrefix + "/loc/:lng/:lat/column", - // pathname ?? state.router.location.pathname - // ); - // if (colMatch != null) { - // s1 = update(s1, { core: { mapLayers: { $add: [MapLayer.COLUMNS] } } }); - // } - - // // If we are disabling the column route, we should remove the column layer - // const colMatch2 = matchPath( - // mapPagePrefix + "/loc/:lng/:lat/column", - // state.router.location.pathname - // ); - - // if (colMatch2 != null && colMatch == null) { - // s1 = update(s1, { core: { mapLayers: { $remove: [MapLayer.COLUMNS] } } }); - // } - - // Set location if (loc != null) { const { lng, lat } = loc.params; return { @@ -177,74 +182,7 @@ export function setInfoMarkerPosition( return state; } -export default appReducer; export * from "./core"; export * from "./hash-string"; export * from "./map"; export * from "./types"; - -/* -function overallReducer(state: AppState, action: Action): AppState { - let pos: MapPosition; - if (action.type === "got-initial-map-state") { - pos = action.data.mapPosition; - } else if (action.type == "map-moved") { - pos = action.data; - } - - if (pos) { - // You can access both app and globe states here - const params = flyToParams(translateCameraPosition(pos)); - //console.log("Set globe position", destination); - return { - ...state, - core: { - ...state.core, - mapPosition: pos, - }, - globe: { - ...state.globe, - flyToProps: { ...params, duration: 0, once: true }, - }, - }; - } - - if (action.type == "map-loading" && !state.core.mapIsLoading) { - return appReducer(state, { - type: "reset-performance-counter", - name: "map-loading", - }); - } - if (action.type == "map-idle" && state.core.mapIsLoading) { - return appReducer(state, { type: "reset-performance-counter" }); - } - - switch (action.type) { - case "@@router/ON_LOCATION_CHANGED": - const isOpen = action.payload.location.pathname != "/"; - return { - ...state, - core: { ...state.core, menuOpen: isOpen, contextPanelOpen: isOpen }, - }; - case "got-initial-map-state": - case "map-moved": - return { - ...state, - core: { - ...state.core, - mapPosition: action.data, - }, - }; - default: - return state; - } -} - -const appReducer = reduceReducers(overallReducer, reducers); - -export type Action = CoreAction | MapAction | GlobeAction | RouterActions; - -export default appReducer; -export * from "./core"; -export * from "./map"; -*/ diff --git a/src/pages/map/map-interface/app-state/reducers/types.ts b/src/pages/map/map-interface/app-state/reducers/types.ts index e535329c..363214f5 100644 --- a/src/pages/map/map-interface/app-state/reducers/types.ts +++ b/src/pages/map/map-interface/app-state/reducers/types.ts @@ -24,6 +24,7 @@ export type AppState = { core: CoreState; router: ReduxRouterState; menu: MenuState; + nextRouterAction: RouterActions | null; }; type OverallActions = { type: "replace-state"; state: AppState }; diff --git a/src/pages/map/map-interface/components/info-drawer/macrostrat-linked.ts b/src/pages/map/map-interface/components/info-drawer/macrostrat-linked.ts index b63f9945..f7cd676f 100644 --- a/src/pages/map/map-interface/components/info-drawer/macrostrat-linked.ts +++ b/src/pages/map/map-interface/components/info-drawer/macrostrat-linked.ts @@ -281,6 +281,7 @@ function LithTypes(props) { [ lith_types.map((lithClass, i) => { return h(LithologyTag, { + key: lithClass.name, data: { ...lithClass, }, diff --git a/src/pages/map/map-interface/index.ts b/src/pages/map/map-interface/index.ts index 5c36deec..6a4d510e 100644 --- a/src/pages/map/map-interface/index.ts +++ b/src/pages/map/map-interface/index.ts @@ -1,6 +1,7 @@ import { ReduxRouter } from "@lagunovsky/redux-react-router"; import h from "@macrostrat/hyper"; import { Route, Routes } from "react-router-dom"; +import { useEffect } from "react"; import "~/styles/global.styl"; import "./searchbar.styl"; @@ -9,7 +10,13 @@ import "./ui-components.styl"; import { createRouterMiddleware } from "@lagunovsky/redux-react-router"; import { Provider } from "react-redux"; import { applyMiddleware, compose, createStore } from "redux"; -import reducerStack, { AppAction, AppState, browserHistory } from "./app-state"; +import reducerStack, { + AppAction, + AppState, + browserHistory, + useAppState, + useAppActions, +} from "./app-state"; /** Redux is used only for the main map applicaton. This heavy state-management approach is * essentially a legacy approach, and we are moving away from this in favor of more lightweight @@ -19,6 +26,7 @@ import reducerStack, { AppAction, AppState, browserHistory } from "./app-state"; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const routerMiddleware = createRouterMiddleware(browserHistory); + // Create the data store let store = createStore( reducerStack, @@ -34,11 +42,23 @@ export default function MapApp({ routerBasename }) { h( ReduxRouter, { basename: routerBasename, store, history: browserHistory }, - [h(Routes, [h(Route, { path: "*", element: h(MapPage) })])] + [h(Routes, [h(Route, { path: "*", element: h(MapPage) })]), h(RouterSync)] ) ); } +function RouterSync() { + /** This is a temporary solution to sync the store with the history object. */ + const nextRouterAction = useAppState((state) => state.nextRouterAction); + const runAction = useAppActions(); + useEffect(() => { + if (nextRouterAction != null) { + runAction(nextRouterAction); + } + }, [nextRouterAction]); + return null; +} + // Extend the window type to include the Redux DevTools types declare global { interface Window { diff --git a/src/pages/map/map-interface/map-page/index.ts b/src/pages/map/map-interface/map-page/index.ts index 3f31cab6..cb11397e 100644 --- a/src/pages/map/map-interface/map-page/index.ts +++ b/src/pages/map/map-interface/map-page/index.ts @@ -3,11 +3,10 @@ import { Suspense, useCallback, useEffect, useRef } from "react"; import { Spinner } from "@blueprintjs/core"; import loadable from "@loadable/component"; import { mapPagePrefix } from "@macrostrat-web/settings"; -import hyper from "@macrostrat/hyper"; import { MapAreaContainer } from "@macrostrat/map-interface"; import classNames from "classnames"; import { useSelector } from "react-redux"; -import { Route, Routes, useParams } from "react-router-dom"; +import { Route, Routes } from "react-router-dom"; import { useTransition } from "transition-hook"; import { useAppActions, @@ -18,14 +17,12 @@ import { import Searchbar from "../components/navbar"; import MapContainer from "./map-view"; import { MenuPage } from "./menu"; -import { info } from "console"; +import h from "./main.module.styl"; const ElevationChart = loadable(() => import("../components/elevation-chart")); const InfoDrawer = loadable(() => import("../components/info-drawer")); const Menu = loadable(() => import("./menu")); -import h from "./main.module.styl"; - function MapView(props) { return h( Suspense, @@ -34,12 +31,13 @@ function MapView(props) { ); } -export const MapPage = ({ +function MapPage({ baseRoute = "/", menuPage = null, }: { + baseRoute?: string; menuPage?: MenuPage; -}) => { +}) { const runAction = useAppActions(); const inputFocus = useAppState((s) => s.core.inputFocus); const infoDrawerOpen = useAppState((s) => s.core.infoDrawerOpen); @@ -92,7 +90,7 @@ export const MapPage = ({ }, [h("div.context-underlay", { onClick: onMouseDown }), h(MapView)] ); -}; +} function MapPageRoutes() { return h(Routes, [ @@ -123,36 +121,8 @@ function InfoDrawerHolder() { }), }), ]), - h(InfoDrawerLocationGrabber), + //h(InfoDrawerLocationGrabber), ]); } -function InfoDrawerLocationGrabber() { - // We could probably do this in the reducer... - const z = Math.round( - useAppState((s) => s.core.mapPosition.target?.zoom) ?? 7 - ); - const infoMarkerPosition = useAppState((s) => s.core.infoMarkerPosition); - const runAction = useAppActions(); - - const { lat, lng } = infoMarkerPosition ?? {}; - - // Todo: this is a pretty janky way to do state management - useEffect(() => { - if (lat == null || lng == null) return; - runAction({ - type: "run-map-query", - lat: Number(lat), - lng: Number(lng), - z, - // Focused column or map unit from active layers. - // This is a bit anachronistic, since we want to be - // able to show columns that aren't necessarily shown on the map - columns: [], - map_id: null, - }); - }, [lat, lng]); - return null; -} - export default MapPageRoutes; diff --git a/src/pages/map/map-interface/map-page/settings-panel.module.styl b/src/pages/map/map-interface/map-page/settings-panel.module.styl index 8e6e08f2..1b3c756b 100644 --- a/src/pages/map/map-interface/map-page/settings-panel.module.styl +++ b/src/pages/map/map-interface/map-page/settings-panel.module.styl @@ -16,11 +16,22 @@ font-size: 0.9em line-height: 1em +.dark-mode-controls + display: flex + gap: 0.5em + align-items: center + &>:first-child + flex-grow: 1 + .settings .auto-button font-size: 12px font-style: italic + &>* + width: 100% + margin: 0.1em 0 0.2em + :global .bp5-control display: flex @@ -34,8 +45,6 @@ position: relative .bp5-button, .bp5-button-group - width: 100% - margin: 0.1em 0 0.2em justify-content: start align-items: center //box-shadow: 0 0 0px 1px var(--card-shadow-color) @@ -52,6 +61,9 @@ .callout-panel border-radius: 3px overflow: hidden + .callout-header + :global(.bp5-button) + width: 100% &.expanded .callout-header :global(.bp5-button) @@ -78,3 +90,6 @@ gap: 0.5em &>* flex: 1 + + + diff --git a/src/pages/map/map-interface/map-page/settings-panel.ts b/src/pages/map/map-interface/map-page/settings-panel.ts index 1940b692..57b60bc7 100644 --- a/src/pages/map/map-interface/map-page/settings-panel.ts +++ b/src/pages/map/map-interface/map-page/settings-panel.ts @@ -4,6 +4,7 @@ import { AnchorButton, Button, + ButtonGroup, Callout, Collapse, Icon, @@ -12,7 +13,6 @@ import { Switch, Tag, } from "@blueprintjs/core"; -import hyper from "@macrostrat/hyper"; import { applyMapPositionToHash } from "@macrostrat/map-interface"; import { DarkModeButton, @@ -27,9 +27,7 @@ import { useAppState, } from "~/pages/map/map-interface/app-state"; -import styles from "./settings-panel.module.styl"; - -const h = hyper.styled(styles); +import h from "./settings-panel.module.styl"; const ExperimentsPanel = (props) => { const dispatch = useAppActions(); @@ -198,33 +196,30 @@ function ThemeButton() { const update = darkModeUpdater(); const icon = darkMode.isAutoset ? "tick" : "desktop"; - const autoButton = h( - Button, - { - minimal: true, - active: darkMode.isAutoset, - rightIcon: h(Icon, { icon, size: 12 }), - intent: darkMode.isAutoset ? "success" : "primary", - className: "auto-button sub-button", - small: true, - onClick(evt) { - if (darkMode.isAutoset) return; - evt.stopPropagation(); - update(null); - }, - }, - - "auto" - ); - const darkModeText = darkMode.isEnabled ? "Turn on the lights" : "Turn off the lights"; return h("div.dark-mode-controls", [ + h(DarkModeButton, { minimal: true, active: false, allowReset: true }, [ + h("span.text", darkModeText), + ]), h( - DarkModeButton, - { minimal: true, active: false, allowReset: true, rightIcon: autoButton }, - [h("span.text", darkModeText)] + Button, + { + minimal: true, + active: darkMode.isAutoset, + rightIcon: h(Icon, { icon, size: 12 }), + intent: darkMode.isAutoset ? "success" : "primary", + className: "auto-button sub-button", + small: true, + onClick(evt) { + if (darkMode.isAutoset) return; + evt.stopPropagation(); + update(null); + }, + }, + + "auto" ), ]); }