Grocy.Api = {}; Grocy.Api.Get = function(apiFunction, success, error) { var xhr = new XMLHttpRequest(); var url = U('/api/' + apiFunction); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200 || xhr.status === 204) { if (success) { if (xhr.status === 200) { success(JSON.parse(xhr.responseText)); } else { success({}); } } } else { if (error) { error(xhr); } } } }; xhr.open('GET', url, true); xhr.send(); }; Grocy.Api.Post = function(apiFunction, jsonData, success, error) { var xhr = new XMLHttpRequest(); var url = U('/api/' + apiFunction); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200 || xhr.status === 204) { if (success) { if (xhr.status === 200) { success(JSON.parse(xhr.responseText)); } else { success({}); } } } else { if (error) { error(xhr); } } } }; xhr.open('POST', url, true); xhr.setRequestHeader('Content-type', 'application/json'); xhr.send(JSON.stringify(jsonData)); }; Grocy.Api.Put = function(apiFunction, jsonData, success, error) { var xhr = new XMLHttpRequest(); var url = U('/api/' + apiFunction); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200 || xhr.status === 204) { if (success) { if (xhr.status === 200) { success(JSON.parse(xhr.responseText)); } else { success({}); } } } else { if (error) { error(xhr); } } } }; xhr.open('PUT', url, true); xhr.setRequestHeader('Content-type', 'application/json'); xhr.send(JSON.stringify(jsonData)); }; Grocy.Api.Delete = function(apiFunction, jsonData, success, error) { var xhr = new XMLHttpRequest(); var url = U('/api/' + apiFunction); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200 || xhr.status === 204) { if (success) { if (xhr.status === 200) { success(JSON.parse(xhr.responseText)); } else { success({}); } } } else { if (error) { error(xhr); } } } }; xhr.open('DELETE', url, true); xhr.setRequestHeader('Content-type', 'application/json'); xhr.send(JSON.stringify(jsonData)); }; Grocy.Api.UploadFile = function(file, group, fileName, success, error) { var xhr = new XMLHttpRequest(); var url = U('/api/files/' + group + '/' + btoa(fileName)); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200 || xhr.status === 204) { if (success) { if (xhr.status === 200) { success(JSON.parse(xhr.responseText)); } else { success({}); } } } else { if (error) { error(xhr); } } } }; xhr.open('PUT', url, true); xhr.setRequestHeader('Content-type', 'application/octet-stream'); xhr.send(file); }; Grocy.Api.DeleteFile = function(fileName, group, success, error) { var xhr = new XMLHttpRequest(); var url = U('/api/files/' + group + '/' + btoa(fileName)); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200 || xhr.status === 204) { if (success) { if (xhr.status === 200) { success(JSON.parse(xhr.responseText)); } else { success({}); } } } else { if (error) { error(xhr); } } } }; xhr.open('DELETE', url, true); xhr.setRequestHeader('Content-type', 'application/json'); xhr.send(); }; U = function(relativePath) { return Grocy.BaseUrl.replace(/\/$/, '') + relativePath; } Grocy.Translator = new Translator(Grocy.LocalizationStrings); Grocy.TranslatorQu = new Translator(Grocy.LocalizationStringsQu); __t = function(text, ...placeholderValues) { if (Grocy.Mode === "dev") { var text2 = text; if (Grocy.LocalizationStrings && !Grocy.LocalizationStrings.messages[""].hasOwnProperty(text2)) { Grocy.Api.Post('system/log-missing-localization', { "text": text2 }); } } return Grocy.Translator.__(text, ...placeholderValues) } __n = function(number, singularForm, pluralForm, isQu = false) { if (Grocy.Mode === "dev") { var singularForm2 = singularForm; if (Grocy.LocalizationStrings && !Grocy.LocalizationStrings.messages[""].hasOwnProperty(singularForm2)) { Grocy.Api.Post('system/log-missing-localization', { "text": singularForm2 }); } } if (pluralForm == null || pluralForm.isEmpty()) { pluralForm = singularForm; } if (isQu) { return Grocy.TranslatorQu.n__(singularForm, pluralForm, Math.abs(number), Math.abs(number)) } else { return Grocy.Translator.n__(singularForm, pluralForm, Math.abs(number), Math.abs(number)) } } if (!Grocy.ActiveNav.isEmpty()) { var menuItem = $('#sidebarResponsive').find("[data-nav-for-page='" + Grocy.ActiveNav + "']"); menuItem.addClass('active-page'); if (menuItem.length) { var parentMenuSelector = menuItem.data("sub-menu-of"); if (typeof parentMenuSelector !== "undefined") { $(parentMenuSelector).collapse("show"); $(parentMenuSelector).prev(".nav-link-collapse").addClass("active-page"); $(parentMenuSelector).on("shown.bs.collapse", function(e) { if (!menuItem.isVisibleInViewport(75)) { menuItem[0].scrollIntoView(); } }) } else { if (!menuItem.isVisibleInViewport(75)) { menuItem[0].scrollIntoView(); } } } } var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.attributeName === "class") { var attributeValue = $(mutation.target).prop(mutation.attributeName); if (attributeValue.contains("sidenav-toggled")) { window.localStorage.setItem("sidebar_state", "collapsed"); } else { window.localStorage.setItem("sidebar_state", "expanded"); } } }); }); observer.observe(document.body, { attributes: true }); if (window.localStorage.getItem("sidebar_state") === "collapsed") { $("#sidenavToggler").click(); } RefreshContextualTimeago = function(rootSelector = "#page-content") { $(rootSelector + " time.timeago").each(function() { var element = $(this); if (!element.hasAttr("datetime")) { element.text("") return } var timestamp = element.attr("datetime"); if (timestamp.isEmpty() || timestamp.length < 10) { element.text("") return } if (!moment(timestamp).isValid()) { element.text("") return } var isNever = timestamp && timestamp.substring(0, 10) == "2999-12-31"; var isToday = timestamp && timestamp.substring(0, 10) == moment().format("YYYY-MM-DD"); var isDateWithoutTime = element.hasClass("timeago-date-only"); if (isNever) { element.prev().text(__t("Never")); element.text(""); } else if (isToday) { element.text(__t("Today")); } else { element.text(moment(timestamp).fromNow()); } if (isDateWithoutTime) { element.prev().text(element.prev().text().substring(0, 10)); } }); } RefreshContextualTimeago(); toastr.options = { toastClass: 'alert', closeButton: true, timeOut: 20000, extendedTimeOut: 5000 }; window.FontAwesomeConfig = { searchPseudoElements: true } Grocy.FrontendHelpers = {}; Grocy.FrontendHelpers.ValidateForm = function(formId, reportValidity = false) { var form = document.getElementById(formId); if (form === null || form === undefined) { return; } $(form).addClass('was-validated'); if (reportValidity) { form.reportValidity(); } return form.checkValidity(); } Grocy.FrontendHelpers.BeginUiBusy = function(formId = null) { $("body").addClass("cursor-busy"); if (formId !== null) { $("#" + formId + " :input").attr("disabled", true); } } Grocy.FrontendHelpers.EndUiBusy = function(formId = null) { $("body").removeClass("cursor-busy"); if (formId !== null) { $("#" + formId + " :input").attr("disabled", false); } } Grocy.FrontendHelpers.ShowGenericError = function(message, exception) { toastr.error(__t(message) + '

' + __t('Click to show technical details'), '', { onclick: function() { var errorDetails = JSON.stringify(exception, null, 4); if (typeof exception === "object" && exception !== null && exception.hasOwnProperty("error_message")) { errorDetails = exception.error_message; } bootbox.alert({ title: __t('Error details'), message: '

' + errorDetails + '

', closeButton: false }); } }); console.error(exception); } Grocy.FrontendHelpers.SaveUserSetting = function(settingsKey, value, force = false) { if (Grocy.UserSettings[settingsKey] == value && !force) { return; } Grocy.UserSettings[settingsKey] = value; jsonData = {}; jsonData.value = value; Grocy.Api.Put('user/settings/' + settingsKey, jsonData, function(result) { // Nothing to do... }, function(xhr) { console.error(xhr); } ); } Grocy.FrontendHelpers.DeleteUserSetting = function(settingsKey, reloadPageOnSuccess = false) { delete Grocy.UserSettings[settingsKey]; Grocy.Api.Delete('user/settings/' + settingsKey, {}, function(result) { if (reloadPageOnSuccess) { location.reload(); } }, function(xhr) { if (!xhr.statusText.isEmpty()) { Grocy.FrontendHelpers.ShowGenericError('Error while deleting, please retry', xhr.response) } } ); } Grocy.FrontendHelpers.RunWebhook = function(webhook, data, repetitions = 1) { Object.assign(data, webhook.extra_data); var hasAlreadyFailed = false; for (i = 0; i < repetitions; i++) { if (webhook.json) { $.ajax(webhook.hook, { "data": JSON.stringify(data), "contentType": "application/json", "type": "POST" }).fail(function(req, status, errorThrown) { if (!hasAlreadyFailed) { hasAlreadyFailed = true; Grocy.FrontendHelpers.ShowGenericError(__t("Error while executing WebHook", { "status": status, "errorThrown": errorThrown })); } }); } else { $.post(webhook.hook, data).fail(function(req, status, errorThrown) { if (!hasAlreadyFailed) { hasAlreadyFailed = true; Grocy.FrontendHelpers.ShowGenericError(__t("Error while executing WebHook", { "status": status, "errorThrown": errorThrown })); } }); } } } $(document).on("keyup paste change", "input, textarea", function() { $(this).closest("form").addClass("is-dirty"); }); $(document).on("click", "select", function() { $(this).closest("form").addClass("is-dirty"); }); // Auto saving user setting controls $(document).on("change", ".user-setting-control", function() { var element = $(this); var settingKey = element.attr("data-setting-key"); if (!element[0].checkValidity()) { return; } var inputType = "unknown"; if (typeof element.attr("type") !== typeof undefined && element.attr("type") !== false) { inputType = element.attr("type").toLowerCase(); } if (inputType === "checkbox") { value = element.is(":checked"); } else { var value = element.val(); } Grocy.FrontendHelpers.SaveUserSetting(settingKey, value); }); // Show file name Bootstrap custom file input $('input.custom-file-input').on('change', function() { $(this).next('.custom-file-label').html(GetFileNameFromPath($(this).val())); }); // Translation of "Browse"-button of Bootstrap custom file input if ($(".custom-file-label").length > 0) { $("