Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 49 additions & 8 deletions app/components/Canvas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ const props = defineProps({
themeType: { type: String, default: 'dark' },
});

const emit = defineEmits(['update:width', 'update:height']);
const emit = defineEmits(['update:width', 'update:height', 'resize-start', 'resize-end']);

const x = ref(null);
const y = ref(null);
Expand All @@ -155,6 +155,10 @@ const { zoom, aspectRatio } = toRefs(props);
let resizeObserver = null;
let observedSceneWindow = null;
let sceneMetricsRequest = null;
let resizeRequest = null;
let pendingResize = null;
let resizeScaleX = 1;
let resizeScaleY = 1;

const ratio = computed(() => {
if (aspectRatio.value) {
Expand Down Expand Up @@ -182,22 +186,55 @@ function onResizeStart(event) {

resizingWidth.value = hasWidth || !!ratio.value;
resizingHeight.value = hasHeight || !!ratio.value;

const container = event.target.parentNode;
const containerRect = container.getBoundingClientRect();

resizeScaleX = containerRect.width / container.offsetWidth || 1;
resizeScaleY = containerRect.height / container.offsetHeight || 1;

emit('resize-start');
}

function onResizeEnd() {
flushResize();

resizingWidth.value = false;
resizingHeight.value = false;

emit('resize-end');
}

function onResize(event) {
const container = event.target.parentNode;
if (event.rect.width) {
const scaleX = container.getBoundingClientRect().width / container.offsetWidth;
emit('update:width', event.rect.width / scaleX);
pendingResize = {
width: event.rect.width,
height: event.rect.height,
};

if (!resizeRequest) {
resizeRequest = requestAnimationFrame(flushResize);
}
}

function flushResize() {
if (resizeRequest) {
cancelAnimationFrame(resizeRequest);
resizeRequest = null;
}

if (!pendingResize) {
return;
}

const { width, height } = pendingResize;
pendingResize = null;

if (width) {
emit('update:width', width / resizeScaleX);
}
if (event.rect.height) {
const scaleY = container.getBoundingClientRect().height / container.offsetHeight;
emit('update:height', event.rect.height / scaleY);

if (height) {
emit('update:height', height / resizeScaleY);
}
}

Expand Down Expand Up @@ -271,6 +308,10 @@ onBeforeUnmount(() => {
cancelAnimationFrame(sceneMetricsRequest);
}

if (resizeRequest) {
cancelAnimationFrame(resizeRequest);
}

resizeObserver?.disconnect();
});
</script>
22 changes: 1 addition & 21 deletions app/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,6 @@
<Monaco
ref="monaco"
class="size-full"
:width="width"
:height="height"
:value="modelValue"
:added="added"
:removed="removed"
Expand All @@ -174,8 +172,7 @@ import {
ArrowDownIcon,
CreditCardIcon,
} from 'lucide-vue-next';
import { ref, watch, toRefs, computed, onMounted, onUnmounted } from 'vue';
import { useResizeObserver } from '@vueuse/core';
import { ref, toRefs, computed, onUnmounted } from 'vue';

const props = defineProps({
id: { type: String, required: true },
Expand Down Expand Up @@ -211,8 +208,6 @@ const { sizes, orientation, language } = toRefs(props);
const { $bus, $shiki } = useNuxtApp();
const { options: languageOptions } = useLanguages();

const width = ref(0);
const height = ref(0);
const root = ref(null);
const toolbar = ref(null);

Expand All @@ -225,20 +220,5 @@ const languageAlias = computed(
language.value
);

function updateMonacoDimensions() {
if (root.value && root.value.offsetParent) {
width.value = root.value.clientWidth;
height.value = root.value.clientHeight;
}
}

useResizeObserver(document.body, updateMonacoDimensions);

onMounted(() => {
updateMonacoDimensions();
watch([sizes, orientation], updateMonacoDimensions);
$bus.$on('editors:refresh', updateMonacoDimensions);
});

onUnmounted(() => $bus.$emit('editors:refresh'));
</script>
86 changes: 47 additions & 39 deletions app/components/Monaco.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div ref="root" :style="{ height: `${height}px` }"></div>
<div ref="root" class="w-full" :style="{ height: height ? `${height}px` : '100%' }"></div>
</template>

<script setup>
Expand All @@ -8,6 +8,7 @@ import { storeToRefs } from 'pinia';
import { useResizeObserver } from '@vueuse/core';
import { uniq, get, range, union, difference } from 'lodash';
import useFonts from '@/composables/useFonts';
import useMonaco from '@/composables/useMonaco';
import useApplicationStore from '@/composables/useApplicationStore';
import usePreferencesStore from '@/composables/usePreferencesStore';

Expand All @@ -25,7 +26,7 @@ const props = defineProps({

const emit = defineEmits(['update:modelValue', 'update:added', 'update:removed', 'update:focused']);

const { language, tabSize, value, width, height, added, removed, focused } = toRefs(props);
const { language, tabSize, value, height, added, removed, focused } = toRefs(props);

const { $bus } = useNuxtApp();

Expand All @@ -34,6 +35,7 @@ const editor = shallowRef(null);
const monacoRef = shallowRef(null);

const { fontFamilies } = useFonts();
const { prepare, loadLanguage, loadTheme } = useMonaco();

const addedLineDecos = ref([]);
const removedLineDecos = ref([]);
Expand All @@ -51,7 +53,11 @@ const {
editorFontSize: fontSize,
} = storeToRefs(usePreferencesStore());

let layoutFrame = null;

function updateLayout() {
layoutFrame = null;

if (root.value && root.value.offsetParent && editor.value) {
editor.value.layout({
width: root.value.clientWidth,
Expand All @@ -60,6 +66,14 @@ function updateLayout() {
}
}

function scheduleLayout() {
if (layoutFrame) {
cancelAnimationFrame(layoutFrame);
}

layoutFrame = requestAnimationFrame(updateLayout);
}

function makeDecoration(range, type) {
return {
options: {
Expand All @@ -74,7 +88,7 @@ function selectionToRange(selection) {
return range(selection.startLineNumber, selection.endLineNumber + 1);
}

useResizeObserver(root, updateLayout);
useResizeObserver(root, scheduleLayout);

const fontFamily = computed(() => {
const font = fontFamilies.value.find((font) => font.name === editorFontFamily.value);
Expand Down Expand Up @@ -112,33 +126,10 @@ const makeDecosCallback = (className, decoRef) => (lines) => {
};

onMounted(async () => {
self.MonacoEnvironment = {
getWorker: () =>
new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker.js', import.meta.url), {
type: 'module',
}),
};

const monaco = await import('monaco-editor');
const activeTheme = isDarkMode.value ? editorDarkTheme.value : editorLightTheme.value;
const monaco = await prepare({ language: language.value, theme: activeTheme });
monacoRef.value = monaco;

// Load themes
const themeList = (await import('../data/monaco-themes/themelist.json')).default;

const themeLoaders = import.meta.glob(
['../data/monaco-themes/*.json', '!../data/monaco-themes/themelist.json'],
{ import: 'default' }
);

await Promise.all(
Object.keys(themeList).map(async (theme) => {
const loader = themeLoaders[`../data/monaco-themes/${themeList[theme]}.json`];
if (loader) {
monaco.editor.defineTheme(theme, await loader());
}
})
);

if (!root.value || !root.value.isConnected) return;

editor.value = monaco.editor.create(root.value, {
Expand All @@ -156,7 +147,7 @@ onMounted(async () => {
renderLineHighlight: false,
scrollBeyondLastLine: false,
lineNumbersMinChars: 6,
theme: isDarkMode.value ? editorDarkTheme.value : editorLightTheme.value,
theme: activeTheme,
});

document.fonts.ready.then(() => monaco.editor.remeasureFonts());
Expand Down Expand Up @@ -193,13 +184,17 @@ onMounted(async () => {
}
});

$bus.$on('editors:refresh', updateLayout);
$bus.$on('editors:refresh', scheduleLayout);

watch(isDarkMode, (enabled) => {
monaco.editor.setTheme(enabled ? editorDarkTheme.value : editorLightTheme.value);
watch(isDarkMode, async (enabled) => {
const theme = enabled ? editorDarkTheme.value : editorLightTheme.value;

await loadTheme(theme);
monaco.editor.setTheme(theme);
});

watch(language, (lang) => {
watch(language, async (lang) => {
await loadLanguage(lang);
monaco.editor.setModelLanguage(editor.value.getModel(), lang);
});

Expand Down Expand Up @@ -238,22 +233,35 @@ onMounted(async () => {
});
});

watch([width, height], updateLayout);
watch(height, scheduleLayout);

watch(editorDarkTheme, async (theme) => {
if (!isDarkMode.value) return;

watch(editorDarkTheme, (theme) => {
if (isDarkMode.value) monaco.editor.setTheme(theme);
await loadTheme(theme);
monaco.editor.setTheme(theme);
});

watch(editorLightTheme, (theme) => {
if (!isDarkMode.value) monaco.editor.setTheme(theme);
watch(editorLightTheme, async (theme) => {
if (isDarkMode.value) return;

await loadTheme(theme);
monaco.editor.setTheme(theme);
});

watch(added, makeDecosCallback('added', addedLineDecos), { immediate: true });
watch(removed, makeDecosCallback('removed', removedLineDecos), { immediate: true });
watch(focused, makeDecosCallback('focused', focusedLineDecos), { immediate: true });
});

onBeforeUnmount(() => editor.value?.dispose());
onBeforeUnmount(() => {
if (layoutFrame) {
cancelAnimationFrame(layoutFrame);
}

$bus.$off('editors:refresh', scheduleLayout);
editor.value?.dispose();
});
</script>

<style>
Expand Down
Loading