var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')) var tooltipList = tooltipTriggerList.map(function(tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl) }) function allowOnlyNumbers(e) { // check input using regex var regex = RegExp(/[0-9]+/g); const test_result = regex.test(e.target.value); if (test_result) { e.target.defaultValue = e.target.value; } else { e.target.value = e.target.defaultValue; } } $(function() { var timer; $(window).resize(function() { clearTimeout(timer); timer = setTimeout(function() { $('.inner-wrap').css("min-height", $(window).height() + "px"); }, 40); }).resize(); }); //feather.replace() /*loop through element types (or all inputs) and create an object array with their values, return that object array*/ function get_elem(_type) { var elems = []; try { $((_type === undefined || _type === null || _type == '' ? "input" : _type)).each(function(index) { let m = $(this); let v; if (is_checkbox(m)) { if (m.is(':checked')) v = m.val(); } else { v = m.val(); } elems.push({ 'id': m.attr('id'), 'val': v }); }); } catch (ex) { console.log(ex); } return elems; } /* get inventory items and show them in the typeahead*/ function get_inventory_typeahead() { try { $.post('account/js-inventory', { 'a': 'get_suggest', 'j': '1' }, function(data) { var $input = $(".typeahead"); $input.typeahead({ source: data, autoSelect: false }); }, 'json'); } catch (ex) { console.log(ex); } } /* add an item to the inventory, and once it's added, set its guid to be the field that had the name value. also, make that field disabled so that the name can't be changed*/ function add_inventory_item(_item_name, _item_tag, _category, _callback) { try { if (isi(_item_name) && _item_name.length >= 3 && _item_name.length <= 45) { if (!isi(_item_tag)) _item_tag = ''; /*tags are optional*/ $.post({ url: 'account/js-inventory', data: { 'a': 'add', 'j': '1', 'name': _item_name, 'tag': _item_tag, 'category': _category }, beforeSend: function(_xhr) {}, }).done(function(_d) { if (typeof _callback === 'function') _callback(_d); }).fail(function(_xhr, _status, _err) {}).always(function() {}); } else { _d = { 'success': false, 'message': 'Invalid inventory name.' }; if (typeof _callback === 'function') _callback(_d); } } catch (ex) { console.log(ex); } } /* associate one or more media guids with an item guid, ex: associate a bunch of media guids with a specific inventory item. you can either pass a specific item (such as a $('#test')) or all selector items (ex: $('.img-snapshot') or a jquery response object (ex: _img) however, the specified object must have a 'attr-synched' and a 'attr-guid' atribute */ function assoc_media(_item_guid, _item_or_selector, _type, _callback) { try { var elems = []; if (isi(_item_guid) && isi(_item_or_selector)) { if (Array.isArray(_item_or_selector)) { _item_or_selector.forEach((guid) => { elems.push({ 'guid': guid }); }); } else { $(_item_or_selector).each(function(index) { if ($(this).attr('attr-synched') == '0' && isi($(this).attr('attr-guid'))) { elems.push({ 'guid': $(this).attr('attr-guid') }); $(this).attr('attr-synched', '1'); } }); } if (elems.length > 0) { /*upload inventory associations if they are ready*/ $.post({ url: 'account/js-media', data: { 'a': 'assoc_media', 'j': '1', 'guid': _item_guid, 'type': _type, 'data': JSON.stringify(elems) }, beforeSend: function(_xhr) {}, uploadProgress: function(e) {}, }).done(function(_d) { if (typeof _callback === 'function') _callback(_d); }).fail(function(_xhr, _status, _err) {}).always(function() {}); } } } catch (ex) { console.log(ex); } } /* disassociate a specific media guid with an item guid, given both the guids and the type: ex: inventory or contacts*/ function disassoc_media(_guid_item, _guid_media, _type, _callback) { try { $.post({ url: 'account/js-media', data: { 'a': 'disassoc_media', 'j': '1', 'guid_item': _guid_item, 'guid_media': _guid_media, 'type': _type }, beforeSend: function(_xhr) {}, uploadProgress: function(e) {}, }).done(function(_d) { if (typeof _callback === 'function') _callback(_d); }).fail(function(_xhr, _status, _err) {}).always(function() {}); } catch (ex) { console.log(ex); } } /*loop through selector, create an elements array containing guids + tags and update the guids+tags*/ function synch_tag(_item_or_selector, _type, _callback) { try { var elems = []; $(_item_or_selector).each(function(index) { if (isi($(this).attr('attr-guid')) && $(this).attr('attr-changed') == '1') { var txt = $(this).parents('div:first').find('.tag-now-input'); if (isi(txt)) { elems.push({ 'guid': $(this).attr('attr-guid'), 'tag': txt.val() }); $(this).attr('attr-changed', '0'); } } }); if (elems.length > 0) { /*upload tags if they are ready*/ $.ajax({ method: 'POST', url: 'account/js-tag', data: 'a=add&j=1&type=' + encodeURIComponent(_type) + '&data=' + encodeURIComponent(JSON.stringify(elems)), dataType: 'json', beforeSend: function(_xhr) {}, uploadProgress: function(e) {}, }).done(function(_d) { if (typeof _callback === 'function') _callback(_d); }).fail(function(_xhr, _status, _err) { }).always(function() { }); } } catch (ex) { console.log(ex); } } /*populates select/option fields with all available options for each of the selects within _input_container. _js = script, ex: js-home _post = {} object array with paramaters, ex: { 'a': 'get_home_option_fields','add_blank':'1'} _parent_container = jquery object representing parent, used for showing errors, ex: $('#tabEditHome') _input_container = optional jquery object representing container where the inputs are, ex: $('#tabEditHome') _callback = callback function called upon successful execution */ function populate_select_fields(_js, _post, _parent_container, _input_container, _callback) { try { $.post(_js, _post, function(_d) { if (pp(_parent_container, _d)) { _d.results.data.forEach((item) => { if (isi(item) && isi(item.id)) { let m = (isi(_input_container) ? _input_container.find('#' + item.id) : $('#' + item.id)); /*ensure field exists*/ if (isi(m)) { if (m.is('select')) { /*ensure field is select type*/ m.find('option').remove().end(); for (opt of item.opt) { if (typeof opt === 'object') { /*supplied object is id=>val type*/ m.append(''); } else { /*supplied object is a flat string*/ m.append(''); } }; } } } }); if (typeof _callback === 'function') _callback(_d); } }); } catch (ex) { console.log(ex); } } /*populates input fields in the _input_container container with user settings from the server. it is assumed that populate_select_fields() already ran succesfully and populated the defaults in all select boxes _js = script, ex: js-home _post = {} object array with paramaters, ex: { 'a': 'get'} _parent_container = jquery object representing parent, used for showing errors, ex: $('#tabEditHome') _input_container = optional jquery object representing container where the inputs are, ex: $('#tabEditHome') _callback = callback function called upon successful execution */ function populate_form_fields(_js, _post, _parent_container, _input_container, _callback) { try { inputs = (isi(_input_container) ? _input_container.find('input,select').not('.force-disable,.skip-disable') : $('input,select').not('.force-disable,.skip-disable')).attr('disabled', 'disabled').addClass('disabled'); /*get those inputs which are not already disabled*/ $.ajax({ method: 'POST', url: _js, data: _post, dataType: 'json', beforeSend: function(_xhr) {}, uploadProgress: function(e) {}, }).done(function(_d) { if (pp(_parent_container, _d)) { _d.results.data.forEach((item) => { if (isi(item) && isi(item.id)) { let m = (isi(_input_container) ? _input_container.find('#' + item.id) : $('#' + item.id)); /*ensure field exists*/ try { if (isi(m) && (m.is('input') || m.is('select') || m.is('hidden') || m.is('textarea'))) { m.val(item.val); m.trigger('change'); } else { console.log('> not found ' + item.id); } } catch (exx) { console.log(exx); } } }); if (typeof _callback === 'function') _callback(_d); } }).fail(function(_xhr, _status, _err) { }).always(function() { if (isi(inputs)) inputs.removeAttr('disabled').removeClass('disabled'); }); } catch (ex) { console.log(ex); } } function get_field_setting(_d, _setting_name) { try { if (isi(_d) && isi(_setting_name)) { for (x in _d) { let item = _d[x]; if (item.id == _setting_name) return item.val; }; } } catch (ex) { console.log(ex); } return null; } function get_guids(_container) { var guids = []; if (_container) { _container.each(function(e) { let guid = $(this).attr('attr-guid'); if (isi(guid)) guids.push(guid); }); } return guids; } /* image upload and resizing */ //https://dejanstojanovic.net/javascript/2016/january/resize-image-on-the-client-side-with-jquery/ $.fn.ImageResize = function(options) { var defaults = { maxWidth: Number.MAX_VALUE, maxHeight: Number.MAX_VALUE, multiple: true, onImageResized: null } var settings = $.extend({}, defaults, options); var selector = $(this); selector.each(function(index) { var control = selector.get(index); if ($(control).prop("tagName").toLowerCase() == "input" && $(control).attr("type").toLowerCase() == "file") { $(control).attr("accept", "image/*"); $(control).attr("multiple", (settings.multiple ? "true" : "false")); control.addEventListener('change', handleFileSelect, false); } else { cosole.log("Invalid file input field"); } }); function handleFileSelect(event) { if (window.File && window.FileList && window.FileReader) { //Check File API support $('.resize-container-message').show(); var count = 0; var files = event.target.files; for (var i = 0; i < files.length; i++) { var file = files[i]; if (!file.type.match('image')) continue; var picReader = new FileReader(); picReader.addEventListener("load", function(event) { var picFile = event.target; var img_extra; var img_meta_exif; /*original image exif tags*/ var img_meta_iptc; /*original image iptc tags*/ var imageData = picFile.result; var img = new Image(); img.src = imageData; img.onload = function() { img_extra = { 'width': img.width, 'height': img.height }; /*capture original image size*/ EXIF.getData(img, function() { try { img_meta_exif = EXIF.getAllTags(this); img_meta_iptc = EXIF.getAllIptcTags(this); } catch (ex) { console.log(ex); } var canvas = $("").get(0); if (img.width > settings.maxWidth || img.height > settings.maxHeight) { var width = settings.maxWidth; var height = settings.maxHeight; if (img.width > settings.maxWidth) { width = settings.maxWidth; var ration = settings.maxWidth / img.width; height = Math.round(img.height * ration); } if (height > settings.maxHeight) { height = settings.maxHeight; var ration = settings.maxHeight / img.height; width = Math.round(img.width * ration); } canvas.width = width; canvas.height = height; var context = canvas.getContext('2d'); context.drawImage(img, 0, 0, width, height); } else { canvas.width = img.width; canvas.height = img.height; var context = canvas.getContext('2d'); context.drawImage(img, 0, 0, img.width, img.height); } imageData = canvas.toDataURL(); $('.resize-container-message').hide(); if (settings.onImageResized != null && typeof(settings.onImageResized) == "function") { settings.onImageResized(imageData, img_meta_exif, img_meta_iptc, img_extra); } }); } img.onerror = function() { } }); //Read the image picReader.readAsDataURL(file); } } else { console.log("Your browser does not support File API"); } } } /*********************** * image upload start * ********************/ var tmp_timer; var upload_queue = []; var uploads_active = 0; var uploads_max = 1; var upload_queue_timer; $(document).ready(function() { $(".image-upload").ImageResize({ maxWidth: MEDIA_MAX_WIDTH, maxHeight: MEDIA_MAX_HEIGHT, onImageResized: function(_image_data, _img_meta_exif, _img_meta_iptc, _img_extra) { var upload_container = $('#upload-template .some-small-div').clone(true); $('#media_container').append(upload_container).ready(function(e) { var item = upload_container.find('.img-upload-preview:first'); var txt = upload_container.find('.tag-now-input'); item.attr('src', _image_data).attr('attr-guid', ''); if (txt!=null&&txt.length>0&&txt.val().length > 0) item.attr('src', _image_data).attr('attr-changed', '1'); /*all one item is checked and tag is filled in for all items*/ var q = { "img": item, "img_meta_exif": _img_meta_exif, "img_meta_iptc": _img_meta_iptc, "img_extra": _img_extra, "settings": get_elem('.upload-settings input') }; upload_queue.push(q); }); } }); }); /* monitor the upload queue and start the next item if its ready*/ function monitor_upload_queue(_callback) { try { if (isi(upload_queue_timer)) clearInterval(upload_queue_timer); upload_queue_timer = setInterval(function() { if (uploads_active < uploads_max && upload_queue.length > 0) { var item = upload_queue.shift(); upload_image(item.img, item.img_meta_exif, item.img_meta_iptc, item.img_extra, item.settings, _callback); } }, 500); } catch (ex) { console.log(ex); } } /* upload a binary image along with its data and optionally callback a function when done*/ function upload_image(_img, _img_meta_exif, _img_meta_iptc, _img_extra, _upload_settings, _callback) { try { var img_data = _img.attr('src'); //$('.div-preparing-images').hide(); if (img_data != null && img_data.length > 0) { var parent = _img.parents('div:first'); var progress = _img.parents('div:first').find('.progress'); var pbar = _img.parents('div:first').find('.progress-bar'); var tag = _img.parents('div:first').find('.tag-now-input').val(); /*tag would only be set if "all one item" is set and #tag_all filled*/ progress.show(); uploads_active++; var blob = data_uri_to_blob(img_data); computeSHA256(blob).then(hash => { return check_duplicate(hash); }).then(_d => { if (_d && _d.success) { if (_d.dup == false) { /*convert fields to formdata objects as required to track down upload progress*/ var form_data = new FormData(); form_data.append('data', blob); form_data.append('a', 'upload'); form_data.append('bin', '1'); form_data.append('type', 'image'); form_data.append('category', 'inventory'); form_data.append('meta_exif', JSON.stringify(_img_meta_exif)); form_data.append('img_extra', JSON.stringify(_img_extra)); form_data.append('upload_settings', JSON.stringify(_upload_settings)); form_data.append('tag', JSON.stringify(tag)); $.ajax({ method: 'POST', url: 'account/upload', data: form_data, processData: false, contentType: false, dataType: 'json', beforeSend: function(_xhr) { //uploads_active++; }, xhr: function() { /* xhr method required for binary file uplodas with progress indication*/ var myXhr = $.ajaxSettings.xhr(); if (myXhr.upload) { myXhr.upload.addEventListener('progress', function(e) { if (e.lengthComputable) { var percent = Math.round((e.loaded * 100) / e.total); if (progress != null) pbar.css('width', percent + '%'); } }, false); } return myXhr; }, }).done(function(_d) { if (_d.success) { _img.attr('attr-uploaded', 1); _img.attr('attr-guid', _d.guid); parent.find('.upload-successful-container').show(); synch_tag(_img, 'media'); } else { /*handle error*/ show_upload_failed(parent,_d); } if (typeof _callback === 'function') { setTimeout(() => { _callback(_d.success, _d, _img); }, 50); } }).fail(function(_xhr, _status, _err) { _img.attr('attr-uploaded', 0); _img.attr('attr-synched', 0); show_upload_failed(parent,_d); }).always(function() { uploads_active--; _img.removeClass('img-upload-progress'); progress.hide(); }); } else { _img.attr('attr-uploaded', 1); _img.attr('attr-guid', _d.guid); parent.find('.upload-successful-container').show(); _img.removeClass('img-upload-progress'); progress.hide(); uploads_active--; if (typeof _callback === 'function') { setTimeout(() => { _callback(_d.success, _d, _img); }, 50); } } } else { parent.find('.upload-failed-container').show(); } }).catch(error => { console.error(`Error computing hash: ${error}`); }); } } catch (ex) { console.log(ex); } } function show_upload_failed(_parent,_d){ try{ if(_parent.find('.upload-failed-message').length==0){ _parent.find('.upload-failed-container').append('
'); } _parent.find('.img-upload-preview').css({ 'filter': 'grayscale(1)', 'opacity': '.6' }); _parent.find('.upload-failed-message').html('Upload Failed'+(isi(_d)&&isi(_d.message)?': '+_d.message:'')); _parent.find('.upload-failed-container').show(); }catch(ex){ console.log(ex); } } /* given a file hash, check to see if the file already exists*/ async function check_duplicate(hash) { const url = 'account/upload'; const params = new URLSearchParams({ h: hash, a: 'check_duplicate', type: 'image' }); try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params }); if (!response.ok) { throw new Error('Network response was not ok'); } const jsonResponse = await response.json(); return jsonResponse; } catch (error) { console.error('There was a problem with the fetch operation:', error.message); } } /* convert data to blob for FormData post via binary mode to support progress*/ function data_uri_to_blob(dataURI) { var splitDataURI = dataURI.split(','); var byteString = atob(splitDataURI[1]); var mimeString = splitDataURI[0].split(':')[1].split(';')[0] var array = []; for (var i = 0; i < byteString.length; i++) { array.push(byteString.charCodeAt(i)); } return new Blob([new Uint8Array(array)], { type: mimeString }); } /* compute and return the hash of a blob*/ async function computeSHA256(blob) { const arrayBuffer = await blob.arrayBuffer(); const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); return hashHex; } /*********************** * image upload end * ********************/