Files
dupe/client/src/components/MatchModal.svelte
wisecolt 52bd325dc6 feat(ui): mail.ru linkleri için eşleştirme ve isim düzenlemesi eklendi
- Dosya eşleştirme arayüzü bağımsız `MatchModal` bileşenine taşındı
- `Files.svelte` ve `Transfers.svelte` yeni bileşen kullanılarak güncellendi
- Mail.ru indirmeleri için dizi adı, sezon ve bölüm eşleştirme özelliği eklendi
- `POST /api/mailru/match` endpointi ile metadata eşleştirme backend desteği sağlandı
- Dosya isimleri "DiziAdi.S01E01.mp4" formatında kaydedilmeye başlandı
2026-01-26 21:22:15 +03:00

612 lines
13 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script>
export let show = false;
export let headerTitle = "Eşlemeyi Düzelt";
export let fileLabel = "Dosya";
export let fileName = "";
export let sizeText = "";
export let titleLabel = "Film Adı";
export let yearLabel = "Yıl";
export let titlePlaceholder = "";
export let yearPlaceholder = "YYYY";
export let showYearInput = true;
export let titleValue = "";
export let yearValue = "";
export let searching = false;
export let results = [];
export let showEmpty = false;
export let emptyText = "Aramanla eşleşen öğe bulunamadı";
export let applyingId = null;
export let onClose = () => {};
export let onTitleInput = () => {};
export let onYearInput = () => {};
export let onSelect = () => {};
</script>
{#if show}
<div class="match-overlay" on:click={onClose}>
<div class="match-overlay-content" on:click|stopPropagation>
<button class="match-close" on:click={onClose} aria-label="Kapat">
<i class="fa-solid fa-xmark"></i>
</button>
<div class="match-header">
<h3 class="match-title">
<i class="fa-solid fa-wand-magic-sparkles"></i>
{headerTitle}
</h3>
<div class="match-subtitle">
{#if fileName}
<span class="match-location" title={fileName}>
<i class="fa-solid fa-file"></i>
<span class="location-text">{fileLabel}: {fileName}</span>
</span>
{/if}
{#if fileName && sizeText}
<span class="match-separator">|</span>
{/if}
{#if sizeText}
<span class="match-size">
<i class="fa-solid fa-database"></i>
{sizeText}
</span>
{/if}
</div>
</div>
<div class="match-body">
<div class="match-inputs">
<div class="input-group title-input">
<label for="match-title">{titleLabel}</label>
<input
id="match-title"
type="text"
bind:value={titleValue}
on:input={onTitleInput}
placeholder={titlePlaceholder}
/>
</div>
{#if showYearInput}
<div class="input-group year-input">
<label for="match-year">{yearLabel}</label>
<input
id="match-year"
type="text"
bind:value={yearValue}
on:input={onYearInput}
placeholder={yearPlaceholder}
maxlength="4"
/>
</div>
{/if}
</div>
<div class="match-divider"></div>
{#if searching}
<div class="search-loading">
<i class="fa-solid fa-spinner fa-spin"></i>
Aranıyor...
</div>
{:else if results.length > 0}
<div class="search-results">
{#each results as result}
<div
class="result-item"
class:applying={applyingId === result.id}
on:click={() => onSelect(result)}
>
<div class="result-poster">
{#if result.poster}
<img src={result.poster} alt={result.title} loading="lazy" />
{:else}
<div class="result-poster-placeholder">
<i class="fa-regular fa-image"></i>
</div>
{/if}
</div>
<div class="result-info">
<div class="result-title">{result.title}</div>
<div class="result-meta">
{#if result.year}
<span class="result-year">
<i class="fa-solid fa-calendar-days"></i>
{result.year}
</span>
{/if}
{#if result.runtime}
<span class="result-separator"></span>
<span class="result-runtime">
<i class="fa-solid fa-clock"></i>
{result.runtime} dk
</span>
{/if}
{#if result.status}
<span class="result-separator"></span>
<span class="result-status">
<i class="fa-solid fa-signal"></i>
{result.status}
</span>
{/if}
</div>
{#if result.genres && result.genres.length > 0}
<div class="result-genres">
{result.genres.slice(0, 3).join(", ")}
</div>
{/if}
{#if result.cast && result.cast.length > 0}
<div class="result-cast">
<i class="fa-solid fa-user"></i>
{result.cast.join(", ")}
</div>
{/if}
{#if result.overview}
<div class="result-overview">{result.overview}</div>
{/if}
</div>
{#if $$slots.resultActions}
<div class="result-actions" on:click|stopPropagation>
<slot name="resultActions" {result} />
</div>
{/if}
</div>
{/each}
</div>
{:else if showEmpty}
<div class="search-empty">{emptyText}</div>
{/if}
</div>
</div>
</div>
{/if}
<style>
.match-overlay {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 5000;
background: rgba(10, 10, 10, 0.28);
backdrop-filter: blur(4px);
padding: 56px 32px;
}
.match-overlay-content {
position: relative;
width: min(60vw, 1100px);
max-height: 60vh;
border-radius: 18px;
background: rgba(12, 12, 12, 0.92);
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.45);
display: flex;
flex-direction: column;
}
.match-close {
position: absolute;
top: 16px;
right: 20px;
background: rgba(0, 0, 0, 0.55);
color: #fafafa;
border: none;
width: 42px;
height: 42px;
border-radius: 50%;
cursor: pointer;
font-size: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
transition: background 0.2s ease;
z-index: 1;
}
.match-close:hover {
background: rgba(0, 0, 0, 0.85);
}
.match-header {
padding: 32px 48px 24px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.match-title {
margin: 0 0 12px 0;
font-size: 26px;
font-weight: 600;
color: #fafafa;
display: flex;
align-items: center;
gap: 12px;
}
.match-title i {
color: #f5b333;
font-size: 24px;
}
.match-subtitle {
display: flex;
align-items: center;
gap: 12px;
font-size: 14px;
color: #c7c7c7;
flex-wrap: wrap;
}
.match-location {
display: inline-flex;
align-items: center;
gap: 6px;
max-width: 60%;
overflow: hidden;
}
.match-location .location-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.match-size {
display: inline-flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
}
.match-location i,
.match-size i {
color: #8d8d8d;
font-size: 13px;
}
.match-separator {
color: #7a7a7a;
}
.match-body {
padding: 28px 48px 36px;
color: #f5f5f5;
overflow-y: auto;
max-height: calc(60vh - 120px);
}
.match-body::-webkit-scrollbar {
width: 8px;
}
.match-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 4px;
}
.match-body::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.5);
border-radius: 4px;
backdrop-filter: blur(4px);
}
.match-body::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.7);
}
.match-inputs {
display: flex;
gap: 16px;
margin-bottom: 24px;
}
.input-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.input-group.title-input {
flex: 3;
}
.input-group.year-input {
flex: 1;
}
.input-group label {
font-size: 13px;
font-weight: 500;
color: #c7c7c7;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.input-group input {
padding: 12px 16px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(255, 255, 255, 0.08);
color: #f5f5f5;
font-size: 15px;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease;
}
.input-group input:focus {
border-color: rgba(245, 179, 51, 0.5);
background: rgba(255, 255, 255, 0.12);
}
.input-group input::placeholder {
color: #7a7a7a;
}
.match-divider {
height: 1px;
background: rgba(255, 255, 255, 0.1);
margin: 0 0 24px 0;
}
.search-loading {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 40px 20px;
color: #c7c7c7;
font-size: 15px;
}
.search-loading i {
font-size: 20px;
color: #f5b333;
}
.search-results {
display: flex;
flex-direction: column;
gap: 12px;
}
.result-item {
display: flex;
gap: 16px;
padding: 12px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.05);
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid transparent;
}
.result-item:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(245, 179, 51, 0.3);
transform: translateY(-2px);
}
.result-item.applying {
opacity: 0.6;
pointer-events: none;
position: relative;
}
.result-item.applying::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
border: 2px solid rgba(245, 179, 51, 0.3);
border-top: 2px solid #f5b333;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
}
.result-poster {
width: 80px;
height: 120px;
flex-shrink: 0;
border-radius: 8px;
overflow: hidden;
background: rgba(255, 255, 255, 0.08);
display: flex;
align-items: center;
justify-content: center;
}
.result-poster img {
width: 100%;
height: 100%;
object-fit: cover;
}
.result-poster-placeholder {
color: #7a7a7a;
font-size: 24px;
}
.result-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 6px;
min-width: 0;
}
.result-title {
font-size: 16px;
font-weight: 600;
color: #fafafa;
}
.result-meta {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: #c7c7c7;
flex-wrap: wrap;
margin-bottom: 4px;
}
.result-separator {
color: #7a7a7a;
}
.result-year,
.result-runtime,
.result-status {
color: #c7c7c7;
display: inline-flex;
align-items: center;
gap: 4px;
}
.result-year i,
.result-runtime i,
.result-status i {
font-size: 13px;
color: #8d8d8d;
}
.result-genres {
font-size: 12px;
color: #8d8d8d;
text-transform: uppercase;
letter-spacing: 0.3px;
margin-bottom: 4px;
}
.result-cast {
font-size: 12px;
color: #9f9f9f;
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
}
.result-cast i {
color: #ffc107;
font-size: 11px;
}
.result-overview {
font-size: 13px;
color: #9f9f9f;
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 4px;
}
.result-actions {
display: flex;
flex-direction: column;
gap: 8px;
margin-left: auto;
width: 160px;
align-items: flex-start;
}
.result-actions label {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto auto;
row-gap: 6px;
font-size: 12px;
color: #c7c7c7;
text-transform: uppercase;
letter-spacing: 0.4px;
width: 100%;
align-items: flex-start;
}
.result-actions select {
display: block;
margin: 0;
align-self: flex-start;
padding: 8px 10px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(255, 255, 255, 0.08);
color: #f5f5f5;
font-size: 13px;
outline: none;
width: 44.5px;
min-width: 44.5px;
max-width: 44.5px;
box-sizing: border-box;
}
:global(.match-modal-select) {
width: 44.5px;
min-width: 44.5px;
max-width: 44.5px;
box-sizing: border-box;
}
@media (max-width: 768px) {
.match-overlay {
padding: 32px 16px;
}
.match-overlay-content {
width: 96vw;
}
.match-header {
padding: 28px 32px 20px;
}
.match-title {
font-size: 22px;
}
.match-body {
padding: 24px 32px 32px;
}
}
@media (max-width: 560px) {
.result-item {
flex-direction: column;
}
.result-actions {
flex-direction: row;
justify-content: flex-start;
}
}
@media (max-width: 480px) {
.match-header {
padding: 24px 24px 18px;
}
.match-body {
padding: 20px 24px 28px;
}
.match-inputs {
flex-direction: column;
gap: 16px;
}
.input-group.title-input,
.input-group.year-input {
flex: 1;
}
}
</style>