Make Applink dialog use ImagePicker component
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const i18n = useI18n();
|
||||
const t = i18n.t;
|
||||
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
import { Dialog, FormGroup, InputDialog, MultiSelect, Radiobutton, TagInput, TextInput } from 'pankow';
|
||||
import { API_ORIGIN } from '../constants.js';
|
||||
import ImagePicker from './ImagePicker.vue';
|
||||
import ApplinksModel from '../models/ApplinksModel.js';
|
||||
import UsersModel from '../models/UsersModel.js';
|
||||
import GroupsModel from '../models/GroupsModel.js';
|
||||
@@ -10,147 +16,147 @@ const applinksModel = ApplinksModel.create();
|
||||
const usersModel = UsersModel.create();
|
||||
const groupsModel = GroupsModel.create();
|
||||
|
||||
export default {
|
||||
name: 'ApplinkDialog',
|
||||
components: {
|
||||
Dialog,
|
||||
FormGroup,
|
||||
InputDialog,
|
||||
MultiSelect,
|
||||
Radiobutton,
|
||||
TagInput,
|
||||
TextInput,
|
||||
},
|
||||
emits: ['success'],
|
||||
data() {
|
||||
return {
|
||||
API_ORIGIN,
|
||||
users: [],
|
||||
groups: [],
|
||||
busy: false,
|
||||
error: {},
|
||||
mode: '',
|
||||
id: '',
|
||||
upstreamUri: '',
|
||||
label: '',
|
||||
tags: [],
|
||||
icon: {},
|
||||
iconUrl: '',
|
||||
accessRestrictionOption: '',
|
||||
accessRestriction: {
|
||||
users: [],
|
||||
groups: [],
|
||||
},
|
||||
const emits = defineEmits(['success']);
|
||||
|
||||
const imagePicker = useTemplateRef('imagePicker');
|
||||
const applinkDialog = useTemplateRef('applinkDialog');
|
||||
const inputDialog = useTemplateRef('inputDialog');
|
||||
const users = ref([]);
|
||||
const groups = ref([]);
|
||||
const busy = ref(false);
|
||||
const error = ref({});
|
||||
const mode = ref('');
|
||||
const id = ref('');
|
||||
const upstreamUri = ref('');
|
||||
const label = ref('');
|
||||
const tags = ref([]);
|
||||
const iconFile = ref(null); // if set to '' we will reset
|
||||
const iconUrl = ref('');
|
||||
const accessRestrictionOption = ref('');
|
||||
const accessRestriction = ref({
|
||||
users: [],
|
||||
groups: [],
|
||||
});
|
||||
|
||||
const isValid = computed(() => {
|
||||
if (busy.value) return false;
|
||||
if (!upstreamUri.value) return false;
|
||||
|
||||
try {
|
||||
new URL(upstreamUri.value);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
function getDataURLFromFile(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = function(event) {
|
||||
resolve(event.target.result);
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isValid() {
|
||||
if (!this.label) return false;
|
||||
if (!this.upstreamUri) return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
iconSrc() {
|
||||
if (this.icon.data === '__original__') { // user clicked reset
|
||||
// https://png-pixel.com/ white pixel placeholder
|
||||
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=';
|
||||
} else if (this.icon.data) { // user uploaded icon
|
||||
return this.icon.data;
|
||||
} else if (this.iconUrl) { // current icon
|
||||
return API_ORIGIN + this.iconUrl;
|
||||
} else {
|
||||
return API_ORIGIN + '/img/appicon_fallback.png';
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetCustomIcon() {
|
||||
this.icon.data = '__original__';
|
||||
},
|
||||
showCustomIconSelector() {
|
||||
this.$refs.iconFileInput.click();
|
||||
},
|
||||
onIconFileInputChanged(event) {
|
||||
const fr = new FileReader();
|
||||
fr.onload = () => this.icon.data = fr.result;
|
||||
fr.readAsDataURL(event.target.files[0]);
|
||||
},
|
||||
async open(applink) {
|
||||
this.mode = applink ? 'edit' : 'new';
|
||||
this.id = applink ? applink.id : '';
|
||||
this.upstreamUri = applink ? applink.upstreamUri : '';
|
||||
this.label = applink ? applink.label : '';
|
||||
this.icon = applink ? {} : { data: '__original__' };
|
||||
this.iconUrl = applink ? applink.iconUrl : '';
|
||||
this.tags = applink ? applink.tags : [];
|
||||
this.accessRestrictionOption = applink && applink.accessRestriction ? 'groups' : 'any';
|
||||
this.accessRestriction = applink && applink.accessRestriction ? applink.accessRestriction : { users: [], groups: [] };
|
||||
reader.onerror = function(event) {
|
||||
reject(event.target.error);
|
||||
};
|
||||
|
||||
// fetch users and groups
|
||||
let [error, result] = await usersModel.list();
|
||||
if (error) return console.error(error);
|
||||
this.users = result;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
[error, result] = await groupsModel.list();
|
||||
if (error) return console.error(error);
|
||||
this.groups = result;
|
||||
function onIconChanged(file) {
|
||||
iconFile.value = file;
|
||||
}
|
||||
|
||||
this.$refs.applinkDialog.open();
|
||||
},
|
||||
async onSubmit() {
|
||||
this.busy = true;
|
||||
function onResetIcon() {
|
||||
iconFile.value = '';
|
||||
imagePicker.value.clear(`${API_ORIGIN}/img/appicon_fallback.png`);
|
||||
}
|
||||
|
||||
const data = {
|
||||
label: this.label,
|
||||
upstreamUri: this.upstreamUri,
|
||||
tags: this.tags,
|
||||
};
|
||||
async function onSubmit() {
|
||||
busy.value = true;
|
||||
|
||||
data.accessRestriction = null;
|
||||
if (this.accessRestrictionOption === 'groups') {
|
||||
data.accessRestriction = { users: [], groups: [] };
|
||||
data.accessRestriction.users = this.accessRestriction.users.map(function (u) { return u.id; });
|
||||
data.accessRestriction.groups = this.accessRestriction.groups.map(function (g) { return g.id; });
|
||||
}
|
||||
const data = {
|
||||
label: label.value,
|
||||
upstreamUri: upstreamUri.value,
|
||||
tags: tags.value,
|
||||
};
|
||||
|
||||
if (this.icon.data === '__original__') { // user reset the icon
|
||||
data.icon = '';
|
||||
} else if (this.icon.data) { // user loaded custom icon
|
||||
data.icon = this.icon.data.replace(/^data:image\/[a-z]+;base64,/, '');
|
||||
}
|
||||
data.accessRestriction = null;
|
||||
if (accessRestrictionOption.value === 'groups') {
|
||||
data.accessRestriction = { users: [], groups: [] };
|
||||
data.accessRestriction.users = accessRestriction.value.users.map(function (u) { return u.id; });
|
||||
data.accessRestriction.groups = accessRestriction.value.groups.map(function (g) { return g.id; });
|
||||
}
|
||||
|
||||
let error;
|
||||
if (this.mode === 'edit') [error] = await applinksModel.update(this.id, data);
|
||||
else [error] = await applinksModel.add(data);
|
||||
if (iconFile.value === '') { // user reset the icon
|
||||
data.icon = '';
|
||||
} else if (iconFile.value) { // user loaded custom icon
|
||||
data.icon = (await getDataURLFromFile(iconFile.value)).replace(/^data:image\/[a-z]+;base64,/, '');
|
||||
}
|
||||
|
||||
if (error) {
|
||||
this.busy = false;
|
||||
return console.error(error);
|
||||
}
|
||||
let error;
|
||||
if (mode.value === 'edit') [error] = await applinksModel.update(id.value, data);
|
||||
else [error] = await applinksModel.add(data);
|
||||
|
||||
this.busy = false;
|
||||
if (error) {
|
||||
busy.value = false;
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
this.$emit('success');
|
||||
this.$refs.applinkDialog.close();
|
||||
},
|
||||
async onRemove() {
|
||||
const yes = await this.$refs.inputDialog.confirm({
|
||||
message: `Really remove applink?`,
|
||||
confirmStyle: 'danger',
|
||||
confirmLabel: this.$t('main.dialog.yes'),
|
||||
rejectLabel: this.$t('main.dialog.cancel')
|
||||
});
|
||||
emits('success');
|
||||
applinkDialog.value.close();
|
||||
|
||||
if (!yes) return;
|
||||
// clear this to retrigger ImagePicker loading
|
||||
iconUrl.value = '';
|
||||
busy.value = false;
|
||||
}
|
||||
|
||||
const [error] = await applinksModel.remove(this.id);
|
||||
if (error) return console.error(error);
|
||||
async function onRemove() {
|
||||
const yes = await inputDialog.value.confirm({
|
||||
message: `Really remove applink?`,
|
||||
confirmStyle: 'danger',
|
||||
confirmLabel: t('main.dialog.yes'),
|
||||
rejectLabel: t('main.dialog.cancel')
|
||||
});
|
||||
|
||||
this.$emit('success');
|
||||
this.$refs.applinkDialog.close();
|
||||
}
|
||||
},
|
||||
};
|
||||
if (!yes) return;
|
||||
|
||||
const [error] = await applinksModel.remove(id.value);
|
||||
if (error) return console.error(error);
|
||||
|
||||
emits('success');
|
||||
applinkDialog.value.close();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open: async function (applink) {
|
||||
mode.value = applink ? 'edit' : 'new';
|
||||
id.value = applink ? applink.id : '';
|
||||
upstreamUri.value = applink ? applink.upstreamUri : '';
|
||||
label.value = applink ? applink.label : '';
|
||||
iconUrl.value = applink ? applink.iconUrl : '';
|
||||
iconFile.value = null;
|
||||
tags.value = applink ? applink.tags : [];
|
||||
accessRestrictionOption.value = applink && applink.accessRestriction ? 'groups' : 'any';
|
||||
accessRestriction.value = applink && applink.accessRestriction ? applink.accessRestriction : { users: [], groups: [] };
|
||||
|
||||
// fetch users and groups
|
||||
let [error, result] = await usersModel.list();
|
||||
if (error) return console.error(error);
|
||||
users.value = result;
|
||||
|
||||
[error, result] = await groupsModel.list();
|
||||
if (error) return console.error(error);
|
||||
groups.value = result;
|
||||
|
||||
applinkDialog.value.open();
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -187,15 +193,9 @@ export default {
|
||||
</FormGroup>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<label for="previewIcon">{{ $t('app.display.icon') }}</label>
|
||||
</div>
|
||||
<div id="previewIcon" class="app-custom-icon" @click="showCustomIconSelector()">
|
||||
<img :src="iconSrc" />
|
||||
<i class="picture-edit-indicator fa fa-pencil-alt"></i>
|
||||
</div>
|
||||
<span style="cursor: pointer;" @click="resetCustomIcon()">{{ $t('app.applinks.clearIconAction') }}</span> - <span class="text-small">{{ $t('app.applinks.clearIconDescription') }}</span>
|
||||
<input type="file" ref="iconFileInput" style="display: none" accept="image/png" @change="onIconFileInputChanged($event)"/>
|
||||
<label for="previewIcon">{{ $t('app.display.icon') }}</label>
|
||||
<ImagePicker ref="imagePicker" v-if="iconUrl" :src="iconUrl" :fallback-src="`${API_ORIGIN}/img/appicon_fallback.png`" @changed="onIconChanged" size="512" display-height="80px"/>
|
||||
<span class="actionable" @click="onResetIcon()">{{ $t('app.applinks.clearIconAction') }}</span> - <span class="text-small">{{ $t('app.applinks.clearIconDescription') }}</span>
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
|
||||
Reference in New Issue
Block a user