initial commit
This commit is contained in:
commit
c92383721d
16 changed files with 786 additions and 0 deletions
214
src/Oh.My.Stitcher/wwwroot/index.html
Normal file
214
src/Oh.My.Stitcher/wwwroot/index.html
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Oh My Stitcher!</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
background-color: #f4f7f9;
|
||||
color: #333;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 2rem;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
}
|
||||
form {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem 1.5rem;
|
||||
}
|
||||
.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
label {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
display: block;
|
||||
}
|
||||
input[type="text"], input[type="number"] {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #dcdcdc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
font-size: 1rem;
|
||||
}
|
||||
button {
|
||||
grid-column: 1 / -1;
|
||||
padding: 1rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background-color: #3498db;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
button:disabled {
|
||||
background-color: #95a5a6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
#output-container {
|
||||
margin-top: 1.5rem;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
#output-container img {
|
||||
max-width: 100%;
|
||||
max-height: 80vh;
|
||||
height: auto;
|
||||
width: auto;
|
||||
border: 1px solid #dcdcdc;
|
||||
border-radius: 4px;
|
||||
background-color: #f8f9fa;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
|
||||
}
|
||||
#status {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
.status-error {
|
||||
color: #c0392b;
|
||||
background-color: #f9e2e2;
|
||||
}
|
||||
.status-info {
|
||||
color: #2980b9;
|
||||
}
|
||||
.status-success {
|
||||
color: #27ae60;
|
||||
background-color: #eaf7f0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Oh My Stitcher!</h1>
|
||||
<form id="generator-form">
|
||||
<div class="full-width">
|
||||
<label for="canvasRect">Canvas Rect (e.g., A1:H12 or B5)</label>
|
||||
<input type="text" id="canvasRect" value="A1:H12" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="cropOffsetX">Crop Offset X</label>
|
||||
<input type="number" id="cropOffsetX" value="0.25" step="0.01" min="0" max="1">
|
||||
</div>
|
||||
<div>
|
||||
<label for="cropOffsetY">Crop Offset Y</label>
|
||||
<input type="number" id="cropOffsetY" value="0.25" step="0.01" min="0" max="1">
|
||||
</div>
|
||||
<div>
|
||||
<label for="cropSizeWidth">Crop Size Width</label>
|
||||
<input type="number" id="cropSizeWidth" value="0.5" step="0.01" min="0" max="1">
|
||||
</div>
|
||||
<div>
|
||||
<label for="cropSizeHeight">Crop Size Height</label>
|
||||
<input type="number" id="cropSizeHeight" value="0.5" step="0.01" min="0" max="1">
|
||||
</div>
|
||||
<div class="full-width">
|
||||
<label for="outputScale">Output Scale</label>
|
||||
<input type="number" id="outputScale" value="0.05" step="0.01" min="0.01" max="0.05">
|
||||
</div>
|
||||
<button type="submit" id="generate-btn">Generate Image</button>
|
||||
</form>
|
||||
</div>
|
||||
<div id="status"></div>
|
||||
<div id="output-container"></div>
|
||||
<script>
|
||||
const form = document.getElementById('generator-form');
|
||||
const generateBtn = document.getElementById('generate-btn');
|
||||
const statusDiv = document.getElementById('status');
|
||||
const outputContainer = document.getElementById('output-container');
|
||||
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
generateBtn.disabled = true;
|
||||
generateBtn.textContent = 'Generating...';
|
||||
statusDiv.textContent = 'Sending request...';
|
||||
statusDiv.className = 'status-info';
|
||||
const existingImg = outputContainer.querySelector('img');
|
||||
if (existingImg) existingImg.remove();
|
||||
|
||||
const payload = {
|
||||
canvas_rect: document.getElementById('canvasRect').value,
|
||||
crop_offset: [
|
||||
parseFloat(document.getElementById('cropOffsetX').value),
|
||||
parseFloat(document.getElementById('cropOffsetY').value)
|
||||
],
|
||||
crop_size: [
|
||||
parseFloat(document.getElementById('cropSizeWidth').value),
|
||||
parseFloat(document.getElementById('cropSizeHeight').value)
|
||||
],
|
||||
output_scale: parseFloat(document.getElementById('outputScale').value)
|
||||
};
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/image/generate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const headersReceivedTime = performance.now();
|
||||
const timeToFirstByte = (headersReceivedTime - startTime).toFixed(1);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(`Server error (${response.status}): ${errorData.title || 'Unknown Error'}`);
|
||||
}
|
||||
|
||||
statusDiv.textContent = `Headers received in ${timeToFirstByte} ms. Downloading image...`;
|
||||
|
||||
const imageBlob = await response.blob();
|
||||
|
||||
const endTime = performance.now();
|
||||
const totalDuration = (endTime - startTime).toFixed(1);
|
||||
|
||||
const imageUrl = URL.createObjectURL(imageBlob);
|
||||
const img = document.createElement('img');
|
||||
img.src = imageUrl;
|
||||
img.onload = () => URL.revokeObjectURL(img.src);
|
||||
|
||||
outputContainer.appendChild(img);
|
||||
|
||||
const sizeInMB = (imageBlob.size / 1024 / 1024).toFixed(2);
|
||||
statusDiv.textContent = `Success! Image (${sizeInMB} MB) received in ${totalDuration} ms.`;
|
||||
statusDiv.className = 'status-success';
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fetch error:', error);
|
||||
statusDiv.textContent = error.message;
|
||||
statusDiv.className = 'status-error';
|
||||
} finally {
|
||||
generateBtn.disabled = false;
|
||||
generateBtn.textContent = 'Generate Image';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue