Skip to main content

Vue

Install​

npm install @crosswalkjs/canvas @crosswalkjs/core

The component​

MapEditor.vue
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
import { createMapper, type Mapper, type ThemeDefinition } from "@crosswalkjs/canvas";
import type { MapDocument, OperatorRegistry } from "@crosswalkjs/core";

const props = withDefaults(
defineProps<{
map: MapDocument;
/** A theme id, or your own theme. */
theme?: string | ThemeDefinition;
/** Your operators. Read once, when the editor is created. */
registry?: OperatorRegistry;
height?: string;
}>(),
{
theme: "ink",
height: "600px",
},
);
const emit = defineEmits<{ change: [map: MapDocument] }>();

const host = ref<HTMLDivElement>();
// Not reactive: Vue doesn't need to track the editor's insides.
let editor: Mapper | undefined;

onMounted(() => {
editor = createMapper(host.value!, {
document: props.map,
theme: props.theme,
registry: props.registry,
onChange: (map) => emit("change", map),
});
});

watch(
() => props.theme,
(theme) => editor?.setTheme(theme),
);

onBeforeUnmount(() => editor?.destroy());

defineExpose({ setDocument: (map: MapDocument) => editor?.setDocument(map) });
</script>

<template>
<div ref="host" :style="{ height }" />
</template>

Using it​

App.vue
<script setup lang="ts">
import { computed, ref } from "vue";
import { createTransform, type MapDocument } from "@crosswalkjs/core";
import MapEditor from "./MapEditor.vue";
import startingMap from "./order.map.json";
import sampleOrder from "./sample-order.json";

const initialMap = startingMap as MapDocument;
const map = ref(initialMap);
const output = computed(() => {
try {
return createTransform(map.value)(sampleOrder);
} catch (error) {
return (error as Error).message;
}
});
</script>

<template>
<MapEditor :map="initialMap" @change="map = $event" />
<pre>{{ JSON.stringify(output, null, 2) }}</pre>
</template>

The editor is still given the starting map, not map (why). To load a different map, call setDocument through a template ref, or change the component's key.

Keep the editor out of reactive state. A ref() or reactive() around the object createMapper returns would make Vue wrap the editor's internals in proxies for no benefit.

Nuxt​

The component needs no changes for Nuxt (why). To leave it out of the server render altogether, name the file MapEditor.client.vue, or wrap it in <ClientOnly>:

<ClientOnly>
<MapEditor :map="map" @change="save" />
<template #fallback>Loading the editor…</template>
</ClientOnly>