Closed basil closed 11 months ago
I think the draggable
and contenteditable
errors are false positives. Looking at the rendered HTML, the values are correctly set to the same values that they are in the Vue 2 app. I.e. draggable is false and content-editable can take on multiple values.
From what I can tell, it is just a warning because the behavior changed from Vue 2 to 3. I understand why they are warning us, since this could potentially break things. But in these specific cases, nothing will break.
Great! At the moment I am not aware of any issues on this branch.
I'm seeing some slower speeds with larger files (the test file I'm using is 8 pages). I'm not sure what the root cause is. I'll continue investigating.
For example, I can open a large file in the Vue 2 version and hold down J to enter a bunch of ison neumes without slow-down. But in Vue 3 that grinds everything to a halt. Quickly typing in lyrics is also laggy.
What I am seeing is that in Vue 2, whenever I add a neume to the end of a document, only the single neume is rendered. The rest of the already-rendered neumes are left as it.
But in Vue 3, it always re-renders everything. There must be some subtle behavioral change that is causing this. Somehow I need to communicate to Vue 3 that most of the elements have remained the same.
Below are screenshots from Vue.js Devtools Performance Timeline.
Vue 2
Only the one neume is updated.
Vue 3
Each little bar is a re-rendered component.
Yikes. I did not notice that, but I am running the application on an 8-core Intel i7-9700K at 4.9 GHz.
From my extremely naïve perspective, two potential theories come to mind:
vue-facing-decorator
analyzes and transforms into the return value of the data
function in the Vue Options API) was getting wrapped in a Vue proxy to watch all read/write events and fire reactive event handlers. This behavior was neither needed nor desired for the audio service and playback service, and in fact it was interfering with the operation of these services. It would not surprise me if something like this was happening with a different class property.Since I am extremely inexperienced with this framework in general and with this code base in particular, please take these theories with a grain of salt and do not waste too much time on them if they do not make sense.
By putting a breakpoint on startMeasure
in runtime-core.esm-bundler.js
I was able to get a stack trace right before the problematic rendering activity. The thing being rendered when we are about to go off the rails is a ContentEditable
that is attached to a TextBox
. I tried deleting anything related to ContentEditable
from TextBox
and the problem goes away:
diff --git a/src/components/Editor.vue b/src/components/Editor.vue
index bbb26e6..21801d2 100644
--- a/src/components/Editor.vue
+++ b/src/components/Editor.vue
@@ -2683,9 +2683,6 @@ export default class Editor extends Vue {
onKeydownTextBox(event: KeyboardEvent) {
let handled = false;
- const index = this.elements.indexOf(this.selectedElement!);
- const htmlElement = (this.$refs[`element-${index}`] as TextBox[])[0];
-
switch (event.code) {
case 'Tab':
this.moveRightThrottled();
@@ -2698,12 +2695,6 @@ export default class Editor extends Vue {
}
break;
case 'ArrowRight':
- if (
- getCursorPosition() === htmlElement.textElement.getInnerText().length
- ) {
- this.moveRightThrottled();
- handled = true;
- }
break;
}
@@ -3003,73 +2994,10 @@ export default class Editor extends Vue {
];
moveLeft() {
- let index = -1;
-
- if (this.selectedElement) {
- index = this.elements.indexOf(this.selectedElement);
- } else if (this.selectionRange) {
- index = this.selectionRange.end;
- }
-
- if (
- index - 1 >= 0 &&
- this.navigableElements.includes(this.elements[index - 1].elementType)
- ) {
- // If the currently selected element is a drop cap or text box, blur it first
- if (this.selectedElement?.elementType === ElementType.DropCap) {
- (this.$refs[`element-${index}`] as DropCap[])[0].blur();
- } else if (this.selectedElement?.elementType === ElementType.TextBox) {
- (this.$refs[`element-${index}`] as TextBox[])[0].blur();
- }
-
- this.selectedElement = this.elements[index - 1];
-
- // If the newly selected element is a drop cap or text box, focus it
- if (this.selectedElement.elementType === ElementType.DropCap) {
- (this.$refs[`element-${index - 1}`] as DropCap[])[0].focus();
- } else if (this.selectedElement.elementType === ElementType.TextBox) {
- (this.$refs[`element-${index - 1}`] as TextBox[])[0].focus();
- }
-
- return true;
- }
-
return false;
}
moveRight() {
- let index = -1;
-
- if (this.selectedElement) {
- index = this.elements.indexOf(this.selectedElement);
- } else if (this.selectionRange) {
- index = this.selectionRange.end;
- }
-
- if (
- index >= 0 &&
- index + 1 < this.elements.length &&
- this.navigableElements.includes(this.elements[index + 1].elementType)
- ) {
- // If the currently selected element is a drop cap, blur it first
- if (this.selectedElement?.elementType === ElementType.DropCap) {
- (this.$refs[`element-${index}`] as DropCap[])[0].blur();
- } else if (this.selectedElement?.elementType === ElementType.TextBox) {
- (this.$refs[`element-${index}`] as TextBox[])[0].blur();
- }
-
- this.selectedElement = this.elements[index + 1];
-
- // If the newly selected element is a drop cap, focus it
- if (this.selectedElement.elementType === ElementType.DropCap) {
- (this.$refs[`element-${index + 1}`] as DropCap[])[0].focus();
- } else if (this.selectedElement.elementType === ElementType.TextBox) {
- (this.$refs[`element-${index + 1}`] as TextBox[])[0].focus();
- }
-
- return true;
- }
-
return false;
}
diff --git a/src/components/TextBox.vue b/src/components/TextBox.vue
index 71f045c..7aaeeb4 100644
--- a/src/components/TextBox.vue
+++ b/src/components/TextBox.vue
@@ -1,27 +1,17 @@
<template>
<div class="text-box-container" :style="containerStyle">
- <ContentEditable
- ref="text"
- class="text-box"
- :class="textBoxClass"
- :style="textBoxStyle"
- :content="content"
- :editable="editMode"
- @blur="updateContent($event)"
- ></ContentEditable>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-facing-decorator';
import { TextBoxElement } from '@/models/Element';
-import ContentEditable from '@/components/ContentEditable.vue';
import { withZoom } from '@/utils/withZoom';
-import { replaceTokens, TokenMetadata } from '@/utils/replaceTokens';
+import { TokenMetadata } from '@/utils/replaceTokens';
import { getFontFamilyWithFallback } from '@/utils/getFontFamilyWithFallback';
@Component({
- components: { ContentEditable },
+ components: {},
emits: ['update:content'],
})
export default class TextBox extends Vue {
@@ -29,18 +19,8 @@ export default class TextBox extends Vue {
@Prop({ default: true }) editMode!: boolean;
@Prop() metadata!: TokenMetadata;
- get textElement() {
- return this.$refs.text as ContentEditable;
- }
-
get content() {
- return this.editMode
- ? this.element.content
- : replaceTokens(
- this.element.content,
- this.metadata,
- this.element.alignment,
- );
+ return this.element.content;
}
get width() {
@@ -87,14 +67,6 @@ export default class TextBox extends Vue {
this.$emit('update:content', content);
}
-
- blur() {
- this.textElement.blur();
- }
-
- focus() {
- this.textElement.focus(true);
- }
}
</script>
So I feel confident that I am on the right track and that something is different in Vue 3 with regard to this.$refs.text
on TextBox
.
Never mind, the above comment was wrong because my Vue.js Devtools Performance Timeline hadn't actually refreshed after I had done the test. I fixed that by switching to the Routes tab and switching back to Timeline. Clearly the same problem is still there. Back to the drawing board…
A large part of the issue is this: https://github.com/vuejs/core/issues/3271. I was able to move the click handler down into the SyllableNeumeBox
component and have the component catch the click and send a new select
event up that the Editor
catches. The syllables now no longer mass re-render.
I'll continue applying this method to other v-for
uses and investigate further.
I've found another piece of the puzzle, perhaps the last one. Vue 3 makes the Editor's score
property reactive and so processPages
triggers a bunch of reactivity-related overhead. Fortunately, there is a method for that!
Updating the save method to use toRaw
, combined with the event changes in my previous comment seem to have fixed the slow-down issues.
const pages = LayoutService.processPages(toRaw(this.score));
It may be a few days before I get all the changes committed but I think I have all the pieces that I need.
Fixes #117
At the moment I am aware of two issues with this branch. First, there is a need to adapt to the Vue 3 mount API changes as described in https://github.com/danielgarthur/neanes/issues/117#issuecomment-1610610696.
The second issue is that when running with
I get
and
I read the page on attribute coercion behavior but could not make heads or tails of it, even after working through the Vue tutorial.
I can get the warnings to go away with
but I am not sure if that is the correct fix.