frayerjj-frontend 0.2.27 → 0.2.29
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.
- package/package.json +3 -2
- package/src/avatar-cropper.js +97 -0
- package/src/init.js +22 -3
- package/src/modal.js +37 -48
- package/src/ajax.js +0 -44
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frayerjj-frontend",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.29",
|
|
4
4
|
"description": "My base frontend for various projects",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@ckeditor/ckeditor5-build-classic": "^44.3.0",
|
|
18
18
|
"@popperjs/core": "^2.11.8",
|
|
19
|
-
"bootstrap": "^5.3.3"
|
|
19
|
+
"bootstrap": "^5.3.3",
|
|
20
|
+
"cropperjs": "^2.0.0"
|
|
20
21
|
}
|
|
21
22
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import Cropper from 'cropperjs';
|
|
2
|
+
import { modal } from './modal';
|
|
3
|
+
|
|
4
|
+
export const avatarCropper = {
|
|
5
|
+
|
|
6
|
+
export: (selection, redirect) => {
|
|
7
|
+
selection.$toCanvas().then(canvas => {
|
|
8
|
+
canvas.toBlob(blob => {
|
|
9
|
+
let formData = new FormData();
|
|
10
|
+
formData.append('file', blob, 'avatar.png');
|
|
11
|
+
// Send the blob to the server
|
|
12
|
+
fetch(window.location.href, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
body: formData,
|
|
15
|
+
headers: {
|
|
16
|
+
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
|
17
|
+
}
|
|
18
|
+
}).then(response => {
|
|
19
|
+
if (response.ok) {
|
|
20
|
+
// Show success message
|
|
21
|
+
modal.alert('Avatar updated successfully.');
|
|
22
|
+
// Redirect to the specified URL
|
|
23
|
+
window.location.href = redirect;
|
|
24
|
+
} else
|
|
25
|
+
// Show error message
|
|
26
|
+
modal.alert('Error updating avatar. Please try again.');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
loadImage: (file, cropperImg, selection, exportBtn) => {
|
|
33
|
+
// If valid image
|
|
34
|
+
if (file && file.type.match(/image.*/)) {
|
|
35
|
+
// Load image
|
|
36
|
+
let reader = new FileReader();
|
|
37
|
+
reader.onload = () => {
|
|
38
|
+
// Set image on Cropper
|
|
39
|
+
cropperImg.onload = () => {
|
|
40
|
+
// Reset selector
|
|
41
|
+
selection.$reset();
|
|
42
|
+
selection.$center();
|
|
43
|
+
// Show export button
|
|
44
|
+
exportBtn.style.display = 'block';
|
|
45
|
+
}
|
|
46
|
+
cropperImg.src = e.target.result;
|
|
47
|
+
};
|
|
48
|
+
reader.readAsDataURL(file);
|
|
49
|
+
} else
|
|
50
|
+
// Show error message
|
|
51
|
+
modal.alert('Invalid file type. Please select an image file.');
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
init:() => {
|
|
55
|
+
let img = document.getElementById('avatar-cropper');
|
|
56
|
+
if (img) {
|
|
57
|
+
|
|
58
|
+
// Set the image to a transparent 1x1 pixel
|
|
59
|
+
img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
|
60
|
+
|
|
61
|
+
// Initialize the cropper
|
|
62
|
+
new Cropper(img);
|
|
63
|
+
let canvas = document.querySelector('cropper-canvas');
|
|
64
|
+
canvas.classList.add('container-fixed');
|
|
65
|
+
window.dispatchEvent(new Event('resize'));
|
|
66
|
+
let selection = document.querySelector('cropper-selection');
|
|
67
|
+
selection.style.overflow = 'hidden';
|
|
68
|
+
selection.style.borderRadius = '50%';
|
|
69
|
+
selection.aspectRatio = 1;
|
|
70
|
+
let cropperImg = document.querySelector('cropper-image');
|
|
71
|
+
|
|
72
|
+
// Set export button
|
|
73
|
+
let exportBtn = document.querySelector(cropperImg.getAttribute('data-export-btn'));
|
|
74
|
+
let redirectUrl = img.getAttribute('data-redirect');
|
|
75
|
+
exportBtn.style.display = 'none';
|
|
76
|
+
exportBtn.addEventListener('click', () => {
|
|
77
|
+
avatarCropper.export(selection, redirectUrl);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Create a file input element
|
|
81
|
+
let fileInput = document.createElement('input');
|
|
82
|
+
fileInput.setAttribute('type', 'file');
|
|
83
|
+
fileInput.setAttribute('accept', 'image/*');
|
|
84
|
+
fileInput.style.display = 'none';
|
|
85
|
+
img.insertAdjacentElement('afterend', fileInput);
|
|
86
|
+
fileInput.addEventListener('change', ev => {
|
|
87
|
+
avatarCropper.loadImage(ev.target.files[0], cropperImg, selection, exportBtn);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Set upload button
|
|
91
|
+
let uploadBtn = document.querySelector(img.getAttribute('data-upload-btn'));
|
|
92
|
+
uploadBtn.addEventListener('click', () => {
|
|
93
|
+
fileInput.click();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
package/src/init.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as bootstrap from 'bootstrap';
|
|
2
|
+
import { avatarCropper } from './avatar-cropper';
|
|
2
3
|
import { createPopper } from '@popperjs/core';
|
|
3
4
|
import { message } from './message';
|
|
4
5
|
import { modal } from './modal';
|
|
5
6
|
import { validate } from './validate';
|
|
6
7
|
import { loading } from './loading';
|
|
7
|
-
import { ajax } from './ajax';
|
|
8
8
|
import { session } from './session';
|
|
9
9
|
import { ckeupload } from './ckeupload';
|
|
10
10
|
import { ClassicEditor } from '@ckeditor/ckeditor5-build-classic';
|
|
@@ -15,7 +15,6 @@ export const init = () => {
|
|
|
15
15
|
window.createPopper = createPopper;
|
|
16
16
|
window.message = message;
|
|
17
17
|
window.modal = modal;
|
|
18
|
-
window.ajax = ajax;
|
|
19
18
|
window.session = session;
|
|
20
19
|
window.validate = validate;
|
|
21
20
|
window.loading = loading;
|
|
@@ -28,7 +27,27 @@ export const init = () => {
|
|
|
28
27
|
validate.init();
|
|
29
28
|
modal.ajax.init();
|
|
30
29
|
ckeupload.init();
|
|
31
|
-
|
|
30
|
+
avatarCropper.init();
|
|
31
|
+
|
|
32
|
+
//Dynamic height for container-fixed
|
|
33
|
+
let container = document.querySelectorAll('.container-fixed');
|
|
34
|
+
if (container.length) {
|
|
35
|
+
let setHeigth = () => {
|
|
36
|
+
let sub = document.querySelectorAll('.navbar,header,footer')
|
|
37
|
+
if (container) {
|
|
38
|
+
let height = window.innerHeight;
|
|
39
|
+
sub.forEach((el) => {
|
|
40
|
+
height -= el.offsetHeight;
|
|
41
|
+
});
|
|
42
|
+
container.forEach((el) => {
|
|
43
|
+
el.style.height = height + 'px';
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
setHeigth();
|
|
48
|
+
window.addEventListener('resize', setHeigth);
|
|
49
|
+
}
|
|
50
|
+
|
|
32
51
|
// Updates the id in the form action inside a modal. Used for delete confirm and edit modals.
|
|
33
52
|
document.querySelectorAll('.modal-uuid-update').forEach(el => {
|
|
34
53
|
message.verbose('Enabling Modal UUID Update');
|
package/src/modal.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Modal } from 'bootstrap';
|
|
2
|
-
import { ajax } from './ajax.js';
|
|
3
2
|
import { message } from './message.js';
|
|
4
3
|
|
|
5
4
|
export const modal = {
|
|
@@ -40,56 +39,46 @@ export const modal = {
|
|
|
40
39
|
bsAjaxModal.show();
|
|
41
40
|
loading.start(0, ajaxModalBody);
|
|
42
41
|
message.verbose('Loading AJAX Modal');
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
images.forEach(image => {
|
|
53
|
-
if (image.complete && --counter == 0) loading.stop();
|
|
54
|
-
else image.addEventListener('load', () => {
|
|
55
|
-
if (--counter == 0) loading.stop();
|
|
56
|
-
});
|
|
42
|
+
fetch(el.getAttribute('modal-load-uri'), { method: 'GET' }).then(response => {
|
|
43
|
+
ajaxModalBody.insertAdjacentHTML('afterbegin', response);
|
|
44
|
+
let images = ajaxModalBody.querySelectorAll('img')
|
|
45
|
+
if (images.length) {
|
|
46
|
+
let counter = images.length;
|
|
47
|
+
images.forEach(image => {
|
|
48
|
+
if (image.complete && --counter == 0) loading.stop();
|
|
49
|
+
else image.addEventListener('load', () => {
|
|
50
|
+
if (--counter == 0) loading.stop();
|
|
57
51
|
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
loading.stop();
|
|
80
|
-
modal.alert(modal.ajax.error.save.msg, modal.ajax.error.save.title);
|
|
81
|
-
}
|
|
82
|
-
});
|
|
52
|
+
});
|
|
53
|
+
} else loading.stop();
|
|
54
|
+
message.verbose('AJAX Modal Loaded');
|
|
55
|
+
if (!el.getAttribute('modal-info')) {
|
|
56
|
+
message.verbose('Adding submit handler to AJAX Modal');
|
|
57
|
+
ajaxModal.querySelector('.btn-save').addEventListener('click', () => {
|
|
58
|
+
message.verbose('Submitting AJAX Modal');
|
|
59
|
+
loading.start(0, ajaxModalBody);
|
|
60
|
+
let form = ajaxModal.querySelector('form');
|
|
61
|
+
fetch(form.getAttribute('action'), {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
body: new FormData(form)
|
|
64
|
+
}).then(response => {
|
|
65
|
+
message.verbose('AJAX Modal Saved');
|
|
66
|
+
loading.stop();
|
|
67
|
+
bsAjaxModal.hide();
|
|
68
|
+
ajaxModal.remove();
|
|
69
|
+
}).catch(() => {
|
|
70
|
+
message.warn('AJAX Modal Save Failed');
|
|
71
|
+
loading.stop();
|
|
72
|
+
modal.alert(modal.ajax.error.save.msg, modal.ajax.error.save.title);
|
|
83
73
|
});
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
failure: () => {
|
|
87
|
-
message.warn('AJAX Modal Load Failed');
|
|
88
|
-
loading.stop();
|
|
89
|
-
bsAjaxModal.hide();
|
|
90
|
-
ajaxModal.remove();
|
|
91
|
-
modal.alert(modal.ajax.error.load.msg, modal.ajax.error.load.title);
|
|
74
|
+
});
|
|
92
75
|
}
|
|
76
|
+
}).catch(() => {
|
|
77
|
+
message.warn('AJAX Modal Load Failed');
|
|
78
|
+
loading.stop();
|
|
79
|
+
bsAjaxModal.hide();
|
|
80
|
+
ajaxModal.remove();
|
|
81
|
+
modal.alert(modal.ajax.error.load.msg, modal.ajax.error.load.title);
|
|
93
82
|
});
|
|
94
83
|
});
|
|
95
84
|
});
|
package/src/ajax.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { message } from './message';
|
|
2
|
-
|
|
3
|
-
export const ajax = (args) => {
|
|
4
|
-
return new Promise((resolve, reject) => {
|
|
5
|
-
|
|
6
|
-
let encodeVars = vars => {
|
|
7
|
-
let s = '';
|
|
8
|
-
for (var v in vars) s += v + '=' + vars[v] + "&";
|
|
9
|
-
return s ? s.substring(0, s.length - 1) : s;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (args.method == 'GET' && args.vars) args.uri += '?' + encodeVars(args.vars);
|
|
13
|
-
message.verbose('Making Request: ' + args.method + ' ' + args.uri);
|
|
14
|
-
|
|
15
|
-
let xhr = new XMLHttpRequest();
|
|
16
|
-
xhr.open(args.method, args.uri, true);
|
|
17
|
-
|
|
18
|
-
if (args.method == 'POST' && args.vars) xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
|
19
|
-
|
|
20
|
-
// Timeout support
|
|
21
|
-
if (args.timeout) {
|
|
22
|
-
xhr.timeout = args.timeout;
|
|
23
|
-
xhr.ontimeout = () => reject(new Error('Request timed out'));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
xhr.onreadystatechange = () => {
|
|
27
|
-
if (xhr.readyState === XMLHttpRequest.DONE) {
|
|
28
|
-
const status = xhr.status;
|
|
29
|
-
if (status === 0 || (status >= 200 && status < 400)) {
|
|
30
|
-
message.verbose('AJAX Request Successful');
|
|
31
|
-
const response = args.json !== false ? JSON.parse(xhr.responseText) : xhr.responseText;
|
|
32
|
-
if (typeof args.success == 'function') args.success.call(xhr, response);
|
|
33
|
-
resolve(response);
|
|
34
|
-
} else {
|
|
35
|
-
message.warn('AJAX Request Failed (HTTP ' + xhr.status + ': ' + xhr.statusText + ')');
|
|
36
|
-
if (typeof args.failure == 'function') args.failure.call(xhr);
|
|
37
|
-
reject(new Error('AJAX Request Failed: ' + xhr.statusText));
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
xhr.send(args.method == 'POST' && args.vars ? encodeVars(args.vars) : null);
|
|
43
|
-
});
|
|
44
|
-
};
|