This commit is contained in:
+72
-7
@@ -4,13 +4,12 @@ const searchForm = document.getElementById("searchForm");
|
||||
const searchQuery = document.getElementById("searchQuery");
|
||||
const searchResults = document.getElementById("searchResults");
|
||||
const searchWarning = document.getElementById("searchWarning");
|
||||
const queryVariants = document.getElementById("queryVariants");
|
||||
const dropzone = document.getElementById("dropzone");
|
||||
const fileInput = document.getElementById("fileInput");
|
||||
const uploadResult = document.getElementById("uploadResult");
|
||||
const downloadForm = document.getElementById("downloadForm");
|
||||
const downloadUrl = document.getElementById("downloadUrl");
|
||||
const startTime = document.getElementById("startTime");
|
||||
const endTime = document.getElementById("endTime");
|
||||
const downloadResult = document.getElementById("downloadResult");
|
||||
const cardTemplate = document.getElementById("searchCardTemplate");
|
||||
const previewModal = document.getElementById("previewModal");
|
||||
@@ -21,6 +20,11 @@ const previewDuration = document.getElementById("previewDuration");
|
||||
const qualitySelect = document.getElementById("qualitySelect");
|
||||
const confirmDownload = document.getElementById("confirmDownload");
|
||||
const closePreviewModal = document.getElementById("closePreviewModal");
|
||||
const startRange = document.getElementById("startRange");
|
||||
const endRange = document.getElementById("endRange");
|
||||
const rangeSummary = document.getElementById("rangeSummary");
|
||||
const setStartFromPreview = document.getElementById("setStartFromPreview");
|
||||
const setEndFromPreview = document.getElementById("setEndFromPreview");
|
||||
|
||||
let pendingDownload = null;
|
||||
|
||||
@@ -29,6 +33,44 @@ function setStatus(label, progress) {
|
||||
statusBar.style.width = `${Math.max(0, Math.min(100, progress))}%`;
|
||||
}
|
||||
|
||||
function toClock(totalSeconds) {
|
||||
const seconds = Math.max(0, Math.floor(Number(totalSeconds) || 0));
|
||||
const hours = String(Math.floor(seconds / 3600)).padStart(2, "0");
|
||||
const minutes = String(Math.floor((seconds % 3600) / 60)).padStart(2, "0");
|
||||
const secs = String(seconds % 60).padStart(2, "0");
|
||||
return `${hours}:${minutes}:${secs}`;
|
||||
}
|
||||
|
||||
function syncRanges() {
|
||||
let start = Number(startRange.value || 0);
|
||||
let end = Number(endRange.value || 0);
|
||||
if (start > end) {
|
||||
if (document.activeElement === startRange) {
|
||||
end = start;
|
||||
endRange.value = String(end);
|
||||
} else {
|
||||
start = end;
|
||||
startRange.value = String(start);
|
||||
}
|
||||
}
|
||||
rangeSummary.textContent = `${toClock(start)} - ${toClock(end)}`;
|
||||
}
|
||||
|
||||
function renderQueryVariants(queries = []) {
|
||||
queryVariants.innerHTML = "";
|
||||
if (!queries.length) {
|
||||
queryVariants.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
for (const item of queries) {
|
||||
const badge = document.createElement("span");
|
||||
badge.className = "rounded-full border border-white/10 bg-white/[0.03] px-3 py-1 text-xs uppercase tracking-[0.18em] text-zinc-300";
|
||||
badge.textContent = item;
|
||||
queryVariants.appendChild(badge);
|
||||
}
|
||||
queryVariants.classList.remove("hidden");
|
||||
}
|
||||
|
||||
function connectWS() {
|
||||
const protocol = window.location.protocol === "https:" ? "wss" : "ws";
|
||||
const socket = new WebSocket(`${protocol}://${window.location.host}/ws`);
|
||||
@@ -68,10 +110,14 @@ async function api(path, options = {}) {
|
||||
|
||||
function renderResults(results) {
|
||||
searchResults.innerHTML = "";
|
||||
if (!results.length) {
|
||||
searchResults.innerHTML = `<div class="rounded-3xl border border-white/10 bg-black/30 p-5 text-sm text-zinc-400">No results matched the current search sources.</div>`;
|
||||
return;
|
||||
}
|
||||
for (const item of results) {
|
||||
const node = cardTemplate.content.firstElementChild.cloneNode(true);
|
||||
node.href = item.link;
|
||||
node.querySelector("img").src = item.thumbnailUrl;
|
||||
node.querySelector("img").src = item.thumbnailUrl || "https://placehold.co/1280x720/0a0a0a/ffffff?text=No+Preview";
|
||||
node.querySelector("img").alt = item.title;
|
||||
node.querySelector("h3").textContent = item.title;
|
||||
node.querySelector("p").textContent = item.reason;
|
||||
@@ -91,6 +137,7 @@ searchForm.addEventListener("submit", async (event) => {
|
||||
body: JSON.stringify({ query: searchQuery.value }),
|
||||
});
|
||||
renderResults(data.results || []);
|
||||
renderQueryVariants(data.queries || []);
|
||||
if (data.warning) {
|
||||
searchWarning.textContent = data.warning;
|
||||
searchWarning.classList.remove("hidden");
|
||||
@@ -99,6 +146,7 @@ searchForm.addEventListener("submit", async (event) => {
|
||||
} catch (error) {
|
||||
searchWarning.textContent = error.message;
|
||||
searchWarning.classList.remove("hidden");
|
||||
renderQueryVariants([]);
|
||||
setStatus("search failed", 100);
|
||||
}
|
||||
});
|
||||
@@ -137,8 +185,12 @@ function openPreviewModal(preview) {
|
||||
option.textContent = item.label;
|
||||
qualitySelect.appendChild(option);
|
||||
}
|
||||
startTime.value = preview.startDefault || "00:00:00";
|
||||
endTime.value = preview.endDefault || "00:00:00";
|
||||
const maxDuration = Number(preview.durationSeconds || 0);
|
||||
startRange.max = String(maxDuration);
|
||||
endRange.max = String(maxDuration);
|
||||
startRange.value = "0";
|
||||
endRange.value = String(maxDuration);
|
||||
syncRanges();
|
||||
previewModal.classList.remove("hidden");
|
||||
previewModal.classList.add("flex");
|
||||
}
|
||||
@@ -149,6 +201,9 @@ function closeModal() {
|
||||
previewVideo.load();
|
||||
previewModal.classList.add("hidden");
|
||||
previewModal.classList.remove("flex");
|
||||
startRange.value = "0";
|
||||
endRange.value = "0";
|
||||
syncRanges();
|
||||
pendingDownload = null;
|
||||
}
|
||||
|
||||
@@ -214,8 +269,8 @@ confirmDownload.addEventListener("click", async () => {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
url: pendingDownload.url,
|
||||
start: startTime.value,
|
||||
end: endTime.value,
|
||||
start: toClock(startRange.value),
|
||||
end: toClock(endRange.value),
|
||||
quality: qualitySelect.value,
|
||||
force: pendingDownload.force,
|
||||
}),
|
||||
@@ -233,6 +288,16 @@ previewModal.addEventListener("click", (event) => {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
startRange.addEventListener("input", syncRanges);
|
||||
endRange.addEventListener("input", syncRanges);
|
||||
setStartFromPreview.addEventListener("click", () => {
|
||||
startRange.value = String(Math.floor(previewVideo.currentTime || 0));
|
||||
syncRanges();
|
||||
});
|
||||
setEndFromPreview.addEventListener("click", () => {
|
||||
endRange.value = String(Math.floor(previewVideo.currentTime || 0));
|
||||
syncRanges();
|
||||
});
|
||||
|
||||
connectWS();
|
||||
setStatus("idle", 0);
|
||||
|
||||
+16
-11
@@ -40,6 +40,7 @@
|
||||
<button class="rounded-2xl border border-white bg-white px-5 py-3 text-sm font-medium text-black transition hover:bg-zinc-200">AI Search</button>
|
||||
</form>
|
||||
<div id="searchWarning" class="mt-3 hidden rounded-2xl border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-200"></div>
|
||||
<div id="queryVariants" class="mt-3 hidden flex-wrap gap-2"></div>
|
||||
<div id="searchResults" class="mt-5 grid gap-4 sm:grid-cols-2 xl:grid-cols-3"></div>
|
||||
</article>
|
||||
|
||||
@@ -69,7 +70,7 @@
|
||||
</main>
|
||||
|
||||
<div id="previewModal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/80 px-4">
|
||||
<div class="w-full max-w-2xl rounded-3xl border border-white/10 bg-zinc-950 p-5 shadow-2xl">
|
||||
<div class="w-full max-w-3xl rounded-3xl border border-white/10 bg-zinc-950 p-5 shadow-2xl">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-[0.3em] text-zinc-500">Download Preview</p>
|
||||
@@ -77,7 +78,7 @@
|
||||
</div>
|
||||
<button id="closePreviewModal" class="rounded-full border border-white/10 px-3 py-2 text-xs uppercase tracking-[0.2em] text-zinc-300">Close</button>
|
||||
</div>
|
||||
<div class="mt-5 grid gap-5 md:grid-cols-[1.1fr_0.9fr]">
|
||||
<div class="mt-5 grid gap-5 md:grid-cols-[1.2fr_0.8fr]">
|
||||
<div class="overflow-hidden rounded-3xl border border-white/10 bg-black/30">
|
||||
<video id="previewVideo" class="hidden aspect-video h-full w-full bg-black object-cover" controls playsinline></video>
|
||||
<img id="previewThumbnail" class="aspect-video h-full w-full object-cover" alt="" />
|
||||
@@ -89,15 +90,19 @@
|
||||
<span id="previewDuration"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<label class="block space-y-2">
|
||||
<span class="text-sm text-zinc-400">Start</span>
|
||||
<input id="startTime" type="text" value="00:00:00" class="w-full rounded-2xl border border-white/10 bg-black/40 px-4 py-3 text-sm text-white" />
|
||||
</label>
|
||||
<label class="block space-y-2">
|
||||
<span class="text-sm text-zinc-400">End</span>
|
||||
<input id="endTime" type="text" value="00:00:00" class="w-full rounded-2xl border border-white/10 bg-black/40 px-4 py-3 text-sm text-white" />
|
||||
</label>
|
||||
<div class="rounded-2xl border border-white/10 bg-white/[0.03] p-4">
|
||||
<div class="mb-3 flex items-center justify-between text-sm text-zinc-400">
|
||||
<span>Crop Range</span>
|
||||
<span id="rangeSummary">00:00:00 - 00:00:00</span>
|
||||
</div>
|
||||
<div class="relative h-8">
|
||||
<input id="startRange" type="range" min="0" max="0" value="0" step="1" class="slider-thumb absolute inset-0 w-full appearance-none bg-transparent" />
|
||||
<input id="endRange" type="range" min="0" max="0" value="0" step="1" class="slider-thumb absolute inset-0 w-full appearance-none bg-transparent" />
|
||||
</div>
|
||||
<div class="mt-3 flex gap-3">
|
||||
<button id="setStartFromPreview" type="button" class="flex-1 rounded-2xl border border-white/10 px-4 py-3 text-sm text-zinc-200 transition hover:border-white/30">Set Start</button>
|
||||
<button id="setEndFromPreview" type="button" class="flex-1 rounded-2xl border border-white/10 px-4 py-3 text-sm text-zinc-200 transition hover:border-white/30">Set End</button>
|
||||
</div>
|
||||
</div>
|
||||
<label class="block space-y-2">
|
||||
<span class="text-sm text-zinc-400">Quality</span>
|
||||
|
||||
@@ -25,3 +25,35 @@ body {
|
||||
.line-clamp-3 {
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
|
||||
.slider-thumb::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-radius: 9999px;
|
||||
border: 2px solid #09090b;
|
||||
background: #fafafa;
|
||||
cursor: pointer;
|
||||
margin-top: -7px;
|
||||
}
|
||||
|
||||
.slider-thumb::-moz-range-thumb {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-radius: 9999px;
|
||||
border: 2px solid #09090b;
|
||||
background: #fafafa;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slider-thumb::-webkit-slider-runnable-track {
|
||||
height: 4px;
|
||||
border-radius: 9999px;
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
.slider-thumb::-moz-range-track {
|
||||
height: 4px;
|
||||
border-radius: 9999px;
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user