frayerjj-frontend 0.5.6 → 0.5.7

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frayerjj-frontend",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "My base frontend for various projects",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -10,8 +10,7 @@
10
10
  },
11
11
  "style": "src/scss/styles.scss",
12
12
  "files": [
13
- "src/index.js",
14
- "src/scss/"
13
+ "src/"
15
14
  ],
16
15
  "scripts": {
17
16
  "build": "echo 'No build step required'",
@@ -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
+ };
@@ -0,0 +1,95 @@
1
+ import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
2
+ import { msg } from "./msg";
3
+
4
+ export const ckeupload = {
5
+ adapter: editor => {
6
+ editor.plugins.get('FileRepository').createUploadAdapter = loader => {
7
+ return new CkeUploadAdapter(loader, editor.config._config.extraParams.uri, editor.config._config.extraParams.token);
8
+ };
9
+ },
10
+ init: () => {
11
+ class CkeUploadAdapter {
12
+ constructor(loader, uri, token) {
13
+ this.loader = loader;
14
+ this.uri = uri;
15
+ this.token = token;
16
+ }
17
+ upload() {
18
+ return this.loader.file.then(file => new Promise((resolve, reject) => {
19
+ this._initRequest();
20
+ this._initListeners(resolve, reject, file);
21
+ this._sendRequest(file);
22
+ }));
23
+ }
24
+ abort() {
25
+ if (this.xhr) this.xhr.abort();
26
+ }
27
+ _initRequest() {
28
+ const xhr = this.xhr = new XMLHttpRequest();
29
+ xhr.open('POST', this.uri, true);
30
+ xhr.setRequestHeader('X-CSRFToken', this.token);
31
+ xhr.responseType = 'json';
32
+ }
33
+ _initListeners(resolve, reject, file) {
34
+ const xhr = this.xhr;
35
+ const loader = this.loader;
36
+ const genericErrorText = `Couldn't upload file: ${ file.name }.`;
37
+ xhr.addEventListener( 'error', () => reject(genericErrorText) );
38
+ xhr.addEventListener( 'abort', () => reject() );
39
+ xhr.addEventListener( 'load', () => {
40
+ const response = xhr.response;
41
+ if (!response || response.error)
42
+ return reject(response && response.error ? response.error.message : genericErrorText);
43
+ resolve({ default: response.url });
44
+ });
45
+ if (xhr.upload) {
46
+ xhr.upload.addEventListener('progress', evt => {
47
+ if (evt.lengthComputable) {
48
+ loader.uploadTotal = evt.total;
49
+ loader.uploaded = evt.loaded;
50
+ }
51
+ });
52
+ }
53
+ }
54
+ _sendRequest(file) {
55
+ const data = new FormData();
56
+ data.append('file', file);
57
+ this.xhr.send(data);
58
+ }
59
+ };
60
+ window.editors = [];
61
+ document.querySelectorAll('.wysiwyg').forEach(el => {
62
+ if (el.getAttribute('data-bs-toggle') === 'tooltip') {
63
+ let placement = el.getAttribute('data-bs-placement'),
64
+ title = el.getAttribute('title');
65
+ el.parentElement.setAttribute('data-bs-toggle', 'tooltip');
66
+ el.parentElement.setAttribute('data-bs-placement', placement);
67
+ el.parentElement.setAttribute('title', title);
68
+ el.removeAttribute('data-bs-toggle');
69
+ el.removeAttribute('data-bs-placement');
70
+ el.removeAttribute('title');
71
+ }
72
+ ClassicEditor.create(el, {
73
+ licenseKey: 'GPL',
74
+ htmlSupport: {
75
+ allow: [{
76
+ name: /.*/,
77
+ attributes: true,
78
+ classes: true,
79
+ styles: true
80
+ }]
81
+ },
82
+ extraParams: {
83
+ uri: document.querySelector('meta[name="asset-upload"]').getAttribute('content'),
84
+ token: document.querySelector('meta[name="csrf-token"]').getAttribute('content')
85
+ },
86
+ extraPlugins: [ CkeUploadAdapter ]
87
+ }).then(editor => {
88
+ window.editors[el.id] = editor;
89
+ msg.verbose('CKEditor Initialized');
90
+ }).catch( error => {
91
+ console.error( 'There was a problem initializing the editor.', error );
92
+ });
93
+ });
94
+ }
95
+ }
@@ -0,0 +1,230 @@
1
+ import { loading } from "./loading";
2
+ import { msg } from "./msg";
3
+ import { modal } from "./modal";
4
+
5
+ export const fileUpload = {
6
+
7
+ imgFormats: ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/webp'],
8
+
9
+ icons: {
10
+ 'application/msword': 'fa-file-word',
11
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'fa-file-word',
12
+ 'application/vnd.ms-excel': 'fa-file-excel',
13
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'fa-file-excel',
14
+ 'application/vnd.ms-powerpoint': 'fa-file-powerpoint',
15
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'fa-file-powerpoint',
16
+ 'application/pdf': 'fa-file-pdf',
17
+ },
18
+
19
+ sampleArgs: {
20
+ name: 'files', // Variable name
21
+ mimes: ['image/jpeg', 'image/png'], // Allowed MIME types
22
+ iconSize: 128, // Icon size
23
+ files: [ // Existing files
24
+ { id: 1, name: 'file1.jpg', mime: 'image/jpeg', uri: '/path/to/file1.jpg' },
25
+ ],
26
+ messages: { // Messages
27
+ instructions: 'Drag and drop files here or click the button below.',
28
+ browse: 'Browse',
29
+ close: 'Close',
30
+ view: 'View',
31
+ delete: 'Delete',
32
+ errorTitle: 'Error',
33
+ uploadError: 'Error uploading file.',
34
+ deleteError: 'Error deleting file.'
35
+ },
36
+ uris: { // URIs
37
+ upload: '/path/to/upload',
38
+ delete: '/path/to/delete'
39
+ }
40
+ },
41
+
42
+ build: {
43
+
44
+ core: args => {
45
+ let container = document.createElement('div');
46
+ let list = document.createElement('ul');
47
+ list.classList.add('upload-files');
48
+ Objects.values(args.files).forEach(file => {
49
+ list.insertAdjacentHTML('beforeend', fileUpload.build.item(args, file));
50
+ });
51
+ let input = document.createElement('input');
52
+ input.type = 'file';
53
+ input.style.display = 'none';
54
+ let instructions = document.createElement('div');
55
+ instructions.classList.add('upload-instructions');
56
+ let icon = document.createElement('span');
57
+ icon.classList.add('fas', 'fa-arrow-alt-circle-u');
58
+ instructions.appendChild(icon);
59
+ let text = document.createElement('p');
60
+ text.textContent = args.messages.instructions ?? fileUpload.sampleArgs.messages.instructions;
61
+ instructions.appendChild(text);
62
+ let button = document.createElement('button');
63
+ button.classList.add('btn', 'btn-outline-secondary', 'btn-browse');
64
+ button.textContent = args.messages.browse ?? fileUpload.sampleArgs.messages.browse;
65
+ instructions.appendChild(button);
66
+ container.appendChild(list);
67
+ container.appendChild(input);
68
+ container.appendChild(instructions);
69
+ return container.outerHTML;
70
+ },
71
+
72
+ item: (args, file) => {
73
+ let item = document.createElement('li');
74
+ item.classList.add('upload-file');
75
+ item.title = file.name;
76
+ item.setAttribute('data-uri', file.uri);
77
+ if (fileUpload.imgFormats.includes(file.mime))
78
+ item.classList.add('upload-type-image');
79
+ else
80
+ item.classList.add('upload-type-file');
81
+ let input = document.createElement('input');
82
+ input.type = 'hidden';
83
+ input.name = args.name + '[]';
84
+ input.value = file.id;
85
+ item.appendChild(input);
86
+ let icon = fileUpload.imgFormats.includes(file.mime) ? fileUpload.build.thumb(file) : fileUpload.build.icon(file);
87
+ item.appendChild(icon);
88
+ let heading = document.createElement('h6');
89
+ heading.textContent = file.name;
90
+ item.appendChild(heading);
91
+ let buttons = document.createElement('div');
92
+ buttons.classList.add('btn-group');
93
+ let view = document.createElement('button');
94
+ view.classList.add('btn', 'btn-outline-secondary', 'btn-view');
95
+ view.textContent = args.messages.view ?? fileUpload.sampleArgs.messages.view;
96
+ buttons.appendChild(view);
97
+ let del = document.createElement('button');
98
+ del.classList.add('btn', 'btn-outline-danger', 'btn-delete');
99
+ del.textContent = args.messages.delete ?? fileUpload.sampleArgs.messages.delete;
100
+ buttons.appendChild(del);
101
+ item.appendChild(buttons);
102
+ return item.outerHTML;
103
+ },
104
+
105
+ icon: file => {
106
+ let icon = document.createElement('span');
107
+ icon.classList.add('border', 'fas');
108
+ if (fileUpload.icons[file.mime])
109
+ icon.classList.add(fileUpload.icons[file.mime]);
110
+ else
111
+ icon.classList.add('fa-file');
112
+ icon.style.width = fileUpload.iconSize + 'px';
113
+ icon.style.height = fileUpload.iconSize + 'px';
114
+ return icon;
115
+ },
116
+
117
+ thumb: file => {
118
+ let thumb = document.createElement('img');
119
+ thumb.src = file.uri;
120
+ thumb.style.width = fileUpload.iconSize + 'px';
121
+ thumb.style.height = fileUpload.iconSize + 'px';
122
+ return thumb;
123
+ }
124
+ },
125
+
126
+ delete: (el, item) => {
127
+ loading.start(1, el);
128
+ let data = new FormData();
129
+ data.append('id', item.querySelector('input[type="hidden"]').value);
130
+ fetch(el.getAttribute('data-delete-uri'), {
131
+ method: 'POST',
132
+ body: data,
133
+ headers: {
134
+ 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
135
+ }
136
+ }).then(response => response.json()).then(result => {
137
+ if (result.success)
138
+ item.remove();
139
+ else
140
+ modal.alert(args.messages.errorTitle ?? fileUpload.sampleArgs.messages.errorTitle, args.messages.deleteError ?? fileUpload.sampleArgs.messages.deleteError);
141
+ loading.stop();
142
+ }).catch(error => {
143
+ modal.alert(args.messages.errorTitle ?? fileUpload.sampleArgs.messages.errorTitle, args.messages.deleteError ?? fileUpload.sampleArgs.messages.deleteError);
144
+ msg.error(error);
145
+ loading.stop();
146
+ });
147
+ },
148
+
149
+ init: () => {
150
+ document.querySelectorAll('.file-upload').forEach(el => {
151
+ // Get arguments
152
+ let args = JSON.parse(el.innerHTML);
153
+ // Build has many input
154
+ el.innerHTML = fileUpload.build.core(args);
155
+ // Add event listeners
156
+ el.querySelector('.btn-browse').addEventListener('click', () => {
157
+ el.querySelector('input[type="file"]').click();
158
+ });
159
+ el.querySelector('input[type="file"]').addEventListener('change', ev => {
160
+ fileUpload.upload(el, args, ev.target.files);
161
+ });
162
+ el.addEventListener('dragover', ev => {
163
+ ev.preventDefault();
164
+ el.classList.add('dragover');
165
+ });
166
+ el.addEventListener('dragleave', ev => {
167
+ el.classList.remove('dragover');
168
+ });
169
+ el.addEventListener('drop', ev => {
170
+ ev.preventDefault();
171
+ el.classList.remove('dragover');
172
+ fileUpload.upload(el, args, ev.dataTransfer.files);
173
+ });
174
+ el.querySelectorAll('.btn-delete').forEach(btn => {
175
+ btn.addEventListener('click', () => {
176
+ fileUpload.delete(el, btn.closest('.upload-file'));
177
+ });
178
+ });
179
+ el.querySelectorAll('.btn-view').forEach(btn => {
180
+ btn.addEventListener('click', () => {
181
+ fileUpload.view(args, btn.closest('.upload-file'));
182
+ });
183
+ });
184
+ });
185
+ },
186
+
187
+ upload: (el, file, args) => {
188
+ loading.start(1, el);
189
+ let data = new FormData();
190
+ data.append('owner', args.name);
191
+ data.append('file', file[0]);
192
+ fetch(args.uris.upload, {
193
+ method: 'POST',
194
+ body: data,
195
+ headers: {
196
+ 'multipart': 'form-data',
197
+ 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
198
+ }
199
+ }).then(response => response.json()).then(result => {
200
+ if (result.success)
201
+ el.querySelector('.upload-files').insertAdjacentHTML('beforeend', fileUpload.build.item(args, result.file));
202
+ else
203
+ modal.alert(args.messages.errorTitle ?? fileUpload.sampleArgs.messages.errorTitle, args.messages.uploadError ?? fileUpload.sampleArgs.messages.uploadError);
204
+ loading.stop();
205
+ }).catch(error => {
206
+ modal.alert(args.messages.errorTitle ?? fileUpload.sampleArgs.messages.errorTitle, args.messages.uploadError ?? fileUpload.sampleArgs.messages.uploadError);
207
+ msg.error(error);
208
+ loading.stop();
209
+ });
210
+ },
211
+
212
+ view: (args, item) => {
213
+ if (item.classList.contains('upload-type-image')) {
214
+ let body = document.createElement('img');
215
+ body.src = item.querySelector('img').src;
216
+ }
217
+ else {
218
+ let body = document.createElement('iframe');
219
+ body.src = `https://docs.google.com/gview?url=${window.location.protocol}//${window.location.host}${item.getAttribute('data-uri')}&embedded=true`;
220
+ }
221
+ let randomId = modal.randomId('fileview');
222
+ document.querySelector('body').insertAdjacentHTML('beforeend', modal.build.modal({
223
+ id: randomId,
224
+ className: 'modal-lg',
225
+ title: item.title,
226
+ body: body.outerHTML,
227
+ buttons: [{ text: args.messages.close ?? fileUpload.sampleArgs.messages.close }]
228
+ }));
229
+ }
230
+ };
package/src/hasMany.js ADDED
@@ -0,0 +1,152 @@
1
+ import { msg } from './msg.js';
2
+ import { modal } from './modal.js';
3
+
4
+ export const hasMany = {
5
+
6
+ sampleArgs: {
7
+ name: 'name', // Variable name
8
+ title: 'Title', // Verbose name
9
+ notb: false, // Use None of the Below
10
+ messages: {
11
+ notbLabel: 'None', // Label for None of the Below
12
+ close: 'Close', // Close button text
13
+ button: 'Select Objects' // OK button text
14
+ },
15
+ options: [
16
+ { id: 1, label: 'Option 1', checked: false },
17
+ { id: 2, label: 'Option 2', checked: true }
18
+ ]
19
+ },
20
+
21
+ build: {
22
+
23
+ core: args => {
24
+ msg.verbose('Building Has Many Inupt');
25
+ // Create container
26
+ let container = document.createElement('div');
27
+ // Create list of selected items
28
+ let list = document.createElement('ul');
29
+ list.id = 'id_' + args.name;
30
+ list.className = 'list-group list-group-flush';
31
+ // Create list of all options for modal
32
+ let modalList = document.createElement('ul');
33
+ modalList.className = 'list-group list-group-flush';
34
+ // Create hidden input for when there is no selected items
35
+ let input = document.createElement('input');
36
+ input.type = 'hidden';
37
+ input.name = args.name + '[]';
38
+ // If using None of the Below
39
+ if (args.notb)
40
+ modalList.insertAdjacentHTML('beforeend', hasMany.build.checkItem(args.name, {
41
+ id: 'notb',
42
+ label: (args.notbLabel ?? hasMany.sampleArgs.messages.notbLabel),
43
+ notb: true
44
+ }));
45
+ // Cycle through all options
46
+ Objects.values(args.options).forEach(option => {
47
+ modalList.insertAdjacentHTML('beforeend', hasMany.build.checkItem(args.name, option));
48
+ // If option is selected, add to list
49
+ if (option.checked)
50
+ list.insertAdjacentHTML('beforeend', hasMany.build.item(args.name, option));
51
+ });
52
+ // Create modal
53
+ let randomId = modal.randomId('hasmany');
54
+ document.querySelector('body').insertAdjacentHTML('beforeend', modal.build.modal({
55
+ id: randomId,
56
+ title: args.title,
57
+ body: modalList.outerHTML,
58
+ buttons: [ { text: args.messages.close ?? hasMany.sampleArgs.messages.close } ]
59
+ }));
60
+ // Create button to open modal
61
+ let button = document.createElement('button');
62
+ button.type = 'button';
63
+ button.className = 'btn btn-outline-secondary btn-block';
64
+ button.setAttribute('data-bs-toggle', 'modal');
65
+ button.setAttribute('data-bs-target', '#' + randomId);
66
+ button.textContent = args.messages.button ?? hasMany.sampleArgs.messages.button;
67
+ // Add elements to container
68
+ container.appendChild(input);
69
+ container.appendChild(list);
70
+ container.appendChild(button);
71
+ // Return container
72
+ return container.outerHTML;
73
+ },
74
+
75
+ checkItem: (name, option) => {
76
+ // Create list item
77
+ let item = document.createElement('li');
78
+ item.className = 'list-group-item';
79
+ // Create checkbox
80
+ let input = document.createElement('input');
81
+ input.classList.add('form-check-input');
82
+ if (option.notb)
83
+ input.classList.add('hasmany-' + name + 'notb');
84
+ else
85
+ input.classList.add('hasmany-' + name);
86
+ input.type = 'checkbox';
87
+ if (option.checked)
88
+ input.checked = true
89
+ input.id = name + option.id;
90
+ input.value = id;
91
+ item.appendChild(input);
92
+ // Create label
93
+ let label = document.createElement('label');
94
+ label.setAttribute('for', name + option.id);
95
+ label.textContent = option.label;
96
+ item.appendChild(label);
97
+ return item.outerHTML;
98
+ },
99
+
100
+ listItem: (name, option) => {
101
+ // Create list item
102
+ let item = document.createElement('li');
103
+ item.id = 'id_' + name + '_' + option.id;
104
+ item.textContent = option.label;
105
+ item.className = 'list-group-item';
106
+ // Create hidden input
107
+ let input = document.createElement('input');
108
+ input.type = 'hidden';
109
+ input.name = name + '[]';
110
+ input.value = option.id;
111
+ item.appendChild(input);
112
+ return item.outerHTML;
113
+ }
114
+ },
115
+
116
+ init: () => {
117
+ document.querySelectorAll('.has-many').forEach(el => {
118
+ // Get arguments
119
+ let args = JSON.parse(el.innerHTML);
120
+ // Build has many input
121
+ el.innerHTML = hasMany.build.core(args);
122
+ // Add event listeners
123
+ document.querySelectorAll('.hasmany-' + args.name).forEach(input => {
124
+ input.addEventListener('change', ev => {
125
+ if (ev.target.checked) {
126
+ // Add item to list
127
+ document.querySelector('#id_' + args.name).insertAdjacentHTML('beforeend', hasMany.build.item(args.name, {
128
+ id: ev.target.value,
129
+ label: ev.target.nextElementSibling.textContent
130
+ }));
131
+ // If using None of the Below, make sure it is unchecked
132
+ if (args.notb)
133
+ document.querySelector('.hasmany-' + args.name + 'notb').checked = false;
134
+ } else
135
+ // Remove item from list
136
+ document.querySelector('#id_' + args.name + '_' + ev.target.value).remove();
137
+ });
138
+ });
139
+ // Add event listener for None of the Below
140
+ if (args.notb)
141
+ document.querySelector('.hasmany-' + args.name + 'notb').addEventListener('change', ev => {
142
+ if (ev.target.checked)
143
+ // Uncheck all other items
144
+ document.querySelectorAll('hasmany-' + args.name).forEach(input => {
145
+ input.checked = false;
146
+ });
147
+ // Remove all items from list
148
+ document.querySelector('#id_' + args.name).innerHTML = '';
149
+ });
150
+ });
151
+ }
152
+ }
package/src/index.js CHANGED
@@ -1,3 +1,2 @@
1
1
  import { init } from './init';
2
-
3
2
  export { init };