refactor: extract reusable UI components and composables from TextSettings
Major refactoring to improve code quality and reduce duplication: TextSettings.vue: 1127 → 269 lines (-76%) New composables: - useCssUpdater.js: generic CSS update/remove functions - useCssSync.js: CSS parsing to form fields New UI components: - UnitToggle.vue: reusable unit selector buttons - InputWithUnit.vue: number input with unit toggle - MarginEditor.vue: simple/detailed margin editor with sync Benefits: - Reusable components for other settings panels - Centralized CSS manipulation logic - Better separation of concerns - Easier to test and maintain 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
94112ab1a8
commit
c4d2015a69
6 changed files with 647 additions and 1006 deletions
119
src/composables/useCssSync.js
Normal file
119
src/composables/useCssSync.js
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import { useStylesheetStore } from '../stores/stylesheet';
|
||||
|
||||
export function useCssSync() {
|
||||
const store = useStylesheetStore();
|
||||
|
||||
/**
|
||||
* Extract a simple CSS value (string)
|
||||
*/
|
||||
const extractValue = (selector, property) => {
|
||||
const block = store.extractBlock(selector);
|
||||
if (!block) return null;
|
||||
|
||||
const match = block.match(new RegExp(`${property}:\\s*([^;]+)`, 'i'));
|
||||
return match ? match[1].trim() : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract a numeric CSS value with unit
|
||||
* Returns { value: number, unit: string } or null
|
||||
*/
|
||||
const extractNumericValue = (selector, property, allowedUnits = ['px', 'em', 'rem', 'mm']) => {
|
||||
const block = store.extractBlock(selector);
|
||||
if (!block) return null;
|
||||
|
||||
const unitsPattern = allowedUnits.join('|');
|
||||
const match = block.match(new RegExp(`${property}:\\s*([0-9.]+)(${unitsPattern})`, 'i'));
|
||||
|
||||
if (match) {
|
||||
return {
|
||||
value: parseFloat(match[1]),
|
||||
unit: match[2].toLowerCase()
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract margin/padding shorthand (handles 1 or 4 values)
|
||||
* Returns { simple: { value, unit } } or { detailed: { top, right, bottom, left } }
|
||||
*/
|
||||
const extractSpacing = (selector, property, allowedUnits = ['mm', 'px']) => {
|
||||
const block = store.extractBlock(selector);
|
||||
if (!block) return null;
|
||||
|
||||
const unitsPattern = allowedUnits.join('|');
|
||||
|
||||
// Check for detailed properties first (property-top, property-right, etc.)
|
||||
const topMatch = block.match(new RegExp(`${property}-top:\\s*([0-9.]+)(${unitsPattern})`, 'i'));
|
||||
|
||||
if (topMatch) {
|
||||
const rightMatch = block.match(new RegExp(`${property}-right:\\s*([0-9.]+)(${unitsPattern})`, 'i'));
|
||||
const bottomMatch = block.match(new RegExp(`${property}-bottom:\\s*([0-9.]+)(${unitsPattern})`, 'i'));
|
||||
const leftMatch = block.match(new RegExp(`${property}-left:\\s*([0-9.]+)(${unitsPattern})`, 'i'));
|
||||
|
||||
return {
|
||||
detailed: {
|
||||
top: topMatch ? { value: parseFloat(topMatch[1]), unit: topMatch[2] } : { value: 0, unit: 'mm' },
|
||||
right: rightMatch ? { value: parseFloat(rightMatch[1]), unit: rightMatch[2] } : { value: 0, unit: 'mm' },
|
||||
bottom: bottomMatch ? { value: parseFloat(bottomMatch[1]), unit: bottomMatch[2] } : { value: 0, unit: 'mm' },
|
||||
left: leftMatch ? { value: parseFloat(leftMatch[1]), unit: leftMatch[2] } : { value: 0, unit: 'mm' },
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check for shorthand property
|
||||
const shorthandMatch = block.match(new RegExp(`${property}:\\s*([^;]+)`, 'i'));
|
||||
if (!shorthandMatch) return null;
|
||||
|
||||
const shorthandValue = shorthandMatch[1].trim();
|
||||
|
||||
// Check for 4-value format: "0mm 0mm 24mm 0mm" (top right bottom left)
|
||||
const fourValuePattern = new RegExp(
|
||||
`^([0-9.]+)(${unitsPattern})\\s+([0-9.]+)(${unitsPattern})\\s+([0-9.]+)(${unitsPattern})\\s+([0-9.]+)(${unitsPattern})$`,
|
||||
'i'
|
||||
);
|
||||
const fourValueMatch = shorthandValue.match(fourValuePattern);
|
||||
|
||||
if (fourValueMatch) {
|
||||
return {
|
||||
detailed: {
|
||||
top: { value: parseFloat(fourValueMatch[1]), unit: fourValueMatch[2] },
|
||||
right: { value: parseFloat(fourValueMatch[3]), unit: fourValueMatch[4] },
|
||||
bottom: { value: parseFloat(fourValueMatch[5]), unit: fourValueMatch[6] },
|
||||
left: { value: parseFloat(fourValueMatch[7]), unit: fourValueMatch[8] },
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Single value format: "10mm"
|
||||
const singleValuePattern = new RegExp(`^([0-9.]+)(${unitsPattern})$`, 'i');
|
||||
const singleValueMatch = shorthandValue.match(singleValuePattern);
|
||||
|
||||
if (singleValueMatch) {
|
||||
return {
|
||||
simple: {
|
||||
value: parseFloat(singleValueMatch[1]),
|
||||
unit: singleValueMatch[2]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a property value equals a specific string
|
||||
*/
|
||||
const hasValue = (selector, property, expectedValue) => {
|
||||
const value = extractValue(selector, property);
|
||||
return value === expectedValue;
|
||||
};
|
||||
|
||||
return {
|
||||
extractValue,
|
||||
extractNumericValue,
|
||||
extractSpacing,
|
||||
hasValue,
|
||||
};
|
||||
}
|
||||
120
src/composables/useCssUpdater.js
Normal file
120
src/composables/useCssUpdater.js
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import { useStylesheetStore } from '../stores/stylesheet';
|
||||
|
||||
export function useCssUpdater() {
|
||||
const store = useStylesheetStore();
|
||||
|
||||
/**
|
||||
* Update or add a CSS property for a given selector
|
||||
*/
|
||||
const updateStyle = (selector, property, value) => {
|
||||
const currentBlock = store.extractBlock(selector) || createRule(selector);
|
||||
|
||||
if (currentBlock.includes(`${property}:`)) {
|
||||
const updatedBlock = currentBlock.replace(
|
||||
new RegExp(`(${property}:\\s*)[^;]+`, 'i'),
|
||||
`$1${value}`
|
||||
);
|
||||
store.content = store.content.replace(currentBlock, updatedBlock);
|
||||
} else {
|
||||
const updatedBlock = currentBlock.replace(
|
||||
/(\s*})$/,
|
||||
` ${property}: ${value};\n$1`
|
||||
);
|
||||
store.content = store.content.replace(currentBlock, updatedBlock);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a CSS property from a selector
|
||||
*/
|
||||
const removeProperty = (selector, property) => {
|
||||
const currentBlock = store.extractBlock(selector);
|
||||
if (!currentBlock) return;
|
||||
|
||||
const updatedBlock = currentBlock.replace(
|
||||
new RegExp(`\\s*${property}:\\s*[^;]+;\\n?`, 'gi'),
|
||||
''
|
||||
);
|
||||
|
||||
if (updatedBlock !== currentBlock) {
|
||||
store.content = store.content.replace(currentBlock, updatedBlock);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove multiple CSS properties from a selector
|
||||
*/
|
||||
const removeProperties = (selector, properties) => {
|
||||
let currentBlock = store.extractBlock(selector);
|
||||
if (!currentBlock) return;
|
||||
|
||||
let updatedBlock = currentBlock;
|
||||
for (const property of properties) {
|
||||
updatedBlock = updatedBlock.replace(
|
||||
new RegExp(`\\s*${property}:\\s*[^;]+;\\n?`, 'gi'),
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
if (updatedBlock !== currentBlock) {
|
||||
store.content = store.content.replace(currentBlock, updatedBlock);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new CSS rule for a selector
|
||||
*/
|
||||
const createRule = (selector) => {
|
||||
store.content += `\n\n${selector} {\n}\n`;
|
||||
return `${selector} {\n}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove detailed margin properties and set shorthand
|
||||
*/
|
||||
const setMargin = (selector, value, unit) => {
|
||||
removeProperties(selector, ['margin-top', 'margin-right', 'margin-bottom', 'margin-left']);
|
||||
updateStyle(selector, 'margin', `${value}${unit}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove shorthand margin and set detailed margins
|
||||
*/
|
||||
const setDetailedMargins = (selector, top, right, bottom, left) => {
|
||||
removeProperty(selector, 'margin');
|
||||
updateStyle(selector, 'margin-top', `${top.value}${top.unit}`);
|
||||
updateStyle(selector, 'margin-right', `${right.value}${right.unit}`);
|
||||
updateStyle(selector, 'margin-bottom', `${bottom.value}${bottom.unit}`);
|
||||
updateStyle(selector, 'margin-left', `${left.value}${left.unit}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove detailed padding properties and set shorthand
|
||||
*/
|
||||
const setPadding = (selector, value, unit) => {
|
||||
removeProperties(selector, ['padding-top', 'padding-right', 'padding-bottom', 'padding-left']);
|
||||
updateStyle(selector, 'padding', `${value}${unit}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove shorthand padding and set detailed padding
|
||||
*/
|
||||
const setDetailedPadding = (selector, top, right, bottom, left) => {
|
||||
removeProperty(selector, 'padding');
|
||||
updateStyle(selector, 'padding-top', `${top.value}${top.unit}`);
|
||||
updateStyle(selector, 'padding-right', `${right.value}${right.unit}`);
|
||||
updateStyle(selector, 'padding-bottom', `${bottom.value}${bottom.unit}`);
|
||||
updateStyle(selector, 'padding-left', `${left.value}${left.unit}`);
|
||||
};
|
||||
|
||||
return {
|
||||
updateStyle,
|
||||
removeProperty,
|
||||
removeProperties,
|
||||
createRule,
|
||||
setMargin,
|
||||
setDetailedMargins,
|
||||
setPadding,
|
||||
setDetailedPadding,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue