2023-04-19 13:57:26 +03:00
|
|
|
import Component from '@glimmer/component';
|
|
|
|
import {action} from '@ember/object';
|
|
|
|
import {inject} from 'ghost-admin/decorators/inject';
|
|
|
|
import {inject as service} from '@ember/service';
|
2024-02-14 00:23:03 +03:00
|
|
|
import {trackEvent} from '../utils/analytics';
|
2023-04-19 13:57:26 +03:00
|
|
|
import {tracked} from '@glimmer/tracking';
|
|
|
|
|
|
|
|
export default class KoenigImageEditor extends Component {
|
|
|
|
@service ajax;
|
|
|
|
@service feature;
|
2023-04-20 18:50:07 +03:00
|
|
|
@service settings;
|
2023-04-19 13:57:26 +03:00
|
|
|
@service ghostPaths;
|
2023-10-05 19:30:56 +03:00
|
|
|
|
2023-04-19 13:57:26 +03:00
|
|
|
@tracked scriptLoaded = false;
|
|
|
|
@tracked cssLoaded = false;
|
2023-10-05 19:30:56 +03:00
|
|
|
@tracked allowClose = false;
|
2023-04-19 13:57:26 +03:00
|
|
|
|
|
|
|
@inject config;
|
|
|
|
|
|
|
|
get isEditorEnabled() {
|
|
|
|
return this.scriptLoaded && this.cssLoaded;
|
|
|
|
}
|
|
|
|
|
2023-04-20 18:50:07 +03:00
|
|
|
get pinturaJsUrl() {
|
2023-06-06 23:40:02 +03:00
|
|
|
if (!this.settings.pintura) {
|
|
|
|
return null;
|
|
|
|
}
|
2023-04-20 18:50:07 +03:00
|
|
|
return this.config.pintura?.js || this.settings.pinturaJsUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
get pinturaCSSUrl() {
|
2023-06-06 23:40:02 +03:00
|
|
|
if (!this.settings.pintura) {
|
|
|
|
return null;
|
|
|
|
}
|
2023-04-20 18:50:07 +03:00
|
|
|
return this.config.pintura?.css || this.settings.pinturaCssUrl;
|
|
|
|
}
|
|
|
|
|
2023-04-19 13:57:26 +03:00
|
|
|
getImageEditorJSUrl() {
|
2023-04-20 18:50:07 +03:00
|
|
|
let importUrl = this.pinturaJsUrl;
|
|
|
|
|
|
|
|
if (!importUrl) {
|
2023-04-19 13:57:26 +03:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// load the script from admin root if relative
|
|
|
|
if (importUrl.startsWith('/')) {
|
|
|
|
importUrl = window.location.origin + this.ghostPaths.adminRoot.replace(/\/$/, '') + importUrl;
|
|
|
|
}
|
|
|
|
return importUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
getImageEditorCSSUrl() {
|
2023-04-20 18:50:07 +03:00
|
|
|
let cssImportUrl = this.pinturaCSSUrl;
|
|
|
|
|
|
|
|
if (!cssImportUrl) {
|
2023-04-19 13:57:26 +03:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// load the css from admin root if relative
|
|
|
|
if (cssImportUrl.startsWith('/')) {
|
|
|
|
cssImportUrl = window.location.origin + this.ghostPaths.adminRoot.replace(/\/$/, '') + cssImportUrl;
|
|
|
|
}
|
|
|
|
return cssImportUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
loadImageEditorJavascript() {
|
|
|
|
const jsUrl = this.getImageEditorJSUrl();
|
|
|
|
|
|
|
|
if (!jsUrl) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (window.pintura) {
|
|
|
|
this.scriptLoaded = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const url = new URL(jsUrl);
|
|
|
|
|
|
|
|
let importScriptPromise;
|
|
|
|
|
|
|
|
if (url.protocol === 'http:') {
|
|
|
|
importScriptPromise = import(`http://${url.host}${url.pathname}`);
|
|
|
|
} else {
|
|
|
|
importScriptPromise = import(`https://${url.host}${url.pathname}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
importScriptPromise.then(() => {
|
|
|
|
this.scriptLoaded = true;
|
|
|
|
}).catch(() => {
|
|
|
|
// log script loading failure
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
// Log script loading error
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
loadImageEditorCSS() {
|
|
|
|
let cssUrl = this.getImageEditorCSSUrl();
|
|
|
|
if (!cssUrl) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Check if the CSS file is already present in the document's head
|
|
|
|
let cssLink = document.querySelector(`link[href="${cssUrl}"]`);
|
|
|
|
if (cssLink) {
|
|
|
|
this.cssLoaded = true;
|
|
|
|
} else {
|
|
|
|
let link = document.createElement('link');
|
|
|
|
link.rel = 'stylesheet';
|
|
|
|
link.type = 'text/css';
|
|
|
|
link.href = cssUrl;
|
|
|
|
link.onload = () => {
|
|
|
|
this.cssLoaded = true;
|
|
|
|
};
|
|
|
|
document.head.appendChild(link);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// Log css loading error
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super(...arguments);
|
|
|
|
|
|
|
|
// Load the image editor script and css if not already loaded
|
|
|
|
this.loadImageEditorJavascript();
|
|
|
|
this.loadImageEditorCSS();
|
|
|
|
}
|
|
|
|
|
2023-10-05 19:30:56 +03:00
|
|
|
willDestroy() {
|
|
|
|
super.willDestroy(...arguments);
|
|
|
|
this.removeCloseHandler();
|
|
|
|
}
|
|
|
|
|
2023-04-19 13:57:26 +03:00
|
|
|
@action
|
2023-05-08 12:52:25 +03:00
|
|
|
async onUploadComplete(urlList) {
|
|
|
|
if (this.args.saveUrl) {
|
|
|
|
const url = urlList[0].url;
|
|
|
|
this.args.saveUrl(url);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-05 19:30:56 +03:00
|
|
|
@action
|
|
|
|
willClose() {
|
|
|
|
if (this.allowClose) {
|
|
|
|
this.allowClose = false;
|
|
|
|
this.removeCloseHandler();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-05-08 12:52:25 +03:00
|
|
|
@action
|
|
|
|
async handleClick(uploader) {
|
2023-04-20 18:50:07 +03:00
|
|
|
if (this.isEditorEnabled && this.args.imageSrc) {
|
2023-10-05 19:30:56 +03:00
|
|
|
this.allowClose = false;
|
|
|
|
this.addCloseHandler();
|
|
|
|
|
2023-04-20 18:50:07 +03:00
|
|
|
// add a timestamp to the image src to bypass cache
|
|
|
|
// avoids cors issues with cached images
|
|
|
|
const imageUrl = new URL(this.args.imageSrc);
|
|
|
|
if (!imageUrl.searchParams.has('v')) {
|
|
|
|
imageUrl.searchParams.set('v', Date.now());
|
|
|
|
}
|
2023-06-07 18:05:38 +03:00
|
|
|
trackEvent('Image Edit Button Clicked', {location: 'admin'});
|
2023-04-20 18:50:07 +03:00
|
|
|
const imageSrc = imageUrl.href;
|
2023-04-19 13:57:26 +03:00
|
|
|
const editor = window.pintura.openDefaultEditor({
|
2023-04-19 16:31:46 +03:00
|
|
|
src: imageSrc,
|
2023-06-07 18:05:38 +03:00
|
|
|
enableTransparencyGrid: true,
|
2023-04-19 13:57:26 +03:00
|
|
|
util: 'crop',
|
|
|
|
utils: [
|
|
|
|
'crop',
|
|
|
|
'filter',
|
|
|
|
'finetune',
|
2023-04-20 18:50:07 +03:00
|
|
|
'redact',
|
|
|
|
'annotate',
|
|
|
|
'trim',
|
2023-10-05 16:08:23 +03:00
|
|
|
'frame',
|
|
|
|
'resize'
|
2023-04-19 13:57:26 +03:00
|
|
|
],
|
2023-04-20 19:27:43 +03:00
|
|
|
frameOptions: [
|
|
|
|
// No frame
|
|
|
|
[undefined, locale => locale.labelNone],
|
|
|
|
|
|
|
|
// Sharp edge frame
|
|
|
|
['solidSharp', locale => locale.frameLabelMatSharp],
|
|
|
|
|
|
|
|
// Rounded edge frame
|
|
|
|
['solidRound', locale => locale.frameLabelMatRound],
|
|
|
|
|
|
|
|
// A single line frame
|
|
|
|
['lineSingle', locale => locale.frameLabelLineSingle],
|
|
|
|
|
|
|
|
// A frame with cornenr hooks
|
|
|
|
['hook', locale => locale.frameLabelCornerHooks],
|
|
|
|
|
|
|
|
// A polaroid frame
|
|
|
|
['polaroid', locale => locale.frameLabelPolaroid]
|
|
|
|
],
|
|
|
|
cropSelectPresetFilter: 'landscape',
|
|
|
|
cropSelectPresetOptions: [
|
|
|
|
[undefined, 'Custom'],
|
|
|
|
[1, 'Square'],
|
|
|
|
// shown when cropSelectPresetFilter is set to 'landscape'
|
|
|
|
[2 / 1, '2:1'],
|
|
|
|
[3 / 2, '3:2'],
|
|
|
|
[4 / 3, '4:3'],
|
|
|
|
[16 / 10, '16:10'],
|
|
|
|
[16 / 9, '16:9'],
|
|
|
|
// shown when cropSelectPresetFilter is set to 'portrait'
|
|
|
|
[1 / 2, '1:2'],
|
|
|
|
[2 / 3, '2:3'],
|
|
|
|
[3 / 4, '3:4'],
|
|
|
|
[10 / 16, '10:16'],
|
|
|
|
[9 / 16, '9:16']
|
|
|
|
],
|
2023-04-19 13:57:26 +03:00
|
|
|
locale: {
|
|
|
|
labelButtonExport: 'Save and close'
|
2023-10-05 19:30:56 +03:00
|
|
|
},
|
|
|
|
willClose: this.willClose
|
2023-04-19 13:57:26 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
editor.on('loaderror', () => {
|
|
|
|
// TODO: log error message
|
|
|
|
});
|
|
|
|
|
|
|
|
editor.on('process', (result) => {
|
|
|
|
// save edited image
|
2023-05-08 12:52:25 +03:00
|
|
|
try {
|
|
|
|
if (this.args.saveImage) {
|
|
|
|
this.args.saveImage(result.dest);
|
|
|
|
}
|
|
|
|
if (this.args.saveUrl) {
|
|
|
|
uploader.setFiles([result.dest]);
|
|
|
|
}
|
2023-06-07 18:05:38 +03:00
|
|
|
trackEvent('Image Edit Saved', {location: 'admin'});
|
2023-05-08 12:52:25 +03:00
|
|
|
} catch (e) {
|
|
|
|
// Failed to save edited image
|
|
|
|
}
|
2023-04-19 13:57:26 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2023-10-05 19:30:56 +03:00
|
|
|
|
|
|
|
@action
|
|
|
|
handleCloseClick(event) {
|
|
|
|
if (event.target.closest('.PinturaModal button[title="Close"]')) {
|
|
|
|
this.allowClose = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addCloseHandler() {
|
|
|
|
window.addEventListener('click', this.handleCloseClick, {capture: true});
|
|
|
|
}
|
|
|
|
|
|
|
|
removeCloseHandler() {
|
|
|
|
window.removeEventListener('click', this.handleCloseClick, {capture: true});
|
|
|
|
}
|
2023-04-19 13:57:26 +03:00
|
|
|
}
|