| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- /**
- * Image2Sand (https://github.com/orionwc/Image2Sand) Initialization
- *
- * This script handles the initialization of the Image2Sand converter.
- *
- */
- // Log types for message styling
- const LOG_TYPE = {
- INFO: 'info',
- SUCCESS: 'success',
- WARNING: 'warning',
- ERROR: 'error'
- };
- /**
- * Display a message to the user
- * @param {string} message - The message to display
- * @param {string} type - The type of message (info, success, warning, error)
- */
- function logMessage(message, type = LOG_TYPE.INFO) {
- console.log(`[${type.toUpperCase()}] ${message}`);
-
- // Create message element
- const messageElement = document.createElement('div');
- messageElement.className = `fixed top-4 right-4 p-4 rounded-lg shadow-lg z-50 transition-opacity duration-300 ${
- type === LOG_TYPE.ERROR ? 'bg-red-100 text-red-700' :
- type === LOG_TYPE.SUCCESS ? 'bg-green-100 text-green-700' :
- type === LOG_TYPE.WARNING ? 'bg-yellow-100 text-yellow-700' :
- 'bg-blue-100 text-blue-700'
- }`;
- messageElement.textContent = message;
-
- // Add to document
- document.body.appendChild(messageElement);
-
- // Remove after 5 seconds
- setTimeout(() => {
- messageElement.style.opacity = '0';
- setTimeout(() => messageElement.remove(), 300);
- }, 5000);
- }
- // Global variables for the image converter
- let originalImage = null;
- let fileName = '';
- let convertedCoordinates = null;
- let currentImageData = null;
- /**
- * Open the image converter dialog with the selected image
- * @param {File} file - The image file to convert
- */
- function openImageConverter(file) {
- if (!file) {
- logMessage('No file selected for conversion.', LOG_TYPE.ERROR);
- return;
- }
- // Check if the file is an image
- if (!file.type.startsWith('image/')) {
- // If not an image, let the original uploadThetaRho handle it
- return;
- }
- // Store the original file name globally for later use
- window._originalUploadedFileName = file.name;
-
- // Remove extension for display/use
- fileName = file.name.replace(/\.[^/.]+$/, '');
-
- // Create an image element to load the file
- const img = new Image();
- img.onload = function() {
- // Draw the image on the canvas
- originalImage = img;
- drawAndPrepImage(img);
-
- // Show the converter dialog
- const overlay = document.getElementById('image-converter');
- overlay.classList.remove('hidden');
- overlay.classList.add('visible');
- // Initialize the UI elements
- initializeUI();
-
- // Convert the image with default settings
- convertImage();
- };
-
- img.onerror = function() {
- logMessage(`Failed to load image: ${file.name}`, LOG_TYPE.ERROR);
- };
-
- // Load the image from the file
- img.src = URL.createObjectURL(file);
- }
- /**
- * Initialize UI elements for the image converter
- */
- function initializeUI() {
- // Set up event listeners for UI controls
- const epsilonSlider = document.getElementById('epsilon-slider');
- const epsilonValueDisplay = document.getElementById('epsilon-value-display');
-
- epsilonSlider.addEventListener('input', function() {
- epsilonValueDisplay.textContent = this.value;
- });
-
- // Set up event listeners for other controls
- //document.getElementById('epsilon-slider').addEventListener('change', convertImage);
- //document.getElementById('dot-number').addEventListener('change', convertImage);
- //document.getElementById('contour-mode').addEventListener('change', convertImage);
- //document.getElementById('is-loop').addEventListener('change', convertImage);
- //document.getElementById('no-shortcuts').addEventListener('change', convertImage);
- }
- /**
- * Save the converted pattern as a .thr file
- */
- async function saveConvertedPattern() {
- convertedCoordinates = document.getElementById('polar-coordinates-textarea').value;
- if (!convertedCoordinates) {
- logMessage('No converted coordinates to save.', LOG_TYPE.ERROR);
- return;
- }
-
- try {
- // Always use the original uploaded file name if available
- let originalName = window._originalUploadedFileName;
- if (!originalName || typeof originalName !== 'string') {
- originalName = fileName || 'pattern';
- }
- // Remove extension
- let baseName = originalName.replace(/\.[^/.]+$/, '');
- // Replace spaces with underscores
- baseName = baseName.replace(/\s+/g, '_').trim();
- // Fallback if baseName is empty
- if (!baseName) baseName = 'pattern';
- let thrFileName = baseName + '.thr';
-
- // Create a Blob with the coordinates
- const blob = new Blob([convertedCoordinates], { type: 'text/plain' });
-
- // Create a FormData object
- const formData = new FormData();
- formData.append('file', new File([blob], thrFileName, { type: 'text/plain' }));
-
- // Show processing indicator
- const processingIndicator = document.getElementById('processing-status');
- const processingMessage = document.getElementById('processing-message');
- if (processingMessage) {
- processingMessage.textContent = `Saving pattern as ${thrFileName}...`;
- }
- processingIndicator.classList.add('visible');
-
- // Upload the file
- const response = await fetch('/upload_theta_rho', {
- method: 'POST',
- body: formData
- });
-
- const result = await response.json();
- if (result.success) {
- const fileInput = document.getElementById('upload_file');
- const finalFileName = 'custom_patterns/' + thrFileName;
- logMessage(`Image converted and saved as ${finalFileName}`, LOG_TYPE.SUCCESS);
-
- // Close the converter dialog
- closeImageConverter();
- // clear the file input
- if (fileInput) {
- fileInput.value = '';
- }
- // Refresh the file list
- await loadThetaRhoFiles();
-
- // Select the newly created file
- const fileList = document.getElementById('theta_rho_files');
- if (fileList) {
- const listItems = fileList.querySelectorAll('li');
- for (const item of listItems) {
- if (item.textContent === finalFileName) {
- selectFile(finalFileName, item);
- break;
- }
- }
- }
- } else {
- logMessage(`Failed to save pattern: ${result.error || 'Unknown error'}`, LOG_TYPE.ERROR);
- }
- } catch (error) {
- logMessage(`Error saving pattern: ${error.message}`, LOG_TYPE.ERROR);
- } finally {
- // Hide processing indicator
- document.getElementById('processing-status').classList.remove('visible');
- }
- }
- /**
- * Clear a canvas
- * @param {string} canvasId - The ID of the canvas element to clear
- */
- function clearCanvas(canvasId) {
- const canvas = document.getElementById(canvasId);
- const ctx = canvas.getContext('2d');
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- }
- /**
- * Close the image converter dialog
- */
- function closeImageConverter() {
- const overlay = document.getElementById('image-converter');
- overlay.classList.remove('visible');
- overlay.classList.add('hidden');
- // Clear the canvases
- clearCanvas('original-image');
- clearCanvas('edge-image');
- clearCanvas('dot-image');
- clearCanvas('connect-image');
-
- // Reset variables
- originalImage = null;
- fileName = '';
- convertedCoordinates = null;
- currentImageData = null;
-
- // Disable the save button
- //document.getElementById('save-pattern-button').disabled = true;
- }
- async function generateOpenAIImage(apiKey, prompt) {
- if (isGeneratingImage) {
- logMessage("Image is still generating - please don't press the button.", LOG_TYPE.INFO);
- } else {
- isGeneratingImage = true;
- clearCanvas('original-image');
- clearCanvas('edge-image');
- clearCanvas('dot-image');
- clearCanvas('connect-image');
- document.getElementById('gen-image-button').disabled = true;
- // Show processing indicator
- const processingIndicator = document.getElementById('processing-status');
- const processingMessage = document.getElementById('processing-message');
- if (processingMessage) {
- processingMessage.textContent = `Generating image...`;
- }
- processingIndicator.classList.add('visible');
- try {
- const fullPrompt = `Draw an image of the following: ${prompt}. Make the line black and the background white. The drawing should be a single line, don't add any additional details to the image.`;
- const response = await fetch('https://api.openai.com/v1/images/generations', {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${apiKey}`,
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- model: 'dall-e-3',
- prompt: fullPrompt,
- size: '1024x1024',
- quality: 'standard',
- response_format: 'b64_json', // Specify base64 encoding
- n: 1
- })
- });
- const data = await response.json();
- //const imageUrl = data.data[0].url;
- if ('error' in data) {
- throw new Error(data.error.message);
- }
- const imageData = data.data[0].b64_json;
- //console.log("Image Data: ", imageData);
- const imgElement = new Image();
- imgElement.onload = function() {
- // Draw the image on the canvas
- originalImage = imgElement;
- drawAndPrepImage(imgElement);
-
- // Convert the image with default settings
- convertImage();
- };
- imgElement.src = `data:image/png;base64,${imageData}`;
- //console.log(`Image generated successfully`);
- logMessage('Image generated successfully', LOG_TYPE.SUCCESS);
- } catch (error) {
- //console.error('Image generation error:', error);
- logMessage('Image generation error: ' + error, LOG_TYPE.ERROR);
- }
- isGeneratingImage = false;
- document.getElementById('gen-image-button').disabled = false;
- document.getElementById('processing-status').classList.remove('visible');
- }
- }
- function regeneratePattern() {
- const generateButton = document.getElementById('generate-button');
- // Disable button & show existing loader
- generateButton.disabled = true;
- generateButton.classList.add('loading');
- // Wrap convertImage() in a Promise
- new Promise((resolve, reject) => {
- try {
- convertImage();
- setTimeout(resolve, 1000);
- } catch (error) {
- reject(error);
- }
- })
- .then(() => {
- logMessage("Pattern regenerated successfully.", LOG_TYPE.SUCCESS);
- })
- .catch(error => {
- logMessage("Error regenerating pattern: " + error.message, LOG_TYPE.ERROR);
- })
- .finally(() => {
- // Re-enable button & hide loader
- generateButton.disabled = false;
- generateButton.classList.remove('loading');
- });
- }
- // Override the uploadThetaRho function to handle image files
- const originalUploadThetaRho = window.uploadThetaRho;
- window.uploadThetaRho = async function() {
- const fileInput = document.getElementById('upload_file');
- const file = fileInput.files[0];
-
- if (!file) {
- logMessage('No file selected for upload.', LOG_TYPE.ERROR);
- return;
- }
-
- // Check if the file is an image
- if (file.type.startsWith('image/')) {
- // Handle image files with the converter
- openImageConverter(file);
- return;
- }
-
- // For non-image files, use the original function
- await originalUploadThetaRho();
- };
- // Remove existing event listener and add a new one
- document.getElementById('gen-image-button')?.addEventListener('click', function() {
- let apiKey = document.getElementById('api-key')?.value || '';
- const googlyEyes = document.getElementById('googly-eyes');
- const promptElement = document.getElementById('prompt');
-
- // Add null checks
- const promptValue = promptElement?.value || '';
- const googlyEyesChecked = googlyEyes?.checked || false;
-
- const prompt = promptValue + (googlyEyesChecked ? ' with disproportionately large googly eyes' : '');
-
- // Show the converter dialog
- const overlay = document.getElementById('image-converter');
- if (overlay) {
- overlay.classList.remove('hidden');
- // Initialize the UI elements
- initializeUI();
- generateOpenAIImage(apiKey, prompt);
- }
- });
- async function loadThetaRhoFiles() {
- const fileList = document.getElementById('theta_rho_files');
- if (!fileList) return;
- // Clear the list
- fileList.innerHTML = '';
- try {
- // Fetch the file list from your backend
- const response = await fetch('/list_theta_rho_files');
- if (!response.ok) throw new Error('Failed to fetch file list');
- const files = await response.json();
- // Populate the list
- files.forEach(filename => {
- const li = document.createElement('li');
- li.textContent = filename;
- fileList.appendChild(li);
- });
- } catch (error) {
- const li = document.createElement('li');
- li.textContent = 'Error loading file list';
- fileList.appendChild(li);
- }
- }
|