Files
P4RS3LT0NGV3/js/tools/TransformTool.js

446 lines
19 KiB
JavaScript

/**
* Transform Tool - Text transformation tool
*/
class TransformTool extends Tool {
constructor() {
super({
id: 'transforms',
name: 'Transform',
icon: 'fa-font',
title: 'Transform text (T)',
order: 1
});
}
getVueData() {
const transforms = (window.transforms && Object.keys(window.transforms).length > 0)
? Object.entries(window.transforms).map(([key, transform]) => ({
name: transform.name,
func: transform.func.bind(transform),
preview: transform.preview.bind(transform),
reverse: transform.reverse ? transform.reverse.bind(transform) : null,
category: transform.category || 'special'
}))
: [];
const categorySet = new Set();
transforms.forEach(transform => {
if (transform.category) {
categorySet.add(transform.category);
}
});
// Legend categories: always alphabetical (for quick link buttons)
const allCategories = Array.from(categorySet);
const categoriesWithoutRandomizer = allCategories.filter(c => c !== 'randomizer');
const legendCategories = [...categoriesWithoutRandomizer.sort((a, b) => a.localeCompare(b)), 'randomizer'];
// Section categories: can be reordered (load saved order or use alphabetical)
const savedOrder = this.loadCategoryOrder();
const sectionCategories = savedOrder && savedOrder.length > 0
? this.mergeCategoryOrder(allCategories, savedOrder)
: [...legendCategories]; // Create a copy so legendCategories remains immutable
// Load last used transforms
const lastUsed = this.loadLastUsed();
// Load favorites
const favorites = this.loadFavorites();
return {
transformInput: '',
transformOutput: '',
activeTransform: null,
transforms: transforms,
legendCategories: legendCategories, // Always alphabetical for legend
categories: sectionCategories, // Custom order for sections
lastUsedTransforms: lastUsed,
showLastUsed: lastUsed.length > 0,
favorites: favorites,
showFavorites: favorites.length > 0
};
}
loadCategoryOrder() {
try {
const saved = localStorage.getItem('transformCategoryOrder');
if (saved) {
return JSON.parse(saved);
}
} catch (e) {
console.warn('Failed to load category order:', e);
}
return null;
}
mergeCategoryOrder(allCategories, savedOrder) {
// Always ensure randomizer is last
const categoriesWithoutRandomizer = allCategories.filter(c => c !== 'randomizer');
if (!savedOrder || savedOrder.length === 0) {
// Default: alphabetical, randomizer last
const sorted = categoriesWithoutRandomizer.sort((a, b) => a.localeCompare(b));
return [...sorted, 'randomizer'];
}
// Use saved order, but filter out categories that no longer exist and remove duplicates
const validSavedOrder = savedOrder
.filter(cat => allCategories.includes(cat))
.filter((cat, index, arr) => arr.indexOf(cat) === index); // Remove duplicates
// Find new categories not in saved order
const newCategories = categoriesWithoutRandomizer.filter(cat => !validSavedOrder.includes(cat));
// Build final order: saved order (filtered, deduplicated) + new categories (alphabetically) + randomizer
const finalOrder = [...validSavedOrder];
if (newCategories.length > 0) {
finalOrder.push(...newCategories.sort((a, b) => a.localeCompare(b)));
}
// Ensure randomizer is always last and remove any duplicates
const finalWithoutRandomizer = finalOrder.filter(c => c !== 'randomizer');
const uniqueFinal = finalWithoutRandomizer.filter((cat, index, arr) => arr.indexOf(cat) === index);
return [...uniqueFinal, 'randomizer'];
}
loadLastUsed() {
try {
const saved = localStorage.getItem('transformLastUsed');
if (saved) {
const data = JSON.parse(saved);
// Filter to only include transforms that still exist
if (window.transforms) {
return data.filter(item => {
return Object.values(window.transforms).some(t => t.name === item.name);
}).slice(0, 5); // Keep only top 5
}
}
} catch (e) {
console.warn('Failed to load last used transforms:', e);
}
return [];
}
saveLastUsed(transformName) {
try {
let lastUsed = this.loadLastUsed();
// Remove if already exists
lastUsed = lastUsed.filter(item => item.name !== transformName);
// Add to front with timestamp
lastUsed.unshift({
name: transformName,
timestamp: Date.now()
});
// Keep only last 10
lastUsed = lastUsed.slice(0, 10);
localStorage.setItem('transformLastUsed', JSON.stringify(lastUsed));
} catch (e) {
console.warn('Failed to save last used transform:', e);
}
}
loadFavorites() {
try {
const saved = localStorage.getItem('transformFavorites');
if (saved) {
const data = JSON.parse(saved);
// Filter to only include transforms that still exist
if (window.transforms) {
return data.filter(transformName => {
return Object.values(window.transforms).some(t => t.name === transformName);
});
}
}
} catch (e) {
console.warn('Failed to load favorites:', e);
}
return [];
}
saveFavorites(favorites) {
try {
localStorage.setItem('transformFavorites', JSON.stringify(favorites));
} catch (e) {
console.warn('Failed to save favorites:', e);
}
}
getVueMethods() {
return {
getDisplayCategory: function(transformName) {
// Find transform by name and return its category property
const transform = this.transforms.find(t => t.name === transformName);
return transform ? transform.category : 'special';
},
getTransformsByCategory: function(category) {
return this.transforms.filter(transform => transform.category === category);
},
isSpecialCategory: function(category) {
return category === 'randomizer';
},
applyTransform: function(transform, event) {
event && event.preventDefault();
event && event.stopPropagation();
if (transform && transform.name === 'Random Mix') {
this.triggerRandomizerChaos();
}
if (this.transformInput) {
this.activeTransform = transform;
// Track last used
this.saveLastUsedTransform(transform.name);
if (transform.name === 'Random Mix') {
this.transformOutput = window.transforms.randomizer.func(this.transformInput);
const transformInfo = window.transforms.randomizer.getLastTransformInfo();
if (transformInfo.length > 0) {
const transformsList = transformInfo.map(t => t.transformName).join(', ');
this.showNotification(`Mixed with: ${transformsList}`, 'success', 'fas fa-random');
}
} else {
this.transformOutput = transform.func(this.transformInput);
}
this.isTransformCopy = true;
this.forceCopyToClipboard(this.transformOutput);
if (transform.name !== 'Random Mix') {
this.showNotification(`${transform.name} applied and copied!`, 'success', 'fas fa-check');
}
document.querySelectorAll('.transform-button').forEach(button => {
button.classList.remove('active');
});
const inputBox = document.querySelector('#transform-input');
if (inputBox) {
this.focusWithoutScroll(inputBox);
const len = inputBox.value.length;
try { inputBox.setSelectionRange(len, len); } catch (_) {}
}
this.isTransformCopy = false;
this.ignoreKeyboardEvents = false;
}
},
saveLastUsedTransform: function(transformName) {
try {
let lastUsed = this.lastUsedTransforms || [];
// Remove if already exists
lastUsed = lastUsed.filter(item => item.name !== transformName);
// Add to front with timestamp
lastUsed.unshift({
name: transformName,
timestamp: Date.now()
});
// Keep only last 5
lastUsed = lastUsed.slice(0, 5);
this.lastUsedTransforms = lastUsed;
this.showLastUsed = lastUsed.length > 0;
localStorage.setItem('transformLastUsed', JSON.stringify(lastUsed));
} catch (e) {
console.warn('Failed to save last used transform:', e);
}
},
getLastUsedTransforms: function() {
if (!this.lastUsedTransforms || this.lastUsedTransforms.length === 0) {
return [];
}
return this.lastUsedTransforms
.map(item => {
return this.transforms.find(t => t.name === item.name);
})
.filter(t => t !== undefined);
},
toggleFavorite: function(transformName, event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
const index = this.favorites.indexOf(transformName);
if (index > -1) {
// Remove from favorites
this.favorites.splice(index, 1);
this.showNotification('Removed from favorites', 'success', 'fas fa-star');
} else {
// Add to favorites
this.favorites.push(transformName);
this.showNotification('Added to favorites', 'success', 'fas fa-star');
}
this.showFavorites = this.favorites.length > 0;
this.saveFavorites(this.favorites);
},
isFavorite: function(transformName) {
return this.favorites && this.favorites.includes(transformName);
},
getFavoriteTransforms: function() {
if (!this.favorites || this.favorites.length === 0) {
return [];
}
return this.favorites
.map(transformName => {
return this.transforms.find(t => t.name === transformName);
})
.filter(t => t !== undefined);
},
saveFavorites: function(favorites) {
try {
localStorage.setItem('transformFavorites', JSON.stringify(favorites));
} catch (e) {
console.warn('Failed to save favorites:', e);
}
},
moveCategoryUp: function(categoryIndex) {
if (categoryIndex <= 0) return;
// Never allow moving randomizer itself
if (this.categories[categoryIndex] === 'randomizer') return;
// Use Vue's array mutation methods for proper reactivity
const categoryToMove = this.categories[categoryIndex];
this.categories.splice(categoryIndex, 1);
this.categories.splice(categoryIndex - 1, 0, categoryToMove);
this.saveCategoryOrder(this.categories);
this.showNotification('Category order saved', 'success', 'fas fa-check');
},
moveCategoryDown: function(categoryIndex) {
// Don't allow moving if already at or past the last valid position
// Last position is reserved for randomizer, so we can't move to it
if (categoryIndex >= this.categories.length - 2) return;
// Never allow moving randomizer itself
if (this.categories[categoryIndex] === 'randomizer') return;
// Use Vue's array mutation methods for proper reactivity
const categoryToMove = this.categories[categoryIndex];
this.categories.splice(categoryIndex, 1);
this.categories.splice(categoryIndex + 1, 0, categoryToMove);
this.saveCategoryOrder(this.categories);
this.showNotification('Category order saved', 'success', 'fas fa-check');
},
saveCategoryOrder: function(categories) {
try {
// Remove duplicates before saving
const uniqueCategories = categories.filter((cat, index, arr) => arr.indexOf(cat) === index);
localStorage.setItem('transformCategoryOrder', JSON.stringify(uniqueCategories));
} catch (e) {
console.warn('Failed to save category order:', e);
}
},
autoTransform: function() {
if (this.transformInput && this.activeTransform && this.activeTab === 'transforms') {
const segments = window.EmojiUtils.splitEmojis(this.transformInput);
const transformedSegments = segments.map(segment => {
if (segment.length > 1 || /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}]/u.test(segment)) {
return segment;
}
return this.activeTransform.func(segment);
});
this.transformOutput = window.EmojiUtils.joinEmojis(transformedSegments);
}
},
initializeCategoryNavigation: function() {
this.$nextTick(() => {
const legendItems = document.querySelectorAll('.transform-category-legend .legend-item');
legendItems.forEach(item => {
const newItem = item.cloneNode(true);
item.parentNode.replaceChild(newItem, item);
});
document.querySelectorAll('.transform-category-legend .legend-item').forEach(item => {
item.addEventListener('click', () => {
const targetId = item.getAttribute('data-target');
if (targetId) {
const targetElement = document.getElementById(targetId);
if (targetElement) {
document.querySelectorAll('.transform-category-legend .legend-item').forEach(li => {
li.classList.remove('active-category');
});
item.classList.add('active-category');
const inputSection = document.querySelector('.input-section');
const inputSectionHeight = inputSection.offsetHeight;
const elementPosition = targetElement.getBoundingClientRect().top + window.pageYOffset;
const offsetPosition = elementPosition - inputSectionHeight - 10;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
targetElement.classList.add('highlight-section');
setTimeout(() => {
targetElement.classList.remove('highlight-section');
}, 1000);
}
}
});
});
});
}
};
}
getVueWatchers() {
return {
transformInput() {
if (this.activeTransform && this.activeTab === 'transforms') {
this.transformOutput = this.activeTransform.func(this.transformInput);
}
}
};
}
getVueLifecycle() {
return {
mounted() {
this.initializeCategoryNavigation();
// Save initial category order to localStorage if it doesn't exist
// This ensures consistent state for category reordering operations
try {
const saved = localStorage.getItem('transformCategoryOrder');
if (!saved && this.categories && this.categories.length > 0) {
this.saveCategoryOrder(this.categories);
}
} catch (e) {
console.warn('Failed to check/save initial category order:', e);
}
}
};
}
onActivate(vueInstance) {
vueInstance.$nextTick(() => {
vueInstance.initializeCategoryNavigation();
});
}
}
// Export
if (typeof module !== 'undefined' && module.exports) {
module.exports = TransformTool;
} else {
window.TransformTool = TransformTool;
}