pyview-web 0.4.2__tar.gz → 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyview-web might be problematic. Click here for more details.

Files changed (59) hide show
  1. {pyview_web-0.4.2 → pyview_web-0.5.1}/PKG-INFO +1 -1
  2. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyproject.toml +1 -1
  3. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/assets/js/app.js +1 -0
  4. pyview_web-0.5.1/pyview/assets/js/uploaders.js +221 -0
  5. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/live_socket.py +44 -4
  6. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/static/assets/app.js +31 -49
  7. pyview_web-0.5.1/pyview/static/assets/uploaders.js +221 -0
  8. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/uploads.py +274 -34
  9. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/ws_handler.py +5 -5
  10. {pyview_web-0.4.2 → pyview_web-0.5.1}/LICENSE +0 -0
  11. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/__init__.py +0 -0
  12. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/assets/package-lock.json +0 -0
  13. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/assets/package.json +0 -0
  14. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/async_stream_runner.py +0 -0
  15. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/auth/__init__.py +0 -0
  16. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/auth/provider.py +0 -0
  17. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/auth/required.py +0 -0
  18. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/changesets/__init__.py +0 -0
  19. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/changesets/changesets.py +0 -0
  20. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/cli/__init__.py +0 -0
  21. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/cli/commands/__init__.py +0 -0
  22. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/cli/commands/create_view.py +0 -0
  23. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/cli/main.py +0 -0
  24. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/csrf.py +0 -0
  25. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/events/BaseEventHandler.py +0 -0
  26. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/events/__init__.py +0 -0
  27. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/events/info_event.py +0 -0
  28. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/instrumentation/__init__.py +0 -0
  29. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/instrumentation/interfaces.py +0 -0
  30. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/instrumentation/noop.py +0 -0
  31. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/js.py +0 -0
  32. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/live_routes.py +0 -0
  33. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/live_view.py +0 -0
  34. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/meta.py +0 -0
  35. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/phx_message.py +0 -0
  36. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/pyview.py +0 -0
  37. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/secret.py +0 -0
  38. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/session.py +0 -0
  39. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/template/__init__.py +0 -0
  40. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/template/context_processor.py +0 -0
  41. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/template/live_template.py +0 -0
  42. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/template/render_diff.py +0 -0
  43. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/template/root_template.py +0 -0
  44. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/template/serializer.py +0 -0
  45. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/template/utils.py +0 -0
  46. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/vendor/__init__.py +0 -0
  47. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/vendor/flet/pubsub/__init__.py +0 -0
  48. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/vendor/flet/pubsub/pub_sub.py +0 -0
  49. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/vendor/ibis/__init__.py +0 -0
  50. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/vendor/ibis/compiler.py +0 -0
  51. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/vendor/ibis/context.py +0 -0
  52. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/vendor/ibis/errors.py +0 -0
  53. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/vendor/ibis/filters.py +0 -0
  54. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/vendor/ibis/loaders.py +0 -0
  55. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/vendor/ibis/nodes.py +0 -0
  56. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/vendor/ibis/template.py +0 -0
  57. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/vendor/ibis/tree.py +0 -0
  58. {pyview_web-0.4.2 → pyview_web-0.5.1}/pyview/vendor/ibis/utils.py +0 -0
  59. {pyview_web-0.4.2 → pyview_web-0.5.1}/readme.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyview-web
3
- Version: 0.4.2
3
+ Version: 0.5.1
4
4
  Summary: LiveView in Python
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -5,7 +5,7 @@ packages = [
5
5
  { include = "pyview" },
6
6
  ]
7
7
 
8
- version = "0.4.2"
8
+ version = "0.5.1"
9
9
  description = "LiveView in Python"
10
10
  authors = ["Larry Ogrodnek <ogrodnek@gmail.com>"]
11
11
  license = "MIT"
@@ -61,6 +61,7 @@ let csrfToken = document
61
61
  let liveSocket = new LiveSocket("/live", Socket, {
62
62
  hooks: Hooks,
63
63
  params: { _csrf_token: csrfToken },
64
+ uploaders: window.Uploaders || {},
64
65
  });
65
66
 
66
67
  // Show progress bar on live navigation and form submits
@@ -0,0 +1,221 @@
1
+ /**
2
+ * PyView External S3 Uploaders
3
+ *
4
+ * Client-side uploaders for external S3 uploads.
5
+ *
6
+ * Available uploaders:
7
+ * - S3: Simple POST upload to S3 using presigned POST URLs
8
+ * - S3Multipart: Multipart upload for large files (>5GB)
9
+ */
10
+
11
+ window.Uploaders = window.Uploaders || {};
12
+
13
+ // S3 Simple POST uploader
14
+ // Uses presigned POST URLs for direct upload to S3
15
+ // Works for files up to ~5GB
16
+ if (!window.Uploaders.S3) {
17
+ window.Uploaders.S3 = function (entries, onViewError) {
18
+ entries.forEach((entry) => {
19
+ let formData = new FormData();
20
+ let { url, fields } = entry.meta;
21
+
22
+ // Add all fields from presigned POST
23
+ Object.entries(fields).forEach(([key, val]) =>
24
+ formData.append(key, val)
25
+ );
26
+ formData.append("file", entry.file);
27
+
28
+ let xhr = new XMLHttpRequest();
29
+ onViewError(() => xhr.abort());
30
+
31
+ xhr.onload = () => {
32
+ if (xhr.status === 204 || xhr.status === 200) {
33
+ entry.progress(100);
34
+ } else {
35
+ entry.error(`S3 upload failed with status ${xhr.status}`);
36
+ }
37
+ };
38
+ xhr.onerror = () => entry.error("Network error during upload");
39
+
40
+ xhr.upload.addEventListener("progress", (event) => {
41
+ if (event.lengthComputable) {
42
+ let percent = Math.round((event.loaded / event.total) * 100);
43
+ if (percent < 100) {
44
+ entry.progress(percent);
45
+ }
46
+ }
47
+ });
48
+
49
+ xhr.open("POST", url, true);
50
+ xhr.send(formData);
51
+ });
52
+ };
53
+ }
54
+
55
+ // S3 Multipart uploader for large files
56
+ // Uploads file in chunks with retry logic and concurrency control
57
+ //
58
+ // - Exponential backoff retry (max 3 attempts per part)
59
+ // - Concurrency limit (max 6 parallel uploads)
60
+ // - Automatic cleanup on fatal errors
61
+ //
62
+ // Based on AWS best practices:
63
+ // https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html
64
+ //
65
+ // Server must:
66
+ // 1. Return metadata with: uploader="S3Multipart", upload_id, part_urls, chunk_size
67
+ // 2. Provide entry_complete callback to finalize the upload
68
+ if (!window.Uploaders.S3Multipart) {
69
+ window.Uploaders.S3Multipart = function (entries, onViewError) {
70
+ entries.forEach((entry) => {
71
+ const { upload_id, part_urls, chunk_size, key } = entry.meta;
72
+ const file = entry.file;
73
+ const parts = []; // Store {PartNumber, ETag} for each uploaded part
74
+
75
+ const MAX_RETRIES = 3;
76
+ const MAX_CONCURRENT = 6;
77
+ let uploadedParts = 0;
78
+ let activeUploads = 0;
79
+ let partIndex = 0;
80
+ let hasError = false;
81
+ const totalParts = part_urls.length;
82
+
83
+ console.log(`[S3Multipart] Starting upload for ${entry.file.name}`);
84
+ console.log(`[S3Multipart] Total parts: ${totalParts}, chunk size: ${chunk_size}`);
85
+ console.log(`[S3Multipart] Max concurrent uploads: ${MAX_CONCURRENT}, max retries: ${MAX_RETRIES}`);
86
+
87
+ // Add a custom method to send completion data directly
88
+ // This bypasses entry.progress() which only handles numbers
89
+ entry.complete = function(completionData) {
90
+ console.log(`[S3Multipart] Calling entry.complete with:`, completionData);
91
+ // Call pushFileProgress directly with the completion data
92
+ entry.view.pushFileProgress(entry.fileEl, entry.ref, completionData);
93
+ };
94
+
95
+ // Upload a single part with retry logic
96
+ const uploadPart = (index, retryCount = 0) => {
97
+ if (hasError) return; // Stop if we've hit a fatal error
98
+
99
+ const partNumber = index + 1;
100
+ const url = part_urls[index];
101
+ const start = index * chunk_size;
102
+ const end = Math.min(start + chunk_size, file.size);
103
+ const chunk = file.slice(start, end);
104
+
105
+ console.log(`[S3Multipart] Starting part ${partNumber}/${totalParts}, size: ${chunk.size} bytes, attempt ${retryCount + 1}`);
106
+
107
+ const xhr = new XMLHttpRequest();
108
+ onViewError(() => xhr.abort());
109
+
110
+ // Track upload progress within this chunk
111
+ xhr.upload.addEventListener("progress", (event) => {
112
+ if (event.lengthComputable) {
113
+ // Calculate overall progress: completed parts + current part's progress
114
+ const completedBytes = uploadedParts * chunk_size;
115
+ const currentPartBytes = event.loaded;
116
+ const totalBytes = file.size;
117
+ const overallPercent = Math.round(((completedBytes + currentPartBytes) / totalBytes) * 100);
118
+
119
+ // Don't report 100% until all parts complete and we send completion data
120
+ if (overallPercent < 100) {
121
+ entry.progress(overallPercent);
122
+ }
123
+ }
124
+ });
125
+
126
+ xhr.onload = () => {
127
+ activeUploads--;
128
+
129
+ if (xhr.status === 200) {
130
+ const etag = xhr.getResponseHeader('ETag');
131
+ console.log(`[S3Multipart] Part ${partNumber} succeeded, ETag: ${etag}`);
132
+
133
+ if (!etag) {
134
+ console.error(`[S3Multipart] Part ${partNumber} missing ETag!`);
135
+ entry.error(`Part ${partNumber} upload succeeded but no ETag returned`);
136
+ hasError = true;
137
+ return;
138
+ }
139
+
140
+ // Store the part with its ETag
141
+ parts.push({
142
+ PartNumber: partNumber,
143
+ ETag: etag.replace(/"/g, '')
144
+ });
145
+ uploadedParts++;
146
+
147
+ // Update progress
148
+ const progressPercent = Math.round((uploadedParts / totalParts) * 100);
149
+ console.log(`[S3Multipart] Progress: ${uploadedParts}/${totalParts} parts (${progressPercent}%)`);
150
+
151
+ if (uploadedParts < totalParts) {
152
+ entry.progress(progressPercent < 100 ? progressPercent : 99);
153
+ uploadNextPart(); // Start next part
154
+ } else {
155
+ // All parts complete!
156
+ const completionData = {
157
+ complete: true,
158
+ upload_id: upload_id,
159
+ key: key,
160
+ parts: parts.sort((a, b) => a.PartNumber - b.PartNumber)
161
+ };
162
+ console.log(`[S3Multipart] All parts complete! Sending completion data`);
163
+ entry.complete(completionData);
164
+ }
165
+ } else {
166
+ // Upload failed - retry with exponential backoff
167
+ console.error(`[S3Multipart] Part ${partNumber} failed with status ${xhr.status}, attempt ${retryCount + 1}`);
168
+
169
+ if (retryCount < MAX_RETRIES) {
170
+ // Exponential backoff: 1s, 2s, 4s, max 10s
171
+ const delay = Math.min(1000 * (2 ** retryCount), 10000);
172
+ console.log(`[S3Multipart] Retrying part ${partNumber} in ${delay}ms...`);
173
+
174
+ setTimeout(() => {
175
+ uploadPart(index, retryCount + 1);
176
+ }, delay);
177
+ } else {
178
+ // Max retries exceeded - fatal error
179
+ console.error(`[S3Multipart] Part ${partNumber} failed after ${MAX_RETRIES} retries, aborting upload`);
180
+ entry.error(`Part ${partNumber} failed after ${MAX_RETRIES} attempts. Upload aborted.`);
181
+ hasError = true;
182
+ }
183
+ }
184
+ };
185
+
186
+ xhr.onerror = () => {
187
+ activeUploads--;
188
+ console.error(`[S3Multipart] Network error on part ${partNumber}, attempt ${retryCount + 1}`);
189
+
190
+ if (retryCount < MAX_RETRIES) {
191
+ const delay = Math.min(1000 * (2 ** retryCount), 10000);
192
+ console.log(`[S3Multipart] Retrying part ${partNumber} after network error in ${delay}ms...`);
193
+
194
+ setTimeout(() => {
195
+ uploadPart(index, retryCount + 1);
196
+ }, delay);
197
+ } else {
198
+ console.error(`[S3Multipart] Part ${partNumber} network error after ${MAX_RETRIES} retries, aborting upload`);
199
+ entry.error(`Part ${partNumber} network error after ${MAX_RETRIES} attempts. Upload aborted.`);
200
+ hasError = true;
201
+ }
202
+ };
203
+
204
+ xhr.open('PUT', url, true);
205
+ xhr.send(chunk);
206
+ activeUploads++;
207
+ };
208
+
209
+ // Upload next part if we haven't hit the concurrency limit
210
+ const uploadNextPart = () => {
211
+ while (partIndex < totalParts && activeUploads < MAX_CONCURRENT && !hasError) {
212
+ uploadPart(partIndex);
213
+ partIndex++;
214
+ }
215
+ };
216
+
217
+ // Start initial batch of uploads
218
+ uploadNextPart();
219
+ });
220
+ };
221
+ }
@@ -47,9 +47,22 @@ class UnconnectedSocket(Generic[T]):
47
47
  connected: bool = False
48
48
 
49
49
  def allow_upload(
50
- self, upload_name: str, constraints: UploadConstraints, auto_upload: bool = False, progress: Optional[Callable] = None
50
+ self,
51
+ upload_name: str,
52
+ constraints: UploadConstraints,
53
+ auto_upload: bool = False,
54
+ progress: Optional[Callable] = None,
55
+ external: Optional[Callable] = None,
56
+ entry_complete: Optional[Callable] = None,
51
57
  ) -> UploadConfig:
52
- return UploadConfig(name=upload_name, constraints=constraints, autoUpload=auto_upload, progress_callback=progress)
58
+ return UploadConfig(
59
+ name=upload_name,
60
+ constraints=constraints,
61
+ autoUpload=auto_upload,
62
+ progress_callback=progress,
63
+ external_callback=external,
64
+ entry_complete_callback=entry_complete,
65
+ )
53
66
 
54
67
 
55
68
  class ConnectedLiveViewSocket(Generic[T]):
@@ -193,13 +206,40 @@ class ConnectedLiveViewSocket(Generic[T]):
193
206
  except Exception:
194
207
  logger.warning("Error sending navigation message", exc_info=True)
195
208
 
209
+ async def redirect(self, path: str, params: dict[str, Any] = {}):
210
+ """Redirect to a new location with full page reload"""
211
+ to = path
212
+ if params:
213
+ to = to + "?" + urlencode(params)
214
+
215
+ message = [
216
+ None,
217
+ None,
218
+ self.topic,
219
+ "redirect",
220
+ {"to": to},
221
+ ]
222
+
223
+ try:
224
+ await self.websocket.send_text(json.dumps(message))
225
+ except Exception:
226
+ logger.warning("Error sending redirect message", exc_info=True)
227
+
196
228
  async def push_event(self, event: str, value: dict[str, Any]):
197
229
  self.pending_events.append((event, value))
198
230
 
199
231
  def allow_upload(
200
- self, upload_name: str, constraints: UploadConstraints, auto_upload: bool = False, progress: Optional[Callable] = None
232
+ self,
233
+ upload_name: str,
234
+ constraints: UploadConstraints,
235
+ auto_upload: bool = False,
236
+ progress: Optional[Callable] = None,
237
+ external: Optional[Callable] = None,
238
+ entry_complete: Optional[Callable] = None,
201
239
  ) -> UploadConfig:
202
- return self.upload_manager.allow_upload(upload_name, constraints, auto_upload, progress)
240
+ return self.upload_manager.allow_upload(
241
+ upload_name, constraints, auto_upload, progress, external, entry_complete
242
+ )
203
243
 
204
244
  async def close(self):
205
245
  self.connected = False
@@ -39,9 +39,9 @@
39
39
  mod
40
40
  ));
41
41
 
42
- // assets/node_modules/nprogress/nprogress.js
42
+ // node_modules/nprogress/nprogress.js
43
43
  var require_nprogress = __commonJS({
44
- "assets/node_modules/nprogress/nprogress.js"(exports, module) {
44
+ "node_modules/nprogress/nprogress.js"(exports, module) {
45
45
  (function(root, factory) {
46
46
  if (typeof define === "function" && define.amd) {
47
47
  define(factory);
@@ -71,8 +71,7 @@
71
71
  var key, value;
72
72
  for (key in options) {
73
73
  value = options[key];
74
- if (value !== void 0 && options.hasOwnProperty(key))
75
- Settings[key] = value;
74
+ if (value !== void 0 && options.hasOwnProperty(key)) Settings[key] = value;
76
75
  }
77
76
  return this;
78
77
  };
@@ -84,8 +83,7 @@
84
83
  var progress = NProgress2.render(!started), bar = progress.querySelector(Settings.barSelector), speed = Settings.speed, ease = Settings.easing;
85
84
  progress.offsetWidth;
86
85
  queue(function(next) {
87
- if (Settings.positionUsing === "")
88
- Settings.positionUsing = NProgress2.getPositioningCSS();
86
+ if (Settings.positionUsing === "") Settings.positionUsing = NProgress2.getPositioningCSS();
89
87
  css(bar, barPositionCSS(n, speed, ease));
90
88
  if (n === 1) {
91
89
  css(progress, {
@@ -113,23 +111,19 @@
113
111
  return typeof NProgress2.status === "number";
114
112
  };
115
113
  NProgress2.start = function() {
116
- if (!NProgress2.status)
117
- NProgress2.set(0);
114
+ if (!NProgress2.status) NProgress2.set(0);
118
115
  var work = function() {
119
116
  setTimeout(function() {
120
- if (!NProgress2.status)
121
- return;
117
+ if (!NProgress2.status) return;
122
118
  NProgress2.trickle();
123
119
  work();
124
120
  }, Settings.trickleSpeed);
125
121
  };
126
- if (Settings.trickle)
127
- work();
122
+ if (Settings.trickle) work();
128
123
  return this;
129
124
  };
130
125
  NProgress2.done = function(force) {
131
- if (!force && !NProgress2.status)
132
- return this;
126
+ if (!force && !NProgress2.status) return this;
133
127
  return NProgress2.inc(0.3 + 0.5 * Math.random()).set(1);
134
128
  };
135
129
  NProgress2.inc = function(amount) {
@@ -171,8 +165,7 @@
171
165
  };
172
166
  })();
173
167
  NProgress2.render = function(fromStart) {
174
- if (NProgress2.isRendered())
175
- return document.getElementById("nprogress");
168
+ if (NProgress2.isRendered()) return document.getElementById("nprogress");
176
169
  addClass(document.documentElement, "nprogress-busy");
177
170
  var progress = document.createElement("div");
178
171
  progress.id = "nprogress";
@@ -213,10 +206,8 @@
213
206
  }
214
207
  };
215
208
  function clamp(n, min, max) {
216
- if (n < min)
217
- return min;
218
- if (n > max)
219
- return max;
209
+ if (n < min) return min;
210
+ if (n > max) return max;
220
211
  return n;
221
212
  }
222
213
  function toBarPerc(n) {
@@ -234,7 +225,7 @@
234
225
  barCSS.transition = "all " + speed + "ms " + ease;
235
226
  return barCSS;
236
227
  }
237
- var queue = function() {
228
+ var queue = /* @__PURE__ */ (function() {
238
229
  var pending = [];
239
230
  function next() {
240
231
  var fn = pending.shift();
@@ -244,11 +235,10 @@
244
235
  }
245
236
  return function(fn) {
246
237
  pending.push(fn);
247
- if (pending.length == 1)
248
- next();
238
+ if (pending.length == 1) next();
249
239
  };
250
- }();
251
- var css = function() {
240
+ })();
241
+ var css = /* @__PURE__ */ (function() {
252
242
  var cssPrefixes = ["Webkit", "O", "Moz", "ms"], cssProps = {};
253
243
  function camelCase(string) {
254
244
  return string.replace(/^-ms-/, "ms-").replace(/-([\da-z])/gi, function(match, letter) {
@@ -257,13 +247,11 @@
257
247
  }
258
248
  function getVendorProp(name) {
259
249
  var style = document.body.style;
260
- if (name in style)
261
- return name;
250
+ if (name in style) return name;
262
251
  var i = cssPrefixes.length, capName = name.charAt(0).toUpperCase() + name.slice(1), vendorName;
263
252
  while (i--) {
264
253
  vendorName = cssPrefixes[i] + capName;
265
- if (vendorName in style)
266
- return vendorName;
254
+ if (vendorName in style) return vendorName;
267
255
  }
268
256
  return name;
269
257
  }
@@ -280,28 +268,25 @@
280
268
  if (args.length == 2) {
281
269
  for (prop in properties) {
282
270
  value = properties[prop];
283
- if (value !== void 0 && properties.hasOwnProperty(prop))
284
- applyCss(element, prop, value);
271
+ if (value !== void 0 && properties.hasOwnProperty(prop)) applyCss(element, prop, value);
285
272
  }
286
273
  } else {
287
274
  applyCss(element, args[1], args[2]);
288
275
  }
289
276
  };
290
- }();
277
+ })();
291
278
  function hasClass(element, name) {
292
279
  var list = typeof element == "string" ? element : classList(element);
293
280
  return list.indexOf(" " + name + " ") >= 0;
294
281
  }
295
282
  function addClass(element, name) {
296
283
  var oldList = classList(element), newList = oldList + name;
297
- if (hasClass(oldList, name))
298
- return;
284
+ if (hasClass(oldList, name)) return;
299
285
  element.className = newList.substring(1);
300
286
  }
301
287
  function removeClass(element, name) {
302
288
  var oldList = classList(element), newList;
303
- if (!hasClass(element, name))
304
- return;
289
+ if (!hasClass(element, name)) return;
305
290
  newList = oldList.replace(" " + name + " ", " ");
306
291
  element.className = newList.substring(1, newList.length - 1);
307
292
  }
@@ -316,12 +301,11 @@
316
301
  }
317
302
  });
318
303
 
319
- // assets/node_modules/phoenix_html/priv/static/phoenix_html.js
304
+ // node_modules/phoenix_html/priv/static/phoenix_html.js
320
305
  (function() {
321
306
  var PolyfillEvent = eventConstructor();
322
307
  function eventConstructor() {
323
- if (typeof window.CustomEvent === "function")
324
- return window.CustomEvent;
308
+ if (typeof window.CustomEvent === "function") return window.CustomEvent;
325
309
  function CustomEvent2(event, params) {
326
310
  params = params || { bubbles: false, cancelable: false, detail: void 0 };
327
311
  var evt = document.createEvent("CustomEvent");
@@ -343,10 +327,8 @@
343
327
  form.method = element.getAttribute("data-method") === "get" ? "get" : "post";
344
328
  form.action = to;
345
329
  form.style.display = "hidden";
346
- if (target)
347
- form.target = target;
348
- else if (targetModifierKey)
349
- form.target = "_blank";
330
+ if (target) form.target = target;
331
+ else if (targetModifierKey) form.target = "_blank";
350
332
  form.appendChild(csrf);
351
333
  form.appendChild(method);
352
334
  document.body.appendChild(form);
@@ -354,8 +336,7 @@
354
336
  }
355
337
  window.addEventListener("click", function(e) {
356
338
  var element = e.target;
357
- if (e.defaultPrevented)
358
- return;
339
+ if (e.defaultPrevented) return;
359
340
  while (element && element.getAttribute) {
360
341
  var phoenixLinkEvent = new PolyfillEvent("phoenix.link.click", {
361
342
  "bubbles": true,
@@ -383,7 +364,7 @@
383
364
  }, false);
384
365
  })();
385
366
 
386
- // assets/node_modules/phoenix/priv/static/phoenix.mjs
367
+ // node_modules/phoenix/priv/static/phoenix.mjs
387
368
  var closure = (value) => {
388
369
  if (typeof value === "function") {
389
370
  return value;
@@ -1354,7 +1335,7 @@
1354
1335
  }
1355
1336
  };
1356
1337
 
1357
- // assets/node_modules/phoenix_live_view/priv/static/phoenix_live_view.esm.js
1338
+ // node_modules/phoenix_live_view/priv/static/phoenix_live_view.esm.js
1358
1339
  var CONSECUTIVE_RELOADS = "consecutive-reloads";
1359
1340
  var MAX_RELOADS = 10;
1360
1341
  var RELOAD_JITTER_MIN = 5e3;
@@ -5457,7 +5438,7 @@ within:
5457
5438
  }
5458
5439
  };
5459
5440
 
5460
- // assets/js/app.js
5441
+ // js/app.js
5461
5442
  var import_nprogress = __toESM(require_nprogress());
5462
5443
  var _a;
5463
5444
  var Hooks2 = (_a = window.Hooks) != null ? _a : {};
@@ -5487,7 +5468,8 @@ within:
5487
5468
  var csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
5488
5469
  var liveSocket = new LiveSocket("/live", Socket, {
5489
5470
  hooks: Hooks2,
5490
- params: { _csrf_token: csrfToken }
5471
+ params: { _csrf_token: csrfToken },
5472
+ uploaders: window.Uploaders || {}
5491
5473
  });
5492
5474
  window.addEventListener("phx:page-loading-start", (info) => import_nprogress.default.start());
5493
5475
  window.addEventListener("phx:page-loading-stop", (info) => import_nprogress.default.done());