feat: add resources and view components
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
@push('scripts')
|
||||
<script src="https://cdn.ckeditor.com/ckeditor5/41.1.0/classic/ckeditor.js" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/filepond/dist/filepond.js" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
/**
|
||||
* 1. TAB PERSISTENCE
|
||||
*/
|
||||
(function () {
|
||||
const STORAGE_KEY = 'mobileConfigActiveTab';
|
||||
document.querySelectorAll('[data-bs-toggle="tab"]').forEach(function (tabEl) {
|
||||
tabEl.addEventListener('shown.bs.tab', e => {
|
||||
localStorage.setItem(STORAGE_KEY, e.target.getAttribute('data-bs-target'));
|
||||
});
|
||||
});
|
||||
|
||||
const savedTab = localStorage.getItem(STORAGE_KEY);
|
||||
if (savedTab) {
|
||||
const tabEl = document.querySelector(`[data-bs-target="${savedTab}"]`);
|
||||
if (tabEl) {
|
||||
setTimeout(() => {
|
||||
bootstrap.Tab.getOrCreateInstance(tabEl).show();
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* 2. RICH EDITORS & FILEPOND
|
||||
*/
|
||||
$('.rich-editor').each(function () {
|
||||
ClassicEditor.create(this, {
|
||||
toolbar: ['undo', 'redo', '|', 'heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList']
|
||||
}).catch(e => console.error(e));
|
||||
});
|
||||
|
||||
FilePond.registerPlugin(FilePondPluginImagePreview);
|
||||
const ponds = {};
|
||||
$('.filepond-mobile').each(function () {
|
||||
const key = $(this).data('key');
|
||||
ponds[key] = FilePond.create(this, {
|
||||
labelIdle: 'Drop file or <span class="filepond--label-action">Browse</span>',
|
||||
imagePreviewHeight: 150,
|
||||
stylePanelLayout: 'compact',
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 3. FORM HANDLING (SAVE CONFIG)
|
||||
*/
|
||||
$('#mobileConfigForm').on('ajaxForm:beforeSend', function (e, formData) {
|
||||
Object.keys(ponds).forEach(key => {
|
||||
const files = ponds[key].getFiles();
|
||||
if (files.length > 0) formData.set(key, files[0].file);
|
||||
});
|
||||
});
|
||||
|
||||
$('#mobileConfigForm').on('ajaxForm:success', function (e, response) {
|
||||
StandardSwal.fire({
|
||||
title: "{{ __('Success!') }}",
|
||||
text: response.message,
|
||||
icon: 'success',
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: true
|
||||
});
|
||||
|
||||
if (response.settings) {
|
||||
Object.keys(response.settings).forEach(key => {
|
||||
const value = response.settings[key];
|
||||
const $previewContainer = $(`#preview-container-${key}`);
|
||||
const $img = $(`#img-preview-${key}`);
|
||||
|
||||
if (value && (key.includes('url') || key.includes('image'))) {
|
||||
const bust = '?v=' + Date.now();
|
||||
const finalUrl = (value.startsWith('http') ? value : ('/' + value.replace(/^\//, ''))) + bust;
|
||||
|
||||
if ($img.length) {
|
||||
$img.attr('src', finalUrl);
|
||||
} else if ($previewContainer.length) {
|
||||
$previewContainer.html(`<img src="${finalUrl}" id="img-preview-${key}" alt="Preview" class="img-fluid rounded" style="max-height: 120px; object-fit: contain;">`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Object.keys(ponds).forEach(key => ponds[key].removeFiles());
|
||||
});
|
||||
|
||||
/**
|
||||
* 4. COLOR PICKER SYNC
|
||||
*/
|
||||
$(document).on('input', '.color-picker-sync', function () {
|
||||
const color = $(this).val();
|
||||
const $container = $(this).closest('.input-group');
|
||||
$container.find('.color-preview-visual').css('background-color', color);
|
||||
$container.find('.color-bar-visual').css('background-color', color);
|
||||
$container.find('.color-input-field').val(color.toUpperCase());
|
||||
});
|
||||
|
||||
$(document).on('input', '.color-input-field', function () {
|
||||
let color = $(this).val().trim();
|
||||
if (color && !color.startsWith('#')) {
|
||||
color = '#' + color;
|
||||
$(this).val(color);
|
||||
}
|
||||
if (/^#[0-9A-F]{3,6}$/i.test(color)) {
|
||||
const $container = $(this).closest('.input-group');
|
||||
$container.find('.color-preview-visual').css('background-color', color);
|
||||
$container.find('.color-bar-visual').css('background-color', color);
|
||||
$container.find('.color-picker-sync').val(color);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* 5. SMART SEARCH ENGINE (TAB FILTER ONLY)
|
||||
*/
|
||||
$('#mobileSearch').on('input', function () {
|
||||
const query = $(this).val().toLowerCase();
|
||||
const $navItems = $('#mobileTabs .nav-item');
|
||||
const $tabPanes = $('.tab-pane');
|
||||
|
||||
if (query === '') {
|
||||
$navItems.show();
|
||||
$tabPanes.find('.mb-3, .mb-4, .row > div, hr, h6').show(); // Reset visibility
|
||||
return;
|
||||
}
|
||||
|
||||
let firstMatchingTabId = null;
|
||||
let currentTabHasMatch = false;
|
||||
const activeTabId = $('.tab-pane.active').attr('id');
|
||||
|
||||
// Reset all content visibility inside panes to "Full"
|
||||
$tabPanes.find('.mb-3, .mb-4, hr, h6, .row > div').show();
|
||||
|
||||
$tabPanes.each(function () {
|
||||
const $pane = $(this);
|
||||
const paneId = $pane.attr('id');
|
||||
let paneHasMatch = false;
|
||||
|
||||
// Check if this tab contains the query anywhere
|
||||
const paneText = $pane.text().toLowerCase();
|
||||
if (paneText.includes(query)) {
|
||||
paneHasMatch = true;
|
||||
}
|
||||
|
||||
// Update Nav Tabs visibility
|
||||
const $navLink = $(`button[data-bs-target="#${paneId}"]`);
|
||||
if (paneHasMatch) {
|
||||
$navLink.parent('.nav-item').show();
|
||||
if (!firstMatchingTabId) firstMatchingTabId = paneId;
|
||||
if (paneId === activeTabId) currentTabHasMatch = true;
|
||||
} else {
|
||||
$navLink.parent('.nav-item').hide();
|
||||
}
|
||||
});
|
||||
|
||||
// Pivot to the first matching tab if current one doesn't match
|
||||
if (!currentTabHasMatch && firstMatchingTabId) {
|
||||
$(`#${firstMatchingTabId}-tab`).tab('show');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 6. LIVE MOCKUP SYNC
|
||||
*/
|
||||
// Sync Text Fields
|
||||
$('input[name="app_name"]').on('input', function () { $('.mockup-app-name').text($(this).val() || 'biiproject'); });
|
||||
$('textarea[name="app_tagline"]').on('input', function () { $('.mockup-app-tagline').text($(this).val() || 'Smart Solution'); });
|
||||
$('input[name="app_version"]').on('input', function () { $('.mockup-version').text('v' + $(this).val()); });
|
||||
|
||||
// Sync Primary Color
|
||||
$(document).on('input', 'input[name="theme_color_primary"], .color-picker-sync', function () {
|
||||
const color = $(this).val();
|
||||
$('.mockup-primary-bg').css('background-color', color);
|
||||
});
|
||||
|
||||
// Sync Logo from FilePond
|
||||
Object.keys(ponds).forEach(key => {
|
||||
if (key === 'logo_url') {
|
||||
ponds[key].on('addfile', (error, file) => {
|
||||
if (!error) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
$('#mockup-logo').attr('src', e.target.result).addClass('animate__animated animate__pulse');
|
||||
setTimeout(() => $('#mockup-logo').removeClass('animate__animated animate__pulse'), 1000);
|
||||
};
|
||||
reader.readAsDataURL(file.file);
|
||||
}
|
||||
});
|
||||
|
||||
ponds[key].on('removefile', () => {
|
||||
// Revert to original or placeholder if needed
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Copy JSON payload to clipboard
|
||||
*/
|
||||
function copyConfigJson() {
|
||||
const jsonText = document.getElementById('json-viewer').textContent;
|
||||
navigator.clipboard.writeText(jsonText).then(() => {
|
||||
StandardSwal.fire({
|
||||
title: "{{ __('Copied!') }}",
|
||||
text: "{{ __('JSON payload has been copied to clipboard.') }}",
|
||||
icon: 'success',
|
||||
timer: 1500,
|
||||
showConfirmButton: false
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
Reference in New Issue
Block a user