refactor: optimize EditorPanel updates with selective debouncing
Implement immediate vs debounced updates based on input type to improve UX responsiveness while preventing excessive re-renders. Update strategy: - Immediate (0ms): select, buttons, checkboxes, color picker - Debounced (1s): text inputs, number inputs, range sliders Changes: - PageSettings.vue: Split watchers for margin values/units and background value/format. Extract update logic into reusable functions. - TextSettings.vue: Add comprehensive watcher system with selective debouncing for all settings (font, size, color, margins, etc.) This ensures button clicks (unit toggles, format switches) apply instantly while typed values (numbers, text) batch updates to reduce CSS re-parsing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
7ed57d000b
commit
467ae905bd
2 changed files with 417 additions and 157 deletions
|
|
@ -2,43 +2,37 @@
|
|||
<section class="settings-section">
|
||||
<h2>Réglage des pages</h2>
|
||||
|
||||
<div class="field">
|
||||
<label for="page-format">Format d'impression</label>
|
||||
<select id="page-format" v-model="pageFormat">
|
||||
<option value="A4">A4</option>
|
||||
<option value="A5">A5</option>
|
||||
<option value="A3">A3</option>
|
||||
<option value="letter">Letter</option>
|
||||
<option value="legal">Legal</option>
|
||||
</select>
|
||||
<div class="settings-subsection">
|
||||
<div class="field">
|
||||
<label for="page-format">Format d'impression</label>
|
||||
<select id="page-format" v-model="pageFormat">
|
||||
<option value="A4">A4</option>
|
||||
<option value="A5">A5</option>
|
||||
<option value="A3">A3</option>
|
||||
<option value="letter">Letter</option>
|
||||
<option value="legal">Legal</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="page-width">Largeur</label>
|
||||
<input
|
||||
id="page-width"
|
||||
type="text"
|
||||
:value="pageWidth"
|
||||
disabled
|
||||
/>
|
||||
<div class="settings-subsection">
|
||||
<div class="field field--view-only">
|
||||
<label for="page-width">Largeur</label>
|
||||
<input id="page-width" type="text" :value="pageWidth" disabled />
|
||||
</div>
|
||||
|
||||
<div class="field field--view-only">
|
||||
<label for="page-height">Hauteur</label>
|
||||
<input id="page-height" type="text" :value="pageHeight" disabled />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="page-height">Hauteur</label>
|
||||
<input
|
||||
id="page-height"
|
||||
type="text"
|
||||
:value="pageHeight"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="subsection">
|
||||
<div class="settings-subsection margins">
|
||||
<h3>Marges</h3>
|
||||
|
||||
<div class="field">
|
||||
<label for="margin-top">Haut</label>
|
||||
<div class="field-with-unit">
|
||||
<div class="input-with-unit">
|
||||
<input
|
||||
id="margin-top"
|
||||
type="number"
|
||||
|
|
@ -66,7 +60,7 @@
|
|||
|
||||
<div class="field">
|
||||
<label for="margin-bottom">Bas</label>
|
||||
<div class="field-with-unit">
|
||||
<div class="input-with-unit">
|
||||
<input
|
||||
id="margin-bottom"
|
||||
type="number"
|
||||
|
|
@ -94,7 +88,7 @@
|
|||
|
||||
<div class="field">
|
||||
<label for="margin-left">Gauche</label>
|
||||
<div class="field-with-unit">
|
||||
<div class="input-with-unit">
|
||||
<input
|
||||
id="margin-left"
|
||||
type="number"
|
||||
|
|
@ -122,7 +116,7 @@
|
|||
|
||||
<div class="field">
|
||||
<label for="margin-right">Droite</label>
|
||||
<div class="field-with-unit">
|
||||
<div class="input-with-unit">
|
||||
<input
|
||||
id="margin-right"
|
||||
type="number"
|
||||
|
|
@ -149,59 +143,53 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="background">Arrière-plan</label>
|
||||
<div class="field-with-unit">
|
||||
<input
|
||||
id="background"
|
||||
type="text"
|
||||
v-model="background.value"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: background.format === 'rgb' }"
|
||||
@click="background.format = 'rgb'"
|
||||
>
|
||||
rgb
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: background.format === 'hex' }"
|
||||
@click="background.format = 'hex'"
|
||||
>
|
||||
hex
|
||||
</button>
|
||||
<div class="settings-subsection">
|
||||
<div class="field">
|
||||
<label for="background">Arrière-plan</label>
|
||||
<div class="input-with-unit">
|
||||
<input id="background" type="text" v-model="background.value" />
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: background.format === 'rgb' }"
|
||||
@click="background.format = 'rgb'"
|
||||
>
|
||||
rgb
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: background.format === 'hex' }"
|
||||
@click="background.format = 'hex'"
|
||||
>
|
||||
hex
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="pattern">Motif</label>
|
||||
<select id="pattern" v-model="pattern">
|
||||
<option value="">Choisissez</option>
|
||||
<option value="dots">Points</option>
|
||||
<option value="lines">Lignes</option>
|
||||
<option value="grid">Grille</option>
|
||||
</select>
|
||||
<div class="settings-subsection">
|
||||
<div class="field">
|
||||
<label for="pattern">Motif</label>
|
||||
<select id="pattern" v-model="pattern">
|
||||
<option value="">Choisissez</option>
|
||||
<option value="dots">Points</option>
|
||||
<option value="lines">Lignes</option>
|
||||
<option value="grid">Grille</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field checkbox-field">
|
||||
<input
|
||||
id="page-numbers"
|
||||
type="checkbox"
|
||||
v-model="pageNumbers"
|
||||
/>
|
||||
<label for="page-numbers">Numéro de page</label>
|
||||
</div>
|
||||
<div class="settings-subsection">
|
||||
<div class="field checkbox-field">
|
||||
<input id="page-numbers" type="checkbox" v-model="pageNumbers" />
|
||||
<label for="page-numbers">Numéro de page</label>
|
||||
</div>
|
||||
|
||||
<div class="field checkbox-field">
|
||||
<input
|
||||
id="running-title"
|
||||
type="checkbox"
|
||||
v-model="runningTitle"
|
||||
/>
|
||||
<label for="running-title">Titre courant</label>
|
||||
<div class="field checkbox-field">
|
||||
<input id="running-title" type="checkbox" v-model="runningTitle" />
|
||||
<label for="running-title">Titre courant</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
|
@ -222,7 +210,7 @@ const pageFormats = {
|
|||
A5: { width: '148mm', height: '210mm' },
|
||||
A3: { width: '297mm', height: '420mm' },
|
||||
letter: { width: '8.5in', height: '11in' },
|
||||
legal: { width: '8.5in', height: '14in' }
|
||||
legal: { width: '8.5in', height: '14in' },
|
||||
};
|
||||
|
||||
const pageWidth = computed(() => pageFormats[pageFormat.value].width);
|
||||
|
|
@ -232,12 +220,12 @@ const margins = ref({
|
|||
top: { value: 20, unit: 'mm' },
|
||||
bottom: { value: 20, unit: 'mm' },
|
||||
left: { value: 20, unit: 'mm' },
|
||||
right: { value: 20, unit: 'mm' }
|
||||
right: { value: 20, unit: 'mm' },
|
||||
});
|
||||
|
||||
const background = ref({
|
||||
value: '',
|
||||
format: 'hex'
|
||||
format: 'hex',
|
||||
});
|
||||
|
||||
const pattern = ref('');
|
||||
|
|
@ -249,57 +237,109 @@ const debouncedUpdate = (callback) => {
|
|||
updateTimer = setTimeout(callback, 1000);
|
||||
};
|
||||
|
||||
const immediateUpdate = (callback) => {
|
||||
callback();
|
||||
};
|
||||
|
||||
watch(pageFormat, (newFormat) => {
|
||||
if (isUpdatingFromStore) return;
|
||||
|
||||
debouncedUpdate(() => {
|
||||
immediateUpdate(() => {
|
||||
stylesheetStore.updateProperty('@page', 'size', newFormat, '');
|
||||
});
|
||||
});
|
||||
|
||||
watch(margins, (newMargins) => {
|
||||
if (isUpdatingFromStore) return;
|
||||
const updateMargins = () => {
|
||||
const marginValue = `${margins.value.top.value}${margins.value.top.unit} ${margins.value.right.value}${margins.value.right.unit} ${margins.value.bottom.value}${margins.value.bottom.unit} ${margins.value.left.value}${margins.value.left.unit}`;
|
||||
|
||||
debouncedUpdate(() => {
|
||||
const marginValue = `${newMargins.top.value}${newMargins.top.unit} ${newMargins.right.value}${newMargins.right.unit} ${newMargins.bottom.value}${newMargins.bottom.unit} ${newMargins.left.value}${newMargins.left.unit}`;
|
||||
const currentBlock = stylesheetStore.extractBlock('@page');
|
||||
const updatedBlock = currentBlock.replace(
|
||||
/(margin:\s*)[^;]+/,
|
||||
`$1${marginValue}`
|
||||
);
|
||||
|
||||
const currentBlock = stylesheetStore.extractBlock('@page');
|
||||
stylesheetStore.content = stylesheetStore.content.replace(
|
||||
currentBlock,
|
||||
updatedBlock
|
||||
);
|
||||
};
|
||||
|
||||
// Watch margin values (number inputs) with debounce
|
||||
watch(
|
||||
() => [
|
||||
margins.value.top.value,
|
||||
margins.value.bottom.value,
|
||||
margins.value.left.value,
|
||||
margins.value.right.value,
|
||||
],
|
||||
() => {
|
||||
if (isUpdatingFromStore) return;
|
||||
debouncedUpdate(updateMargins);
|
||||
}
|
||||
);
|
||||
|
||||
// Watch margin units (button clicks) without debounce
|
||||
watch(
|
||||
() => [
|
||||
margins.value.top.unit,
|
||||
margins.value.bottom.unit,
|
||||
margins.value.left.unit,
|
||||
margins.value.right.unit,
|
||||
],
|
||||
() => {
|
||||
if (isUpdatingFromStore) return;
|
||||
immediateUpdate(updateMargins);
|
||||
}
|
||||
);
|
||||
|
||||
const updateBackground = () => {
|
||||
if (!background.value.value) return;
|
||||
|
||||
const currentBlock = stylesheetStore.extractBlock('@page');
|
||||
|
||||
if (currentBlock.includes('background:')) {
|
||||
const updatedBlock = currentBlock.replace(
|
||||
/(margin:\s*)[^;]+/,
|
||||
`$1${marginValue}`
|
||||
/(background:\s*)[^;]+/,
|
||||
`$1${background.value.value}`
|
||||
);
|
||||
stylesheetStore.content = stylesheetStore.content.replace(
|
||||
currentBlock,
|
||||
updatedBlock
|
||||
);
|
||||
} else {
|
||||
const updatedBlock = currentBlock.replace(
|
||||
/(\s*})$/,
|
||||
` background: ${background.value.value};\n$1`
|
||||
);
|
||||
stylesheetStore.content = stylesheetStore.content.replace(
|
||||
currentBlock,
|
||||
updatedBlock
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
stylesheetStore.content = stylesheetStore.content.replace(currentBlock, updatedBlock);
|
||||
});
|
||||
}, { deep: true });
|
||||
// Watch background value (text input) with debounce
|
||||
watch(
|
||||
() => background.value.value,
|
||||
() => {
|
||||
if (isUpdatingFromStore) return;
|
||||
debouncedUpdate(updateBackground);
|
||||
}
|
||||
);
|
||||
|
||||
watch(background, (newBg) => {
|
||||
if (!newBg.value) return;
|
||||
if (isUpdatingFromStore) return;
|
||||
|
||||
debouncedUpdate(() => {
|
||||
const currentBlock = stylesheetStore.extractBlock('@page');
|
||||
|
||||
if (currentBlock.includes('background:')) {
|
||||
const updatedBlock = currentBlock.replace(
|
||||
/(background:\s*)[^;]+/,
|
||||
`$1${newBg.value}`
|
||||
);
|
||||
stylesheetStore.content = stylesheetStore.content.replace(currentBlock, updatedBlock);
|
||||
} else {
|
||||
const updatedBlock = currentBlock.replace(
|
||||
/(\s*})$/,
|
||||
` background: ${newBg.value};\n$1`
|
||||
);
|
||||
stylesheetStore.content = stylesheetStore.content.replace(currentBlock, updatedBlock);
|
||||
}
|
||||
});
|
||||
}, { deep: true });
|
||||
// Watch background format (button clicks) without debounce
|
||||
watch(
|
||||
() => background.value.format,
|
||||
() => {
|
||||
if (isUpdatingFromStore) return;
|
||||
immediateUpdate(updateBackground);
|
||||
}
|
||||
);
|
||||
|
||||
watch(pattern, (newPattern) => {
|
||||
if (!newPattern || isUpdatingFromStore) return;
|
||||
|
||||
debouncedUpdate(() => {
|
||||
immediateUpdate(() => {
|
||||
// TODO: implement pattern application
|
||||
});
|
||||
});
|
||||
|
|
@ -307,7 +347,7 @@ watch(pattern, (newPattern) => {
|
|||
watch(pageNumbers, (enabled) => {
|
||||
if (isUpdatingFromStore) return;
|
||||
|
||||
debouncedUpdate(() => {
|
||||
immediateUpdate(() => {
|
||||
// TODO: implement page numbers toggle
|
||||
});
|
||||
});
|
||||
|
|
@ -315,7 +355,7 @@ watch(pageNumbers, (enabled) => {
|
|||
watch(runningTitle, (enabled) => {
|
||||
if (isUpdatingFromStore) return;
|
||||
|
||||
debouncedUpdate(() => {
|
||||
immediateUpdate(() => {
|
||||
// TODO: implement running title toggle
|
||||
});
|
||||
});
|
||||
|
|
@ -331,12 +371,26 @@ const syncFromStore = () => {
|
|||
pageFormat.value = sizeMatch[1];
|
||||
}
|
||||
|
||||
const marginMatch = pageBlock.match(/margin:\s*([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)/i);
|
||||
const marginMatch = pageBlock.match(
|
||||
/margin:\s*([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)/i
|
||||
);
|
||||
if (marginMatch) {
|
||||
margins.value.top = { value: parseFloat(marginMatch[1]), unit: marginMatch[2] };
|
||||
margins.value.right = { value: parseFloat(marginMatch[3]), unit: marginMatch[4] };
|
||||
margins.value.bottom = { value: parseFloat(marginMatch[5]), unit: marginMatch[6] };
|
||||
margins.value.left = { value: parseFloat(marginMatch[7]), unit: marginMatch[8] };
|
||||
margins.value.top = {
|
||||
value: parseFloat(marginMatch[1]),
|
||||
unit: marginMatch[2],
|
||||
};
|
||||
margins.value.right = {
|
||||
value: parseFloat(marginMatch[3]),
|
||||
unit: marginMatch[4],
|
||||
};
|
||||
margins.value.bottom = {
|
||||
value: parseFloat(marginMatch[5]),
|
||||
unit: marginMatch[6],
|
||||
};
|
||||
margins.value.left = {
|
||||
value: parseFloat(marginMatch[7]),
|
||||
unit: marginMatch[8],
|
||||
};
|
||||
}
|
||||
|
||||
const bgMatch = pageBlock.match(/background:\s*([^;]+)/);
|
||||
|
|
@ -348,11 +402,14 @@ const syncFromStore = () => {
|
|||
}
|
||||
};
|
||||
|
||||
watch(() => stylesheetStore.content, () => {
|
||||
if (!isUpdatingFromStore) {
|
||||
syncFromStore();
|
||||
watch(
|
||||
() => stylesheetStore.content,
|
||||
() => {
|
||||
if (!isUpdatingFromStore) {
|
||||
syncFromStore();
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
syncFromStore();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue