UMD browser bundle is at lib/msal-browser.min.js, not dist/. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2485 lines
No EOL
107 KiB
HTML
2485 lines
No EOL
107 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>OMG Static - H&M</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
background-color: #f5f5f5;
|
||
padding: 20px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
background: white;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.header {
|
||
background: linear-gradient(135deg, #dc3545 0%, #b02a37 100%);
|
||
color: white;
|
||
padding: 30px;
|
||
text-align: center;
|
||
}
|
||
|
||
.header h1 {
|
||
font-size: 2.5em;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.header p {
|
||
opacity: 0.9;
|
||
font-size: 1.1em;
|
||
}
|
||
|
||
.form-section {
|
||
padding: 30px;
|
||
}
|
||
|
||
.dropdown-container {
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
label {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
font-size: 1.1em;
|
||
}
|
||
|
||
select {
|
||
width: 100%;
|
||
padding: 12px;
|
||
border: 2px solid #ddd;
|
||
border-radius: 6px;
|
||
font-size: 16px;
|
||
background-color: white;
|
||
transition: border-color 0.3s ease;
|
||
}
|
||
|
||
select:focus {
|
||
outline: none;
|
||
border-color: #dc3545;
|
||
}
|
||
|
||
.variables-container {
|
||
margin: 30px 0;
|
||
min-height: 100px;
|
||
}
|
||
|
||
.variable-group {
|
||
margin-bottom: 20px;
|
||
padding: 20px;
|
||
background-color: #f8f9fa;
|
||
border-radius: 6px;
|
||
border-left: 4px solid #dc3545;
|
||
}
|
||
|
||
.variable-group label {
|
||
margin-bottom: 5px;
|
||
font-size: 1em;
|
||
color: #555;
|
||
}
|
||
|
||
.variable-group input, .variable-group select {
|
||
margin-bottom: 10px;
|
||
padding: 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.variable-group input:focus, .variable-group select:focus {
|
||
border-color: #dc3545;
|
||
}
|
||
|
||
.submit-container {
|
||
text-align: center;
|
||
margin-top: 30px;
|
||
padding-top: 20px;
|
||
border-top: 1px solid #eee;
|
||
}
|
||
|
||
.submit-btn {
|
||
background: linear-gradient(135deg, #dc3545 0%, #b02a37 100%);
|
||
color: white;
|
||
border: none;
|
||
padding: 15px 40px;
|
||
font-size: 18px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||
}
|
||
|
||
.submit-btn:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 5px 15px rgba(220, 53, 69, 0.4);
|
||
}
|
||
|
||
.submit-btn:disabled {
|
||
background: #ccc;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.loading {
|
||
display: none;
|
||
margin-top: 20px;
|
||
text-align: center;
|
||
color: #dc3545;
|
||
}
|
||
|
||
.result {
|
||
margin-top: 20px;
|
||
padding: 15px;
|
||
border-radius: 4px;
|
||
display: none;
|
||
white-space: pre-wrap;
|
||
font-family: monospace;
|
||
font-size: 12px;
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.result.success {
|
||
background-color: #d4edda;
|
||
color: #155724;
|
||
border: 1px solid #c3e6cb;
|
||
}
|
||
|
||
.result.error {
|
||
background-color: #f8d7da;
|
||
color: #721c24;
|
||
border: 1px solid #f5c6cb;
|
||
}
|
||
|
||
.debug-log {
|
||
margin-top: 10px;
|
||
padding: 10px;
|
||
background-color: #f8f9fa;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 4px;
|
||
font-family: monospace;
|
||
font-size: 11px;
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
white-space: pre-wrap;
|
||
display: none;
|
||
}
|
||
|
||
.toggle-debug {
|
||
margin-top: 10px;
|
||
padding: 5px 10px;
|
||
background: #6c757d;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 3px;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.toggle-debug:hover {
|
||
background: #545b62;
|
||
}
|
||
|
||
.ratio-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.ratio-table th,
|
||
.ratio-table td {
|
||
padding: 8px 12px;
|
||
text-align: left;
|
||
border: 1px solid #ddd;
|
||
}
|
||
|
||
.ratio-table th {
|
||
background-color: #f8f9fa;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.ratio-table tr:nth-child(even) {
|
||
background-color: #f9f9f9;
|
||
}
|
||
|
||
.ratio-table input {
|
||
width: 80px;
|
||
padding: 4px 8px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 3px;
|
||
text-align: center;
|
||
}
|
||
|
||
.ratio-table input:focus {
|
||
border-color: #dc3545;
|
||
outline: none;
|
||
}
|
||
|
||
.ratio-table-container {
|
||
margin-top: 15px;
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
@media (max-width: 600px) {
|
||
.header h1 {
|
||
font-size: 2em;
|
||
}
|
||
|
||
.form-section {
|
||
padding: 20px;
|
||
}
|
||
}
|
||
|
||
#authOverlay {
|
||
position: fixed;
|
||
top: 0; left: 0; right: 0; bottom: 0;
|
||
background: #f5f5f5;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.auth-box {
|
||
background: white;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||
padding: 40px;
|
||
text-align: center;
|
||
max-width: 400px;
|
||
width: 100%;
|
||
}
|
||
|
||
.auth-box h2 {
|
||
margin-bottom: 15px;
|
||
color: #333;
|
||
}
|
||
|
||
.auth-box p {
|
||
color: #666;
|
||
}
|
||
|
||
.user-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-top: 10px;
|
||
justify-content: center;
|
||
}
|
||
|
||
.user-info span {
|
||
font-size: 0.9em;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.user-info button {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
color: white;
|
||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||
padding: 6px 14px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 0.85em;
|
||
}
|
||
|
||
.user-info button:hover {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
}
|
||
</style>
|
||
<script src="https://cdn.jsdelivr.net/npm/@azure/msal-browser@3/lib/msal-browser.min.js"></script>
|
||
</head>
|
||
<body>
|
||
<div id="authOverlay">
|
||
<div class="auth-box">
|
||
<h2>OMG Static – H&M</h2>
|
||
<p id="authStatus">Loading...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="container" style="display:none">
|
||
<div class="header">
|
||
<h1>OMG Static - H&M <small style="opacity: 0.7; font-size: 0.5em;">v1.01</small></h1>
|
||
<p>Select an action and configure the required parameters</p>
|
||
<div class="user-info" id="userInfo" style="display:none">
|
||
<span id="userName"></span>
|
||
<button id="logoutBtn">Sign out</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-section">
|
||
<div class="dropdown-container">
|
||
<label for="actionSelect">Select Action:</label>
|
||
<select id="actionSelect">
|
||
<option value="">-- Select an action --</option>
|
||
<option value="relink">Relink Images – Search and update missing images</option>
|
||
<option value="image-status">Image Status – Check for missing images</option>
|
||
<option value="export-indd">Export INDD – Export indd files from OMG Static</option>
|
||
<option value="export-pdf">Export PDF – Exports and renames R100 files for OMG</option>
|
||
<option value="ratio-check">Ratio Check – Export a indd for the minimum and maximum output</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="variables-container" id="variablesContainer">
|
||
<p style="text-align: center; color: #999; font-style: italic;">Please select an action to see the required variables</p>
|
||
</div>
|
||
|
||
<div class="submit-container">
|
||
<button class="submit-btn" id="submitBtn" disabled>Submit</button>
|
||
<div class="loading" id="loading">Processing request...</div>
|
||
<div class="result" id="result"></div>
|
||
<button class="toggle-debug" id="toggleDebug" style="display: none;">Show Debug Log</button>
|
||
<div class="debug-log" id="debugLog"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const actionSelect = document.getElementById('actionSelect');
|
||
const variablesContainer = document.getElementById('variablesContainer');
|
||
const submitBtn = document.getElementById('submitBtn');
|
||
const loading = document.getElementById('loading');
|
||
const result = document.getElementById('result');
|
||
const toggleDebug = document.getElementById('toggleDebug');
|
||
const debugLog = document.getElementById('debugLog');
|
||
|
||
let debugMessages = [];
|
||
let appConfig = null;
|
||
let msalInstance = null;
|
||
let currentAccount = null;
|
||
|
||
// Load configuration on page load
|
||
async function loadConfig() {
|
||
try {
|
||
const response = await fetch('config.php', {
|
||
method: 'GET',
|
||
headers: {
|
||
'X-Requested-With': 'XMLHttpRequest',
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`Config load failed: ${response.status} ${response.statusText}`);
|
||
}
|
||
|
||
appConfig = await response.json();
|
||
logDebug('Configuration loaded successfully');
|
||
|
||
} catch (error) {
|
||
console.error('Failed to load configuration:', error);
|
||
showResult('Error: Failed to load application configuration. Please check server setup.', 'error');
|
||
submitBtn.disabled = true;
|
||
}
|
||
}
|
||
|
||
async function initAuth(config) {
|
||
const msalConfig = {
|
||
auth: {
|
||
clientId: config.azure.clientId,
|
||
authority: `https://login.microsoftonline.com/${config.azure.tenantId}`,
|
||
redirectUri: config.azure.redirectUri
|
||
},
|
||
cache: { cacheLocation: 'sessionStorage', storeAuthStateInCookie: false }
|
||
};
|
||
msalInstance = new msal.PublicClientApplication(msalConfig);
|
||
await msalInstance.initialize();
|
||
|
||
const response = await msalInstance.handleRedirectPromise();
|
||
if (response) {
|
||
currentAccount = response.account;
|
||
} else {
|
||
const accounts = msalInstance.getAllAccounts();
|
||
if (accounts.length > 0) currentAccount = accounts[0];
|
||
}
|
||
|
||
if (!currentAccount) {
|
||
document.getElementById('authStatus').textContent = 'Redirecting to Microsoft login...';
|
||
await msalInstance.loginRedirect({ scopes: ['openid', 'profile', 'email'] });
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function showApp(account) {
|
||
document.getElementById('authOverlay').style.display = 'none';
|
||
document.querySelector('.container').style.display = 'block';
|
||
document.getElementById('userName').textContent = account.name || account.username;
|
||
document.getElementById('userInfo').style.display = 'flex';
|
||
document.getElementById('logoutBtn').addEventListener('click', () => {
|
||
msalInstance.logoutRedirect({ account: currentAccount });
|
||
});
|
||
}
|
||
|
||
// Initialize application
|
||
document.addEventListener('DOMContentLoaded', async function() {
|
||
await loadConfig();
|
||
const authenticated = await initAuth(appConfig);
|
||
if (!authenticated) return;
|
||
showApp(currentAccount);
|
||
initializeApp();
|
||
});
|
||
|
||
// Helper function to build API URLs with config
|
||
function buildApiUrl(command, params = {}) {
|
||
if (!appConfig) {
|
||
throw new Error('Configuration not loaded');
|
||
}
|
||
|
||
const baseParams = {
|
||
command: command,
|
||
authDomain: appConfig.api.authDomain,
|
||
authUsername: encodeURIComponent(appConfig.api.authUsername),
|
||
authPassword: encodeURIComponent(appConfig.api.authPassword),
|
||
clientId: appConfig.api.clientId
|
||
};
|
||
|
||
const allParams = { ...baseParams, ...params };
|
||
const paramString = Object.entries(allParams)
|
||
.map(([key, value]) => `${key}=${value}`)
|
||
.join('&');
|
||
|
||
return `${appConfig.api.baseUrl}?${paramString}`;
|
||
}
|
||
|
||
function logDebug(message, data = null) {
|
||
const timestamp = new Date().toLocaleTimeString();
|
||
const logEntry = `[${timestamp}] ${message}`;
|
||
debugMessages.push(logEntry);
|
||
|
||
if (data) {
|
||
debugMessages.push(JSON.stringify(data, null, 2));
|
||
}
|
||
|
||
debugLog.textContent = debugMessages.join('\n');
|
||
debugLog.scrollTop = debugLog.scrollHeight;
|
||
console.log(logEntry, data);
|
||
}
|
||
|
||
function clearDebugLog() {
|
||
debugMessages = [];
|
||
debugLog.textContent = '';
|
||
}
|
||
|
||
function initializeApp() {
|
||
toggleDebug.addEventListener('click', function() {
|
||
if (debugLog.style.display === 'none' || debugLog.style.display === '') {
|
||
debugLog.style.display = 'block';
|
||
toggleDebug.textContent = 'Hide Debug Log';
|
||
} else {
|
||
debugLog.style.display = 'none';
|
||
toggleDebug.textContent = 'Show Debug Log';
|
||
}
|
||
});
|
||
|
||
const actionVariables = {
|
||
'relink': [
|
||
{ name: 'linkMethod', label: 'Link Method', type: 'radio', required: true, options: ['Link by Folder', 'Link by File'] },
|
||
{ name: 'folderId', label: 'Folder ID', type: 'text', required: true, placeholder: 'Enter folder ID', conditional: 'Link by Folder' },
|
||
{ name: 'documentId', label: 'Document ID', type: 'text', required: true, placeholder: 'Enter document ID', conditional: 'Link by File' }
|
||
],
|
||
'image-status': [
|
||
{ name: 'folderId', label: 'Folder ID', type: 'text', required: true, placeholder: 'Enter folder ID' }
|
||
],
|
||
'export-indd': [
|
||
{ name: 'folderId', label: 'Folder ID', type: 'text', required: true, placeholder: 'Enter folder ID' }
|
||
],
|
||
'export-pdf': [
|
||
{ name: 'folderId', label: 'Folder ID', type: 'text', required: true, placeholder: 'Enter folder ID' }
|
||
],
|
||
'ratio-check': [
|
||
{ name: 'folderId', label: 'Folder ID', type: 'text', required: true, placeholder: 'Enter folder ID' },
|
||
{ name: 'ratioRules', label: 'Ratio Rules', type: 'ratio-table', required: false }
|
||
]
|
||
};
|
||
|
||
actionSelect.addEventListener('change', function() {
|
||
const selectedAction = this.value;
|
||
|
||
if (selectedAction && actionVariables[selectedAction]) {
|
||
generateVariableInputs(actionVariables[selectedAction]);
|
||
submitBtn.disabled = false;
|
||
} else {
|
||
variablesContainer.innerHTML = '<p style="text-align: center; color: #999; font-style: italic;">Please select an action to see the required variables</p>';
|
||
submitBtn.disabled = true;
|
||
}
|
||
|
||
hideResult();
|
||
});
|
||
|
||
function generateVariableInputs(variables) {
|
||
variablesContainer.innerHTML = '';
|
||
|
||
variables.forEach(variable => {
|
||
const variableGroup = document.createElement('div');
|
||
variableGroup.className = 'variable-group';
|
||
|
||
// Handle conditional fields - initially hide them
|
||
if (variable.conditional) {
|
||
variableGroup.style.display = 'none';
|
||
variableGroup.setAttribute('data-conditional', variable.conditional);
|
||
}
|
||
|
||
const label = document.createElement('label');
|
||
label.textContent = variable.label + (variable.required ? ' *' : '');
|
||
label.setAttribute('for', variable.name);
|
||
|
||
let input;
|
||
if (variable.type === 'select') {
|
||
input = document.createElement('select');
|
||
input.innerHTML = '<option value="">-- Select --</option>';
|
||
variable.options.forEach(option => {
|
||
const optionElement = document.createElement('option');
|
||
optionElement.value = option;
|
||
optionElement.textContent = option;
|
||
input.appendChild(optionElement);
|
||
});
|
||
} else if (variable.type === 'radio') {
|
||
// Create radio button group
|
||
const radioContainer = document.createElement('div');
|
||
radioContainer.style.marginTop = '10px';
|
||
radioContainer.style.display = 'flex';
|
||
radioContainer.style.gap = '20px';
|
||
|
||
variable.options.forEach(option => {
|
||
const radioWrapper = document.createElement('div');
|
||
radioWrapper.style.display = 'flex';
|
||
radioWrapper.style.alignItems = 'center';
|
||
|
||
const radioInput = document.createElement('input');
|
||
radioInput.type = 'radio';
|
||
radioInput.name = variable.name;
|
||
radioInput.value = option;
|
||
radioInput.id = `${variable.name}_${option.replace(/\\s+/g, '_')}`;
|
||
|
||
const radioLabel = document.createElement('label');
|
||
radioLabel.setAttribute('for', radioInput.id);
|
||
radioLabel.textContent = option;
|
||
radioLabel.style.marginLeft = '8px';
|
||
radioLabel.style.fontWeight = 'normal';
|
||
|
||
// Add event listener for conditional field showing
|
||
radioInput.addEventListener('change', function() {
|
||
if (this.checked) {
|
||
handleConditionalFields(variable.name, this.value);
|
||
}
|
||
});
|
||
|
||
radioWrapper.appendChild(radioInput);
|
||
radioWrapper.appendChild(radioLabel);
|
||
radioContainer.appendChild(radioWrapper);
|
||
});
|
||
|
||
input = radioContainer;
|
||
} else if (variable.type === 'ratio-table') {
|
||
input = createRatioTable();
|
||
} else {
|
||
input = document.createElement('input');
|
||
input.type = variable.type;
|
||
input.placeholder = variable.placeholder || '';
|
||
input.id = variable.name;
|
||
input.name = variable.name;
|
||
input.required = variable.required;
|
||
}
|
||
|
||
variableGroup.appendChild(label);
|
||
variableGroup.appendChild(input);
|
||
variablesContainer.appendChild(variableGroup);
|
||
});
|
||
}
|
||
|
||
function createRatioTable() {
|
||
const ratios = [
|
||
{ ratio: '020', minDefault: '015', maxDefault: '035' },
|
||
{ ratio: '050', minDefault: '036', maxDefault: '058' },
|
||
{ ratio: '067', minDefault: '059', maxDefault: '076' },
|
||
{ ratio: '100', minDefault: '077', maxDefault: '110' },
|
||
{ ratio: '120', minDefault: '111', maxDefault: '130' },
|
||
{ ratio: '140', minDefault: '131', maxDefault: '170' },
|
||
{ ratio: '200', minDefault: '171', maxDefault: '227' },
|
||
{ ratio: '254', minDefault: '228', maxDefault: '277' },
|
||
{ ratio: '300', minDefault: '278', maxDefault: '344' },
|
||
{ ratio: '388', minDefault: '345', maxDefault: '414' },
|
||
{ ratio: '440', minDefault: '415', maxDefault: '530' },
|
||
{ ratio: '600', minDefault: '531', maxDefault: '620' }
|
||
];
|
||
|
||
const container = document.createElement('div');
|
||
container.className = 'ratio-table-container';
|
||
|
||
const table = document.createElement('table');
|
||
table.className = 'ratio-table';
|
||
|
||
// Create header
|
||
const thead = document.createElement('thead');
|
||
const headerRow = document.createElement('tr');
|
||
|
||
['Ratio', 'Minimum Ratio', 'Maximum Ratio'].forEach(header => {
|
||
const th = document.createElement('th');
|
||
th.textContent = header;
|
||
headerRow.appendChild(th);
|
||
});
|
||
thead.appendChild(headerRow);
|
||
table.appendChild(thead);
|
||
|
||
// Create body
|
||
const tbody = document.createElement('tbody');
|
||
|
||
ratios.forEach(ratioData => {
|
||
const row = document.createElement('tr');
|
||
|
||
// Ratio column (read-only)
|
||
const ratioCell = document.createElement('td');
|
||
ratioCell.textContent = ratioData.ratio;
|
||
ratioCell.style.fontWeight = 'bold';
|
||
row.appendChild(ratioCell);
|
||
|
||
// Minimum ratio column (editable)
|
||
const minCell = document.createElement('td');
|
||
const minInput = document.createElement('input');
|
||
minInput.type = 'text';
|
||
minInput.value = ratioData.minDefault;
|
||
minInput.name = `ratio_${ratioData.ratio}_min`;
|
||
minInput.setAttribute('data-ratio', ratioData.ratio);
|
||
minInput.setAttribute('data-type', 'min');
|
||
minCell.appendChild(minInput);
|
||
row.appendChild(minCell);
|
||
|
||
// Maximum ratio column (editable)
|
||
const maxCell = document.createElement('td');
|
||
const maxInput = document.createElement('input');
|
||
maxInput.type = 'text';
|
||
maxInput.value = ratioData.maxDefault;
|
||
maxInput.name = `ratio_${ratioData.ratio}_max`;
|
||
maxInput.setAttribute('data-ratio', ratioData.ratio);
|
||
maxInput.setAttribute('data-type', 'max');
|
||
maxCell.appendChild(maxInput);
|
||
row.appendChild(maxCell);
|
||
|
||
tbody.appendChild(row);
|
||
});
|
||
|
||
table.appendChild(tbody);
|
||
container.appendChild(table);
|
||
|
||
return container;
|
||
}
|
||
|
||
function handleConditionalFields(radioName, selectedValue) {
|
||
// Hide all conditional fields for this radio group first
|
||
const conditionalFields = variablesContainer.querySelectorAll('[data-conditional]');
|
||
conditionalFields.forEach(field => {
|
||
field.style.display = 'none';
|
||
// Clear values in hidden fields
|
||
const inputs = field.querySelectorAll('input, select');
|
||
inputs.forEach(input => input.value = '');
|
||
});
|
||
|
||
// Show the field that matches the selected value
|
||
const targetField = variablesContainer.querySelector(`[data-conditional="${selectedValue}"]`);
|
||
if (targetField) {
|
||
targetField.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
submitBtn.addEventListener('click', async function() {
|
||
const selectedAction = actionSelect.value;
|
||
if (!selectedAction) return;
|
||
|
||
const formData = collectFormData();
|
||
if (!validateFormData(formData)) {
|
||
showResult('Please fill in all required fields.', 'error');
|
||
return;
|
||
}
|
||
|
||
await submitData(selectedAction, formData);
|
||
});
|
||
} // end initializeApp
|
||
|
||
function collectFormData() {
|
||
const formData = {};
|
||
const inputs = variablesContainer.querySelectorAll('input, select');
|
||
|
||
inputs.forEach(input => {
|
||
if (input.name) {
|
||
if (input.type === 'radio') {
|
||
if (input.checked) {
|
||
formData[input.name] = input.value;
|
||
}
|
||
} else if (input.name.startsWith('ratio_')) {
|
||
// Handle ratio table inputs specially
|
||
const ratio = input.getAttribute('data-ratio');
|
||
const type = input.getAttribute('data-type');
|
||
|
||
if (!formData.ratioRules) {
|
||
formData.ratioRules = {};
|
||
}
|
||
if (!formData.ratioRules[ratio]) {
|
||
formData.ratioRules[ratio] = {};
|
||
}
|
||
formData.ratioRules[ratio][type] = input.value;
|
||
} else {
|
||
formData[input.name] = input.value;
|
||
}
|
||
}
|
||
});
|
||
|
||
return formData;
|
||
}
|
||
|
||
function validateFormData(formData) {
|
||
const selectedAction = actionSelect.value;
|
||
if (!selectedAction || !actionVariables[selectedAction]) return false;
|
||
|
||
const requiredFields = actionVariables[selectedAction].filter(v => v.required);
|
||
|
||
for (let field of requiredFields) {
|
||
// Skip validation for conditional fields that are not currently shown
|
||
if (field.conditional) {
|
||
const conditionalField = variablesContainer.querySelector(`[data-conditional="${field.conditional}"]`);
|
||
if (!conditionalField || conditionalField.style.display === 'none') {
|
||
continue; // Skip validation for hidden conditional fields
|
||
}
|
||
}
|
||
|
||
if (!formData[field.name] || formData[field.name].trim() === '') {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
async function submitData(action, data) {
|
||
submitBtn.disabled = true;
|
||
loading.style.display = 'block';
|
||
hideResult();
|
||
|
||
try {
|
||
if (action === 'export-indd') {
|
||
await handleExportINDD(data);
|
||
} else if (action === 'export-pdf') {
|
||
await handleExportPDF(data);
|
||
} else if (action === 'relink') {
|
||
await handleRelink(data);
|
||
} else if (action === 'image-status') {
|
||
await handleImageStatus(data);
|
||
} else if (action === 'ratio-check') {
|
||
await handleRatioCheck(data);
|
||
} else {
|
||
const apiEndpoint = getApiEndpoint(action);
|
||
const response = await fetch(apiEndpoint, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
action: action,
|
||
parameters: data
|
||
})
|
||
});
|
||
|
||
if (response.ok) {
|
||
const result = await response.json();
|
||
showResult(`Request submitted successfully! Response: ${JSON.stringify(result, null, 2)}`, 'success');
|
||
} else {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('API call failed:', error);
|
||
showResult(`Error: ${error.message}. Check console for details.`, 'error');
|
||
} finally {
|
||
submitBtn.disabled = false;
|
||
loading.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
async function handleExportINDD(data) {
|
||
const { folderId } = data;
|
||
|
||
if (!appConfig) {
|
||
throw new Error('Configuration not loaded');
|
||
}
|
||
|
||
// Step 1: Get document list
|
||
const listUrl = buildApiUrl('document.list', {
|
||
folderId: folderId,
|
||
include: 'id%2Cname'
|
||
});
|
||
|
||
showResult('Step 1: Fetching document list...', 'success');
|
||
|
||
const listResponse = await fetch(listUrl);
|
||
if (!listResponse.ok) {
|
||
throw new Error(`Failed to fetch document list: ${listResponse.status} ${listResponse.statusText}`);
|
||
}
|
||
|
||
const listText = await listResponse.text();
|
||
let documents = [];
|
||
|
||
// Check if response is XML
|
||
if (listText.trim().startsWith('<?xml')) {
|
||
// Parse XML response
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(listText, 'text/xml');
|
||
|
||
// Check for API errors
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
throw new Error(`API Error: ${errorElement.textContent}`);
|
||
}
|
||
|
||
// Extract documents from XML
|
||
const documentElements = xmlDoc.querySelectorAll('document');
|
||
documents = Array.from(documentElements).map(doc => ({
|
||
id: doc.querySelector('id')?.textContent || '',
|
||
name: doc.querySelector('name')?.textContent || ''
|
||
}));
|
||
} else {
|
||
// Try to parse as JSON (fallback)
|
||
try {
|
||
const listData = JSON.parse(listText);
|
||
if (listData.data && Array.isArray(listData.data)) {
|
||
documents = listData.data;
|
||
} else {
|
||
throw new Error('Invalid JSON response format');
|
||
}
|
||
} catch (jsonError) {
|
||
throw new Error(`Unable to parse API response. Raw response: ${listText.substring(0, 200)}...`);
|
||
}
|
||
}
|
||
showResult(`Step 1 Complete: Found ${documents.length} documents. Starting exports...`, 'success');
|
||
|
||
// Step 2: Export each document
|
||
const results = [];
|
||
let successCount = 0;
|
||
let errorCount = 0;
|
||
|
||
for (let i = 0; i < documents.length; i++) {
|
||
const doc = documents[i];
|
||
const { id, name } = doc;
|
||
|
||
showResult(`Exporting ${i + 1}/${documents.length}: ${name}...`, 'success');
|
||
|
||
try {
|
||
const exportUrl = buildApiUrl('document.export.indd', {
|
||
id: id,
|
||
result: 'asset',
|
||
assetProjectId: appConfig.assets.inddProjectId,
|
||
assetFolderIdentifier: appConfig.assets.inddFolderIdentifier,
|
||
assetName: encodeURIComponent(name),
|
||
assetOverwrite: '1'
|
||
});
|
||
|
||
const exportResponse = await fetch(exportUrl);
|
||
|
||
if (exportResponse.ok) {
|
||
const exportText = await exportResponse.text();
|
||
let exportResult;
|
||
|
||
// Handle XML or JSON response
|
||
if (exportText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(exportText, 'text/xml');
|
||
|
||
// Check for errors in XML
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
results.push({ id, name, status: 'error', error: `API Error: ${errorElement.textContent}` });
|
||
errorCount++;
|
||
continue;
|
||
}
|
||
|
||
// Extract success info from XML
|
||
const successElement = xmlDoc.querySelector('success');
|
||
exportResult = {
|
||
success: successElement ? successElement.textContent : 'Export completed',
|
||
format: 'xml'
|
||
};
|
||
} else {
|
||
try {
|
||
exportResult = JSON.parse(exportText);
|
||
} catch (jsonError) {
|
||
exportResult = { message: 'Export completed', rawResponse: exportText.substring(0, 100) };
|
||
}
|
||
}
|
||
|
||
results.push({ id, name, status: 'success', result: exportResult });
|
||
successCount++;
|
||
} else {
|
||
results.push({ id, name, status: 'error', error: `${exportResponse.status} ${exportResponse.statusText}` });
|
||
errorCount++;
|
||
}
|
||
} catch (error) {
|
||
results.push({ id, name, status: 'error', error: error.message });
|
||
errorCount++;
|
||
}
|
||
|
||
// Add a small delay between requests to avoid overwhelming the server
|
||
if (i < documents.length - 1) {
|
||
await new Promise(resolve => setTimeout(resolve, 500));
|
||
}
|
||
}
|
||
|
||
// Final result summary
|
||
const resultMessage = `Export Complete!
|
||
|
||
Total Documents: ${documents.length}
|
||
Successful: ${successCount}
|
||
Failed: ${errorCount}`;
|
||
|
||
if (errorCount === 0) {
|
||
showResult(resultMessage, 'success');
|
||
} else {
|
||
showResult(resultMessage, errorCount === documents.length ? 'error' : 'success');
|
||
}
|
||
}
|
||
|
||
async function handleExportPDF(data) {
|
||
const { folderId } = data;
|
||
|
||
if (!appConfig) {
|
||
throw new Error('Configuration not loaded');
|
||
}
|
||
|
||
// Step 1: Get document list
|
||
const listUrl = buildApiUrl('document.list', {
|
||
folderId: folderId,
|
||
include: 'id%2Cname'
|
||
});
|
||
|
||
showResult('Step 1: Fetching document list...', 'success');
|
||
|
||
const listResponse = await fetch(listUrl);
|
||
if (!listResponse.ok) {
|
||
throw new Error(`Failed to fetch document list: ${listResponse.status} ${listResponse.statusText}`);
|
||
}
|
||
|
||
const listText = await listResponse.text();
|
||
let documents = [];
|
||
|
||
// Check if response is XML
|
||
if (listText.trim().startsWith('<?xml')) {
|
||
// Parse XML response
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(listText, 'text/xml');
|
||
|
||
// Check for API errors
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
throw new Error(`API Error: ${errorElement.textContent}`);
|
||
}
|
||
|
||
// Extract documents from XML
|
||
const documentElements = xmlDoc.querySelectorAll('document');
|
||
documents = Array.from(documentElements).map(doc => ({
|
||
id: doc.querySelector('id')?.textContent || '',
|
||
name: doc.querySelector('name')?.textContent || ''
|
||
}));
|
||
} else {
|
||
// Try to parse as JSON (fallback)
|
||
try {
|
||
const listData = JSON.parse(listText);
|
||
if (listData.data && Array.isArray(listData.data)) {
|
||
documents = listData.data;
|
||
} else {
|
||
throw new Error('Invalid JSON response format');
|
||
}
|
||
} catch (jsonError) {
|
||
throw new Error(`Unable to parse API response. Raw response: ${listText.substring(0, 200)}...`);
|
||
}
|
||
}
|
||
|
||
// Filter documents that contain R100 in the name
|
||
const r100Documents = documents.filter(doc => doc.name.includes('R100'));
|
||
|
||
showResult(`Step 1 Complete: Found ${documents.length} total documents, ${r100Documents.length} contain R100. Starting PDF exports...`, 'success');
|
||
|
||
if (r100Documents.length === 0) {
|
||
showResult('No documents containing R100 found in this folder.', 'success');
|
||
return;
|
||
}
|
||
|
||
// Step 2: Export each R100 document as PDF
|
||
const results = [];
|
||
let successCount = 0;
|
||
let errorCount = 0;
|
||
|
||
for (let i = 0; i < r100Documents.length; i++) {
|
||
const doc = r100Documents[i];
|
||
const { id, name } = doc;
|
||
|
||
// Replace R100 with RATIOS in the asset name
|
||
const assetName = name.replace('R100', 'RATIOS');
|
||
|
||
showResult(`Exporting PDF ${i + 1}/${r100Documents.length}: ${assetName}...`, 'success');
|
||
|
||
try {
|
||
const exportUrl = buildApiUrl('document.export.pdf', {
|
||
id: id,
|
||
presetId: appConfig.assets.pdfPresetId,
|
||
optionAddRegMarks: '1',
|
||
result: 'asset',
|
||
assetProjectId: appConfig.assets.pdfProjectId,
|
||
assetFolderIdentifier: appConfig.assets.pdfFolderIdentifier,
|
||
assetName: encodeURIComponent(assetName),
|
||
assetOverwrite: '1'
|
||
});
|
||
|
||
const exportResponse = await fetch(exportUrl);
|
||
|
||
if (exportResponse.ok) {
|
||
const exportText = await exportResponse.text();
|
||
let exportResult;
|
||
|
||
// Handle XML or JSON response
|
||
if (exportText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(exportText, 'text/xml');
|
||
|
||
// Check for errors in XML
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
results.push({ id, name, assetName, status: 'error', error: `API Error: ${errorElement.textContent}` });
|
||
errorCount++;
|
||
continue;
|
||
}
|
||
|
||
// Extract success info from XML
|
||
const successElement = xmlDoc.querySelector('success');
|
||
exportResult = {
|
||
success: successElement ? successElement.textContent : 'PDF Export completed',
|
||
format: 'xml'
|
||
};
|
||
} else {
|
||
try {
|
||
exportResult = JSON.parse(exportText);
|
||
} catch (jsonError) {
|
||
exportResult = { message: 'PDF Export completed', rawResponse: exportText.substring(0, 100) };
|
||
}
|
||
}
|
||
|
||
results.push({ id, name, assetName, status: 'success', result: exportResult });
|
||
successCount++;
|
||
} else {
|
||
results.push({ id, name, assetName, status: 'error', error: `${exportResponse.status} ${exportResponse.statusText}` });
|
||
errorCount++;
|
||
}
|
||
} catch (error) {
|
||
results.push({ id, name, assetName, status: 'error', error: error.message });
|
||
errorCount++;
|
||
}
|
||
|
||
// Add a small delay between requests to avoid overwhelming the server
|
||
if (i < r100Documents.length - 1) {
|
||
await new Promise(resolve => setTimeout(resolve, 500));
|
||
}
|
||
}
|
||
|
||
// Final result summary
|
||
const resultMessage = `PDF Export Complete!
|
||
|
||
R100 Documents Found: ${r100Documents.length}
|
||
Successful: ${successCount}
|
||
Failed: ${errorCount}`;
|
||
|
||
if (errorCount === 0) {
|
||
showResult(resultMessage, 'success');
|
||
} else {
|
||
showResult(resultMessage, errorCount === r100Documents.length ? 'error' : 'success');
|
||
}
|
||
}
|
||
|
||
async function handleRelink(data) {
|
||
const { linkMethod, folderId, documentId } = data;
|
||
|
||
if (!appConfig) {
|
||
throw new Error('Configuration not loaded');
|
||
}
|
||
|
||
if (linkMethod === 'Link by Folder') {
|
||
await handleLinkByFolder(folderId);
|
||
}
|
||
|
||
if (linkMethod === 'Link by File') {
|
||
await handleLinkByFile(documentId);
|
||
}
|
||
}
|
||
|
||
async function handleImageStatus(data) {
|
||
const { folderId } = data;
|
||
|
||
if (!appConfig) {
|
||
throw new Error('Configuration not loaded');
|
||
}
|
||
|
||
clearDebugLog();
|
||
toggleDebug.style.display = 'block';
|
||
|
||
showResult('Step 1: Getting document list from folder...', 'success');
|
||
logDebug(`Starting Image Status check for folder ID: ${folderId}`);
|
||
|
||
// Step 1: Get document list from folder
|
||
const documentListUrl = buildApiUrl('document.list', {
|
||
folderId: folderId,
|
||
include: 'id'
|
||
});
|
||
logDebug('API Call: document.list', { url: documentListUrl.replace(encodeURIComponent(appConfig.api.authPassword), '***') });
|
||
|
||
const documentListResponse = await fetch(documentListUrl);
|
||
if (!documentListResponse.ok) {
|
||
logDebug(`Document list API failed: ${documentListResponse.status} ${documentListResponse.statusText}`);
|
||
throw new Error(`Failed to fetch document list: ${documentListResponse.status} ${documentListResponse.statusText}`);
|
||
}
|
||
|
||
const documentListText = await documentListResponse.text();
|
||
let documents = [];
|
||
|
||
logDebug('Document list response received', { responseLength: documentListText.length, startsWithXML: documentListText.trim().startsWith('<?xml') });
|
||
|
||
if (documentListText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(documentListText, 'text/xml');
|
||
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
logDebug(`Document list API returned error: ${errorElement.textContent}`);
|
||
throw new Error(`API Error: ${errorElement.textContent}`);
|
||
}
|
||
|
||
const documentElements = xmlDoc.querySelectorAll('document');
|
||
documents = Array.from(documentElements).map(doc => ({
|
||
id: doc.querySelector('id')?.textContent || ''
|
||
}));
|
||
|
||
logDebug(`Found ${documents.length} documents in folder`, {
|
||
documentIds: documents.map(doc => doc.id)
|
||
});
|
||
} else {
|
||
logDebug('Non-XML response received', { response: documentListText.substring(0, 500) });
|
||
throw new Error('Invalid response format from document list API');
|
||
}
|
||
|
||
if (documents.length === 0) {
|
||
showResult('No documents found in the specified folder.', 'success');
|
||
return;
|
||
}
|
||
|
||
showResult(`Found ${documents.length} documents. Checking image status for each document...`, 'success');
|
||
|
||
const documentStatusResults = [];
|
||
|
||
// Process each document to get image status
|
||
for (let i = 0; i < documents.length; i++) {
|
||
const document = documents[i];
|
||
const documentId = document.id;
|
||
|
||
showResult(`Checking document ${i + 1}/${documents.length} (ID: ${documentId})...`, 'success');
|
||
logDebug(`Checking image status for document ${i + 1}/${documents.length}`, { documentId });
|
||
|
||
try {
|
||
// Get image list with linkState for this document
|
||
const imageListUrl = buildApiUrl('document.image.list', {
|
||
id: documentId,
|
||
include: 'linkState'
|
||
});
|
||
logDebug('API Call: document.image.list', { url: imageListUrl.replace(encodeURIComponent(appConfig.api.authPassword), '***'), documentId });
|
||
|
||
const imageListResponse = await fetch(imageListUrl);
|
||
if (!imageListResponse.ok) {
|
||
logDebug(`Image list API failed for document ${documentId}: ${imageListResponse.status} ${imageListResponse.statusText}`);
|
||
throw new Error(`Failed to fetch image list for document ${documentId}: ${imageListResponse.status} ${imageListResponse.statusText}`);
|
||
}
|
||
|
||
const imageListText = await imageListResponse.text();
|
||
let images = [];
|
||
|
||
logDebug(`Image list response received for document ${documentId}`, {
|
||
responseLength: imageListText.length,
|
||
startsWithXML: imageListText.trim().startsWith('<?xml')
|
||
});
|
||
|
||
if (imageListText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(imageListText, 'text/xml');
|
||
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
logDebug(`Image list API returned error for document ${documentId}: ${errorElement.textContent}`);
|
||
throw new Error(`Image List API Error for document ${documentId}: ${errorElement.textContent}`);
|
||
}
|
||
|
||
const imageElements = xmlDoc.querySelectorAll('image');
|
||
images = Array.from(imageElements).map(img => ({
|
||
linkState: img.querySelector('linkState')?.textContent || 'Unknown'
|
||
}));
|
||
|
||
logDebug(`Parsed ${images.length} images from document ${documentId}`, {
|
||
linkStates: images.map(img => img.linkState)
|
||
});
|
||
} else {
|
||
logDebug(`Non-XML response received for document ${documentId}`, { response: imageListText.substring(0, 500) });
|
||
throw new Error(`Invalid response format from image list API for document ${documentId}`);
|
||
}
|
||
|
||
// Count images by link state
|
||
const linkedImages = images.filter(img => img.linkState === 'NORMAL').length;
|
||
const missingImages = images.filter(img => img.linkState === 'MISSING').length;
|
||
const totalImages = images.length;
|
||
|
||
documentStatusResults.push({
|
||
documentId: documentId,
|
||
status: 'success',
|
||
totalImages: totalImages,
|
||
linkedImages: linkedImages,
|
||
missingImages: missingImages,
|
||
otherImages: totalImages - linkedImages - missingImages
|
||
});
|
||
|
||
logDebug(`Document ${documentId} image status completed`, {
|
||
totalImages,
|
||
linkedImages,
|
||
missingImages
|
||
});
|
||
|
||
} catch (error) {
|
||
logDebug(`Error checking image status for document ${documentId}: ${error.message}`, { error: error.stack });
|
||
documentStatusResults.push({
|
||
documentId: documentId,
|
||
status: 'error',
|
||
error: error.message
|
||
});
|
||
}
|
||
|
||
// Small delay between requests to avoid overwhelming the server
|
||
if (i < documents.length - 1) {
|
||
await new Promise(resolve => setTimeout(resolve, appConfig.performance.statusCheckDelay));
|
||
}
|
||
}
|
||
|
||
// Calculate totals
|
||
const successfulChecks = documentStatusResults.filter(doc => doc.status === 'success');
|
||
const totalLinkedImages = successfulChecks.reduce((sum, doc) => sum + doc.linkedImages, 0);
|
||
const totalMissingImages = successfulChecks.reduce((sum, doc) => sum + doc.missingImages, 0);
|
||
const totalAllImages = successfulChecks.reduce((sum, doc) => sum + doc.totalImages, 0);
|
||
const errorCount = documentStatusResults.filter(doc => doc.status === 'error').length;
|
||
|
||
// Final summary
|
||
const resultMessage = `Image Status Check Complete!
|
||
|
||
Total Documents Checked: ${documents.length}
|
||
Successful Checks: ${successfulChecks.length}
|
||
Failed Checks: ${errorCount}
|
||
|
||
Overall Totals:
|
||
- Total Images: ${totalAllImages}
|
||
- Linked Images (Normal): ${totalLinkedImages}
|
||
- Missing Images: ${totalMissingImages}
|
||
|
||
Document Details:
|
||
${documentStatusResults.map(doc =>
|
||
doc.status === 'success'
|
||
? `Document ${doc.documentId}: ${doc.linkedImages} linked, ${doc.missingImages} missing (${doc.totalImages} total)`
|
||
: `Document ${doc.documentId}: ERROR - ${doc.error}`
|
||
).join('\n')}`;
|
||
|
||
showResult(resultMessage, 'success');
|
||
}
|
||
|
||
async function handleLinkByFile(documentId) {
|
||
clearDebugLog();
|
||
toggleDebug.style.display = 'block';
|
||
|
||
showResult('Step 1: Opening document session...', 'success');
|
||
logDebug(`Starting relink process for document ID: ${documentId}`);
|
||
|
||
// Step 1: Open document session
|
||
const sessionOpenUrl = buildApiUrl('document.session.open', { documentId: documentId });
|
||
logDebug('API Call: document.session.open', { url: sessionOpenUrl.replace(encodeURIComponent(appConfig.api.authPassword), '***') });
|
||
|
||
const sessionOpenResponse = await fetch(sessionOpenUrl);
|
||
if (!sessionOpenResponse.ok) {
|
||
logDebug(`Session open API failed: ${sessionOpenResponse.status} ${sessionOpenResponse.statusText}`);
|
||
throw new Error(`Failed to open document session: ${sessionOpenResponse.status} ${sessionOpenResponse.statusText}`);
|
||
}
|
||
|
||
const sessionOpenText = await sessionOpenResponse.text();
|
||
let editSessionId = '';
|
||
|
||
logDebug('Session open response received', { responseLength: sessionOpenText.length, startsWithXML: sessionOpenText.trim().startsWith('<?xml') });
|
||
|
||
if (sessionOpenText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(sessionOpenText, 'text/xml');
|
||
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
logDebug(`Session open API returned error: ${errorElement.textContent}`);
|
||
throw new Error(`Session Open API Error: ${errorElement.textContent}`);
|
||
}
|
||
|
||
// Extract editSession ID
|
||
editSessionId = xmlDoc.querySelector('editSession')?.textContent || '';
|
||
if (!editSessionId) {
|
||
logDebug('No editSession ID found in response', { response: sessionOpenText });
|
||
throw new Error('No editSession ID received from session open API');
|
||
}
|
||
|
||
logDebug('Document session opened successfully', { editSessionId });
|
||
} else {
|
||
logDebug('Non-XML response received from session open', { response: sessionOpenText.substring(0, 500) });
|
||
throw new Error('Invalid response format from session open API');
|
||
}
|
||
|
||
let successCount = 0;
|
||
let errorCount = 0;
|
||
let images = [];
|
||
let missingImages = [];
|
||
const results = [];
|
||
|
||
try {
|
||
showResult('Step 2: Getting image list from document...', 'success');
|
||
|
||
// Step 2: Get document image list
|
||
const imageListUrl = buildApiUrl('document.image.list', { id: documentId });
|
||
logDebug('API Call: document.image.list', { url: imageListUrl.replace(encodeURIComponent(appConfig.api.authPassword), '***') });
|
||
|
||
const imageListResponse = await fetch(imageListUrl);
|
||
if (!imageListResponse.ok) {
|
||
logDebug(`Image list API failed: ${imageListResponse.status} ${imageListResponse.statusText}`);
|
||
throw new Error(`Failed to fetch image list: ${imageListResponse.status} ${imageListResponse.statusText}`);
|
||
}
|
||
|
||
const imageListText = await imageListResponse.text();
|
||
|
||
logDebug('Image list response received', { responseLength: imageListText.length, startsWithXML: imageListText.trim().startsWith('<?xml') });
|
||
|
||
if (imageListText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(imageListText, 'text/xml');
|
||
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
logDebug(`Image list API returned error: ${errorElement.textContent}`);
|
||
throw new Error(`API Error: ${errorElement.textContent}`);
|
||
}
|
||
|
||
const imageElements = xmlDoc.querySelectorAll('image');
|
||
images = Array.from(imageElements).map(img => {
|
||
// Extract filename from absolutePath since name tag contains user info
|
||
const absolutePath = img.querySelector('absolutePath')?.textContent || '';
|
||
const decodedAbsolutePath = decodeURIComponent(absolutePath);
|
||
const pathParts = decodedAbsolutePath.split('/');
|
||
const actualImageName = pathParts[pathParts.length - 1];
|
||
|
||
return {
|
||
id: img.querySelector('id')?.textContent || '',
|
||
name: actualImageName,
|
||
absolutePath: absolutePath,
|
||
linkState: img.querySelector('linkState')?.textContent || ''
|
||
};
|
||
});
|
||
|
||
logDebug(`Parsed ${images.length} images from document`, {
|
||
images: images.map(img => ({
|
||
id: img.id,
|
||
name: img.name,
|
||
linkState: img.linkState,
|
||
absolutePath: img.absolutePath.substring(0, 50) + '...'
|
||
}))
|
||
});
|
||
} else {
|
||
logDebug('Non-XML response received', { response: imageListText.substring(0, 500) });
|
||
}
|
||
|
||
showResult(`Found ${images.length} images in document. Checking link states...`, 'success');
|
||
|
||
// Only process MISSING images
|
||
missingImages = images.filter(img => img.linkState === 'MISSING');
|
||
|
||
logDebug('Image link state analysis', {
|
||
totalImages: images.length,
|
||
normalImages: images.filter(img => img.linkState === 'Normal').length,
|
||
missingImages: missingImages.length,
|
||
linkStates: [...new Set(images.map(img => img.linkState))]
|
||
});
|
||
|
||
if (missingImages.length > 0) {
|
||
showResult(`Found ${missingImages.length} missing images. Starting relink process...`, 'success');
|
||
logDebug('Starting relink process for missing images', {
|
||
missingImages: missingImages.map(img => ({
|
||
id: img.id,
|
||
name: img.name,
|
||
linkState: img.linkState
|
||
}))
|
||
});
|
||
|
||
// Process each missing image
|
||
for (let i = 0; i < missingImages.length; i++) {
|
||
const image = missingImages[i];
|
||
showResult(`Processing ${i + 1}/${missingImages.length}: ${image.name} (${image.linkState})...`, 'success');
|
||
|
||
logDebug(`Processing image ${i + 1}/${missingImages.length}`, {
|
||
id: image.id,
|
||
name: image.name,
|
||
linkState: image.linkState,
|
||
absolutePath: image.absolutePath
|
||
});
|
||
|
||
try {
|
||
logDebug('Processing image for relink', {
|
||
name: image.name,
|
||
absolutePath: image.absolutePath,
|
||
linkState: image.linkState
|
||
});
|
||
|
||
if (image.name === 'H&M-logo_sRGB.ai') {
|
||
logDebug('Detected H&M sRGB logo - using configured asset identifier', {
|
||
imageId: image.id,
|
||
imageName: image.name
|
||
});
|
||
const relinkSuccess = await relinkSpecificImage(documentId, image.id, appConfig.assets.hmSRGBLogoAsset, editSessionId);
|
||
results.push({
|
||
name: image.name,
|
||
imageId: image.id,
|
||
status: relinkSuccess ? 'success' : 'failed',
|
||
method: 'H&M sRGB logo'
|
||
});
|
||
if (relinkSuccess) {
|
||
successCount++;
|
||
} else {
|
||
errorCount++;
|
||
}
|
||
} else if (image.name === 'H&M-logo_CMYK_coated.eps') {
|
||
logDebug('Detected H&M CMYK logo - using configured asset identifier', {
|
||
imageId: image.id,
|
||
imageName: image.name
|
||
});
|
||
const relinkSuccess = await relinkSpecificImage(documentId, image.id, appConfig.assets.hmCMYKLogoAsset, editSessionId);
|
||
results.push({
|
||
name: image.name,
|
||
imageId: image.id,
|
||
status: relinkSuccess ? 'success' : 'failed',
|
||
method: 'H&M CMYK logo'
|
||
});
|
||
if (relinkSuccess) {
|
||
successCount++;
|
||
} else {
|
||
errorCount++;
|
||
}
|
||
} else {
|
||
logDebug('Processing generic image through asset lookup process');
|
||
const result = await relinkGenericImage(documentId, image, editSessionId);
|
||
results.push(result);
|
||
if (result.status === 'success') {
|
||
successCount++;
|
||
} else {
|
||
errorCount++;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
logDebug(`Error processing image: ${error.message}`, { error: error.stack });
|
||
results.push({ name: image.name, status: 'error', error: error.message });
|
||
errorCount++;
|
||
}
|
||
|
||
// Configurable delay between requests to avoid timeouts
|
||
if (i < missingImages.length - 1) {
|
||
await new Promise(resolve => setTimeout(resolve, appConfig.performance.relinkDelay));
|
||
}
|
||
}
|
||
} else {
|
||
logDebug('No missing images found');
|
||
}
|
||
|
||
} finally {
|
||
// Always save and close the document session
|
||
showResult('Saving and closing document session...', 'success');
|
||
logDebug('Saving and closing document session', { editSessionId });
|
||
|
||
try {
|
||
// Save session
|
||
const sessionSaveUrl = buildApiUrl('document.session.save', {
|
||
documentId: documentId,
|
||
editSession: editSessionId
|
||
});
|
||
logDebug('API Call: document.session.save', { url: sessionSaveUrl.replace(encodeURIComponent(appConfig.api.authPassword), '***') });
|
||
|
||
const sessionSaveResponse = await fetch(sessionSaveUrl);
|
||
if (!sessionSaveResponse.ok) {
|
||
logDebug(`Session save API failed: ${sessionSaveResponse.status} ${sessionSaveResponse.statusText}`);
|
||
} else {
|
||
logDebug('Session save response received');
|
||
}
|
||
|
||
// Close session
|
||
const sessionCloseUrl = buildApiUrl('document.session.close', {
|
||
documentId: documentId,
|
||
editSession: editSessionId
|
||
});
|
||
logDebug('API Call: document.session.close', { url: sessionCloseUrl.replace(encodeURIComponent(appConfig.api.authPassword), '***') });
|
||
|
||
const sessionCloseResponse = await fetch(sessionCloseUrl);
|
||
if (!sessionCloseResponse.ok) {
|
||
logDebug(`Session close API failed: ${sessionCloseResponse.status} ${sessionCloseResponse.statusText}`);
|
||
} else {
|
||
logDebug('Document session saved and closed successfully');
|
||
}
|
||
|
||
} catch (sessionError) {
|
||
logDebug(`Session management error: ${sessionError.message}`);
|
||
}
|
||
}
|
||
|
||
// Final summary
|
||
const resultMessage = `Relink Complete!
|
||
|
||
Total Images: ${images.length}
|
||
Missing Images: ${missingImages.length}
|
||
Successfully Relinked: ${successCount}
|
||
Failed: ${errorCount}`;
|
||
|
||
showResult(resultMessage, errorCount > 0 ? 'error' : 'success');
|
||
}
|
||
|
||
async function handleLinkByFolder(folderId) {
|
||
clearDebugLog();
|
||
toggleDebug.style.display = 'block';
|
||
|
||
showResult('Step 1: Getting document list from folder...', 'success');
|
||
logDebug(`Starting Link by Folder process for folder ID: ${folderId}`);
|
||
|
||
// Step 1: Get document list from folder
|
||
const documentListUrl = buildApiUrl('document.list', {
|
||
folderId: folderId,
|
||
include: 'id'
|
||
});
|
||
logDebug('API Call: document.list', { url: documentListUrl.replace(encodeURIComponent(appConfig.api.authPassword), '***') });
|
||
|
||
const documentListResponse = await fetch(documentListUrl);
|
||
if (!documentListResponse.ok) {
|
||
logDebug(`Document list API failed: ${documentListResponse.status} ${documentListResponse.statusText}`);
|
||
throw new Error(`Failed to fetch document list: ${documentListResponse.status} ${documentListResponse.statusText}`);
|
||
}
|
||
|
||
const documentListText = await documentListResponse.text();
|
||
let documents = [];
|
||
|
||
logDebug('Document list response received', { responseLength: documentListText.length, startsWithXML: documentListText.trim().startsWith('<?xml') });
|
||
|
||
if (documentListText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(documentListText, 'text/xml');
|
||
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
logDebug(`Document list API returned error: ${errorElement.textContent}`);
|
||
throw new Error(`API Error: ${errorElement.textContent}`);
|
||
}
|
||
|
||
const documentElements = xmlDoc.querySelectorAll('document');
|
||
documents = Array.from(documentElements).map(doc => ({
|
||
id: doc.querySelector('id')?.textContent || ''
|
||
}));
|
||
|
||
logDebug(`Found ${documents.length} documents in folder`, {
|
||
documentIds: documents.map(doc => doc.id)
|
||
});
|
||
} else {
|
||
logDebug('Non-XML response received', { response: documentListText.substring(0, 500) });
|
||
throw new Error('Invalid response format from document list API');
|
||
}
|
||
|
||
if (documents.length === 0) {
|
||
showResult('No documents found in the specified folder.', 'success');
|
||
return;
|
||
}
|
||
|
||
showResult(`Found ${documents.length} documents. Starting relink process for each document...`, 'success');
|
||
|
||
let totalSuccessCount = 0;
|
||
let totalErrorCount = 0;
|
||
let processedDocuments = 0;
|
||
const documentResults = [];
|
||
|
||
// Process each document
|
||
for (let i = 0; i < documents.length; i++) {
|
||
const document = documents[i];
|
||
const documentId = document.id;
|
||
|
||
showResult(`Processing document ${i + 1}/${documents.length} (ID: ${documentId})...`, 'success');
|
||
logDebug(`Processing document ${i + 1}/${documents.length}`, { documentId });
|
||
|
||
try {
|
||
// Use the existing handleLinkByFile function for each document
|
||
const result = await processDocumentRelink(documentId);
|
||
|
||
documentResults.push({
|
||
documentId: documentId,
|
||
status: 'completed',
|
||
successCount: result.successCount,
|
||
errorCount: result.errorCount,
|
||
totalImages: result.totalImages,
|
||
missingImages: result.missingImages
|
||
});
|
||
|
||
totalSuccessCount += result.successCount;
|
||
totalErrorCount += result.errorCount;
|
||
processedDocuments++;
|
||
|
||
logDebug(`Document ${documentId} completed`, {
|
||
successCount: result.successCount,
|
||
errorCount: result.errorCount,
|
||
missingImages: result.missingImages
|
||
});
|
||
|
||
} catch (error) {
|
||
logDebug(`Error processing document ${documentId}: ${error.message}`, { error: error.stack });
|
||
documentResults.push({
|
||
documentId: documentId,
|
||
status: 'error',
|
||
error: error.message
|
||
});
|
||
totalErrorCount++;
|
||
}
|
||
|
||
// Small delay between documents to avoid overwhelming the server
|
||
if (i < documents.length - 1) {
|
||
showResult(`Completed document ${documentId}. Waiting before processing next document...`, 'success');
|
||
await new Promise(resolve => setTimeout(resolve, appConfig.performance.documentDelay));
|
||
}
|
||
}
|
||
|
||
// Final summary
|
||
const resultMessage = `Link by Folder Complete!
|
||
|
||
Total Documents: ${documents.length}
|
||
Successfully Processed: ${processedDocuments}
|
||
Total Images Relinked: ${totalSuccessCount}
|
||
Total Failures: ${totalErrorCount}
|
||
|
||
Document Details:
|
||
${documentResults.map(doc =>
|
||
doc.status === 'completed'
|
||
? `Document ${doc.documentId}: ${doc.successCount} relinked, ${doc.errorCount} failed (${doc.missingImages} missing images)`
|
||
: `Document ${doc.documentId}: ERROR - ${doc.error}`
|
||
).join('\n')}`;
|
||
|
||
if (totalErrorCount === 0) {
|
||
showResult(resultMessage, 'success');
|
||
} else {
|
||
showResult(resultMessage, 'success');
|
||
}
|
||
}
|
||
|
||
async function handleRatioCheck(data) {
|
||
const { folderId, ratioRules } = data;
|
||
|
||
if (!appConfig) {
|
||
throw new Error('Configuration not loaded');
|
||
}
|
||
|
||
clearDebugLog();
|
||
toggleDebug.style.display = 'block';
|
||
|
||
showResult('Step 1: Getting document list from folder...', 'success');
|
||
logDebug(`Starting Ratio Check for folder ID: ${folderId}`);
|
||
logDebug('Ratio Rules:', ratioRules);
|
||
|
||
// Step 1: Get document list from folder with dimensions
|
||
const documentListUrl = buildApiUrl('document.list', {
|
||
folderId: folderId,
|
||
include: 'id%2Cname%2Cwidth%2Cheight'
|
||
});
|
||
logDebug('API Call: document.list', { url: documentListUrl.replace(encodeURIComponent(appConfig.api.authPassword), '***') });
|
||
|
||
const documentListResponse = await fetch(documentListUrl);
|
||
if (!documentListResponse.ok) {
|
||
logDebug(`Document list API failed: ${documentListResponse.status} ${documentListResponse.statusText}`);
|
||
throw new Error(`Failed to fetch document list: ${documentListResponse.status} ${documentListResponse.statusText}`);
|
||
}
|
||
|
||
const documentListText = await documentListResponse.text();
|
||
let documents = [];
|
||
|
||
logDebug('Document list response received', { responseLength: documentListText.length, startsWithXML: documentListText.trim().startsWith('<?xml') });
|
||
|
||
if (documentListText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(documentListText, 'text/xml');
|
||
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
logDebug(`Document list API returned error: ${errorElement.textContent}`);
|
||
throw new Error(`API Error: ${errorElement.textContent}`);
|
||
}
|
||
|
||
const documentElements = xmlDoc.querySelectorAll('document');
|
||
documents = Array.from(documentElements).map(doc => ({
|
||
id: doc.querySelector('id')?.textContent || '',
|
||
name: doc.querySelector('name')?.textContent || '',
|
||
width: parseInt(doc.querySelector('width')?.textContent || '0'),
|
||
height: parseInt(doc.querySelector('height')?.textContent || '0')
|
||
}));
|
||
|
||
logDebug(`Found ${documents.length} documents in folder`, {
|
||
documentIds: documents.map(doc => ({ id: doc.id, name: doc.name, dimensions: `${doc.width}x${doc.height}` }))
|
||
});
|
||
} else {
|
||
logDebug('Non-XML response received', { response: documentListText.substring(0, 500) });
|
||
throw new Error('Invalid response format from document list API');
|
||
}
|
||
|
||
if (documents.length === 0) {
|
||
showResult('No documents found in the specified folder.', 'success');
|
||
return;
|
||
}
|
||
|
||
// Step 2: Find documents for each ratio
|
||
const ratioDocuments = {};
|
||
const ratioKeys = Object.keys(ratioRules);
|
||
|
||
ratioKeys.forEach(ratioKey => {
|
||
const targetPattern = `R${ratioKey}.indd`;
|
||
const matchingDocs = documents.filter(doc => doc.name.endsWith(targetPattern));
|
||
|
||
if (matchingDocs.length > 0) {
|
||
ratioDocuments[ratioKey] = matchingDocs[0]; // Take first match
|
||
logDebug(`Found document for ratio ${ratioKey}:`, matchingDocs[0]);
|
||
} else {
|
||
logDebug(`No document found for ratio ${ratioKey} (looking for pattern: ${targetPattern})`);
|
||
}
|
||
});
|
||
|
||
const foundRatios = Object.keys(ratioDocuments);
|
||
showResult(`Found ${foundRatios.length} matching documents. Starting ratio processing...`, 'success');
|
||
|
||
if (foundRatios.length === 0) {
|
||
showResult('No documents found matching the ratio patterns (R020.indd, R050.indd, etc.)', 'error');
|
||
return;
|
||
}
|
||
|
||
// Step 3: Process each ratio document
|
||
let totalSuccessCount = 0;
|
||
let totalErrorCount = 0;
|
||
const processingResults = [];
|
||
|
||
for (let i = 0; i < foundRatios.length; i++) {
|
||
const ratioKey = foundRatios[i];
|
||
const document = ratioDocuments[ratioKey];
|
||
const ratioConfig = ratioRules[ratioKey];
|
||
|
||
showResult(`Processing ratio ${ratioKey} (${i + 1}/${foundRatios.length}): ${document.name}...`, 'success');
|
||
logDebug(`Processing ratio ${ratioKey}`, { document, ratioConfig });
|
||
|
||
try {
|
||
const result = await processRatioDocument(document, ratioKey, ratioConfig);
|
||
|
||
processingResults.push({
|
||
ratio: ratioKey,
|
||
documentName: document.name,
|
||
status: 'completed',
|
||
minSuccess: result.minSuccess,
|
||
maxSuccess: result.maxSuccess,
|
||
errors: result.errors
|
||
});
|
||
|
||
totalSuccessCount += (result.minSuccess ? 1 : 0) + (result.maxSuccess ? 1 : 0);
|
||
totalErrorCount += result.errors.length;
|
||
|
||
} catch (error) {
|
||
logDebug(`Error processing ratio ${ratioKey}: ${error.message}`, { error: error.stack });
|
||
processingResults.push({
|
||
ratio: ratioKey,
|
||
documentName: document.name,
|
||
status: 'error',
|
||
error: error.message
|
||
});
|
||
totalErrorCount++;
|
||
}
|
||
|
||
// Delay between ratios
|
||
if (i < foundRatios.length - 1) {
|
||
showResult(`Completed ratio ${ratioKey}. Waiting before processing next ratio...`, 'success');
|
||
await new Promise(resolve => setTimeout(resolve, appConfig.performance.documentDelay));
|
||
}
|
||
}
|
||
|
||
// Final summary
|
||
const resultMessage = `Ratio Check Complete!
|
||
|
||
Ratios Processed: ${foundRatios.length}
|
||
Total Exports: ${totalSuccessCount}
|
||
Total Failures: ${totalErrorCount}
|
||
|
||
Processing Details:
|
||
${processingResults.map(result => {
|
||
if (result.status === 'completed') {
|
||
const minStatus = result.minSuccess ? '✓' : '✗';
|
||
const maxStatus = result.maxSuccess ? '✓' : '✗';
|
||
const errorText = result.errors.length > 0 ? ` (${result.errors.length} errors)` : '';
|
||
return `Ratio ${result.ratio}: Min ${minStatus} Max ${maxStatus}${errorText}`;
|
||
} else {
|
||
return `Ratio ${result.ratio}: ERROR - ${result.error}`;
|
||
}
|
||
}).join('\n')}`;
|
||
|
||
if (totalErrorCount === 0) {
|
||
showResult(resultMessage, 'success');
|
||
} else {
|
||
showResult(resultMessage, 'success');
|
||
}
|
||
}
|
||
|
||
async function processRatioDocument(document, ratioKey, ratioConfig) {
|
||
const { id: documentId, name: documentName, height: originalHeight } = document;
|
||
const { min: minRatio, max: maxRatio } = ratioConfig;
|
||
|
||
logDebug(`Processing document ${documentId} for ratio ${ratioKey}`, {
|
||
documentName,
|
||
originalHeight,
|
||
minRatio,
|
||
maxRatio
|
||
});
|
||
|
||
// Step 1: Open document session
|
||
const sessionOpenUrl = buildApiUrl('document.session.open', { documentId: documentId });
|
||
logDebug('API Call: document.session.open', { url: sessionOpenUrl.replace(encodeURIComponent(appConfig.api.authPassword), '***') });
|
||
|
||
const sessionOpenResponse = await fetch(sessionOpenUrl);
|
||
if (!sessionOpenResponse.ok) {
|
||
throw new Error(`Failed to open document session: ${sessionOpenResponse.status} ${sessionOpenResponse.statusText}`);
|
||
}
|
||
|
||
const sessionOpenText = await sessionOpenResponse.text();
|
||
let editSessionId = '';
|
||
|
||
if (sessionOpenText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(sessionOpenText, 'text/xml');
|
||
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
throw new Error(`Session Open API Error: ${errorElement.textContent}`);
|
||
}
|
||
|
||
editSessionId = xmlDoc.querySelector('editSession')?.textContent || '';
|
||
if (!editSessionId) {
|
||
throw new Error('No editSession ID received from session open API');
|
||
}
|
||
|
||
logDebug('Document session opened successfully', { editSessionId });
|
||
} else {
|
||
throw new Error('Invalid response format from session open API');
|
||
}
|
||
|
||
const results = { minSuccess: false, maxSuccess: false, errors: [] };
|
||
|
||
try {
|
||
// Process minimum ratio
|
||
logDebug(`Processing minimum ratio: ${minRatio}`);
|
||
try {
|
||
await processRatioExport(documentId, editSessionId, documentName, originalHeight, ratioKey, minRatio, 'min');
|
||
results.minSuccess = true;
|
||
logDebug('Minimum ratio export successful');
|
||
} catch (error) {
|
||
results.errors.push(`Min ratio error: ${error.message}`);
|
||
logDebug('Minimum ratio export failed', { error: error.message });
|
||
}
|
||
|
||
// Process maximum ratio
|
||
logDebug(`Processing maximum ratio: ${maxRatio}`);
|
||
try {
|
||
await processRatioExport(documentId, editSessionId, documentName, originalHeight, ratioKey, maxRatio, 'max');
|
||
results.maxSuccess = true;
|
||
logDebug('Maximum ratio export successful');
|
||
} catch (error) {
|
||
results.errors.push(`Max ratio error: ${error.message}`);
|
||
logDebug('Maximum ratio export failed', { error: error.message });
|
||
}
|
||
|
||
} finally {
|
||
// Always close the session WITHOUT saving so the master reverts to original state
|
||
try {
|
||
const sessionCloseUrl = buildApiUrl('document.session.close', {
|
||
documentId: documentId,
|
||
editSession: editSessionId
|
||
});
|
||
|
||
logDebug('API Call: document.session.close (no save - preserving master)', { url: sessionCloseUrl.replace(encodeURIComponent(appConfig.api.authPassword), '***') });
|
||
|
||
const sessionCloseResponse = await fetch(sessionCloseUrl);
|
||
if (!sessionCloseResponse.ok) {
|
||
logDebug(`Session close API failed: ${sessionCloseResponse.status} ${sessionCloseResponse.statusText}`);
|
||
} else {
|
||
logDebug(`Document session ${editSessionId} closed successfully (no save - master preserved)`);
|
||
}
|
||
} catch (closeError) {
|
||
logDebug(`Error closing session: ${closeError.message}`);
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
async function processRatioExport(documentId, editSessionId, documentName, originalHeight, ratioKey, ratioValue, ratioType) {
|
||
// Calculate new width based on ratio
|
||
const ratioDecimal = parseInt(ratioValue) / 100;
|
||
const newWidth = Math.round(originalHeight * ratioDecimal * 100) / 100; // Round to 2 decimal places
|
||
|
||
logDebug(`Calculated dimensions for ratio ${ratioValue}:`, {
|
||
originalHeight,
|
||
ratioDecimal,
|
||
newWidth
|
||
});
|
||
|
||
// Step 1: Resize document
|
||
const resizeUrl = buildApiUrl('document.resize', {
|
||
id: documentId,
|
||
editSession: editSessionId,
|
||
width: newWidth.toString(),
|
||
height: originalHeight.toString()
|
||
});
|
||
|
||
logDebug('API Call: document.resize', {
|
||
url: resizeUrl.replace(encodeURIComponent(appConfig.api.authPassword), '***'),
|
||
width: newWidth,
|
||
height: originalHeight
|
||
});
|
||
|
||
const resizeResponse = await fetch(resizeUrl);
|
||
if (!resizeResponse.ok) {
|
||
throw new Error(`Failed to resize document: ${resizeResponse.status} ${resizeResponse.statusText}`);
|
||
}
|
||
|
||
const resizeText = await resizeResponse.text();
|
||
logDebug('Resize response received', { responseLength: resizeText.length });
|
||
|
||
// Validate resize response
|
||
if (resizeText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(resizeText, 'text/xml');
|
||
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
throw new Error(`Resize API Error: ${errorElement.textContent}`);
|
||
}
|
||
}
|
||
|
||
// Step 2: Export document
|
||
const baseName = documentName.replace('.indd', '');
|
||
const exportAssetName = `${baseName}-${ratioValue}.indd`;
|
||
|
||
const exportUrl = buildApiUrl('document.export.indd', {
|
||
editSession: editSessionId,
|
||
id: documentId,
|
||
result: 'asset',
|
||
assetProjectId: appConfig.assets.inddProjectId,
|
||
assetFolderIdentifier: appConfig.assets.ratioCheckFolderIdentifier,
|
||
assetName: encodeURIComponent(exportAssetName),
|
||
assetOverwrite: '1'
|
||
});
|
||
|
||
logDebug('API Call: document.export.indd', {
|
||
url: exportUrl.replace(encodeURIComponent(appConfig.api.authPassword), '***'),
|
||
assetName: exportAssetName
|
||
});
|
||
|
||
const exportResponse = await fetch(exportUrl);
|
||
if (!exportResponse.ok) {
|
||
throw new Error(`Failed to export document: ${exportResponse.status} ${exportResponse.statusText}`);
|
||
}
|
||
|
||
const exportText = await exportResponse.text();
|
||
logDebug('Export response received', { responseLength: exportText.length });
|
||
|
||
// Validate export response
|
||
if (exportText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(exportText, 'text/xml');
|
||
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
throw new Error(`Export API Error: ${errorElement.textContent}`);
|
||
}
|
||
|
||
const successElement = xmlDoc.querySelector('success');
|
||
if (!successElement) {
|
||
throw new Error('No success response received from export API');
|
||
}
|
||
|
||
logDebug(`Successfully exported ${exportAssetName}`);
|
||
} else {
|
||
logDebug('Non-XML export response', { response: exportText.substring(0, 200) });
|
||
}
|
||
}
|
||
|
||
// Helper function that extracts the core relink logic without the UI setup
|
||
async function processDocumentRelink(documentId) {
|
||
logDebug(`Starting relink process for document ID: ${documentId}`);
|
||
|
||
// Step 1: Open document session
|
||
const sessionOpenUrl = buildApiUrl('document.session.open', { documentId: documentId });
|
||
|
||
const sessionOpenResponse = await fetch(sessionOpenUrl);
|
||
if (!sessionOpenResponse.ok) {
|
||
throw new Error(`Failed to open document session: ${sessionOpenResponse.status} ${sessionOpenResponse.statusText}`);
|
||
}
|
||
|
||
const sessionOpenText = await sessionOpenResponse.text();
|
||
let editSessionId = '';
|
||
|
||
if (sessionOpenText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(sessionOpenText, 'text/xml');
|
||
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
throw new Error(`Session Open API Error: ${errorElement.textContent}`);
|
||
}
|
||
|
||
editSessionId = xmlDoc.querySelector('editSession')?.textContent || '';
|
||
if (!editSessionId) {
|
||
throw new Error('No editSession ID received from session open API');
|
||
}
|
||
} else {
|
||
throw new Error('Invalid response format from session open API');
|
||
}
|
||
|
||
let successCount = 0;
|
||
let errorCount = 0;
|
||
let images = [];
|
||
let missingImages = [];
|
||
|
||
try {
|
||
// Step 2: Get document image list
|
||
const imageListUrl = buildApiUrl('document.image.list', { id: documentId });
|
||
|
||
const imageListResponse = await fetch(imageListUrl);
|
||
if (!imageListResponse.ok) {
|
||
throw new Error(`Failed to fetch image list: ${imageListResponse.status} ${imageListResponse.statusText}`);
|
||
}
|
||
|
||
const imageListText = await imageListResponse.text();
|
||
|
||
if (imageListText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(imageListText, 'text/xml');
|
||
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
throw new Error(`API Error: ${errorElement.textContent}`);
|
||
}
|
||
|
||
const imageElements = xmlDoc.querySelectorAll('image');
|
||
images = Array.from(imageElements).map(img => {
|
||
const absolutePath = img.querySelector('absolutePath')?.textContent || '';
|
||
const decodedAbsolutePath = decodeURIComponent(absolutePath);
|
||
const pathParts = decodedAbsolutePath.split('/');
|
||
const actualImageName = pathParts[pathParts.length - 1];
|
||
|
||
return {
|
||
id: img.querySelector('id')?.textContent || '',
|
||
name: actualImageName,
|
||
absolutePath: absolutePath,
|
||
linkState: img.querySelector('linkState')?.textContent || ''
|
||
};
|
||
});
|
||
}
|
||
|
||
// Only process MISSING images
|
||
missingImages = images.filter(img => img.linkState === 'MISSING');
|
||
|
||
if (missingImages.length > 0) {
|
||
// Process each missing image
|
||
for (let i = 0; i < missingImages.length; i++) {
|
||
const image = missingImages[i];
|
||
|
||
try {
|
||
if (image.name === 'H&M-logo_sRGB.ai') {
|
||
const relinkSuccess = await relinkSpecificImage(documentId, image.id, appConfig.assets.hmSRGBLogoAsset, editSessionId);
|
||
if (relinkSuccess) {
|
||
successCount++;
|
||
} else {
|
||
errorCount++;
|
||
}
|
||
} else if (image.name === 'H&M-logo_CMYK_coated.eps') {
|
||
const relinkSuccess = await relinkSpecificImage(documentId, image.id, appConfig.assets.hmCMYKLogoAsset, editSessionId);
|
||
if (relinkSuccess) {
|
||
successCount++;
|
||
} else {
|
||
errorCount++;
|
||
}
|
||
} else {
|
||
const result = await relinkGenericImage(documentId, image, editSessionId);
|
||
if (result.status === 'success') {
|
||
successCount++;
|
||
} else {
|
||
errorCount++;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
errorCount++;
|
||
}
|
||
|
||
// Configurable delay between requests to avoid timeouts
|
||
if (i < missingImages.length - 1) {
|
||
await new Promise(resolve => setTimeout(resolve, appConfig.performance.relinkDelay));
|
||
}
|
||
}
|
||
}
|
||
|
||
} finally {
|
||
// Always save and close the document session
|
||
await closeDocumentSession(editSessionId, documentId);
|
||
}
|
||
|
||
return {
|
||
successCount,
|
||
errorCount,
|
||
totalImages: images.length,
|
||
missingImages: missingImages.length
|
||
};
|
||
}
|
||
|
||
// Helper function to save and close document session
|
||
async function closeDocumentSession(editSessionId, documentId) {
|
||
try {
|
||
// Save session
|
||
const sessionSaveUrl = buildApiUrl('document.session.save', {
|
||
documentId: documentId,
|
||
editSession: editSessionId
|
||
});
|
||
const sessionSaveResponse = await fetch(sessionSaveUrl);
|
||
if (!sessionSaveResponse.ok) {
|
||
throw new Error(`Failed to save document session: ${sessionSaveResponse.status} ${sessionSaveResponse.statusText}`);
|
||
}
|
||
|
||
// Close session
|
||
const sessionCloseUrl = buildApiUrl('document.session.close', {
|
||
documentId: documentId,
|
||
editSession: editSessionId
|
||
});
|
||
const sessionCloseResponse = await fetch(sessionCloseUrl);
|
||
if (!sessionCloseResponse.ok) {
|
||
throw new Error(`Failed to close document session: ${sessionCloseResponse.status} ${sessionCloseResponse.statusText}`);
|
||
}
|
||
|
||
logDebug(`Document session ${editSessionId} saved and closed successfully for document ${documentId}`);
|
||
|
||
} catch (sessionError) {
|
||
logDebug(`Session management error for document ${documentId}: ${sessionError.message}`);
|
||
throw sessionError; // Re-throw to handle at document level
|
||
}
|
||
}
|
||
|
||
async function relinkSpecificImage(documentId, frameId, assetIdentifier, editSessionId) {
|
||
const relinkUrl = buildApiUrl('document.image.relink', {
|
||
id: documentId,
|
||
frameId: frameId,
|
||
assetProjectId: appConfig.assets.inddProjectId,
|
||
assetIdentifier: assetIdentifier,
|
||
editSession: editSessionId
|
||
});
|
||
|
||
logDebug('Relink API call', {
|
||
documentId,
|
||
frameId,
|
||
assetIdentifier,
|
||
url: relinkUrl.replace(encodeURIComponent(appConfig.api.authPassword), '***')
|
||
});
|
||
|
||
const response = await fetch(relinkUrl);
|
||
if (!response.ok) {
|
||
logDebug(`Relink API HTTP error: ${response.status} ${response.statusText}`);
|
||
throw new Error(`Failed to relink image: ${response.status} ${response.statusText}`);
|
||
}
|
||
|
||
const responseText = await response.text();
|
||
logDebug('Relink API response received', {
|
||
documentId,
|
||
frameId,
|
||
assetIdentifier,
|
||
responseLength: responseText.length,
|
||
startsWithXML: responseText.trim().startsWith('<?xml'),
|
||
response: responseText.substring(0, 200) + '...'
|
||
});
|
||
|
||
if (responseText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(responseText, 'text/xml');
|
||
|
||
const errorElement = xmlDoc.querySelector('error');
|
||
if (errorElement) {
|
||
logDebug(`Relink API returned error: ${errorElement.textContent}`, {
|
||
documentId,
|
||
frameId,
|
||
assetIdentifier
|
||
});
|
||
throw new Error(`Relink API Error: ${errorElement.textContent}`);
|
||
}
|
||
|
||
const successElement = xmlDoc.querySelector('success');
|
||
if (!successElement) {
|
||
logDebug('Relink API did not return success element', {
|
||
documentId,
|
||
frameId,
|
||
assetIdentifier,
|
||
fullResponse: responseText
|
||
});
|
||
throw new Error('No success response received from relink API');
|
||
}
|
||
|
||
logDebug('Relink successful - success element found', {
|
||
documentId,
|
||
frameId,
|
||
assetIdentifier
|
||
});
|
||
return true; // Successfully relinked
|
||
}
|
||
|
||
logDebug('Invalid response format from relink API', { response: responseText.substring(0, 500) });
|
||
throw new Error('Invalid response format from relink API');
|
||
}
|
||
|
||
async function relinkGenericImage(documentId, image, editSessionId) {
|
||
try {
|
||
logDebug(`Processing generic image: ${image.name}`, {
|
||
name: image.name,
|
||
absolutePath: image.absolutePath
|
||
});
|
||
// Step 1: Get document info to find folder ID
|
||
const docInfoUrl = buildApiUrl('document.info', {
|
||
id: documentId,
|
||
include: 'folderId'
|
||
});
|
||
|
||
const docInfoResponse = await fetch(docInfoUrl);
|
||
const docInfoText = await docInfoResponse.text();
|
||
|
||
let folderId = '';
|
||
if (docInfoText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(docInfoText, 'text/xml');
|
||
folderId = xmlDoc.querySelector('folderId')?.textContent || '';
|
||
}
|
||
|
||
if (!folderId) {
|
||
return { name: image.name, status: 'error', error: 'Could not get folder ID' };
|
||
}
|
||
|
||
// Step 2: Get folder info to find folder name
|
||
const folderInfoUrl = buildApiUrl('document.folder.info', {
|
||
id: folderId,
|
||
include: 'name'
|
||
});
|
||
|
||
const folderInfoResponse = await fetch(folderInfoUrl);
|
||
const folderInfoText = await folderInfoResponse.text();
|
||
|
||
let folderName = '';
|
||
if (folderInfoText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(folderInfoText, 'text/xml');
|
||
folderName = xmlDoc.querySelector('name')?.textContent || '';
|
||
}
|
||
|
||
if (!folderName) {
|
||
return { name: image.name, status: 'error', error: 'Could not get folder name' };
|
||
}
|
||
|
||
// Step 3: Find matching campaign folder in assets
|
||
const assetListUrl = buildApiUrl('asset.list', {
|
||
projectId: appConfig.assets.inddProjectId,
|
||
include: 'name%2Cidentifier'
|
||
});
|
||
|
||
const assetListResponse = await fetch(assetListUrl);
|
||
const assetListText = await assetListResponse.text();
|
||
|
||
let campaignFolder = null;
|
||
if (assetListText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(assetListText, 'text/xml');
|
||
const assetElements = xmlDoc.querySelectorAll('asset');
|
||
|
||
for (let asset of assetElements) {
|
||
const name = asset.querySelector('name')?.textContent || '';
|
||
if (name === folderName) {
|
||
campaignFolder = {
|
||
name: name,
|
||
identifier: asset.querySelector('identifier')?.textContent || ''
|
||
};
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!campaignFolder) {
|
||
return { name: image.name, status: 'error', error: `No matching campaign folder found for: ${folderName}` };
|
||
}
|
||
|
||
// Step 4: Look for Links folder within campaign
|
||
const campaignAssetsUrl = buildApiUrl('asset.list', {
|
||
projectId: appConfig.assets.inddProjectId,
|
||
folderIdentifier: campaignFolder.identifier,
|
||
include: 'name%2Cidentifier'
|
||
});
|
||
|
||
const campaignAssetsResponse = await fetch(campaignAssetsUrl);
|
||
const campaignAssetsText = await campaignAssetsResponse.text();
|
||
|
||
let linksFolder = null;
|
||
if (campaignAssetsText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(campaignAssetsText, 'text/xml');
|
||
const assetElements = xmlDoc.querySelectorAll('asset');
|
||
|
||
for (let asset of assetElements) {
|
||
const name = asset.querySelector('name')?.textContent || '';
|
||
if (name === 'Links') {
|
||
linksFolder = {
|
||
name: name,
|
||
identifier: asset.querySelector('identifier')?.textContent || ''
|
||
};
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!linksFolder) {
|
||
return { name: image.name, status: 'error', error: 'Links folder not found in campaign' };
|
||
}
|
||
|
||
// Step 5: Get RGB/CMYK folders
|
||
const linksAssetsUrl = buildApiUrl('asset.list', {
|
||
projectId: appConfig.assets.inddProjectId,
|
||
folderIdentifier: linksFolder.identifier,
|
||
include: 'name%2Cidentifier'
|
||
});
|
||
|
||
const linksAssetsResponse = await fetch(linksAssetsUrl);
|
||
const linksAssetsText = await linksAssetsResponse.text();
|
||
|
||
let targetFolder = null;
|
||
const useCMYK = image.name.endsWith('39L_TAC330.tif');
|
||
const targetFolderName = useCMYK ? 'CMYK' : 'RGB';
|
||
|
||
if (linksAssetsText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(linksAssetsText, 'text/xml');
|
||
const assetElements = xmlDoc.querySelectorAll('asset');
|
||
|
||
for (let asset of assetElements) {
|
||
const name = asset.querySelector('name')?.textContent || '';
|
||
if (name === targetFolderName) {
|
||
targetFolder = {
|
||
name: name,
|
||
identifier: asset.querySelector('identifier')?.textContent || ''
|
||
};
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!targetFolder) {
|
||
return { name: image.name, status: 'error', error: `${targetFolderName} folder not found` };
|
||
}
|
||
|
||
// Step 6: Find matching image in target folder
|
||
const targetAssetsUrl = buildApiUrl('asset.list', {
|
||
projectId: appConfig.assets.inddProjectId,
|
||
folderIdentifier: targetFolder.identifier,
|
||
include: 'name%2Cidentifier'
|
||
});
|
||
|
||
const targetAssetsResponse = await fetch(targetAssetsUrl);
|
||
const targetAssetsText = await targetAssetsResponse.text();
|
||
|
||
let matchingAsset = null;
|
||
if (targetAssetsText.trim().startsWith('<?xml')) {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(targetAssetsText, 'text/xml');
|
||
const assetElements = xmlDoc.querySelectorAll('asset');
|
||
|
||
for (let asset of assetElements) {
|
||
const assetName = asset.querySelector('name')?.textContent || '';
|
||
|
||
// Primary matching using extracted filename from absolutePath
|
||
if (assetName === image.name ||
|
||
assetName.includes(image.name) ||
|
||
image.name.includes(assetName) ||
|
||
// Also check filename without extension
|
||
assetName.includes(image.name.split('.')[0]) ||
|
||
image.name.includes(assetName.split('.')[0])) {
|
||
matchingAsset = {
|
||
name: assetName,
|
||
identifier: asset.querySelector('identifier')?.textContent || ''
|
||
};
|
||
logDebug(`Found matching asset: ${assetName} for image: ${image.name}`);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!matchingAsset) {
|
||
return { name: image.name, status: 'error', error: `No matching asset found in ${targetFolderName} folder` };
|
||
}
|
||
|
||
// Step 7: Relink the image
|
||
const relinkSuccess = await relinkSpecificImage(documentId, image.id, matchingAsset.identifier, editSessionId);
|
||
|
||
return {
|
||
name: image.name,
|
||
status: relinkSuccess ? 'success' : 'failed',
|
||
method: `Found in ${targetFolderName} folder`,
|
||
matchedAsset: matchingAsset.name
|
||
};
|
||
|
||
} catch (error) {
|
||
return { name: image.name, status: 'error', error: error.message };
|
||
}
|
||
}
|
||
|
||
function getApiEndpoint(action) {
|
||
const endpoints = {
|
||
'relink': '/api/relink-images',
|
||
'export-indd': '/api/export-indd',
|
||
'export-pdf': '/api/export-pdf',
|
||
'ratio-check': '/api/ratio-check'
|
||
};
|
||
|
||
return endpoints[action] || '/api/generic';
|
||
}
|
||
|
||
function showResult(message, type) {
|
||
result.textContent = message;
|
||
result.className = `result ${type}`;
|
||
result.style.display = 'block';
|
||
}
|
||
|
||
function hideResult() {
|
||
result.style.display = 'none';
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |