✨ Add unit tests for useLineSelection composable
This commit is contained in:
209
code/websites/pokedex.online/src/composables/useLineSelection.js
Normal file
209
code/websites/pokedex.online/src/composables/useLineSelection.js
Normal file
@@ -0,0 +1,209 @@
|
||||
import { ref, computed } from 'vue';
|
||||
import { useClipboard } from '@/composables/useClipboard.js';
|
||||
|
||||
/**
|
||||
* useLineSelection - Composable for managing line selection operations
|
||||
*
|
||||
* Handles:
|
||||
* - Single, range, and multi-line selection with Shift/Ctrl modifiers
|
||||
* - Copy selected/all lines to clipboard
|
||||
* - Export selected/all lines to JSON file
|
||||
* - URL sharing
|
||||
* - Selection state management
|
||||
*
|
||||
* @param {Ref<Set>} displayLines - Current displayed lines with metadata
|
||||
* @param {Ref<string>} fileContent - Full file content for export
|
||||
* @param {Ref<string>} selectedFile - Current file name for export naming
|
||||
* @returns {Object} Line selection composable API
|
||||
*/
|
||||
export function useLineSelection(displayLines, fileContent, selectedFile) {
|
||||
// Clipboard composable
|
||||
const clipboard = useClipboard();
|
||||
|
||||
// Selection state - use Set for O(1) lookups
|
||||
const selectedLines = ref(new Set());
|
||||
|
||||
/**
|
||||
* Check if any lines are selected
|
||||
*/
|
||||
const hasSelection = computed(() => selectedLines.value.size > 0);
|
||||
|
||||
/**
|
||||
* Get count of selected lines
|
||||
*/
|
||||
const selectionCount = computed(() => selectedLines.value.size);
|
||||
|
||||
/**
|
||||
* Get selected line numbers as sorted array
|
||||
*/
|
||||
const selectedLineNumbers = computed(() => {
|
||||
return [...selectedLines.value].sort((a, b) => a - b);
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle line selection with modifiers
|
||||
*
|
||||
* Supports:
|
||||
* - Single click: Select only that line
|
||||
* - Shift+click: Select range from last selected to current
|
||||
* - Ctrl/Cmd+click: Toggle individual line
|
||||
*/
|
||||
function toggleLineSelection(lineNumber, event) {
|
||||
if (event.shiftKey && selectedLines.value.size > 0) {
|
||||
// Range selection
|
||||
const lastSelected = Math.max(...selectedLines.value);
|
||||
const start = Math.min(lastSelected, lineNumber);
|
||||
const end = Math.max(lastSelected, lineNumber);
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
selectedLines.value.add(i);
|
||||
}
|
||||
} else if (event.ctrlKey || event.metaKey) {
|
||||
// Toggle individual line
|
||||
if (selectedLines.value.has(lineNumber)) {
|
||||
selectedLines.value.delete(lineNumber);
|
||||
} else {
|
||||
selectedLines.value.add(lineNumber);
|
||||
}
|
||||
} else {
|
||||
// Single selection
|
||||
selectedLines.value.clear();
|
||||
selectedLines.value.add(lineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all selections
|
||||
*/
|
||||
function clearSelection() {
|
||||
selectedLines.value.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content of selected lines
|
||||
*/
|
||||
function getSelectedContent() {
|
||||
if (selectedLines.value.size === 0) return '';
|
||||
|
||||
const lines = selectedLineNumbers.value;
|
||||
const content = lines
|
||||
.map(lineNum => {
|
||||
return (
|
||||
displayLines.value.find(l => l.lineNumber === lineNum)?.content || ''
|
||||
);
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy selected lines to clipboard
|
||||
*/
|
||||
async function copySelected() {
|
||||
if (selectedLines.value.size === 0) return;
|
||||
|
||||
const content = getSelectedContent();
|
||||
await clipboard.copyToClipboard(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy all content to clipboard
|
||||
*/
|
||||
async function copyAll() {
|
||||
await clipboard.copyToClipboard(fileContent.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export selected lines to JSON file
|
||||
*/
|
||||
function exportSelected() {
|
||||
if (selectedLines.value.size === 0) return;
|
||||
|
||||
const content = getSelectedContent();
|
||||
downloadFile(
|
||||
content,
|
||||
`${selectedFile.value}-selected-${Date.now()}.json`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all content to JSON file
|
||||
*/
|
||||
function exportAll() {
|
||||
downloadFile(fileContent.value, `${selectedFile.value}-${Date.now()}.json`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Download content as file
|
||||
*/
|
||||
function downloadFile(content, filename) {
|
||||
const blob = new Blob([content], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Share current URL (copy to clipboard)
|
||||
*/
|
||||
async function shareUrl() {
|
||||
const url = globalThis.location.href;
|
||||
await clipboard.copyToClipboard(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select all available lines
|
||||
*/
|
||||
function selectAll() {
|
||||
displayLines.value.forEach(line => {
|
||||
selectedLines.value.add(line.lineNumber);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invert selection (select all not selected, deselect all selected)
|
||||
*/
|
||||
function invertSelection() {
|
||||
const allLineNumbers = new Set(
|
||||
displayLines.value.map(line => line.lineNumber)
|
||||
);
|
||||
const newSelection = new Set();
|
||||
|
||||
allLineNumbers.forEach(lineNum => {
|
||||
if (!selectedLines.value.has(lineNum)) {
|
||||
newSelection.add(lineNum);
|
||||
}
|
||||
});
|
||||
|
||||
selectedLines.value.clear();
|
||||
newSelection.forEach(lineNum => selectedLines.value.add(lineNum));
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
selectedLines,
|
||||
|
||||
// Computed
|
||||
hasSelection,
|
||||
selectionCount,
|
||||
selectedLineNumbers,
|
||||
|
||||
// Methods
|
||||
toggleLineSelection,
|
||||
clearSelection,
|
||||
getSelectedContent,
|
||||
copySelected,
|
||||
copyAll,
|
||||
exportSelected,
|
||||
exportAll,
|
||||
shareUrl,
|
||||
selectAll,
|
||||
invertSelection
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user