Packages
phoenix_kit
1.7.131
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/modules/storage/web/settings.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title="Media Settings"
current_path={@current_path}
current_locale={@current_locale}
project_title={@project_title}
>
<div class="container mx-auto px-4 py-8">
<.admin_page_header
back={PhoenixKit.Utils.Routes.path("/admin/modules")}
title={gettext("Media Settings")}
subtitle={
gettext(
"Configure media storage buckets and file processing settings for your application"
)
}
/>
<%!-- Module Status Alert --%>
<div class="alert alert-info mb-6">
<.icon name="hero-information-circle" class="w-5 h-5" />
<div>
<h3 class="font-bold">{gettext("Core System Module")}</h3>
<p class="text-sm">
{gettext("Media is a core system module that is always enabled and cannot be disabled.")}
{gettext("It provides essential file management functionality across PhoenixKit.")}
</p>
</div>
</div>
<%!-- Repair Media Module Section --%>
<div class="alert alert-warning mb-6">
<.icon name="hero-wrench-screwdriver" class="w-5 h-5" />
<div class="flex-1">
<h3 class="font-bold">{gettext("Media Module Repair")}</h3>
<p class="text-sm">
{gettext(
"If your media module isn't working correctly, use this to reset configuration to defaults."
)}
{gettext("This will NOT delete any uploaded files.")}
</p>
</div>
<button
phx-click="repair_storage_module"
data-confirm={
gettext(
"This will reset dimensions and settings to defaults, and create a local bucket if none exist. Your uploaded files will NOT be deleted. Continue?"
)
}
class="btn btn-warning btn-sm"
>
<.icon name="hero-wrench-screwdriver" class="w-4 h-4 mr-1" /> {gettext(
"Repair Media Module"
)}
</button>
</div>
<%!-- System Dependencies Check --%>
<%= if match?({:error, :not_installed}, @imagemagick_status) do %>
<div class="alert alert-error mb-6">
<.icon name="hero-exclamation-circle" class="w-5 h-5" />
<div>
<h3 class="font-bold">{gettext("ImageMagick Not Installed")}</h3>
<p class="text-sm">
{gettext("ImageMagick is required for image processing and variant generation.")}
{gettext("Without it, image resizing, cropping, and format conversion will not work.")}
</p>
<p class="text-sm mt-2">
<strong>{gettext("Installation:")}</strong>
<br />macOS:
<code class="bg-base-200 px-2 py-1 rounded text-xs">brew install imagemagick</code>
<br />Ubuntu/Debian:
<code class="bg-base-200 px-2 py-1 rounded text-xs">
sudo apt-get install imagemagick
</code>
<br />CentOS/RHEL:
<code class="bg-base-200 px-2 py-1 rounded text-xs">
sudo yum install ImageMagick
</code>
</p>
</div>
</div>
<% end %>
<%= if match?({:error, :not_installed}, @ffmpeg_status) do %>
<div class="alert alert-warning mb-6">
<.icon name="hero-exclamation-triangle" class="w-5 h-5" />
<div>
<h3 class="font-bold">{gettext("FFmpeg Not Installed")}</h3>
<p class="text-sm">
{gettext(
"FFmpeg is required for video processing, transcoding, and thumbnail generation."
)}
{gettext("Without it, video file processing will not work.")}
</p>
<p class="text-sm mt-2">
<strong>{gettext("Installation:")}</strong>
<br />macOS:
<code class="bg-base-200 px-2 py-1 rounded text-xs">brew install ffmpeg</code>
<br />Ubuntu/Debian:
<code class="bg-base-200 px-2 py-1 rounded text-xs">sudo apt-get install ffmpeg</code>
<br />CentOS/RHEL:
<code class="bg-base-200 px-2 py-1 rounded text-xs">sudo yum install ffmpeg</code>
</p>
</div>
</div>
<% end %>
<%!-- Media Buckets --%>
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card-body">
<div class="flex justify-between items-center mb-4">
<h2 class="card-title text-2xl">
<.icon name="hero-inbox-stack" class="w-6 h-6 mr-2" /> {gettext("Media Buckets")}
</h2>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/settings/media/buckets/new")}
class="btn btn-primary"
>
<.icon name="hero-plus" class="w-4 h-4 mr-1" /> {gettext("Add Bucket")}
</.link>
</div>
<%= if Enum.empty?(@buckets) do %>
<div class="alert alert-warning">
<.icon name="hero-exclamation-triangle" class="w-5 h-5" />
<div>
<h3 class="font-bold">{gettext("No Media Buckets Configured")}</h3>
<p class="text-sm">
{gettext(
"You need to create at least one media bucket before files can be uploaded."
)}
{gettext(
"Buckets can be local directories or cloud storage providers (AWS S3, Backblaze B2, Cloudflare R2)."
)}
</p>
<div class="mt-3">
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/settings/media/buckets/new")}
class="btn btn-warning btn-sm"
>
<.icon name="hero-plus" class="w-4 h-4 mr-1" /> {gettext(
"Create Your First Bucket"
)}
</.link>
</div>
</div>
</div>
<% else %>
<.table_default
id="buckets-table"
variant="zebra"
toggleable={true}
items={@buckets}
card_title={fn bucket -> bucket.name end}
card_fields={
fn bucket ->
[
%{
label: gettext("Provider"),
value: String.upcase(bucket.provider)
},
%{
label: gettext("Priority"),
value:
if(bucket.priority == 0,
do: gettext("Random"),
else: to_string(bucket.priority)
)
},
%{
label: gettext("Files"),
value: "#{Map.get(@bucket_file_counts, bucket.uuid, 0)}"
},
%{
label: gettext("Status"),
value: if(bucket.enabled, do: gettext("Enabled"), else: gettext("Disabled"))
}
]
end
}
>
<.table_default_header>
<.table_default_row>
<.table_default_header_cell>{gettext("Name")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Provider")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Priority")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Files")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Status")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Actions")}</.table_default_header_cell>
</.table_default_row>
</.table_default_header>
<.table_default_body>
<%= for bucket <- @buckets do %>
<.table_default_row>
<.table_default_cell>
<div class="flex items-center gap-2">
<%= case bucket.provider do %>
<% "local" -> %>
<.icon name="hero-folder" class="w-5 h-5 text-primary" />
<% "s3" -> %>
<.icon name="hero-cloud" class="w-5 h-5 text-warning" />
<% "b2" -> %>
<.icon name="hero-cube" class="w-5 h-5 text-info" />
<% "r2" -> %>
<.icon name="hero-cloud-arrow-up" class="w-5 h-5 text-accent" />
<% "tigris" -> %>
<.icon name="hero-globe-alt" class="w-5 h-5 text-secondary" />
<% _ -> %>
<.icon name="hero-server" class="w-5 h-5 text-base-content/50" />
<% end %>
<div>
<div class="font-bold">{bucket.name}</div>
<div class="text-xs text-base-content/60 font-mono">
{get_bucket_full_path(bucket)}
</div>
</div>
</div>
</.table_default_cell>
<.table_default_cell>
<span class="badge badge-ghost badge-sm h-auto">
{String.upcase(bucket.provider)}
</span>
</.table_default_cell>
<.table_default_cell>
<%= if bucket.priority == 0 do %>
<span class="text-xs text-base-content/60">{gettext("Random")} (0)</span>
<% else %>
<span class="font-semibold">{bucket.priority}</span>
<% end %>
</.table_default_cell>
<.table_default_cell>
<span class="text-sm font-semibold">
{Map.get(@bucket_file_counts, bucket.uuid, 0)} {gettext("files")}
</span>
</.table_default_cell>
<.table_default_cell>
<%= if bucket.enabled do %>
<span class="badge badge-success badge-sm h-auto">
{gettext("Enabled")}
</span>
<% else %>
<span class="badge badge-error badge-sm h-auto">
{gettext("Disabled")}
</span>
<% end %>
</.table_default_cell>
<.table_default_cell>
<.table_row_menu
id={"bucket-menu-#{bucket.uuid}"}
label={gettext("Bucket actions")}
>
<.table_row_menu_link
navigate={
PhoenixKit.Utils.Routes.path(
"/admin/settings/media/buckets/#{bucket.uuid}/edit"
)
}
icon="hero-pencil"
label={gettext("Edit bucket")}
/>
<.table_row_menu_button
phx-click="toggle_bucket"
phx-value-id={bucket.uuid}
icon={if bucket.enabled, do: "hero-eye-slash", else: "hero-eye"}
label={if bucket.enabled, do: gettext("Disable"), else: gettext("Enable")}
/>
<.table_row_menu_button
phx-click="delete_bucket"
phx-value-id={bucket.uuid}
data-confirm={
gettext(
"Are you sure you want to delete this bucket? This will remove all location records."
)
}
icon="hero-trash"
label={gettext("Delete bucket")}
class="text-error"
/>
</.table_row_menu>
</.table_default_cell>
</.table_default_row>
<% end %>
</.table_default_body>
<:card_actions :let={bucket}>
<.link
navigate={
PhoenixKit.Utils.Routes.path(
"/admin/settings/media/buckets/#{bucket.uuid}/edit"
)
}
class="btn btn-xs btn-outline btn-info"
>
<.icon name="hero-pencil" class="w-3 h-3" /> {gettext("Edit")}
</.link>
<button
phx-click="toggle_bucket"
phx-value-id={bucket.uuid}
class="btn btn-xs btn-outline"
>
{if bucket.enabled, do: gettext("Disable"), else: gettext("Enable")}
</button>
<button
phx-click="delete_bucket"
phx-value-id={bucket.uuid}
data-confirm={
gettext(
"Are you sure you want to delete this bucket? This will remove all location records."
)
}
class="btn btn-xs btn-outline btn-error"
>
<.icon name="hero-trash" class="w-3 h-3" /> {gettext("Delete")}
</button>
</:card_actions>
</.table_default>
<div class="stats stats-vertical lg:stats-horizontal shadow mt-6">
<div class="stat">
<div class="stat-title">{gettext("Total Buckets")}</div>
<div class="stat-value text-2xl">{length(@buckets)}</div>
<div class="stat-desc">{gettext("Configured media locations")}</div>
</div>
<div class="stat">
<div class="stat-title">{gettext("Active Buckets")}</div>
<div class="stat-value text-2xl text-success">
{Enum.count(@buckets, & &1.enabled)}
</div>
<div class="stat-desc">{gettext("Currently in use")}</div>
</div>
<div class="stat">
<div class="stat-title">{gettext("Default Bucket")}</div>
<div class="stat-value text-2xl">
<%= if @default_bucket_uuid do %>
<span class="text-success">{gettext("Set")}</span>
<% else %>
<span class="text-warning">{gettext("None")}</span>
<% end %>
</div>
<div class="stat-desc">{gettext("Primary media location")}</div>
</div>
</div>
<% end %>
</div>
</div>
<%!-- Media Configuration --%>
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card-body">
<h2 class="card-title text-2xl mb-4">
<.icon name="hero-cog-6-tooth" class="w-6 h-6 mr-2" /> {gettext("Media Configuration")}
</h2>
<.form
for={%{"form_redundancy" => @form_redundancy}}
phx-change="update_storage_form"
class="grid grid-cols-1 md:grid-cols-2 gap-6"
>
<%!-- Redundancy Settings --%>
<div class="form-control">
<div class="mb-2">
<span class="label-text font-semibold">{gettext("Redundancy Copies")}</span>
<span class="block text-xs text-base-content/60">
{gettext("Number of copies to store")}
</span>
</div>
<%= if @active_buckets_count == 0 do %>
<div class="alert alert-warning">
<.icon name="hero-exclamation-triangle" class="w-4 h-4" />
<span class="text-sm">
{gettext("No active buckets available. Enable buckets to configure redundancy.")}
</span>
</div>
<% else %>
<%= if @form_redundancy > @max_redundancy do %>
<div class="alert alert-warning">
<.icon name="hero-exclamation-triangle" class="w-4 h-4" />
<span class="text-sm">
Current setting (#{@form_redundancy} copies) exceeds available buckets (#{@max_redundancy}). {gettext(
"Reduce redundancy or enable more buckets."
)}
</span>
</div>
<% end %>
<input
type="number"
name="form_redundancy"
value={@form_redundancy}
min="1"
max={@max_redundancy}
class={"input input-bordered #{if @form_redundancy > @max_redundancy, do: "input-warning", else: ""}"}
/>
<p class="text-xs text-base-content/70 mt-1">
{gettext("Enter number of copies")} (1-{@max_redundancy}).
<%= if @max_redundancy == 1 do %>
{gettext(
"Only 1 active bucket available. Add more buckets to enable redundancy."
)}
<% else %>
{gettext("Maximum %{count} copies based on %{buckets} active bucket(s).",
count: @max_redundancy,
buckets: @active_buckets_count
)}
<% end %>
</p>
<% end %>
</div>
<%!-- Auto-Variant Generation --%>
<div class="form-control">
<label class="flex items-start gap-3 cursor-pointer py-2">
<input
type="checkbox"
name="form_auto_generate_variants"
checked={@form_auto_generate_variants}
phx-click="toggle_form_variants"
value="true"
class="checkbox checkbox-primary mt-0.5 flex-shrink-0"
/>
<span>
<span class="font-semibold text-sm">{gettext("Auto-Generate Variants")}</span>
<span class="block text-xs text-base-content/70 mt-0.5">
{gettext("Automatically resize images and videos to configured dimensions")}
</span>
</span>
</label>
</div>
<%!-- Deep Zoom Tile Generation --%>
<div class="form-control">
<label class="flex items-start gap-3 cursor-pointer py-2">
<input
type="checkbox"
name="form_tile_generation_enabled"
checked={@form_tile_generation_enabled}
phx-click="toggle_form_tile_generation"
value="true"
class="checkbox checkbox-primary mt-0.5 flex-shrink-0"
/>
<span>
<span class="font-semibold text-sm">
{gettext("Deep Zoom Tile Generation")}
</span>
<span class="block text-xs text-base-content/70 mt-0.5">
{gettext(
"Lazily generate DZI tile pyramids when users zoom deeply into images. With this off the MediaBrowser still swaps between medium/large variants while zooming, but never generates tiles or hits the lazy tile endpoint. ImageMagick is required when enabled."
)}
</span>
</span>
</label>
</div>
<%!-- Max Upload Size --%>
<div class="form-control">
<div class="mb-2">
<span class="label-text font-semibold">{gettext("Max Upload Size (MB)")}</span>
<span class="block text-xs text-base-content/60">
{gettext("Maximum file size allowed per upload")}
</span>
</div>
<input
type="number"
name="form_max_upload_size_mb"
value={@form_max_upload_size_mb}
min="1"
class="input input-bordered"
/>
<p class="text-xs text-base-content/70 mt-1">
{gettext("Current limit: %{size} MB", size: @form_max_upload_size_mb)}
</p>
</div>
</.form>
<%!-- Apply Button --%>
<div class="form-control mt-4">
<button
type="button"
phx-click="apply_storage_settings"
disabled={
@form_redundancy == @redundancy_copies and
@form_auto_generate_variants == @auto_generate_variants and
@form_tile_generation_enabled == @tile_generation_enabled and
@form_max_upload_size_mb == @max_upload_size_mb
}
phx-disable-with={gettext("Applying...")}
class="btn btn-primary"
>
<.icon name="hero-check" class="w-4 h-4 mr-2" /> {gettext("Apply Changes")}
</button>
<span class="label-text-alt text-xs ml-2">
<%= if @form_redundancy == @redundancy_copies and @form_auto_generate_variants == @auto_generate_variants and @form_tile_generation_enabled == @tile_generation_enabled and @form_max_upload_size_mb == @max_upload_size_mb do %>
{gettext("No changes to apply")}
<% else %>
{gettext("Changes will be saved immediately")}
<% end %>
</span>
</div>
<%!-- Quick Actions --%>
<div class="divider">{gettext("Quick Actions")}</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/settings/media/dimensions")}
class="btn btn-outline"
>
<.icon name="hero-photo" class="w-4 h-4 mr-2" /> {gettext("Manage Dimensions")}
</.link>
<button class="btn btn-outline" disabled>
<.icon name="hero-chart-bar" class="w-4 h-4 mr-2" /> {gettext("Media Stats")}
<span class="badge badge-xs ml-1">{gettext("Soon")}</span>
</button>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/settings/media/health")}
class="btn btn-outline"
>
<.icon name="hero-arrow-path" class="w-4 h-4 mr-2" /> {gettext("Sync Files")}
</.link>
</div>
</div>
</div>
<%!-- Coming Soon Features --%>
<div class="card bg-base-100 shadow-xl border-2 border-dashed border-base-300">
<div class="card-body">
<h2 class="card-title text-2xl mb-4">
<.icon name="hero-rocket-launch" class="w-6 h-6 mr-2" /> {gettext(
"Advanced Features Coming Soon"
)}
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<%!-- Multi-Location Media Storage --%>
<div class="border border-base-300 rounded-lg p-4">
<div class="flex items-start gap-3">
<div class="text-3xl">🗂️</div>
<div>
<h3 class="font-bold mb-1">{gettext("Multi-Location Media Storage")}</h3>
<p class="text-sm text-base-content/70">
{gettext("Store files across 1-5 redundant locations with automatic failover")}
</p>
</div>
</div>
</div>
<%!-- S3 Integration --%>
<div class="border border-base-300 rounded-lg p-4">
<div class="flex items-start gap-3">
<div class="text-3xl">☁️</div>
<div>
<h3 class="font-bold mb-1">{gettext("AWS S3 Integration")}</h3>
<p class="text-sm text-base-content/70">
{gettext("Store files in Amazon S3 buckets for cloud redundancy")}
</p>
</div>
</div>
</div>
<%!-- Remote Servers --%>
<div class="border border-base-300 rounded-lg p-4">
<div class="flex items-start gap-3">
<div class="text-3xl">🖥️</div>
<div>
<h3 class="font-bold mb-1">{gettext("Remote Servers")}</h3>
<p class="text-sm text-base-content/70">
{gettext("Distribute files across multiple remote servers and datacenters")}
</p>
</div>
</div>
</div>
<%!-- Admin Dashboard --%>
<div class="border border-base-300 rounded-lg p-4">
<div class="flex items-start gap-3">
<div class="text-3xl">📊</div>
<div>
<h3 class="font-bold mb-1">{gettext("Admin Dashboard")}</h3>
<p class="text-sm text-base-content/70">
{gettext("Monitor media health, locations, and file distribution")}
</p>
</div>
</div>
</div>
<%!-- File Tracking --%>
<div class="border border-base-300 rounded-lg p-4">
<div class="flex items-start gap-3">
<div class="text-3xl">📋</div>
<div>
<h3 class="font-bold mb-1">{gettext("File Tracking")}</h3>
<p class="text-sm text-base-content/70">
{gettext("PostgreSQL-based file registry with location tracking")}
</p>
</div>
</div>
</div>
<%!-- Automatic Failover --%>
<div class="border border-base-300 rounded-lg p-4">
<div class="flex items-start gap-3">
<div class="text-3xl">🔄</div>
<div>
<h3 class="font-bold mb-1">{gettext("Automatic Failover")}</h3>
<p class="text-sm text-base-content/70">
{gettext("Seamless file retrieval even when media locations fail")}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<%!-- Upload Test Section --%>
<%!-- Help Section --%>
<div class="alert mt-6">
<.icon name="hero-light-bulb" class="w-5 h-5" />
<div>
<h3 class="font-bold">{gettext("Media System Architecture")}</h3>
<p class="text-sm">
{gettext(
"The media system is designed for distributed file management with automatic redundancy."
)}
{gettext(
"Files are tracked in PostgreSQL with support for multiple media locations (local, S3, remote servers)."
)}
{gettext(
"When a file is requested, the system automatically tries each location until it successfully retrieves the file."
)}
</p>
</div>
</div>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>
<%!-- Add CSS for drag and drop --%>
<style>
#upload-dropzone.dragover {
@apply border-primary bg-primary/5;
}
</style>
<script>
// Simple drag and drop styling
document.addEventListener('DOMContentLoaded', function() {
const dropzone = document.getElementById('upload-dropzone');
const fileInput = document.getElementById('file-upload');
if (dropzone && fileInput) {
dropzone.addEventListener('dragover', function(e) {
e.preventDefault();
dropzone.classList.add('dragover');
});
dropzone.addEventListener('dragleave', function(e) {
e.preventDefault();
dropzone.classList.remove('dragover');
});
dropzone.addEventListener('drop', function(e) {
e.preventDefault();
dropzone.classList.remove('dragover');
});
}
});
</script>