Skip to content

Commit

Permalink
Update to v3
Browse files Browse the repository at this point in the history
  • Loading branch information
KaiHuebner committed Feb 23, 2024
1 parent aaf4772 commit 1f335b8
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 123 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@rollup/plugin-terser": "^0.4.4",
"@solidjs/testing-library": "^0.8.6",
"@testing-library/jest-dom": "^6.4.2",
"@types/mapbox-gl": "^2.7.21",
"@types/mapbox-gl": "^3.1.0",
"@vitest/coverage-v8": "^1.3.1",
"jsdom": "^24.0.0",
"jsdom-worker": "^0.3.0",
Expand Down
20 changes: 10 additions & 10 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

215 changes: 109 additions & 106 deletions src/components/Layer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,205 +1,208 @@
import { onCleanup, createEffect, Component, createUniqueId } from 'solid-js'
import { useMapContext } from '../MapProvider'
import { useSourceId } from '../Source'
import { layerEvents } from '../../events'
import { baseStyle, layoutStyles } from '../../styles'
import type { layerEventTypes } from '../../events'
import { onCleanup, createEffect, Component, createUniqueId } from "solid-js";
import { useMapContext } from "../MapProvider";
import { useSourceId } from "../Source";
import { layerEvents } from "../../events";
import { baseStyle, layoutStyles } from "../../styles";
import type { layerEventTypes } from "../../events";
import type {
FilterSpecification,
StyleSpecification,
} from 'mapbox-gl/src/style-spec/types.js'
import type { CustomLayerInterface } from 'mapbox-gl/src/style/style_layer/custom_style_layer'
} from "mapbox-gl/src/style-spec/types.js";
import type { CustomLayerInterface } from "mapbox-gl/src/style/style_layer/custom_style_layer";

const diff = (
newProps: StyleSpecification = {},
prevProps: StyleSpecification = {}
prevProps: StyleSpecification = {},
): [string, any][] => {
const keys = new Set([...Object.keys(newProps), ...Object.keys(prevProps)])
const keys = new Set([...Object.keys(newProps), ...Object.keys(prevProps)]);
return [...keys].reduce((acc, key: string) => {
const value = newProps[key]
const value = newProps[key];
if (value !== prevProps[key]) {
acc.push([key, value])
acc.push([key, value]);
}
return acc
}, [])
}
return acc;
}, []);
};

type Props = {
id?: string
id?: string;
/** A string that uniquely identifies the layer. If not provided, a unique ID will be generated. */
style?: StyleSpecification
style?: StyleSpecification;
/** A Mapbox Style Specification object that defines the visual appearance of the layer. */
customLayer?: CustomLayerInterface
customLayer?: CustomLayerInterface;
/** An object that implements the `CustomLayerInterface` interface, which allows you to create custom layers using WebGL. */
filter?: FilterSpecification
filter?: FilterSpecification;
/** A Mapbox filter specification that defines which features of the layer to include or exclude from the layer. */
visible?: boolean
visible?: boolean;
/** A boolean that determines whether the layer is visible or not. */
sourceId?: string
sourceId?: string;
/** A string that specifies the ID of the source that the layer uses for its data. */
slot?: string
slot?: "bottom" | "middle" | "top" | string;
/** A string that specifies the slot to which the layer belongs. */
beforeType?:
| 'background'
| 'fill'
| 'line'
| 'symbol'
| 'raster'
| 'circle'
| 'fill-extrusion'
| 'heatmap'
| 'hillshade'
| 'sky'
| "background"
| "fill"
| "line"
| "symbol"
| "raster"
| "circle"
| "fill-extrusion"
| "heatmap"
| "hillshade"
| "sky"
| string;
/** A string that specifies the type of layer before which the current layer should be inserted. */
beforeId?: string
beforeId?: string;
/** A string that specifies the ID of the layer before which the current layer should be inserted. */
featureState?: { id: number | string; state: object }
featureState?: { id: number | string; state: object };
/** An object that specifies the state of a feature in the layer. The object consists of an ID (either a number or a string) and an object containing the state. */
children?: any
children?: any;
/** Any content that should be rendered within the layer. */
} & layerEventTypes
} & layerEventTypes;

const newKey = (key, type) =>
(key.startsWith(type) || key.startsWith('icon') || key.startsWith('text')
? ''
: type + '-') + key.replace(/[A-Z]/g, (s) => '-' + s.toLowerCase())
(key.startsWith(type) || key.startsWith("icon") || key.startsWith("text")
? ""
: type + "-") + key.replace(/[A-Z]/g, (s) => "-" + s.toLowerCase());

const updateStyle = (oldStyle) => {
if (!oldStyle) return
let layout = {}
let paint = {}
let style = {}
if (!oldStyle) return;
let layout = {};
let paint = {};
let style = {};

Object.entries(oldStyle).forEach(([key, value]) => {
if (baseStyle.includes(key)) style[key] = value
if (baseStyle.includes(key)) style[key] = value;
else {
const nk = newKey(key, oldStyle.type)
layoutStyles.includes(nk) ? (layout[nk] = value) : (paint[nk] = value)
const nk = newKey(key, oldStyle.type);
layoutStyles.includes(nk) ? (layout[nk] = value) : (paint[nk] = value);
}
})
});
if (oldStyle.paint)
Object.entries(oldStyle.paint).forEach(
([key, value]) => (paint[newKey(key, oldStyle.type)] = value)
)
([key, value]) => (paint[newKey(key, oldStyle.type)] = value),
);
if (oldStyle.layout)
Object.entries(oldStyle.layout).forEach(
([key, value]) => (layout[newKey(key, oldStyle.type)] = value)
)
return { ...style, paint, layout } as StyleSpecification
}
([key, value]) => (layout[newKey(key, oldStyle.type)] = value),
);
return { ...style, paint, layout } as StyleSpecification;
};

export const Layer: Component<Props> = (props) => {
const [ctx] = useMapContext()
const sourceId: string = props.style?.source || useSourceId()
props.id = props.id || props.customLayer?.id || createUniqueId()
const [ctx] = useMapContext();
const sourceId: string = props.style?.source || useSourceId();
props.id = props.id || props.customLayer?.id || createUniqueId();

const debug = (text, value?) => {
;(ctx.map.debug || ctx.map.debugEvents) &&
console.debug('%c[MapGL]', 'color: #10b981', text, value || '')
}
(ctx.map.debug || ctx.map.debugEvents) &&
console.debug("%c[MapGL]", "color: #10b981", text, value || "");
};

// Add Layer
ctx.map.addLayer(
props.customLayer || {
...updateStyle(props.style),
id: props.id,
source: sourceId,
slot: props.slot || '',
slot: props.slot || "",
metadata: {
smg: { beforeType: props.beforeType, beforeId: props.beforeId },
},
},
props.beforeType
? ctx.map.getStyle().layers.find((l) => l.type === props.beforeType)?.id
: props.beforeId
)
ctx.map.layerIdList.push(props.id)
if (props.customLayer) ctx.map.fire('load')
debug('Add Layer:', props.id)
: props.beforeId,
);
ctx.map.layerIdList.push(props.id);
if (props.customLayer) ctx.map.fire("load");
debug("Add Layer:", props.id);

// Hook up events
layerEvents.forEach((item) => {
if (props[item]) {
const event = item.slice(2).toLowerCase()
const event = item.slice(2).toLowerCase();
ctx.map.on(event, props.id, (evt) => {
evt.clickOnLayer = true
props[item](evt)
evt.clickOnLayer = true;
props[item](evt);
ctx.map.debugEvent &&
debug(`Layer '${event}' event on '${props.id}':`, evt)
})
debug(`Layer '${event}' event on '${props.id}':`, evt);
});
}
})
});

// Update Style
createEffect((prev: StyleSpecification) => {
const style = updateStyle(props.style)
if (style === prev) return
const style = updateStyle(props.style);
if (style === prev) return;

if (style.layout !== prev?.layout)
diff(style.layout, prev?.layout).forEach(([key, value]) =>
ctx.map.setLayoutProperty(props.id, key, value, { validate: false })
)
ctx.map.setLayoutProperty(props.id, key, value, { validate: false }),
);

if (style.paint !== prev?.paint)
diff(style.paint, prev?.paint).forEach(([key, value]) =>
ctx.map.setPaintProperty(props.id, key, value, { validate: false })
)
ctx.map.setPaintProperty(props.id, key, value, { validate: false }),
);

if (style.minzoom !== prev?.minzoom || style.maxzoom !== prev?.maxzoom)
ctx.map.setLayerZoomRange(props.id, style.minzoom, style.maxzoom)
ctx.map.setLayerZoomRange(props.id, style.minzoom, style.maxzoom);

if (style.filter !== prev?.filter)
ctx.map.setFilter(props.id, style.filter, { validate: false })
ctx.map.setFilter(props.id, style.filter, { validate: false });

debug('Update Layer Style:', props.id)
return style
}, updateStyle(props.style))
debug("Update Layer Style:", props.id);
return style;
}, updateStyle(props.style));

// Update Visibility
createEffect((prev: boolean) => {
if (props.visible === prev) return
if (props.visible === prev) return;

ctx.map.setLayoutProperty(
props.id,
'visibility',
props.visible ? 'visible' : 'none',
{ validate: false }
)
debug(`Update Visibility (${props.id}):`, props.visible.toString())
return props.visible
}, props.visible)
"visibility",
props.visible ? "visible" : "none",
{ validate: false },
);
debug(`Update Visibility (${props.id}):`, props.visible.toString());
return props.visible;
}, props.visible);

// Update Filter
createEffect(async () => {
if (!props.filter) return
if (!props.filter) return;

!ctx.map.isStyleLoaded() && (await ctx.map.once('styledata'))
ctx.map.setFilter(props.id, props.filter)
debug(`Update Filter (${props.id}):`, props.filter)
})
!ctx.map.isStyleLoaded() && (await ctx.map.once("styledata"));
ctx.map.setFilter(props.id, props.filter);
debug(`Update Filter (${props.id}):`, props.filter);
});

// Update Feature State
createEffect(async () => {
if (!props.featureState || props.featureState.id === null) return
if (!props.featureState || props.featureState.id === null) return;

!ctx.map.isStyleLoaded() && (await ctx.map.once('styledata'))
!ctx.map.isStyleLoaded() && (await ctx.map.once("styledata"));

ctx.map.removeFeatureState({
source: sourceId,
sourceLayer: props.style['source-layer'],
})
sourceLayer: props.style["source-layer"],
});
ctx.map.setFeatureState(
{
source: sourceId,
sourceLayer: props.style['source-layer'],
sourceLayer: props.style["source-layer"],
id: props.featureState.id,
},
props.featureState.state
)
})
props.featureState.state,
);
});

//Remove Layer
onCleanup(() => ctx.map?.getLayer(props.id) && ctx.map?.removeLayer(props.id))
onCleanup(
() => ctx.map?.getLayer(props.id) && ctx.map?.removeLayer(props.id),
);

return props.children
}
return props.children;
};
2 changes: 1 addition & 1 deletion src/components/MapGL/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
| viewport | object | Current viewport of the map, contains: `latitude, longitude, zoom, ...` |
| onViewportChange | Viewport | Set the map viewport |
| options | object | [Mapbox map parameter](https://docs.mapbox.com/mapbox-gl-js/api/map/#map-parameters) |
| config | { importID, configName, value } | Sets configuration in Mapbox Standard Style |
| config | object | Sets configuration in Mapbox Standard Style |
| transitionType | string | flyTo^, easeTo, jumpTo |
| on\[Event] | Event | Any [Map Event](https://docs.mapbox.com/mapbox-gl-js/api/map/#map-events) - eg.: onMouseMove |
| onUserInteraction | boolean | Event Listeners for user interactions with the map |
Expand Down
Loading

0 comments on commit 1f335b8

Please sign in to comment.