diff --git a/dashboard/src/components/ApplinkDialog.vue b/dashboard/src/components/ApplinkDialog.vue index a9f514e02..9e22e3997 100644 --- a/dashboard/src/components/ApplinkDialog.vue +++ b/dashboard/src/components/ApplinkDialog.vue @@ -7,6 +7,7 @@ const t = i18n.t; import { computed, ref, useTemplateRef } from 'vue'; import { Dialog, FormGroup, InputDialog, MultiSelect, Radiobutton, TagInput, TextInput } from '@cloudron/pankow'; import { API_ORIGIN } from '../constants.js'; +import { getDataURLFromFile } from '../utils.js'; import ImagePicker from './ImagePicker.vue'; import ApplinksModel from '../models/ApplinksModel.js'; import UsersModel from '../models/UsersModel.js'; @@ -30,7 +31,7 @@ const id = ref(''); const upstreamUri = ref(''); const label = ref(''); const tags = ref([]); -const iconFile = ref(null); // if set to '' we will reset +const iconFile = ref(null); const iconUrl = ref(''); const accessRestrictionOption = ref(''); const accessRestriction = ref({ @@ -52,30 +53,10 @@ const isValid = computed(() => { return true; }); -function getDataURLFromFile(file) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - - reader.onload = function(event) { - resolve(event.target.result); - }; - - reader.onerror = function(event) { - reject(event.target.error); - }; - - reader.readAsDataURL(file); - }); -} - function onIconChanged(file) { iconFile.value = file; } -function onResetIcon() { - iconFile.value = ''; -} - async function onSubmit() { busy.value = true; @@ -92,9 +73,9 @@ async function onSubmit() { data.accessRestriction.groups = accessRestriction.value.groups.map(function (g) { return g.id; }); } - if (iconFile.value === '') { // user reset the icon + if (iconFile.value === 'fallback') { // user reset the icon data.icon = ''; - } else if (iconFile.value) { // user loaded custom icon + } else if (iconFile.value !== 'src') { // user loaded custom icon data.icon = (await getDataURLFromFile(iconFile.value)).replace(/^data:image\/[a-z]+;base64,/, ''); } @@ -110,8 +91,6 @@ async function onSubmit() { emits('success'); applinkDialog.value.close(); - // clear this to retrigger ImagePicker loading - iconUrl.value = ''; busy.value = false; } @@ -139,7 +118,7 @@ defineExpose({ upstreamUri.value = applink ? applink.upstreamUri : ''; label.value = applink ? applink.label : ''; iconUrl.value = applink ? applink.iconUrl : 'fallback'; - iconFile.value = null; + iconFile.value = applink?.iconUrl ? 'src' : 'fallback'; tags.value = applink ? applink.tags : []; accessRestrictionOption.value = applink && applink.accessRestriction ? 'groups' : 'any'; accessRestriction.value = applink && applink.accessRestriction ? applink.accessRestriction : { users: [], groups: [] }; @@ -193,7 +172,7 @@ defineExpose({
- +
diff --git a/dashboard/src/components/ImagePicker.vue b/dashboard/src/components/ImagePicker.vue index 364f5339a..466710dec 100644 --- a/dashboard/src/components/ImagePicker.vue +++ b/dashboard/src/components/ImagePicker.vue @@ -28,6 +28,7 @@ const busy = ref(false); watchEffect(() => { internalSrc.value = props.src; + isChanged.value = false; }); function dataURLtoFile(dataURL, filename) { @@ -70,6 +71,7 @@ async function onSave() { function onCancel() { internalSrc.value = props.src || props.fallbackSrc; isChanged.value = false; + emit('changed', 'src'); } function onEdit() { @@ -77,14 +79,17 @@ function onEdit() { } async function onUnset() { - if (typeof props.unsetHandler !== 'function') return; - busy.value = true; - const error = await props.unsetHandler(); - if (!error) isChanged.value = false; + if (typeof props.unsetHandler === 'function') { + const error = await props.unsetHandler(); + if (!error) isChanged.value = false; + } else { + isChanged.value = false; + } internalSrc.value = props.fallbackSrc; + emit('changed', 'fallback'); busy.value = false; } @@ -142,7 +147,7 @@ function onChanged(event) { internalSrc.value = canvas.toDataURL('image/png'); isChanged.value = true; - emit('changed', file); // <— notify parent + emit('changed', file); }; image.src = fr.result; @@ -178,6 +183,8 @@ function onError() { diff --git a/dashboard/src/components/app/Display.vue b/dashboard/src/components/app/Display.vue index 37040a608..540d7c7c1 100644 --- a/dashboard/src/components/app/Display.vue +++ b/dashboard/src/components/app/Display.vue @@ -5,6 +5,7 @@ import { FormGroup, TextInput, Button, TagInput } from '@cloudron/pankow'; import ImagePicker from '../ImagePicker.vue'; import AppsModel from '../../models/AppsModel.js'; import { API_ORIGIN } from '../../constants.js'; +import { getDataURLFromFile } from '../../utils.js'; const props = defineProps([ 'app' ]); @@ -19,22 +20,6 @@ const label = ref(''); const tags = ref([]); const iconUrl = ref(''); -function getDataURLFromFile(file) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - - reader.onload = function(event) { - resolve(event.target.result); - }; - - reader.onerror = function(event) { - reject(event.target.error); - }; - - reader.readAsDataURL(file); - }); -} - const haveValuesChanged = computed(() => { return (label.value !== props.app.label) || tags.value.join() !== props.app.tags.join(); }); diff --git a/dashboard/src/utils.js b/dashboard/src/utils.js index 84d099a01..6942cc609 100644 --- a/dashboard/src/utils.js +++ b/dashboard/src/utils.js @@ -589,6 +589,22 @@ function stripSsoInfo(notes, sso) { return notes.replace(/.*?<\/sso>/gs, '').replace(//gs, '').replace(/<\/nosso>/gs, ''); } +function getDataURLFromFile(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + + reader.onload = function(event) { + resolve(event.target.result); + }; + + reader.onerror = function(event) { + reject(event.target.error); + }; + + reader.readAsDataURL(file); + }); +} + // named exports export { download, @@ -599,6 +615,7 @@ export { taskNameFromInstallationState, redirectIfNeeded, stripSsoInfo, + getDataURLFromFile }; // default export @@ -611,4 +628,5 @@ export default { taskNameFromInstallationState, redirectIfNeeded, stripSsoInfo, + getDataURLFromFile };