From 9f2de5f5dd1ac5f15ae6f2b763dff8f2e275d1b9 Mon Sep 17 00:00:00 2001 From: Christopher Lang Date: Sun, 17 Nov 2024 22:12:47 -0800 Subject: [PATCH] Add start of Form file upload support, addresses #67. Still needs error checking, validation, and cleanup of files. --- tetra/components/base.py | 38 ++++++++++++++- tetra/js/tetra.core.js | 64 ++++++++++++++++++++------ tetra/static/tetra/js/tetra.js | 51 +++++++++++++++----- tetra/static/tetra/js/tetra.js.map | 4 +- tetra/static/tetra/js/tetra.min.js | 2 +- tetra/static/tetra/js/tetra.min.js.map | 6 +-- tetra/views.py | 19 ++++++-- 7 files changed, 146 insertions(+), 38 deletions(-) diff --git a/tetra/components/base.py b/tetra/components/base.py index b9312b5..d3ba142 100644 --- a/tetra/components/base.py +++ b/tetra/components/base.py @@ -1,17 +1,19 @@ import logging from copy import copy -from typing import Optional, Self, Any +from typing import Optional, Self, Any, Dict from types import FunctionType from enum import Enum import inspect import re import itertools +import uuid from weakref import WeakKeyDictionary from functools import wraps from threading import local from django import forms from django.core.exceptions import ImproperlyConfigured +from django.core.files.storage import default_storage from django.db import models from django.db.models import QuerySet from django.forms import Form, modelform_factory, BaseForm, FileField @@ -489,6 +491,8 @@ def make_script(cls, component_var=None) -> str: method_data = copy(method) method_data["endpoint"] = (cls._component_url(method["name"]),) component_server_methods.append(method_data) + + component_server_methods.append({"name": "_upload_temp_file", "endpoint": cls._component_url("_upload_temp_file")}) if not component_var: component_var = cls.script if cls.has_script() else "{}" return render_to_string( @@ -725,6 +729,7 @@ class FormComponent(Component, metaclass=FormComponentMetaClass): form_class: type(forms.BaseForm) = None form_submitted: bool = False form_errors: dict = {} # TODO: make protected + include in render context + form_temp_files: dict = {} _form: Form = None @@ -762,7 +767,10 @@ def _add_alpine_models_to_fields(self, form) -> None: for field_name, field in form.fields.items(): if field_name in self._public_properties: if isinstance(field, FileField): - pass + form.fields[field_name].widget.attrs.update({"@change": "_uploadFile"}) + if hasattr(field, "temp_file"): + # TODO: Check if we need to send back the temp file name and which attribute to use, might not be necessary + form.fields[field_name].widget.attrs.update({"data-tetra-temp-file": field.temp_file}) else: # form.fields[field_name].initial = getattr(self, field_name) form.fields[field_name].widget.attrs.update({"x-model": field_name}) @@ -802,6 +810,17 @@ def submit(self) -> None: The component will validate the data against the form, and if the form is valid, it will call form_valid(), else form_invalid(). """ + + # find all temporary files in the form and read them and write to the form fields + for field_name, file_details in self.form_temp_files.items(): + if file_details: + storage = self._form.fields[field_name].storage if hasattr(self._form.fields[field_name], 'storage') else default_storage + with storage.open(file_details["temp_name"], 'rb') as file: + storage.save(file_details["original_name"], file) + # TODO: Add error checking and double check the form value is being set correctly + storage.delete(file_details["temp_name"]) + self._form.fields[field_name].initial = file_details["original_name"] + self.form_submitted = True if self._form.is_valid(): @@ -816,6 +835,21 @@ def submit(self) -> None: setattr(self, attr, TetraJSONEncoder().default(value)) self.form_invalid(self._form) + @public + def _upload_temp_file(self, form_field, original_name, file) -> str | None: + """Uploads a file to the server temporarily.""" + # TODO: Add validation + if file and form_field in self._form.fields: + temp_file_name = f"tetra_temp_upload/{uuid.uuid4()}" + storage = self._form.fields[form_field].storage if hasattr(self._form.fields[form_field], 'storage') else default_storage + storage.save(temp_file_name, file) + # TODO: Add error checking, double check this - it seems like we need call setattr as well as setting directly? + self.form_temp_files[form_field] = dict(temp_name = temp_file_name, original_name = original_name) + setattr(self, self.form_temp_files[form_field]["temp_name"], temp_file_name) + setattr(self, self.form_temp_files[form_field]["original_name"], original_name) + return temp_file_name + return None + def clear(self): """Clears the form data (sets all values to defaults) and renders the component.""" diff --git a/tetra/js/tetra.core.js b/tetra/js/tetra.core.js index 76aaef7..a0198c3 100644 --- a/tetra/js/tetra.core.js +++ b/tetra/js/tetra.core.js @@ -79,6 +79,19 @@ const Tetra = { window.history.pushState(null, '', url); } }, + _uploadFile(event) { + // TODO: Consider how multiple files can be handled + const file = event.target.files[0]; + const method = '_upload_temp_file'; + const endpoint = this.__serverMethods.find(item => item.name === '_upload_temp_file').endpoint; + const args = [event.target.name, event.target.files[0].name]; + Tetra.callServerMethodWithFile(this, method, endpoint, file, args).then((result) => { + //TODO: Determine if we need to do anything with the resulting filename + //event.target.dataset.tetraTempFileName = result; + //this._updateData(result); + }); + + }, // Tetra private: __initServerWatchers() { this.__serverMethods.forEach(item => { @@ -198,21 +211,9 @@ const Tetra = { }); }, - async callServerMethod(component, methodName, methodEndpoint, args) { - // TODO: error handling - let body = Tetra.getStateWithChildren(component); - body.args = args; - const response = await fetch(methodEndpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRFToken': window.__tetra_csrfToken, - }, - mode: 'same-origin', - body: Tetra.jsonEncode(body), - }); + async handleServerMethodResponse(response, component) { if (response.status === 200) { - const respData = Tetra.jsonDecode(await response.text()); + const respData = Tetra.jsonDecode(await response.text()); if (respData.success) { let loadingResources = []; respData.js.forEach(src => { @@ -249,6 +250,41 @@ const Tetra = { throw new Error(`Server responded with an error ${response.status} (${response.statusText})`); } }, + async callServerMethod(component, methodName, methodEndpoint, args) { + // TODO: error handling + let body = Tetra.getStateWithChildren(component); + body.args = args; + const response = await fetch(methodEndpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': window.__tetra_csrfToken, + }, + mode: 'same-origin', + body: Tetra.jsonEncode(body), + }); + return await this.handleServerMethodResponse(response, component); + }, + + async callServerMethodWithFile(component, methodName, methodEndpoint, file, args) { + // TODO: error handling + let state = Tetra.getStateWithChildren(component); + state.args = args; + let formData = new FormData(); + formData.append('file', file); + formData.append('state', Tetra.jsonEncode(state)); + //body.args = args; + const response = await fetch(methodEndpoint, { + method: 'POST', + headers: { + //'Content-Type': 'application/json', + 'X-CSRFToken': window.__tetra_csrfToken, + }, + mode: 'same-origin', + body: formData, + }); + return await this.handleServerMethodResponse(response, component); + }, jsonReplacer(key, value) { if (value instanceof Date) { diff --git a/tetra/static/tetra/js/tetra.js b/tetra/static/tetra/js/tetra.js index 28b12d6..060332c 100644 --- a/tetra/static/tetra/js/tetra.js +++ b/tetra/static/tetra/js/tetra.js @@ -75,6 +75,14 @@ window.history.pushState(null, "", url); } }, + _uploadFile(event) { + const file = event.target.files[0]; + const method = "_upload_temp_file"; + const endpoint = this.__serverMethods.find((item) => item.name === "_upload_temp_file").endpoint; + const args = [event.target.name, event.target.files[0].name]; + Tetra.callServerMethodWithFile(this, method, endpoint, file, args).then((result) => { + }); + }, __initServerWatchers() { this.__serverMethods.forEach((item) => { if (item.watch) { @@ -186,18 +194,7 @@ document.head.appendChild(link); }); }, - async callServerMethod(component, methodName, methodEndpoint, args) { - let body = Tetra.getStateWithChildren(component); - body.args = args; - const response = await fetch(methodEndpoint, { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-CSRFToken": window.__tetra_csrfToken - }, - mode: "same-origin", - body: Tetra.jsonEncode(body) - }); + async handleServerMethodResponse(response, component) { if (response.status === 200) { const respData = Tetra.jsonDecode(await response.text()); if (respData.success) { @@ -234,6 +231,36 @@ throw new Error(`Server responded with an error ${response.status} (${response.statusText})`); } }, + async callServerMethod(component, methodName, methodEndpoint, args) { + let body = Tetra.getStateWithChildren(component); + body.args = args; + const response = await fetch(methodEndpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-CSRFToken": window.__tetra_csrfToken + }, + mode: "same-origin", + body: Tetra.jsonEncode(body) + }); + return await this.handleServerMethodResponse(response, component); + }, + async callServerMethodWithFile(component, methodName, methodEndpoint, file, args) { + let state = Tetra.getStateWithChildren(component); + state.args = args; + let formData = new FormData(); + formData.append("file", file); + formData.append("state", Tetra.jsonEncode(state)); + const response = await fetch(methodEndpoint, { + method: "POST", + headers: { + "X-CSRFToken": window.__tetra_csrfToken + }, + mode: "same-origin", + body: formData + }); + return await this.handleServerMethodResponse(response, component); + }, jsonReplacer(key, value) { if (value instanceof Date) { return { diff --git a/tetra/static/tetra/js/tetra.js.map b/tetra/static/tetra/js/tetra.js.map index bc6cbac..3464b13 100644 --- a/tetra/static/tetra/js/tetra.js.map +++ b/tetra/static/tetra/js/tetra.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../../../js/tetra.core.js", "../../../js/tetra.js"], - "sourcesContent": ["const Tetra = {\n init() {\n Alpine.magic('static', () => Tetra.$static);\n },\n\n $static() {\n return (path) => {\n return window.__tetra_staticRoot+path;\n }\n },\n\n alpineComponentMixins() {\n return {\n // Alpine.js lifecycle:\n init() {\n this.$dispatch('tetra:child-component-init', {component: this});\n this.__initServerWatchers();\n if (this.__initInner) {\n this.__initInner();\n }\n },\n destroy() {\n this.$dispatch('tetra:child-component-destroy', {component: this});\n if (this.__destroyInner) {\n this.__destroyInner();\n }\n },\n\n // Tetra built ins:\n _updateHtml(html) {\n Alpine.morph(this.$root, html, {\n updating(el, toEl, childrenOnly, skip) {\n if (toEl.hasAttribute && toEl.hasAttribute('x-data-maintain') && el.hasAttribute && el.hasAttribute('x-data')) {\n toEl.setAttribute('x-data', el.getAttribute('x-data'));\n toEl.removeAttribute('x-data-maintain');\n } else if (toEl.hasAttribute && toEl.hasAttribute('x-data-update') && el.hasAttribute && el.hasAttribute('x-data')) {\n let data = Tetra.jsonDecode(toEl.getAttribute('x-data-update'));\n let old_data = Tetra.jsonDecode(toEl.getAttribute('x-data-update-old'));\n let comp = window.Alpine.$data(el);\n for (const key in data) {\n if (old_data.hasOwnProperty(key) && (old_data[key] !== comp[key])) {\n // If the data that was submitted to the server has since changed we don't overwrite it\n continue\n }\n comp[key] = data[key];\n }\n toEl.setAttribute('x-data', el.getAttribute('x-data'));\n toEl.removeAttribute('x-data-update');\n }\n },\n lookahead: true\n });\n },\n _updateData(data) {\n for (const key in data) {\n this[key] = data[key];\n }\n },\n _removeComponent() {\n this.$root.remove();\n },\n _replaceComponent(html) {\n this.$root.insertAdjacentHTML('afterend', html);\n this.$root.remove();\n },\n _redirect(url) {\n document.location = url;\n },\n _dispatch(name, data) {\n this.$dispatch(name, {\n _component: this,\n ...data\n });\n },\n _pushUrl(url, replace=false) {\n if(replace){\n window.history.replaceState(null, '', url);\n } else {\n window.history.pushState(null, '', url);\n }\n },\n // Tetra private:\n __initServerWatchers() {\n this.__serverMethods.forEach(item => {\n if (item.watch) {\n item.watch.forEach(propName => {\n this.$watch(propName, async (value, oldValue) => {\n await this[item.name](value, oldValue, propName);\n })\n })\n }\n })\n },\n __childComponents: {},\n __rootBind: {\n ['@tetra:child-component-init'](event) {\n event.stopPropagation();\n const comp = event.detail.component;\n if (comp.key === this.key) {\n return\n }\n if (comp.key) {\n this.__childComponents[comp.key] = comp;\n }\n comp._parent = this;\n },\n ['@tetra:child-component-destroy'](event) {\n event.stopPropagation();\n const comp = event.detail.component;\n if (comp.key === this.key) {\n return\n }\n delete this.__childComponents[comp.key];\n event.detail.component._parent = null;\n }\n }\n }\n },\n\n makeServerMethods(serverMethods) {\n const methods = {};\n serverMethods.forEach((serverMethod) => {\n var func = async function(...args) {\n // TODO: ensure only one concurrent?\n return await Tetra.callServerMethod(this, serverMethod.name, serverMethod.endpoint, args)\n }\n if (serverMethod.debounce) {\n func = Tetra.debounce(func, serverMethod.debounce, serverMethod.debounce_immediate)\n } else if (serverMethod.throttle) {\n func = Tetra.throttle(func, serverMethod.throttle, {\n leading: serverMethod.throttle_leading,\n trailing: serverMethod.throttle_trailing\n })\n }\n methods[serverMethod.name] = func;\n })\n return methods\n },\n\n makeAlpineComponent(componentName, script, serverMethods, serverProperties) {\n Alpine.data(\n componentName, \n (initialDataJson) => {\n const {init, destroy, ...script_rest} = script;\n const initialData = Tetra.jsonDecode(initialDataJson);\n const data = {\n componentName,\n __initInner: init,\n __destroyInner: destroy,\n __serverMethods: serverMethods,\n __serverProperties: serverProperties,\n ...(initialData || {}),\n ...script_rest,\n ...Tetra.makeServerMethods(serverMethods),\n ...Tetra.alpineComponentMixins(),\n }\n return data\n }\n )\n },\n\n getStateWithChildren(component) {\n const data = {}\n component.__serverProperties.forEach((key) => {\n data[key] = component[key]\n })\n const r = {\n state: component.__state,\n data: data,\n children: []\n }\n for (const key in component.__childComponents) {\n const comp = component.__childComponents[key];\n r.children.push(Tetra.getStateWithChildren(comp));\n }\n return r;\n },\n\n loadScript(src) {\n return new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = src;\n script.onload = resolve;\n script.onerror = reject;\n document.head.appendChild(script);\n });\n },\n\n loadStyles(href) {\n return new Promise((resolve, reject) => {\n const link = document.createElement('link');\n link.href = href;\n link.rel = 'stylesheet';\n link.type = 'text/css';\n link.onload = resolve;\n link.onerror = reject;\n document.head.appendChild(link);\n });\n },\n\n async callServerMethod(component, methodName, methodEndpoint, args) {\n // TODO: error handling\n let body = Tetra.getStateWithChildren(component);\n body.args = args;\n const response = await fetch(methodEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-CSRFToken': window.__tetra_csrfToken,\n },\n mode: 'same-origin',\n body: Tetra.jsonEncode(body),\n });\n if (response.status === 200) {\n const respData = Tetra.jsonDecode(await response.text()); \n if (respData.success) {\n let loadingResources = [];\n respData.js.forEach(src => {\n if (!document.querySelector(`script[src=\"${CSS.escape(src)}\"]`)) {\n loadingResources.push(Tetra.loadScript(src));\n }\n })\n respData.styles.forEach(src => {\n if (!document.querySelector(`link[href=\"${CSS.escape(src)}\"]`)) {\n loadingResources.push(Tetra.loadStyles(src));\n }\n })\n await Promise.all(loadingResources);\n if (respData.callbacks) {\n respData.callbacks.forEach((item) => {\n // iterate down path to callback\n let obj = component;\n item.callback.forEach((name, i) => {\n if (i === item.callback.length-1) {\n obj[name](...item.args);\n } else {\n obj = obj[name];\n console.log(name, obj)\n }\n })\n })\n }\n return respData.result;\n } else {\n // TODO: better errors\n throw new Error('Error processing public method');\n }\n } else {\n throw new Error(`Server responded with an error ${response.status} (${response.statusText})`);\n }\n },\n\n jsonReplacer(key, value) {\n if (value instanceof Date) {\n return {\n __type: 'datetime',\n value: value.toISOString(),\n };\n } else if (value instanceof Set) {\n return {\n __type: 'set',\n value: Array.from(value)\n };\n } \n return value;\n },\n\n jsonReviver(key, value) {\n if (value && typeof value === 'object' && value.__type) {\n if (value.__type === 'datetime') {\n return new Date(value);\n } else if (value.__type === 'set') {\n return new Set(value);\n }\n }\n return value\n },\n\n jsonEncode(obj) {\n return JSON.stringify(obj, Tetra.jsonReplacer);\n },\n\n jsonDecode(s) {\n var obj= JSON.parse(s, Tetra.jsonReviver);\n return obj\n },\n\n debounce(func, wait, immediate) {\n var timeout\n return function() {\n var context = this, args = arguments\n var later = function () {\n timeout = null\n if (!immediate) func.apply(context, args);\n }\n var callNow = immediate && !timeout;\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n if (callNow) func.apply(context, args);\n }\n },\n\n throttle(func, wait, options) {\n // From Underscore.js\n // https://underscorejs.org\n // (c) 2009-2022 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and\n // Investigative Reporters & Editors\n // Underscore may be freely distributed under the MIT license.\n // --\n // Returns a function, that, when invoked, will only be triggered at most once\n // during a given window of time. Normally, the throttled function will run\n // as much as it can, without ever going more than once per `wait` duration;\n // but if you'd like to disable the execution on the leading edge, pass\n // `{leading: false}`. To disable execution on the trailing edge, ditto.\n var timeout, context, args, result;\n var previous = 0;\n if (!options) options = {};\n\n var later = function() {\n previous = options.leading === false ? 0 : now();\n timeout = null;\n result = func.apply(context, args);\n if (!timeout) context = args = null;\n };\n\n var throttled = function() {\n var _now = new Date().getTime();\n if (!previous && options.leading === false) previous = _now;\n var remaining = wait - (_now - previous);\n context = this;\n args = arguments;\n if (remaining <= 0 || remaining > wait) {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n previous = _now;\n result = func.apply(context, args);\n if (!timeout) context = args = null;\n } else if (!timeout && options.trailing !== false) {\n timeout = setTimeout(later, remaining);\n }\n return result;\n };\n\n throttled.cancel = function() {\n clearTimeout(timeout);\n previous = 0;\n timeout = context = args = null;\n };\n\n return throttled;\n }\n\n}\n\nexport default Tetra;\n", "import Tetra from './tetra.core'\n\nwindow.Tetra = Tetra;\nwindow.document.addEventListener('alpine:init', () => {\n Tetra.init();\n})\n"], - "mappings": "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", + "sourcesContent": ["const Tetra = {\n init() {\n Alpine.magic('static', () => Tetra.$static);\n },\n\n $static() {\n return (path) => {\n return window.__tetra_staticRoot+path;\n }\n },\n\n alpineComponentMixins() {\n return {\n // Alpine.js lifecycle:\n init() {\n this.$dispatch('tetra:child-component-init', {component: this});\n this.__initServerWatchers();\n if (this.__initInner) {\n this.__initInner();\n }\n },\n destroy() {\n this.$dispatch('tetra:child-component-destroy', {component: this});\n if (this.__destroyInner) {\n this.__destroyInner();\n }\n },\n\n // Tetra built ins:\n _updateHtml(html) {\n Alpine.morph(this.$root, html, {\n updating(el, toEl, childrenOnly, skip) {\n if (toEl.hasAttribute && toEl.hasAttribute('x-data-maintain') && el.hasAttribute && el.hasAttribute('x-data')) {\n toEl.setAttribute('x-data', el.getAttribute('x-data'));\n toEl.removeAttribute('x-data-maintain');\n } else if (toEl.hasAttribute && toEl.hasAttribute('x-data-update') && el.hasAttribute && el.hasAttribute('x-data')) {\n let data = Tetra.jsonDecode(toEl.getAttribute('x-data-update'));\n let old_data = Tetra.jsonDecode(toEl.getAttribute('x-data-update-old'));\n let comp = window.Alpine.$data(el);\n for (const key in data) {\n if (old_data.hasOwnProperty(key) && (old_data[key] !== comp[key])) {\n // If the data that was submitted to the server has since changed we don't overwrite it\n continue\n }\n comp[key] = data[key];\n }\n toEl.setAttribute('x-data', el.getAttribute('x-data'));\n toEl.removeAttribute('x-data-update');\n }\n },\n lookahead: true\n });\n },\n _updateData(data) {\n for (const key in data) {\n this[key] = data[key];\n }\n },\n _removeComponent() {\n this.$root.remove();\n },\n _replaceComponent(html) {\n this.$root.insertAdjacentHTML('afterend', html);\n this.$root.remove();\n },\n _redirect(url) {\n document.location = url;\n },\n _dispatch(name, data) {\n this.$dispatch(name, {\n _component: this,\n ...data\n });\n },\n _pushUrl(url, replace=false) {\n if(replace){\n window.history.replaceState(null, '', url);\n } else {\n window.history.pushState(null, '', url);\n }\n },\n _uploadFile(event) {\n // TODO: Consider how multiple files can be handled\n const file = event.target.files[0];\n const method = '_upload_temp_file';\n const endpoint = this.__serverMethods.find(item => item.name === '_upload_temp_file').endpoint;\n const args = [event.target.name, event.target.files[0].name];\n Tetra.callServerMethodWithFile(this, method, endpoint, file, args).then((result) => {\n //TODO: Determine if we need to do anything with the resulting filename\n //event.target.dataset.tetraTempFileName = result;\n //this._updateData(result);\n });\n\n },\n // Tetra private:\n __initServerWatchers() {\n this.__serverMethods.forEach(item => {\n if (item.watch) {\n item.watch.forEach(propName => {\n this.$watch(propName, async (value, oldValue) => {\n await this[item.name](value, oldValue, propName);\n })\n })\n }\n })\n },\n __childComponents: {},\n __rootBind: {\n ['@tetra:child-component-init'](event) {\n event.stopPropagation();\n const comp = event.detail.component;\n if (comp.key === this.key) {\n return\n }\n if (comp.key) {\n this.__childComponents[comp.key] = comp;\n }\n comp._parent = this;\n },\n ['@tetra:child-component-destroy'](event) {\n event.stopPropagation();\n const comp = event.detail.component;\n if (comp.key === this.key) {\n return\n }\n delete this.__childComponents[comp.key];\n event.detail.component._parent = null;\n }\n }\n }\n },\n\n makeServerMethods(serverMethods) {\n const methods = {};\n serverMethods.forEach((serverMethod) => {\n var func = async function(...args) {\n // TODO: ensure only one concurrent?\n return await Tetra.callServerMethod(this, serverMethod.name, serverMethod.endpoint, args)\n }\n if (serverMethod.debounce) {\n func = Tetra.debounce(func, serverMethod.debounce, serverMethod.debounce_immediate)\n } else if (serverMethod.throttle) {\n func = Tetra.throttle(func, serverMethod.throttle, {\n leading: serverMethod.throttle_leading,\n trailing: serverMethod.throttle_trailing\n })\n }\n methods[serverMethod.name] = func;\n })\n return methods\n },\n\n makeAlpineComponent(componentName, script, serverMethods, serverProperties) {\n Alpine.data(\n componentName, \n (initialDataJson) => {\n const {init, destroy, ...script_rest} = script;\n const initialData = Tetra.jsonDecode(initialDataJson);\n const data = {\n componentName,\n __initInner: init,\n __destroyInner: destroy,\n __serverMethods: serverMethods,\n __serverProperties: serverProperties,\n ...(initialData || {}),\n ...script_rest,\n ...Tetra.makeServerMethods(serverMethods),\n ...Tetra.alpineComponentMixins(),\n }\n return data\n }\n )\n },\n\n getStateWithChildren(component) {\n const data = {}\n component.__serverProperties.forEach((key) => {\n data[key] = component[key]\n })\n const r = {\n state: component.__state,\n data: data,\n children: []\n }\n for (const key in component.__childComponents) {\n const comp = component.__childComponents[key];\n r.children.push(Tetra.getStateWithChildren(comp));\n }\n return r;\n },\n\n loadScript(src) {\n return new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = src;\n script.onload = resolve;\n script.onerror = reject;\n document.head.appendChild(script);\n });\n },\n\n loadStyles(href) {\n return new Promise((resolve, reject) => {\n const link = document.createElement('link');\n link.href = href;\n link.rel = 'stylesheet';\n link.type = 'text/css';\n link.onload = resolve;\n link.onerror = reject;\n document.head.appendChild(link);\n });\n },\n\n async handleServerMethodResponse(response, component) {\n if (response.status === 200) {\n const respData = Tetra.jsonDecode(await response.text());\n if (respData.success) {\n let loadingResources = [];\n respData.js.forEach(src => {\n if (!document.querySelector(`script[src=\"${CSS.escape(src)}\"]`)) {\n loadingResources.push(Tetra.loadScript(src));\n }\n })\n respData.styles.forEach(src => {\n if (!document.querySelector(`link[href=\"${CSS.escape(src)}\"]`)) {\n loadingResources.push(Tetra.loadStyles(src));\n }\n })\n await Promise.all(loadingResources);\n if (respData.callbacks) {\n respData.callbacks.forEach((item) => {\n // iterate down path to callback\n let obj = component;\n item.callback.forEach((name, i) => {\n if (i === item.callback.length-1) {\n obj[name](...item.args);\n } else {\n obj = obj[name];\n console.log(name, obj)\n }\n })\n })\n }\n return respData.result;\n } else {\n // TODO: better errors\n throw new Error('Error processing public method');\n }\n } else {\n throw new Error(`Server responded with an error ${response.status} (${response.statusText})`);\n }\n },\n async callServerMethod(component, methodName, methodEndpoint, args) {\n // TODO: error handling\n let body = Tetra.getStateWithChildren(component);\n body.args = args;\n const response = await fetch(methodEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-CSRFToken': window.__tetra_csrfToken,\n },\n mode: 'same-origin',\n body: Tetra.jsonEncode(body),\n });\n return await this.handleServerMethodResponse(response, component);\n },\n\n async callServerMethodWithFile(component, methodName, methodEndpoint, file, args) {\n // TODO: error handling\n let state = Tetra.getStateWithChildren(component);\n state.args = args;\n let formData = new FormData();\n formData.append('file', file);\n formData.append('state', Tetra.jsonEncode(state));\n //body.args = args;\n const response = await fetch(methodEndpoint, {\n method: 'POST',\n headers: {\n //'Content-Type': 'application/json',\n 'X-CSRFToken': window.__tetra_csrfToken,\n },\n mode: 'same-origin',\n body: formData,\n });\n return await this.handleServerMethodResponse(response, component);\n },\n\n jsonReplacer(key, value) {\n if (value instanceof Date) {\n return {\n __type: 'datetime',\n value: value.toISOString(),\n };\n } else if (value instanceof Set) {\n return {\n __type: 'set',\n value: Array.from(value)\n };\n } \n return value;\n },\n\n jsonReviver(key, value) {\n if (value && typeof value === 'object' && value.__type) {\n if (value.__type === 'datetime') {\n return new Date(value);\n } else if (value.__type === 'set') {\n return new Set(value);\n }\n }\n return value\n },\n\n jsonEncode(obj) {\n return JSON.stringify(obj, Tetra.jsonReplacer);\n },\n\n jsonDecode(s) {\n var obj= JSON.parse(s, Tetra.jsonReviver);\n return obj\n },\n\n debounce(func, wait, immediate) {\n var timeout\n return function() {\n var context = this, args = arguments\n var later = function () {\n timeout = null\n if (!immediate) func.apply(context, args);\n }\n var callNow = immediate && !timeout;\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n if (callNow) func.apply(context, args);\n }\n },\n\n throttle(func, wait, options) {\n // From Underscore.js\n // https://underscorejs.org\n // (c) 2009-2022 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and\n // Investigative Reporters & Editors\n // Underscore may be freely distributed under the MIT license.\n // --\n // Returns a function, that, when invoked, will only be triggered at most once\n // during a given window of time. Normally, the throttled function will run\n // as much as it can, without ever going more than once per `wait` duration;\n // but if you'd like to disable the execution on the leading edge, pass\n // `{leading: false}`. To disable execution on the trailing edge, ditto.\n var timeout, context, args, result;\n var previous = 0;\n if (!options) options = {};\n\n var later = function() {\n previous = options.leading === false ? 0 : now();\n timeout = null;\n result = func.apply(context, args);\n if (!timeout) context = args = null;\n };\n\n var throttled = function() {\n var _now = new Date().getTime();\n if (!previous && options.leading === false) previous = _now;\n var remaining = wait - (_now - previous);\n context = this;\n args = arguments;\n if (remaining <= 0 || remaining > wait) {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n previous = _now;\n result = func.apply(context, args);\n if (!timeout) context = args = null;\n } else if (!timeout && options.trailing !== false) {\n timeout = setTimeout(later, remaining);\n }\n return result;\n };\n\n throttled.cancel = function() {\n clearTimeout(timeout);\n previous = 0;\n timeout = context = args = null;\n };\n\n return throttled;\n }\n\n}\n\nexport default Tetra;\n", "import Tetra from './tetra.core'\n\nwindow.Tetra = Tetra;\nwindow.document.addEventListener('alpine:init', () => {\n Tetra.init();\n})\n"], + "mappings": "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", "names": [] } diff --git a/tetra/static/tetra/js/tetra.min.js b/tetra/static/tetra/js/tetra.min.js index f406a9f..527bc42 100644 --- a/tetra/static/tetra/js/tetra.min.js +++ b/tetra/static/tetra/js/tetra.min.js @@ -1,2 +1,2 @@ -(()=>{var s={init(){Alpine.magic("static",()=>s.$static)},$static(){return e=>window.__tetra_staticRoot+e},alpineComponentMixins(){return{init(){this.$dispatch("tetra:child-component-init",{component:this}),this.__initServerWatchers(),this.__initInner&&this.__initInner()},destroy(){this.$dispatch("tetra:child-component-destroy",{component:this}),this.__destroyInner&&this.__destroyInner()},_updateHtml(e){Alpine.morph(this.$root,e,{updating(t,r,n,i){if(r.hasAttribute&&r.hasAttribute("x-data-maintain")&&t.hasAttribute&&t.hasAttribute("x-data"))r.setAttribute("x-data",t.getAttribute("x-data")),r.removeAttribute("x-data-maintain");else if(r.hasAttribute&&r.hasAttribute("x-data-update")&&t.hasAttribute&&t.hasAttribute("x-data")){let a=s.jsonDecode(r.getAttribute("x-data-update")),c=s.jsonDecode(r.getAttribute("x-data-update-old")),d=window.Alpine.$data(t);for(let o in a)c.hasOwnProperty(o)&&c[o]!==d[o]||(d[o]=a[o]);r.setAttribute("x-data",t.getAttribute("x-data")),r.removeAttribute("x-data-update")}},lookahead:!0})},_updateData(e){for(let t in e)this[t]=e[t]},_removeComponent(){this.$root.remove()},_replaceComponent(e){this.$root.insertAdjacentHTML("afterend",e),this.$root.remove()},_redirect(e){document.location=e},_dispatch(e,t){this.$dispatch(e,{_component:this,...t})},_pushUrl(e,t=!1){t?window.history.replaceState(null,"",e):window.history.pushState(null,"",e)},__initServerWatchers(){this.__serverMethods.forEach(e=>{e.watch&&e.watch.forEach(t=>{this.$watch(t,async(r,n)=>{await this[e.name](r,n,t)})})})},__childComponents:{},__rootBind:{["@tetra:child-component-init"](e){e.stopPropagation();let t=e.detail.component;t.key!==this.key&&(t.key&&(this.__childComponents[t.key]=t),t._parent=this)},["@tetra:child-component-destroy"](e){e.stopPropagation();let t=e.detail.component;t.key!==this.key&&(delete this.__childComponents[t.key],e.detail.component._parent=null)}}}},makeServerMethods(e){let t={};return e.forEach(r=>{var n=async function(...i){return await s.callServerMethod(this,r.name,r.endpoint,i)};r.debounce?n=s.debounce(n,r.debounce,r.debounce_immediate):r.throttle&&(n=s.throttle(n,r.throttle,{leading:r.throttle_leading,trailing:r.throttle_trailing})),t[r.name]=n}),t},makeAlpineComponent(e,t,r,n){Alpine.data(e,i=>{let{init:a,destroy:c,...d}=t,o=s.jsonDecode(i);return{componentName:e,__initInner:a,__destroyInner:c,__serverMethods:r,__serverProperties:n,...o||{},...d,...s.makeServerMethods(r),...s.alpineComponentMixins()}})},getStateWithChildren(e){let t={};e.__serverProperties.forEach(n=>{t[n]=e[n]});let r={state:e.__state,data:t,children:[]};for(let n in e.__childComponents){let i=e.__childComponents[n];r.children.push(s.getStateWithChildren(i))}return r},loadScript(e){return new Promise((t,r)=>{let n=document.createElement("script");n.src=e,n.onload=t,n.onerror=r,document.head.appendChild(n)})},loadStyles(e){return new Promise((t,r)=>{let n=document.createElement("link");n.href=e,n.rel="stylesheet",n.type="text/css",n.onload=t,n.onerror=r,document.head.appendChild(n)})},async callServerMethod(e,t,r,n){let i=s.getStateWithChildren(e);i.args=n;let a=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json","X-CSRFToken":window.__tetra_csrfToken},mode:"same-origin",body:s.jsonEncode(i)});if(a.status===200){let c=s.jsonDecode(await a.text());if(c.success){let d=[];return c.js.forEach(o=>{document.querySelector(`script[src="${CSS.escape(o)}"]`)||d.push(s.loadScript(o))}),c.styles.forEach(o=>{document.querySelector(`link[href="${CSS.escape(o)}"]`)||d.push(s.loadStyles(o))}),await Promise.all(d),c.callbacks&&c.callbacks.forEach(o=>{let l=e;o.callback.forEach((h,p)=>{p===o.callback.length-1?l[h](...o.args):(l=l[h],console.log(h,l))})}),c.result}else throw new Error("Error processing public method")}else throw new Error(`Server responded with an error ${a.status} (${a.statusText})`)},jsonReplacer(e,t){return t instanceof Date?{__type:"datetime",value:t.toISOString()}:t instanceof Set?{__type:"set",value:Array.from(t)}:t},jsonReviver(e,t){if(t&&typeof t=="object"&&t.__type){if(t.__type==="datetime")return new Date(t);if(t.__type==="set")return new Set(t)}return t},jsonEncode(e){return JSON.stringify(e,s.jsonReplacer)},jsonDecode(e){var t=JSON.parse(e,s.jsonReviver);return t},debounce(e,t,r){var n;return function(){var i=this,a=arguments,c=function(){n=null,r||e.apply(i,a)},d=r&&!n;clearTimeout(n),n=setTimeout(c,t),d&&e.apply(i,a)}},throttle(e,t,r){var n,i,a,c,d=0;r||(r={});var o=function(){d=r.leading===!1?0:now(),n=null,c=e.apply(i,a),n||(i=a=null)},l=function(){var h=new Date().getTime();!d&&r.leading===!1&&(d=h);var p=t-(h-d);return i=this,a=arguments,p<=0||p>t?(n&&(clearTimeout(n),n=null),d=h,c=e.apply(i,a),n||(i=a=null)):!n&&r.trailing!==!1&&(n=setTimeout(o,p)),c};return l.cancel=function(){clearTimeout(n),d=0,n=i=a=null},l}},u=s;window.Tetra=u;window.document.addEventListener("alpine:init",()=>{u.init()});})(); +(()=>{var o={init(){Alpine.magic("static",()=>o.$static)},$static(){return t=>window.__tetra_staticRoot+t},alpineComponentMixins(){return{init(){this.$dispatch("tetra:child-component-init",{component:this}),this.__initServerWatchers(),this.__initInner&&this.__initInner()},destroy(){this.$dispatch("tetra:child-component-destroy",{component:this}),this.__destroyInner&&this.__destroyInner()},_updateHtml(t){Alpine.morph(this.$root,t,{updating(e,n,r,i){if(n.hasAttribute&&n.hasAttribute("x-data-maintain")&&e.hasAttribute&&e.hasAttribute("x-data"))n.setAttribute("x-data",e.getAttribute("x-data")),n.removeAttribute("x-data-maintain");else if(n.hasAttribute&&n.hasAttribute("x-data-update")&&e.hasAttribute&&e.hasAttribute("x-data")){let a=o.jsonDecode(n.getAttribute("x-data-update")),s=o.jsonDecode(n.getAttribute("x-data-update-old")),c=window.Alpine.$data(e);for(let d in a)s.hasOwnProperty(d)&&s[d]!==c[d]||(c[d]=a[d]);n.setAttribute("x-data",e.getAttribute("x-data")),n.removeAttribute("x-data-update")}},lookahead:!0})},_updateData(t){for(let e in t)this[e]=t[e]},_removeComponent(){this.$root.remove()},_replaceComponent(t){this.$root.insertAdjacentHTML("afterend",t),this.$root.remove()},_redirect(t){document.location=t},_dispatch(t,e){this.$dispatch(t,{_component:this,...e})},_pushUrl(t,e=!1){e?window.history.replaceState(null,"",t):window.history.pushState(null,"",t)},_uploadFile(t){let e=t.target.files[0],n="_upload_temp_file",r=this.__serverMethods.find(a=>a.name==="_upload_temp_file").endpoint,i=[t.target.name,t.target.files[0].name];o.callServerMethodWithFile(this,n,r,e,i).then(a=>{})},__initServerWatchers(){this.__serverMethods.forEach(t=>{t.watch&&t.watch.forEach(e=>{this.$watch(e,async(n,r)=>{await this[t.name](n,r,e)})})})},__childComponents:{},__rootBind:{["@tetra:child-component-init"](t){t.stopPropagation();let e=t.detail.component;e.key!==this.key&&(e.key&&(this.__childComponents[e.key]=e),e._parent=this)},["@tetra:child-component-destroy"](t){t.stopPropagation();let e=t.detail.component;e.key!==this.key&&(delete this.__childComponents[e.key],t.detail.component._parent=null)}}}},makeServerMethods(t){let e={};return t.forEach(n=>{var r=async function(...i){return await o.callServerMethod(this,n.name,n.endpoint,i)};n.debounce?r=o.debounce(r,n.debounce,n.debounce_immediate):n.throttle&&(r=o.throttle(r,n.throttle,{leading:n.throttle_leading,trailing:n.throttle_trailing})),e[n.name]=r}),e},makeAlpineComponent(t,e,n,r){Alpine.data(t,i=>{let{init:a,destroy:s,...c}=e,d=o.jsonDecode(i);return{componentName:t,__initInner:a,__destroyInner:s,__serverMethods:n,__serverProperties:r,...d||{},...c,...o.makeServerMethods(n),...o.alpineComponentMixins()}})},getStateWithChildren(t){let e={};t.__serverProperties.forEach(r=>{e[r]=t[r]});let n={state:t.__state,data:e,children:[]};for(let r in t.__childComponents){let i=t.__childComponents[r];n.children.push(o.getStateWithChildren(i))}return n},loadScript(t){return new Promise((e,n)=>{let r=document.createElement("script");r.src=t,r.onload=e,r.onerror=n,document.head.appendChild(r)})},loadStyles(t){return new Promise((e,n)=>{let r=document.createElement("link");r.href=t,r.rel="stylesheet",r.type="text/css",r.onload=e,r.onerror=n,document.head.appendChild(r)})},async handleServerMethodResponse(t,e){if(t.status===200){let n=o.jsonDecode(await t.text());if(n.success){let r=[];return n.js.forEach(i=>{document.querySelector(`script[src="${CSS.escape(i)}"]`)||r.push(o.loadScript(i))}),n.styles.forEach(i=>{document.querySelector(`link[href="${CSS.escape(i)}"]`)||r.push(o.loadStyles(i))}),await Promise.all(r),n.callbacks&&n.callbacks.forEach(i=>{let a=e;i.callback.forEach((s,c)=>{c===i.callback.length-1?a[s](...i.args):(a=a[s],console.log(s,a))})}),n.result}else throw new Error("Error processing public method")}else throw new Error(`Server responded with an error ${t.status} (${t.statusText})`)},async callServerMethod(t,e,n,r){let i=o.getStateWithChildren(t);i.args=r;let a=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json","X-CSRFToken":window.__tetra_csrfToken},mode:"same-origin",body:o.jsonEncode(i)});return await this.handleServerMethodResponse(a,t)},async callServerMethodWithFile(t,e,n,r,i){let a=o.getStateWithChildren(t);a.args=i;let s=new FormData;s.append("file",r),s.append("state",o.jsonEncode(a));let c=await fetch(n,{method:"POST",headers:{"X-CSRFToken":window.__tetra_csrfToken},mode:"same-origin",body:s});return await this.handleServerMethodResponse(c,t)},jsonReplacer(t,e){return e instanceof Date?{__type:"datetime",value:e.toISOString()}:e instanceof Set?{__type:"set",value:Array.from(e)}:e},jsonReviver(t,e){if(e&&typeof e=="object"&&e.__type){if(e.__type==="datetime")return new Date(e);if(e.__type==="set")return new Set(e)}return e},jsonEncode(t){return JSON.stringify(t,o.jsonReplacer)},jsonDecode(t){var e=JSON.parse(t,o.jsonReviver);return e},debounce(t,e,n){var r;return function(){var i=this,a=arguments,s=function(){r=null,n||t.apply(i,a)},c=n&&!r;clearTimeout(r),r=setTimeout(s,e),c&&t.apply(i,a)}},throttle(t,e,n){var r,i,a,s,c=0;n||(n={});var d=function(){c=n.leading===!1?0:now(),r=null,s=t.apply(i,a),r||(i=a=null)},l=function(){var h=new Date().getTime();!c&&n.leading===!1&&(c=h);var p=e-(h-c);return i=this,a=arguments,p<=0||p>e?(r&&(clearTimeout(r),r=null),c=h,s=t.apply(i,a),r||(i=a=null)):!r&&n.trailing!==!1&&(r=setTimeout(d,p)),s};return l.cancel=function(){clearTimeout(r),c=0,r=i=a=null},l}},u=o;window.Tetra=u;window.document.addEventListener("alpine:init",()=>{u.init()});})(); //# sourceMappingURL=tetra.min.js.map diff --git a/tetra/static/tetra/js/tetra.min.js.map b/tetra/static/tetra/js/tetra.min.js.map index d3c8ba4..ea8c1e9 100644 --- a/tetra/static/tetra/js/tetra.min.js.map +++ b/tetra/static/tetra/js/tetra.min.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../../../js/tetra.core.js", "../../../js/tetra.js"], - "sourcesContent": ["const Tetra = {\n init() {\n Alpine.magic('static', () => Tetra.$static);\n },\n\n $static() {\n return (path) => {\n return window.__tetra_staticRoot+path;\n }\n },\n\n alpineComponentMixins() {\n return {\n // Alpine.js lifecycle:\n init() {\n this.$dispatch('tetra:child-component-init', {component: this});\n this.__initServerWatchers();\n if (this.__initInner) {\n this.__initInner();\n }\n },\n destroy() {\n this.$dispatch('tetra:child-component-destroy', {component: this});\n if (this.__destroyInner) {\n this.__destroyInner();\n }\n },\n\n // Tetra built ins:\n _updateHtml(html) {\n Alpine.morph(this.$root, html, {\n updating(el, toEl, childrenOnly, skip) {\n if (toEl.hasAttribute && toEl.hasAttribute('x-data-maintain') && el.hasAttribute && el.hasAttribute('x-data')) {\n toEl.setAttribute('x-data', el.getAttribute('x-data'));\n toEl.removeAttribute('x-data-maintain');\n } else if (toEl.hasAttribute && toEl.hasAttribute('x-data-update') && el.hasAttribute && el.hasAttribute('x-data')) {\n let data = Tetra.jsonDecode(toEl.getAttribute('x-data-update'));\n let old_data = Tetra.jsonDecode(toEl.getAttribute('x-data-update-old'));\n let comp = window.Alpine.$data(el);\n for (const key in data) {\n if (old_data.hasOwnProperty(key) && (old_data[key] !== comp[key])) {\n // If the data that was submitted to the server has since changed we don't overwrite it\n continue\n }\n comp[key] = data[key];\n }\n toEl.setAttribute('x-data', el.getAttribute('x-data'));\n toEl.removeAttribute('x-data-update');\n }\n },\n lookahead: true\n });\n },\n _updateData(data) {\n for (const key in data) {\n this[key] = data[key];\n }\n },\n _removeComponent() {\n this.$root.remove();\n },\n _replaceComponent(html) {\n this.$root.insertAdjacentHTML('afterend', html);\n this.$root.remove();\n },\n _redirect(url) {\n document.location = url;\n },\n _dispatch(name, data) {\n this.$dispatch(name, {\n _component: this,\n ...data\n });\n },\n _pushUrl(url, replace=false) {\n if(replace){\n window.history.replaceState(null, '', url);\n } else {\n window.history.pushState(null, '', url);\n }\n },\n // Tetra private:\n __initServerWatchers() {\n this.__serverMethods.forEach(item => {\n if (item.watch) {\n item.watch.forEach(propName => {\n this.$watch(propName, async (value, oldValue) => {\n await this[item.name](value, oldValue, propName);\n })\n })\n }\n })\n },\n __childComponents: {},\n __rootBind: {\n ['@tetra:child-component-init'](event) {\n event.stopPropagation();\n const comp = event.detail.component;\n if (comp.key === this.key) {\n return\n }\n if (comp.key) {\n this.__childComponents[comp.key] = comp;\n }\n comp._parent = this;\n },\n ['@tetra:child-component-destroy'](event) {\n event.stopPropagation();\n const comp = event.detail.component;\n if (comp.key === this.key) {\n return\n }\n delete this.__childComponents[comp.key];\n event.detail.component._parent = null;\n }\n }\n }\n },\n\n makeServerMethods(serverMethods) {\n const methods = {};\n serverMethods.forEach((serverMethod) => {\n var func = async function(...args) {\n // TODO: ensure only one concurrent?\n return await Tetra.callServerMethod(this, serverMethod.name, serverMethod.endpoint, args)\n }\n if (serverMethod.debounce) {\n func = Tetra.debounce(func, serverMethod.debounce, serverMethod.debounce_immediate)\n } else if (serverMethod.throttle) {\n func = Tetra.throttle(func, serverMethod.throttle, {\n leading: serverMethod.throttle_leading,\n trailing: serverMethod.throttle_trailing\n })\n }\n methods[serverMethod.name] = func;\n })\n return methods\n },\n\n makeAlpineComponent(componentName, script, serverMethods, serverProperties) {\n Alpine.data(\n componentName, \n (initialDataJson) => {\n const {init, destroy, ...script_rest} = script;\n const initialData = Tetra.jsonDecode(initialDataJson);\n const data = {\n componentName,\n __initInner: init,\n __destroyInner: destroy,\n __serverMethods: serverMethods,\n __serverProperties: serverProperties,\n ...(initialData || {}),\n ...script_rest,\n ...Tetra.makeServerMethods(serverMethods),\n ...Tetra.alpineComponentMixins(),\n }\n return data\n }\n )\n },\n\n getStateWithChildren(component) {\n const data = {}\n component.__serverProperties.forEach((key) => {\n data[key] = component[key]\n })\n const r = {\n state: component.__state,\n data: data,\n children: []\n }\n for (const key in component.__childComponents) {\n const comp = component.__childComponents[key];\n r.children.push(Tetra.getStateWithChildren(comp));\n }\n return r;\n },\n\n loadScript(src) {\n return new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = src;\n script.onload = resolve;\n script.onerror = reject;\n document.head.appendChild(script);\n });\n },\n\n loadStyles(href) {\n return new Promise((resolve, reject) => {\n const link = document.createElement('link');\n link.href = href;\n link.rel = 'stylesheet';\n link.type = 'text/css';\n link.onload = resolve;\n link.onerror = reject;\n document.head.appendChild(link);\n });\n },\n\n async callServerMethod(component, methodName, methodEndpoint, args) {\n // TODO: error handling\n let body = Tetra.getStateWithChildren(component);\n body.args = args;\n const response = await fetch(methodEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-CSRFToken': window.__tetra_csrfToken,\n },\n mode: 'same-origin',\n body: Tetra.jsonEncode(body),\n });\n if (response.status === 200) {\n const respData = Tetra.jsonDecode(await response.text()); \n if (respData.success) {\n let loadingResources = [];\n respData.js.forEach(src => {\n if (!document.querySelector(`script[src=\"${CSS.escape(src)}\"]`)) {\n loadingResources.push(Tetra.loadScript(src));\n }\n })\n respData.styles.forEach(src => {\n if (!document.querySelector(`link[href=\"${CSS.escape(src)}\"]`)) {\n loadingResources.push(Tetra.loadStyles(src));\n }\n })\n await Promise.all(loadingResources);\n if (respData.callbacks) {\n respData.callbacks.forEach((item) => {\n // iterate down path to callback\n let obj = component;\n item.callback.forEach((name, i) => {\n if (i === item.callback.length-1) {\n obj[name](...item.args);\n } else {\n obj = obj[name];\n console.log(name, obj)\n }\n })\n })\n }\n return respData.result;\n } else {\n // TODO: better errors\n throw new Error('Error processing public method');\n }\n } else {\n throw new Error(`Server responded with an error ${response.status} (${response.statusText})`);\n }\n },\n\n jsonReplacer(key, value) {\n if (value instanceof Date) {\n return {\n __type: 'datetime',\n value: value.toISOString(),\n };\n } else if (value instanceof Set) {\n return {\n __type: 'set',\n value: Array.from(value)\n };\n } \n return value;\n },\n\n jsonReviver(key, value) {\n if (value && typeof value === 'object' && value.__type) {\n if (value.__type === 'datetime') {\n return new Date(value);\n } else if (value.__type === 'set') {\n return new Set(value);\n }\n }\n return value\n },\n\n jsonEncode(obj) {\n return JSON.stringify(obj, Tetra.jsonReplacer);\n },\n\n jsonDecode(s) {\n var obj= JSON.parse(s, Tetra.jsonReviver);\n return obj\n },\n\n debounce(func, wait, immediate) {\n var timeout\n return function() {\n var context = this, args = arguments\n var later = function () {\n timeout = null\n if (!immediate) func.apply(context, args);\n }\n var callNow = immediate && !timeout;\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n if (callNow) func.apply(context, args);\n }\n },\n\n throttle(func, wait, options) {\n // From Underscore.js\n // https://underscorejs.org\n // (c) 2009-2022 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and\n // Investigative Reporters & Editors\n // Underscore may be freely distributed under the MIT license.\n // --\n // Returns a function, that, when invoked, will only be triggered at most once\n // during a given window of time. Normally, the throttled function will run\n // as much as it can, without ever going more than once per `wait` duration;\n // but if you'd like to disable the execution on the leading edge, pass\n // `{leading: false}`. To disable execution on the trailing edge, ditto.\n var timeout, context, args, result;\n var previous = 0;\n if (!options) options = {};\n\n var later = function() {\n previous = options.leading === false ? 0 : now();\n timeout = null;\n result = func.apply(context, args);\n if (!timeout) context = args = null;\n };\n\n var throttled = function() {\n var _now = new Date().getTime();\n if (!previous && options.leading === false) previous = _now;\n var remaining = wait - (_now - previous);\n context = this;\n args = arguments;\n if (remaining <= 0 || remaining > wait) {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n previous = _now;\n result = func.apply(context, args);\n if (!timeout) context = args = null;\n } else if (!timeout && options.trailing !== false) {\n timeout = setTimeout(later, remaining);\n }\n return result;\n };\n\n throttled.cancel = function() {\n clearTimeout(timeout);\n previous = 0;\n timeout = context = args = null;\n };\n\n return throttled;\n }\n\n}\n\nexport default Tetra;\n", "import Tetra from './tetra.core'\n\nwindow.Tetra = Tetra;\nwindow.document.addEventListener('alpine:init', () => {\n Tetra.init();\n})\n"], - "mappings": "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", - "names": ["Tetra", "path", "html", "el", "toEl", "childrenOnly", "skip", "data", "old_data", "comp", "key", "url", "name", "replace", "item", "propName", "value", "oldValue", "event", "serverMethods", "methods", "serverMethod", "func", "args", "componentName", "script", "serverProperties", "initialDataJson", "init", "destroy", "script_rest", "initialData", "component", "src", "resolve", "reject", "href", "link", "methodName", "methodEndpoint", "body", "response", "respData", "loadingResources", "obj", "i", "s", "wait", "immediate", "timeout", "context", "later", "callNow", "options", "result", "previous", "throttled", "_now", "remaining", "tetra_core_default", "tetra_core_default"] + "sourcesContent": ["const Tetra = {\n init() {\n Alpine.magic('static', () => Tetra.$static);\n },\n\n $static() {\n return (path) => {\n return window.__tetra_staticRoot+path;\n }\n },\n\n alpineComponentMixins() {\n return {\n // Alpine.js lifecycle:\n init() {\n this.$dispatch('tetra:child-component-init', {component: this});\n this.__initServerWatchers();\n if (this.__initInner) {\n this.__initInner();\n }\n },\n destroy() {\n this.$dispatch('tetra:child-component-destroy', {component: this});\n if (this.__destroyInner) {\n this.__destroyInner();\n }\n },\n\n // Tetra built ins:\n _updateHtml(html) {\n Alpine.morph(this.$root, html, {\n updating(el, toEl, childrenOnly, skip) {\n if (toEl.hasAttribute && toEl.hasAttribute('x-data-maintain') && el.hasAttribute && el.hasAttribute('x-data')) {\n toEl.setAttribute('x-data', el.getAttribute('x-data'));\n toEl.removeAttribute('x-data-maintain');\n } else if (toEl.hasAttribute && toEl.hasAttribute('x-data-update') && el.hasAttribute && el.hasAttribute('x-data')) {\n let data = Tetra.jsonDecode(toEl.getAttribute('x-data-update'));\n let old_data = Tetra.jsonDecode(toEl.getAttribute('x-data-update-old'));\n let comp = window.Alpine.$data(el);\n for (const key in data) {\n if (old_data.hasOwnProperty(key) && (old_data[key] !== comp[key])) {\n // If the data that was submitted to the server has since changed we don't overwrite it\n continue\n }\n comp[key] = data[key];\n }\n toEl.setAttribute('x-data', el.getAttribute('x-data'));\n toEl.removeAttribute('x-data-update');\n }\n },\n lookahead: true\n });\n },\n _updateData(data) {\n for (const key in data) {\n this[key] = data[key];\n }\n },\n _removeComponent() {\n this.$root.remove();\n },\n _replaceComponent(html) {\n this.$root.insertAdjacentHTML('afterend', html);\n this.$root.remove();\n },\n _redirect(url) {\n document.location = url;\n },\n _dispatch(name, data) {\n this.$dispatch(name, {\n _component: this,\n ...data\n });\n },\n _pushUrl(url, replace=false) {\n if(replace){\n window.history.replaceState(null, '', url);\n } else {\n window.history.pushState(null, '', url);\n }\n },\n _uploadFile(event) {\n // TODO: Consider how multiple files can be handled\n const file = event.target.files[0];\n const method = '_upload_temp_file';\n const endpoint = this.__serverMethods.find(item => item.name === '_upload_temp_file').endpoint;\n const args = [event.target.name, event.target.files[0].name];\n Tetra.callServerMethodWithFile(this, method, endpoint, file, args).then((result) => {\n //TODO: Determine if we need to do anything with the resulting filename\n //event.target.dataset.tetraTempFileName = result;\n //this._updateData(result);\n });\n\n },\n // Tetra private:\n __initServerWatchers() {\n this.__serverMethods.forEach(item => {\n if (item.watch) {\n item.watch.forEach(propName => {\n this.$watch(propName, async (value, oldValue) => {\n await this[item.name](value, oldValue, propName);\n })\n })\n }\n })\n },\n __childComponents: {},\n __rootBind: {\n ['@tetra:child-component-init'](event) {\n event.stopPropagation();\n const comp = event.detail.component;\n if (comp.key === this.key) {\n return\n }\n if (comp.key) {\n this.__childComponents[comp.key] = comp;\n }\n comp._parent = this;\n },\n ['@tetra:child-component-destroy'](event) {\n event.stopPropagation();\n const comp = event.detail.component;\n if (comp.key === this.key) {\n return\n }\n delete this.__childComponents[comp.key];\n event.detail.component._parent = null;\n }\n }\n }\n },\n\n makeServerMethods(serverMethods) {\n const methods = {};\n serverMethods.forEach((serverMethod) => {\n var func = async function(...args) {\n // TODO: ensure only one concurrent?\n return await Tetra.callServerMethod(this, serverMethod.name, serverMethod.endpoint, args)\n }\n if (serverMethod.debounce) {\n func = Tetra.debounce(func, serverMethod.debounce, serverMethod.debounce_immediate)\n } else if (serverMethod.throttle) {\n func = Tetra.throttle(func, serverMethod.throttle, {\n leading: serverMethod.throttle_leading,\n trailing: serverMethod.throttle_trailing\n })\n }\n methods[serverMethod.name] = func;\n })\n return methods\n },\n\n makeAlpineComponent(componentName, script, serverMethods, serverProperties) {\n Alpine.data(\n componentName, \n (initialDataJson) => {\n const {init, destroy, ...script_rest} = script;\n const initialData = Tetra.jsonDecode(initialDataJson);\n const data = {\n componentName,\n __initInner: init,\n __destroyInner: destroy,\n __serverMethods: serverMethods,\n __serverProperties: serverProperties,\n ...(initialData || {}),\n ...script_rest,\n ...Tetra.makeServerMethods(serverMethods),\n ...Tetra.alpineComponentMixins(),\n }\n return data\n }\n )\n },\n\n getStateWithChildren(component) {\n const data = {}\n component.__serverProperties.forEach((key) => {\n data[key] = component[key]\n })\n const r = {\n state: component.__state,\n data: data,\n children: []\n }\n for (const key in component.__childComponents) {\n const comp = component.__childComponents[key];\n r.children.push(Tetra.getStateWithChildren(comp));\n }\n return r;\n },\n\n loadScript(src) {\n return new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = src;\n script.onload = resolve;\n script.onerror = reject;\n document.head.appendChild(script);\n });\n },\n\n loadStyles(href) {\n return new Promise((resolve, reject) => {\n const link = document.createElement('link');\n link.href = href;\n link.rel = 'stylesheet';\n link.type = 'text/css';\n link.onload = resolve;\n link.onerror = reject;\n document.head.appendChild(link);\n });\n },\n\n async handleServerMethodResponse(response, component) {\n if (response.status === 200) {\n const respData = Tetra.jsonDecode(await response.text());\n if (respData.success) {\n let loadingResources = [];\n respData.js.forEach(src => {\n if (!document.querySelector(`script[src=\"${CSS.escape(src)}\"]`)) {\n loadingResources.push(Tetra.loadScript(src));\n }\n })\n respData.styles.forEach(src => {\n if (!document.querySelector(`link[href=\"${CSS.escape(src)}\"]`)) {\n loadingResources.push(Tetra.loadStyles(src));\n }\n })\n await Promise.all(loadingResources);\n if (respData.callbacks) {\n respData.callbacks.forEach((item) => {\n // iterate down path to callback\n let obj = component;\n item.callback.forEach((name, i) => {\n if (i === item.callback.length-1) {\n obj[name](...item.args);\n } else {\n obj = obj[name];\n console.log(name, obj)\n }\n })\n })\n }\n return respData.result;\n } else {\n // TODO: better errors\n throw new Error('Error processing public method');\n }\n } else {\n throw new Error(`Server responded with an error ${response.status} (${response.statusText})`);\n }\n },\n async callServerMethod(component, methodName, methodEndpoint, args) {\n // TODO: error handling\n let body = Tetra.getStateWithChildren(component);\n body.args = args;\n const response = await fetch(methodEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-CSRFToken': window.__tetra_csrfToken,\n },\n mode: 'same-origin',\n body: Tetra.jsonEncode(body),\n });\n return await this.handleServerMethodResponse(response, component);\n },\n\n async callServerMethodWithFile(component, methodName, methodEndpoint, file, args) {\n // TODO: error handling\n let state = Tetra.getStateWithChildren(component);\n state.args = args;\n let formData = new FormData();\n formData.append('file', file);\n formData.append('state', Tetra.jsonEncode(state));\n //body.args = args;\n const response = await fetch(methodEndpoint, {\n method: 'POST',\n headers: {\n //'Content-Type': 'application/json',\n 'X-CSRFToken': window.__tetra_csrfToken,\n },\n mode: 'same-origin',\n body: formData,\n });\n return await this.handleServerMethodResponse(response, component);\n },\n\n jsonReplacer(key, value) {\n if (value instanceof Date) {\n return {\n __type: 'datetime',\n value: value.toISOString(),\n };\n } else if (value instanceof Set) {\n return {\n __type: 'set',\n value: Array.from(value)\n };\n } \n return value;\n },\n\n jsonReviver(key, value) {\n if (value && typeof value === 'object' && value.__type) {\n if (value.__type === 'datetime') {\n return new Date(value);\n } else if (value.__type === 'set') {\n return new Set(value);\n }\n }\n return value\n },\n\n jsonEncode(obj) {\n return JSON.stringify(obj, Tetra.jsonReplacer);\n },\n\n jsonDecode(s) {\n var obj= JSON.parse(s, Tetra.jsonReviver);\n return obj\n },\n\n debounce(func, wait, immediate) {\n var timeout\n return function() {\n var context = this, args = arguments\n var later = function () {\n timeout = null\n if (!immediate) func.apply(context, args);\n }\n var callNow = immediate && !timeout;\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n if (callNow) func.apply(context, args);\n }\n },\n\n throttle(func, wait, options) {\n // From Underscore.js\n // https://underscorejs.org\n // (c) 2009-2022 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and\n // Investigative Reporters & Editors\n // Underscore may be freely distributed under the MIT license.\n // --\n // Returns a function, that, when invoked, will only be triggered at most once\n // during a given window of time. Normally, the throttled function will run\n // as much as it can, without ever going more than once per `wait` duration;\n // but if you'd like to disable the execution on the leading edge, pass\n // `{leading: false}`. To disable execution on the trailing edge, ditto.\n var timeout, context, args, result;\n var previous = 0;\n if (!options) options = {};\n\n var later = function() {\n previous = options.leading === false ? 0 : now();\n timeout = null;\n result = func.apply(context, args);\n if (!timeout) context = args = null;\n };\n\n var throttled = function() {\n var _now = new Date().getTime();\n if (!previous && options.leading === false) previous = _now;\n var remaining = wait - (_now - previous);\n context = this;\n args = arguments;\n if (remaining <= 0 || remaining > wait) {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n previous = _now;\n result = func.apply(context, args);\n if (!timeout) context = args = null;\n } else if (!timeout && options.trailing !== false) {\n timeout = setTimeout(later, remaining);\n }\n return result;\n };\n\n throttled.cancel = function() {\n clearTimeout(timeout);\n previous = 0;\n timeout = context = args = null;\n };\n\n return throttled;\n }\n\n}\n\nexport default Tetra;\n", "import Tetra from './tetra.core'\n\nwindow.Tetra = Tetra;\nwindow.document.addEventListener('alpine:init', () => {\n Tetra.init();\n})\n"], + "mappings": "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", + "names": ["Tetra", "path", "html", "el", "toEl", "childrenOnly", "skip", "data", "old_data", "comp", "key", "url", "name", "replace", "event", "file", "method", "endpoint", "item", "args", "result", "propName", "value", "oldValue", "serverMethods", "methods", "serverMethod", "func", "componentName", "script", "serverProperties", "initialDataJson", "init", "destroy", "script_rest", "initialData", "component", "r", "src", "resolve", "reject", "href", "link", "response", "respData", "loadingResources", "obj", "i", "methodName", "methodEndpoint", "body", "state", "formData", "s", "wait", "immediate", "timeout", "context", "later", "callNow", "options", "previous", "throttled", "_now", "remaining", "tetra_core_default", "tetra_core_default"] } diff --git a/tetra/views.py b/tetra/views.py index 70de151..dfe8687 100644 --- a/tetra/views.py +++ b/tetra/views.py @@ -19,10 +19,21 @@ def component_method( if method_name not in (m["name"] for m in Component._public_methods): return HttpResponseNotFound() - try: - data = from_json(request.body.decode()) - except json.decoder.JSONDecodeError: - return HttpResponseBadRequest() + # check if request is form data + if request.content_type == "multipart/form-data": + try: + data = from_json(request.POST["state"]) + if "args" not in data: + data["args"] = [] + data["args"].extend(request.FILES.values()) + except json.decoder.JSONDecodeError: + return HttpResponseBadRequest() + else: + try: + data = from_json(request.body.decode()) + + except json.decoder.JSONDecodeError: + return HttpResponseBadRequest() if not ( isinstance(data, dict) and "args" in data and isinstance(data["args"], list)