Skip to content

Commit 65d970a

Browse files
committed
feat(ol-style-icon): allow to place icon content in slot
closes #295
1 parent 9a30ace commit 65d970a

10 files changed

+135
-19
lines changed

docs/componentsguide/styles/icon/index.md

+26-7
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,51 @@ Use it inside ol-style to style points
66

77
[[toc]]
88

9-
## Demo
10-
119
<script setup>
1210
import IconDemo from "@demos/IconDemo.vue"
11+
import IconDemoHtmlContent from "@demos/IconDemoHtmlContent.vue"
1312
</script>
1413

15-
<ClientOnly>
16-
<IconDemo />
17-
</ClientOnly>
18-
1914
## Setup
2015

2116
<!--@include: ../../styles.plugin.md-->
2217

2318
## Usage
2419

2520
| Plugin Usage | Explicit Import |
26-
| ----------------- | :--------------------: |
21+
|-------------------|:----------------------:|
2722
| `<ol-style-icon>` | `<Styles.OlStyleIcon>` |
2823

24+
### `src` URL
25+
26+
You can pass an icon image by setting the `src` attribute as shown below.
27+
28+
<ClientOnly>
29+
<IconDemo />
30+
</ClientOnly>
31+
2932
::: code-group
3033

3134
<<< ../../../../src/demos/IconDemo.vue
3235

3336
:::
3437

38+
### Slot
39+
40+
If no `src` Attribute is set, the component will render the HTML content within the slot into an image data URL and use the generated image as icon.
41+
42+
<ClientOnly>
43+
<IconDemoHtmlContent />
44+
</ClientOnly>
45+
46+
::: code-group
47+
48+
<<< ../../../../src/demos/IconDemoHtmlContent.vue
49+
50+
:::
51+
52+
## Properties
53+
3554
### Props from OpenLayers
3655

3756
Properties are passed-trough from OpenLayers directly.

package-lock.json

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
"dependencies": {
7878
"@turf/buffer": "^6.5.0",
7979
"@turf/helpers": "^6.5.0",
80+
"dom-to-image-more": "^3.3.0",
8081
"file-saver": "^2.0.5",
8182
"jspdf": "^2.5.1",
8283
"ol": "^9.1.0",

src/components/styles/OlStyleIcon.vue

+38-11
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,29 @@
11
<template>
2-
<div v-if="false"></div>
2+
<div
3+
ref="htmlContent"
4+
v-if="!srcImageUrl"
5+
style="display: flex; position: absolute; z-index: -1000"
6+
>
7+
<slot />
8+
</div>
39
</template>
410
<script setup lang="ts">
511
import Icon, { type Options } from "ol/style/Icon";
612
import type { Ref } from "vue";
7-
import { inject, watch, onMounted, onUnmounted, computed } from "vue";
13+
import {
14+
inject,
15+
watch,
16+
onMounted,
17+
onUnmounted,
18+
ref,
19+
useSlots,
20+
computed,
21+
} from "vue";
822
import type Style from "ol/style/Style";
923
import type Draw from "ol/interaction/Draw";
1024
import type Modify from "ol/interaction/Modify";
1125
import usePropsAsObjectProperties from "@/composables/usePropsAsObjectProperties";
26+
import domtoimage from "dom-to-image-more";
1227
1328
const props = withDefaults(defineProps<Options>(), {
1429
anchorOrigin: "top-left",
@@ -21,6 +36,11 @@ const props = withDefaults(defineProps<Options>(), {
2136
rotation: 0,
2237
});
2338
39+
const slots = useSlots();
40+
41+
const htmlContent = ref<HTMLElement>();
42+
const srcImageUrl = ref<string>("");
43+
2444
const style = inject<Ref<Style | null> | null>("style", null);
2545
const styledObj = inject<Ref<Draw | Modify | Style | null> | null>(
2646
"styledObj",
@@ -30,7 +50,10 @@ const styledObj = inject<Ref<Draw | Modify | Style | null> | null>(
3050
const properties = usePropsAsObjectProperties(props);
3151
3252
const icon = computed(() => {
33-
const ic = new Icon(properties);
53+
const ic = new Icon({
54+
...properties,
55+
src: properties.src || srcImageUrl.value,
56+
});
3457
ic.load();
3558
return ic;
3659
});
@@ -47,15 +70,19 @@ watch(properties, () => {
4770
applyStyle();
4871
});
4972
50-
watch(
51-
() => style?.value,
52-
() => {
53-
applyStyle();
54-
},
55-
);
73+
watch(style, () => {
74+
applyStyle();
75+
});
5676
57-
onMounted(() => {
58-
style?.value?.setImage(icon.value);
77+
onMounted(async () => {
78+
if (slots.default && htmlContent.value) {
79+
srcImageUrl.value = await domtoimage.toSvg(htmlContent.value, {
80+
width: htmlContent.value.offsetWidth,
81+
height: htmlContent.value.offsetHeight,
82+
copyDefaultStyles: true,
83+
});
84+
}
85+
applyStyle();
5986
});
6087
6188
onUnmounted(() => {

src/demos/IconDemoHtmlContent.vue

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<template>
2+
<ol-map
3+
ref="map"
4+
:loadTilesWhileAnimating="true"
5+
:loadTilesWhileInteracting="true"
6+
style="height: 400px"
7+
>
8+
<ol-view
9+
ref="view"
10+
:center="center"
11+
:zoom="zoom"
12+
:projection="projection"
13+
/>
14+
15+
<ol-tile-layer>
16+
<ol-source-osm />
17+
</ol-tile-layer>
18+
19+
<ol-vector-layer>
20+
<ol-source-vector
21+
url="https://raw.githubusercontent.com/alpers/Turkey-Maps-GeoJSON/master/tr-cities-airports.json"
22+
:format="geoJson"
23+
:projection="projection"
24+
>
25+
</ol-source-vector>
26+
<ol-style>
27+
<ol-style-stroke color="red" :width="2"></ol-style-stroke>
28+
<ol-style-fill color="rgba(255,255,255,0.1)"></ol-style-fill>
29+
<ol-style-icon :scale="1">
30+
<span class="marker">📍</span>
31+
</ol-style-icon>
32+
</ol-style>
33+
</ol-vector-layer>
34+
</ol-map>
35+
</template>
36+
37+
<script setup>
38+
import { ref, inject } from "vue";
39+
40+
const center = ref([34, 39.13]);
41+
const projection = ref("EPSG:4326");
42+
const zoom = ref(6.8);
43+
const format = inject("ol-format");
44+
const geoJson = new format.GeoJSON();
45+
</script>
46+
47+
<style scoped>
48+
.marker {
49+
background-color: #ffddaa;
50+
padding: 10px;
51+
border-radius: 25px;
52+
margin: 5px;
53+
font-size: 25px;
54+
}
55+
</style>

src/dom-to-image.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declare module "dom-to-image-more";
Binary file not shown.

tests/style.test.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,20 @@ test.describe("ol-style-flowline", () => {
4343
});
4444

4545
test.describe("ol-style-icon", () => {
46-
test("should render", async ({ page }) => {
46+
test("should render icons defined by src URL", async ({ page }) => {
4747
const map = new MapPage(page);
4848
await map.goto("/componentsguide/styles/icon/");
4949
await map.waitUntilReady();
5050
await map.waitUntilCanvasLoaded();
5151
await map.checkCanvasScreenshot();
5252
});
53+
test("should render icons from slot conent", async ({ page }) => {
54+
const map = new MapPage(page, 1);
55+
await map.goto("/componentsguide/styles/icon/");
56+
await map.waitUntilReady();
57+
await map.waitUntilCanvasLoaded();
58+
await map.checkCanvasScreenshot();
59+
});
5360
});
5461

5562
test.describe("ol-style-stroke", () => {

0 commit comments

Comments
 (0)