weiro/assets/js/controllers/edit_upload.js

233 lines
7 KiB
JavaScript
Raw Normal View History

2026-03-28 10:42:35 +00:00
import feather from "feather-icons/dist/feather.js";
2026-03-25 10:09:57 +00:00
import Handlebars from "handlebars";
import {Controller} from "@hotwired/stimulus";
2026-03-26 11:14:57 +00:00
Handlebars.registerHelper("submit_on", function (id, event) {
return `data-action="${event}->edit-upload#updateProcessor" data-edit-upload-id-param="${id}"`
});
2026-03-25 10:09:57 +00:00
const processorFrame = Handlebars.compile(`
<div class="card mb-3">
2026-03-28 10:42:35 +00:00
<div class="card-header d-flex justify-content-between align-items-center">
2026-03-25 10:09:57 +00:00
<span>{{name}}</span>
2026-03-28 10:42:35 +00:00
<a href="#" class="float-end"
2026-03-26 10:44:20 +00:00
data-action="edit-upload#removeProcessor"
data-edit-upload-id-param="{{id}}"
2026-03-28 10:42:35 +00:00
><i data-feather="x" width="18" height="18"></i></a>
2026-03-25 10:09:57 +00:00
</div>
<div class="card-body">
2026-03-26 11:14:57 +00:00
<form data-role="processor-params" data-params-id="{{id}}">{{{props}}}</form>
2026-03-25 10:09:57 +00:00
</div>
</div>
`);
2026-03-26 10:44:20 +00:00
const processorUIs = {
"shadow": {
2026-03-25 10:09:57 +00:00
label: "Shadow",
2026-03-26 11:14:57 +00:00
template: Handlebars.compile(`
<div class="row mb-3 align-items-center">
<label for="{{id}}_color" class="col-sm col-form-label">Colour</label>
<div class="col-sm">
<input name="color" class="form-control" id="{{id}}_color" type="color" value="{{props.color}}" {{{submit_on id 'change'}}}>
</div>
</div>
<div class="row mb-3">
<label for="{{id}}_offset_y" class="col-sm col-form-label">Offset Y</label>
<div class="col-sm">
<input name="offset_y" class="form-control" id="{{id}}_{{props.color}}" type="number" value="{{props.offset_y}}" {{{submit_on id 'blur'}}}>
</div>
2026-03-26 11:14:57 +00:00
</div>
`),
2026-03-25 10:09:57 +00:00
},
2026-03-26 10:44:20 +00:00
"resize": {
2026-03-25 10:09:57 +00:00
label: "Resize",
template: Handlebars.compile(`
<div class="mb-3">
<label for="{{id}}_width" class="form-label">Width</label>
<input name="width" class="form-control" id="{{id}}_width">
</div>
<div class="mb-3">
<label for="{{id}}_height" class="form-label">Height</label>
<input name="width" class="form-control" id="{{id}}_width">
</div>
`),
2026-03-26 10:44:20 +00:00
},
};
2026-03-25 10:09:57 +00:00
export default class UploadEditController extends Controller {
2026-03-25 11:35:53 +00:00
static targets = ['processList', 'preview'];
static values = {
uploadId: Number,
siteId: Number,
};
2026-03-25 10:09:57 +00:00
connect() {
this._rebuildProcessList();
2026-03-25 11:35:53 +00:00
this._createSession();
2026-03-25 10:09:57 +00:00
}
async addProcessor(ev) {
ev.preventDefault();
await this._addProcessor({
type: "shadow"
});
}
2026-03-26 10:44:20 +00:00
async removeProcessor(ev) {
ev.preventDefault();
let id = ev.params.id;
await this._removeProcessor(id);
}
2026-03-28 10:42:35 +00:00
async saveUpload(ev) {
ev.preventDefault();
await this._save("replace");
}
async saveNewUpload(ev) {
ev.preventDefault();
await this._save("copy");
}
async updateProcessor(ev) {
ev.preventDefault();
let id = ev.params.id;
let paramParentEl = ev.target.closest('[data-role="processor-params"]');
let params = Object.fromEntries(new FormData(paramParentEl).entries());
await this._updateProcessor(id, params);
}
2026-03-25 10:09:57 +00:00
_rebuildProcessList() {
let el = this.processListTarget;
2026-03-26 10:44:20 +00:00
if ((!this._state) || (!this._state.session) || (!this._state.session.processors)) {
return;
}
el.innerHTML = "";
for (let p of this._state.session.processors) {
let ui = processorUIs[p.type];
if (!ui) {
continue;
}
let cardOuter = processorFrame({
id: p.id,
name: ui.label,
props: ui.template(p),
});
el.innerHTML += cardOuter;
}
2026-03-28 10:42:35 +00:00
feather.replace();
2026-03-25 10:09:57 +00:00
}
2026-03-25 11:35:53 +00:00
async _createSession() {
try {
let resp = await fetch(`/sites/${this.siteIdValue}/imageedit/`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
2026-03-25 11:35:53 +00:00
body: JSON.stringify({
"base_upload": this.uploadIdValue,
})
});
this._state = await resp.json();
2026-03-26 10:44:20 +00:00
this._rebuildProcessList();
this.previewTarget.src = this._state.preview_url;
2026-03-25 11:35:53 +00:00
} catch (e) {
console.error(e);
}
}
async _addProcessor(processor) {
try {
let resp = await fetch(`/sites/${this.siteIdValue}/imageedit/${this._state.session.guid}/processors`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(processor)
});
this._state = await resp.json();
2026-03-26 10:44:20 +00:00
this._rebuildProcessList();
this.previewTarget.src = this._state.preview_url;
} catch (e) {
console.error(e);
}
}
async _updateProcessor(processorID, params) {
await this._doReturningState(async () => {
return (await fetch(`/sites/${this.siteIdValue}/imageedit/${this._state.session.guid}`, {
method: 'PATCH',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
processor: {
id: processorID,
props: params,
}
})
})).json();
})
}
2026-03-26 10:44:20 +00:00
async _removeProcessor(processorID) {
await this._doReturningState(async () => {
return (await fetch(`/sites/${this.siteIdValue}/imageedit/${this._state.session.guid}/processors/${processorID}`, {
method: 'DELETE',
})).json();
})
}
2026-03-28 10:42:35 +00:00
async _save(mode) {
if (!this._state || !this._state.session) {
return;
}
try {
let resp = await fetch(`/sites/${this.siteIdValue}/imageedit/${this._state.session.guid}/save`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ mode })
});
if (!resp.ok) {
console.error("Save failed:", resp.statusText);
return;
}
let result = await resp.json();
window.location.href = `/sites/${this.siteIdValue}/uploads/${result.upload_id}`;
} catch (e) {
console.error(e);
}
}
2026-03-26 10:44:20 +00:00
async _doReturningState(fn) {
try {
this._state = await fn();
this._rebuildProcessList();
this.previewTarget.src = this._state.preview_url;
} catch (e) {
console.error(e);
}
2026-03-26 10:44:20 +00:00
}
2026-03-25 10:09:57 +00:00
}