Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion css/limeread.css

Large diffs are not rendered by default.

59 changes: 53 additions & 6 deletions src/flimix/library/movies/create.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,63 @@
{% block main_class %}bg-stone-50{% endblock %}

{% block main_content %}
<div class="w-full max-w-3xl mx-auto">
<!-- Sticky Header -->
<div class="sticky top-16 z-40 shadow-sm bg-white dark:bg-neutral-800 border-b border-gray-200 dark:border-neutral-700 py-4 mb-5 -mt-10 -mx-4 sm:-mx-6 lg:-mx-8">
{% include "./includes/create_header.html" %}
</div>

<!-- Main Page Container -->
<div class="w-full max-w-3xl mx-auto pb-10" x-data="{
status: 'idle',
fileName: '',
fileSize: '',
progress: 0,
movieTitle: '',
movieDescription: '',
movieReleaseYear: '',
coverImage: '',
handleFileSelect(e) {
const file = e.target.files[0];
if (!file) return;
this.fileName = file.name;
this.fileSize = (file.size / (1024 * 1024)).toFixed(1) + ' MB';
this.status = 'uploading';
this.progress = 0;

// Auto extract movie title from file name (without extension)
const nameWithoutExt = file.name.substring(0, file.name.lastIndexOf('.')) || file.name;
// Clean it up (replace dashes/underscores with spaces, capitalize)
const cleanedName = nameWithoutExt.replace(/[_\-\.]+/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
if (!this.movieTitle) {
this.movieTitle = cleanedName;
}

let interval = setInterval(() => {
this.progress += 10;
if (this.progress >= 100) {
clearInterval(interval);
this.status = 'ready';
}
}, 150);
},
reset() {
this.status = 'idle';
this.fileName = '';
this.fileSize = '';
this.progress = 0;
}
}">
Comment on lines +11 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The setInterval used to simulate the upload progress is stored in a local variable and is never cleared if the user cancels the upload (by calling reset()) or selects a new file. This causes a memory leak and a state race condition: even after clicking cancel, the interval will continue running in the background and eventually set this.status = 'ready' and this.progress = 100, messing up the UI state.

To fix this, store the interval ID in the Alpine component's state (e.g., uploadInterval) and clear it both in reset() and before starting a new upload in handleFileSelect().

<div class="w-full max-w-3xl mx-auto pb-10" x-data="{
  status: 'idle',
  fileName: '',
  fileSize: '',
  progress: 0,
  movieTitle: '',
  movieDescription: '',
  movieReleaseYear: '',
  coverImage: '',
  uploadInterval: null,
  handleFileSelect(e) {
    const file = e.target.files[0];
    if (!file) return;
    this.fileName = file.name;
    this.fileSize = (file.size / (1024 * 1024)).toFixed(1) + ' MB';
    this.status = 'uploading';
    this.progress = 0;
    
    // Auto extract movie title from file name (without extension)
    const nameWithoutExt = file.name.substring(0, file.name.lastIndexOf('.')) || file.name;
    // Clean it up (replace dashes/underscores with spaces, capitalize)
    const cleanedName = nameWithoutExt.replace(/[_-\.]+/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
    if (!this.movieTitle) {
      this.movieTitle = cleanedName;
    }

    if (this.uploadInterval) {
      clearInterval(this.uploadInterval);
    }

    this.uploadInterval = setInterval(() => {
      this.progress += 10;
      if (this.progress >= 100) {
        clearInterval(this.uploadInterval);
        this.uploadInterval = null;
        this.status = 'ready';
      }
    }, 150);
  },
  reset() {
    if (this.uploadInterval) {
      clearInterval(this.uploadInterval);
      this.uploadInterval = null;
    }
    this.status = 'idle';
    this.fileName = '';
    this.fileSize = '';
    this.progress = 0;
  }
}">

<div class="space-y-5">
<!-- Upload Movie Section -->
<div>
{% include "./includes/create_breadcrumbs.html" %}
{% include "./includes/create_header.html" %}
{% include "./includes/upload_movie.html" %}
</div>

<div>
<div
class="pb-12 mb-12 border-b border-gray-200 md:last:pb-0 md:last:mb-0 md:last:border-b-0 dark:border-neutral-700">
{% include "./includes/create_form.html" %}
<div class="space-y-5 pb-12 mb-12 border-b border-gray-200 md:last:pb-0 md:last:mb-0 md:last:border-b-0 dark:border-neutral-700">
{% include "./includes/movie_info.html" %}
{% include "./includes/movie_media.html" %}
{% include "./includes/movie_access.html" %}
{% include "./includes/create_genre_modal.html" %}
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/flimix/library/movies/includes/create_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -274,13 +274,13 @@
<button type="button"
class="py-2 px-3 text-nowrap inline-flex justify-center items-center text-start bg-white border border-gray-200 text-gray-800 text-sm font-medium rounded-lg shadow-sm align-middle hover:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-700 dark:text-neutral-300 dark:hover:bg-neutral-700 dark:focus:bg-neutral-700"
data-hs-overlay="#hs-pro-daem">
Save as draft
Cancel
</button>

<button type="button"
class="py-2 px-3 text-nowrap inline-flex justify-center items-center gap-x-2 text-start bg-blue-600 border border-blue-600 text-white text-sm font-medium rounded-lg shadow-sm align-middle hover:bg-blue-700 disabled:opacity-50 disabled:pointer-events-none focus:outline-none focus:ring-1 focus:ring-blue-300 dark:focus:ring-blue-500"
data-hs-overlay="#hs-pro-daem">
Publish
Save as draft
</button>
</div>
</div>
Expand Down
34 changes: 31 additions & 3 deletions src/flimix/library/movies/includes/create_header.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
<div class="flex flex-wrap justify-between items-center gap-2 mt-2">
<div>
<h1 class="text-lg md:text-xl font-semibold text-stone-800 dark:text-neutral-200">Create Movie</h1>
<div class="max-w-3xl mx-auto flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-5">
<!-- Header Title -->
<div class="flex flex-wrap justify-between items-center gap-4">
<a :href="previewPath + '/flimix/library/movies/'"
class="size-[2.375rem] inline-flex justify-center items-center rounded-xl bg-white border border-gray-200 text-gray-800 shadow-sm hover:bg-gray-50 focus:outline-none dark:bg-neutral-800 dark:border-neutral-700 dark:text-neutral-200 dark:hover:bg-neutral-700">
<!-- Back Arrow SVG -->
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m12 19-7-7 7-7"/>
<path d="M19 12H5"/>
</svg>
</a>
<div>
<h1 class="text-lg md:text-xl font-semibold text-gray-800 dark:text-neutral-200">
Add Movie
</h1>
<p class="text-sm text-gray-500 dark:text-neutral-500">
Add a movie and prepare it for streaming.
</p>
</div>
</div>
<!-- End Header Title -->

<!-- Actions -->
<div class="inline-flex sm:justify-end items-center gap-x-3">
<div class="flex justify-end items-center gap-x-2">
<!-- Save Button -->
<button type="button"
class="py-2 px-3 text-nowrap inline-flex justify-center items-center gap-x-2 bg-blue-600 border border-blue-600 text-white text-sm font-medium rounded-lg shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-blue-500 dark:border-blue-500 dark:hover:bg-blue-600">
Save
</button>
</div>
</div>
</div>
Loading
Loading