diff --git a/html/src/app.js b/html/src/app.js
index e3634819..86004eea 100644
--- a/html/src/app.js
+++ b/html/src/app.js
@@ -15116,13 +15116,21 @@ speechSynthesis.getVoices();
};
$app.methods.addFavoriteAvatar = function (ref, group) {
- API.addFavorite({
+ return API.addFavorite({
type: 'avatar',
favoriteId: ref.id,
tags: group.name
});
};
+ $app.methods.addFavoriteUser = function (ref, group) {
+ return API.addFavorite({
+ type: 'friend',
+ favoriteId: ref.id,
+ tags: group.name
+ });
+ };
+
$app.methods.moveFavorite = function (ref, group, type) {
API.deleteFavorite({
objectId: ref.id
@@ -19175,6 +19183,9 @@ speechSynthesis.getVoices();
};
$app.methods.userImage = function (user) {
+ if (typeof user === 'undefined') {
+ return '';
+ }
if (this.displayVRCPlusIconsAsAvatar && user.userIcon) {
return user.userIcon;
}
@@ -20217,6 +20228,374 @@ speechSynthesis.getVoices();
this.updateWorldExportDialog();
};
+ // App: avatar favorite import
+
+ $app.data.avatarImportDialog = {
+ visible: false,
+ loading: false,
+ progress: 0,
+ progressTotal: 0,
+ input: '',
+ avatarIdList: new Set(),
+ errors: '',
+ avatarImportFavoriteGroup: null,
+ importProgress: 0,
+ importProgressTotal: 0
+ };
+
+ $app.data.avatarImportTable = {
+ data: [],
+ tableProps: {
+ stripe: true,
+ size: 'mini'
+ },
+ layout: 'table'
+ };
+
+ $app.methods.showAvatarImportDialog = function () {
+ this.$nextTick(() => adjustDialogZ(this.$refs.avatarImportDialog.$el));
+ var D = this.avatarImportDialog;
+ this.resetAvatarImport();
+ D.visible = true;
+ };
+
+ $app.methods.processAvatarImportList = async function () {
+ var D = this.avatarImportDialog;
+ D.loading = true;
+ var regexAvatarId =
+ /avtr_[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}/g;
+ var match = [];
+ var avatarIdList = new Set();
+ while ((match = regexAvatarId.exec(D.input)) !== null) {
+ avatarIdList.add(match[0]);
+ }
+ D.input = '';
+ D.errors = '';
+ D.progress = 0;
+ D.progressTotal = avatarIdList.size;
+ var data = Array.from(avatarIdList);
+ for (var i = 0; i < data.length; ++i) {
+ if (!D.visible) {
+ this.resetAvatarImport();
+ }
+ if (!D.loading || !D.visible) {
+ break;
+ }
+ var avatarId = data[i];
+ if (!D.avatarIdList.has(avatarId)) {
+ try {
+ var args = await API.getAvatar({
+ avatarId
+ });
+ this.avatarImportTable.data.push(args.ref);
+ D.avatarIdList.add(avatarId);
+ } catch (err) {
+ D.errors = D.errors.concat(
+ `AvatarId: ${avatarId}\n${err}\n\n`
+ );
+ }
+ }
+ D.progress++;
+ if (D.progress === avatarIdList.size) {
+ D.progress = 0;
+ }
+ }
+ D.loading = false;
+ };
+
+ $app.methods.deleteItemAvatarImport = function (ref) {
+ var D = this.avatarImportDialog;
+ removeFromArray(this.avatarImportTable.data, ref);
+ D.avatarIdList.delete(ref.id);
+ };
+
+ $app.methods.resetAvatarImport = function () {
+ var D = this.avatarImportDialog;
+ D.input = '';
+ D.errors = '';
+ };
+
+ $app.methods.clearAvatarImportTable = function () {
+ var D = this.avatarImportDialog;
+ this.avatarImportTable.data = [];
+ D.avatarIdList = new Set();
+ };
+
+ $app.methods.selectAvatarImportGroup = function (group) {
+ var D = this.avatarImportDialog;
+ D.avatarImportFavoriteGroup = group;
+ };
+
+ $app.methods.cancelAvatarImport = function () {
+ var D = this.avatarImportDialog;
+ D.loading = false;
+ };
+
+ $app.methods.importAvatarImportTable = async function () {
+ var D = this.avatarImportDialog;
+ D.loading = true;
+ if (!D.avatarImportFavoriteGroup) {
+ return;
+ }
+ var data = [...this.avatarImportTable.data].reverse();
+ D.importProgressTotal = data.length;
+ try {
+ for (var i = data.length - 1; i >= 0; i--) {
+ if (!D.loading || !D.visible) {
+ break;
+ }
+ var ref = data[i];
+ await this.addFavoriteAvatar(ref, D.avatarImportFavoriteGroup);
+ removeFromArray(this.avatarImportTable.data, ref);
+ D.avatarIdList.delete(ref.id);
+ D.importProgress++;
+ }
+ } catch (err) {
+ D.errors = `Name: ${ref.name}\nAvatarId: ${ref.id}\n${err}\n\n`;
+ } finally {
+ D.importProgress = 0;
+ D.importProgressTotal = 0;
+ D.loading = false;
+ }
+ };
+
+ API.$on('LOGIN', function () {
+ $app.clearAvatarImportTable();
+ $app.resetAvatarImport();
+ $app.avatarImportDialog.visible = false;
+ $app.avatarImportFavoriteGroup = null;
+
+ $app.avatarExportDialogVisible = false;
+ $app.avatarExportFavoriteGroup = null;
+ });
+
+ // App: avatar favorite export
+
+ $app.data.avatarExportDialogRef = {};
+ $app.data.avatarExportDialogVisible = false;
+ $app.data.avatarExportContent = '';
+ $app.data.avatarExportFavoriteGroup = null;
+
+ $app.methods.showAvatarExportDialog = function () {
+ this.$nextTick(() =>
+ adjustDialogZ(this.$refs.avatarExportDialogRef.$el)
+ );
+ this.avatarExportFavoriteGroup = null;
+ this.updateAvatarExportDialog();
+ this.avatarExportDialogVisible = true;
+ };
+
+ $app.methods.updateAvatarExportDialog = function () {
+ var _ = function (str) {
+ if (/[\x00-\x1f,"]/.test(str) === true) {
+ return `"${str.replace(/"/g, '""')}"`;
+ }
+ return str;
+ };
+ var lines = ['AvatarID,Name'];
+ API.favoriteAvatarGroups.forEach((group) => {
+ if (
+ !this.avatarExportFavoriteGroup ||
+ this.avatarExportFavoriteGroup === group
+ ) {
+ $app.favoriteAvatars.forEach((ref) => {
+ if (group.key === ref.groupKey) {
+ lines.push(`${_(ref.id)},${_(ref.name)}`);
+ }
+ });
+ }
+ });
+ this.avatarExportContent = lines.join('\n');
+ };
+
+ $app.methods.selectAvatarExportGroup = function (group) {
+ this.avatarExportFavoriteGroup = group;
+ this.updateAvatarExportDialog();
+ };
+
+ // App: friend favorite import
+
+ $app.data.friendImportDialog = {
+ visible: false,
+ loading: false,
+ progress: 0,
+ progressTotal: 0,
+ input: '',
+ userIdList: new Set(),
+ errors: '',
+ friendImportFavoriteGroup: null,
+ importProgress: 0,
+ importProgressTotal: 0
+ };
+
+ $app.data.friendImportTable = {
+ data: [],
+ tableProps: {
+ stripe: true,
+ size: 'mini'
+ },
+ layout: 'table'
+ };
+
+ $app.methods.showFriendImportDialog = function () {
+ this.$nextTick(() => adjustDialogZ(this.$refs.friendImportDialog.$el));
+ var D = this.friendImportDialog;
+ this.resetFriendImport();
+ D.visible = true;
+ };
+
+ $app.methods.processFriendImportList = async function () {
+ var D = this.friendImportDialog;
+ D.loading = true;
+ var regexFriendId =
+ /usr_[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}/g;
+ var match = [];
+ var userIdList = new Set();
+ while ((match = regexFriendId.exec(D.input)) !== null) {
+ userIdList.add(match[0]);
+ }
+ D.input = '';
+ D.errors = '';
+ D.progress = 0;
+ D.progressTotal = userIdList.size;
+ var data = Array.from(userIdList);
+ for (var i = 0; i < data.length; ++i) {
+ if (!D.visible) {
+ this.resetFriendImport();
+ }
+ if (!D.loading || !D.visible) {
+ break;
+ }
+ var userId = data[i];
+ if (!D.userIdList.has(userId)) {
+ try {
+ var args = await API.getUser({
+ userId
+ });
+ this.friendImportTable.data.push(args.ref);
+ D.userIdList.add(userId);
+ } catch (err) {
+ D.errors = D.errors.concat(`UserId: ${userId}\n${err}\n\n`);
+ }
+ }
+ D.progress++;
+ if (D.progress === userIdList.size) {
+ D.progress = 0;
+ }
+ }
+ D.loading = false;
+ };
+
+ $app.methods.deleteItemFriendImport = function (ref) {
+ var D = this.friendImportDialog;
+ removeFromArray(this.friendImportTable.data, ref);
+ D.userIdList.delete(ref.id);
+ };
+
+ $app.methods.resetFriendImport = function () {
+ var D = this.friendImportDialog;
+ D.input = '';
+ D.errors = '';
+ };
+
+ $app.methods.clearFriendImportTable = function () {
+ var D = this.friendImportDialog;
+ this.friendImportTable.data = [];
+ D.userIdList = new Set();
+ };
+
+ $app.methods.selectFriendImportGroup = function (group) {
+ var D = this.friendImportDialog;
+ D.friendImportFavoriteGroup = group;
+ };
+
+ $app.methods.cancelFriendImport = function () {
+ var D = this.friendImportDialog;
+ D.loading = false;
+ };
+
+ $app.methods.importFriendImportTable = async function () {
+ var D = this.friendImportDialog;
+ D.loading = true;
+ if (!D.friendImportFavoriteGroup) {
+ return;
+ }
+ var data = [...this.friendImportTable.data].reverse();
+ D.importProgressTotal = data.length;
+ try {
+ for (var i = data.length - 1; i >= 0; i--) {
+ if (!D.loading || !D.visible) {
+ break;
+ }
+ var ref = data[i];
+ await this.addFavoriteUser(ref, D.friendImportFavoriteGroup);
+ removeFromArray(this.friendImportTable.data, ref);
+ D.userIdList.delete(ref.id);
+ D.importProgress++;
+ }
+ } catch (err) {
+ D.errors = `Name: ${ref.displayName}\nUserId: ${ref.id}\n${err}\n\n`;
+ } finally {
+ D.importProgress = 0;
+ D.importProgressTotal = 0;
+ D.loading = false;
+ }
+ };
+
+ API.$on('LOGIN', function () {
+ $app.clearFriendImportTable();
+ $app.resetFriendImport();
+ $app.friendImportDialog.visible = false;
+ $app.friendImportFavoriteGroup = null;
+
+ $app.friendExportDialogVisible = false;
+ $app.friendExportFavoriteGroup = null;
+ });
+
+ // App: friend favorite export
+
+ $app.data.friendExportDialogRef = {};
+ $app.data.friendExportDialogVisible = false;
+ $app.data.friendExportContent = '';
+ $app.data.friendExportFavoriteGroup = null;
+
+ $app.methods.showFriendExportDialog = function () {
+ this.$nextTick(() =>
+ adjustDialogZ(this.$refs.friendExportDialogRef.$el)
+ );
+ this.friendExportFavoriteGroup = null;
+ this.updateFriendExportDialog();
+ this.friendExportDialogVisible = true;
+ };
+
+ $app.methods.updateFriendExportDialog = function () {
+ var _ = function (str) {
+ if (/[\x00-\x1f,"]/.test(str) === true) {
+ return `"${str.replace(/"/g, '""')}"`;
+ }
+ return str;
+ };
+ var lines = ['UserID,Name'];
+ API.favoriteFriendGroups.forEach((group) => {
+ if (
+ !this.friendExportFavoriteGroup ||
+ this.friendExportFavoriteGroup === group
+ ) {
+ $app.favoriteFriends.forEach((ref) => {
+ if (group.key === ref.groupKey) {
+ lines.push(`${_(ref.id)},${_(ref.name)}`);
+ }
+ });
+ }
+ });
+ this.friendExportContent = lines.join('\n');
+ };
+
+ $app.methods.selectFriendExportGroup = function (group) {
+ this.friendExportFavoriteGroup = group;
+ this.updateFriendExportDialog();
+ };
+
// App: user dialog notes
API.saveNote = function (params) {
diff --git a/html/src/index.pug b/html/src/index.pug
index 69a001f2..6fed2fe4 100644
--- a/html/src/index.pug
+++ b/html/src/index.pug
@@ -506,6 +506,8 @@ html
el-tabs(type="card" v-loading="API.isFavoriteLoading")
el-tab-pane(label="Friends")
el-collapse(style="border:0")
+ el-button(size="small" @click="showFriendExportDialog") Export
+ el-button(size="small" @click="showFriendImportDialog") Import
el-collapse-item(v-for="group in API.favoriteFriendGroups" :key="group.name")
template(slot="title")
span(v-text="group.displayName" style="font-weight:bold;font-size:14px;margin-left:10px")
@@ -576,6 +578,8 @@ html
el-button(type="text" icon="el-icon-close" size="mini" @click.stop="deleteFavorite(favorite.id)" style="margin-left:5px")
el-tab-pane(label="Avatars")
el-collapse(style="border:0")
+ el-button(size="small" @click="showAvatarExportDialog") Export
+ el-button(size="small" @click="showAvatarImportDialog") Import
el-collapse-item(v-for="group in API.favoriteAvatarGroups" :key="group.name")
template(slot="title")
span(v-text="group.displayName" style="font-weight:bold;font-size:14px;margin-left:10px")
@@ -885,7 +889,7 @@ html
span.header Friends List
div(style="float:right;font-size:13px")
span Load missing entries
- el-tooltip(placement="top" style="margin-left:5px" content="This spams the API a little so use it sparingly")
+ el-tooltip(placement="top" style="margin-left:5px" content="This takes a lot of API requests so use it sparingly")
i.el-icon-warning
template(v-if="friendsListLoading")
span(v-text="friendsListLoadingProgress" style="margin-left:5px")
@@ -3040,6 +3044,116 @@ html
template(v-once #default="scope")
el-button(type="text" icon="el-icon-close" size="mini" @click="deleteItemWorldImport(scope.row)")
+ //- dialog: export avatar list
+ el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="avatarExportDialogRef" :visible.sync="avatarExportDialogVisible" title="Avatar Favorites Export" width="650px")
+ el-dropdown(@click.native.stop trigger="click" size="small")
+ el-button(size="mini")
+ span(v-if="avatarExportFavoriteGroup") {{ avatarExportFavoriteGroup.displayName }} ({{ avatarExportFavoriteGroup.count }}/{{ avatarExportFavoriteGroup.capacity }}) #[i.el-icon-arrow-down.el-icon--right]
+ span(v-else) All Favorites #[i.el-icon-arrow-down.el-icon--right]
+ el-dropdown-menu(#default="dropdown")
+ el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectAvatarExportGroup(null)") All Favorites
+ template(v-for="groupAPI in API.favoriteAvatarGroups" :key="groupAPI.name")
+ el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectAvatarExportGroup(groupAPI)" :disabled="groupAPI.count >= groupAPI.capacity") {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
+ br
+ el-input(type="textarea" v-if="avatarExportDialogVisible" v-model="avatarExportContent" size="mini" rows="15" resize="none" readonly style="margin-top:15px" @click.native="$event.target.tagName === 'TEXTAREA' && $event.target.select()")
+
+ //- dialog: Avatar import dialog
+ el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="avatarImportDialog" :visible.sync="avatarImportDialog.visible" title="Avatar Favorites Import" width="650px")
+ div(style="font-size:12px")
+ | Enter a list of avatar IDs
+ el-input(type="textarea" v-model="avatarImportDialog.input" size="mini" rows="10" resize="none" style="margin-top:15px")
+ el-button(size="small" @click="processAvatarImportList" :disabled="!avatarImportDialog.input") Process List
+ span(v-if="avatarImportDialog.progress" style="margin-top:10px") #[i.el-icon-loading(style="margin-right:5px")] Progress: {{ avatarImportDialog.progress }}/{{ avatarImportDialog.progressTotal }}
+ br
+ el-dropdown(@click.native.stop trigger="click" size="small")
+ el-button(size="mini")
+ span(v-if="avatarImportDialog.avatarImportFavoriteGroup") {{ avatarImportDialog.avatarImportFavoriteGroup.displayName }} ({{ avatarImportDialog.avatarImportFavoriteGroup.count }}/{{ avatarImportDialog.avatarImportFavoriteGroup.capacity }}) #[i.el-icon-arrow-down.el-icon--right]
+ span(v-else) Select Group #[i.el-icon-arrow-down.el-icon--right]
+ el-dropdown-menu(#default="dropdown")
+ template(v-for="groupAPI in API.favoriteAvatarGroups" :key="groupAPI.name")
+ el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectAvatarImportGroup(groupAPI)" :disabled="groupAPI.count >= groupAPI.capacity") {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
+ el-button(size="small" @click="importAvatarImportTable" style="margin:5px" :disabled="avatarImportTable.data.length === 0 || !avatarImportDialog.avatarImportFavoriteGroup") Import Avatars
+ el-button(v-if="avatarImportDialog.loading" size="small" @click="cancelAvatarImport" style="margin-top:10px") Cancel
+ span(v-if="avatarImportDialog.avatarImportFavoriteGroup") {{ avatarImportTable.data.length }} / {{ avatarImportDialog.avatarImportFavoriteGroup.capacity - avatarImportDialog.avatarImportFavoriteGroup.count }}
+ span(v-if="avatarImportDialog.importProgress" style="margin:10px") #[i.el-icon-loading(style="margin-right:5px")] Import Progress: {{ avatarImportDialog.importProgress }}/{{ avatarImportDialog.importProgressTotal }}
+ br
+ el-button(size="small" @click="clearAvatarImportTable") Clear Table
+ template(v-if="avatarImportDialog.errors")
+ el-button(size="small" @click="avatarImportDialog.errors = ''" style="margin-left:5px") Clear Errors
+ h2(style="font-weight:bold;margin:0") Errors:
+ pre(v-text="avatarImportDialog.errors" style="white-space:pre-wrap;font-size:12px")
+ data-tables(v-if="avatarImportDialog.visible" v-bind="avatarImportTable" v-loading="avatarImportDialog.loading" style="margin-top:10px")
+ el-table-column(label="Image" width="70" prop="thumbnailImageUrl")
+ template(v-once #default="scope")
+ el-popover(placement="right" height="500px" trigger="hover")
+ img.friends-list-avatar(slot="reference" v-lazy="scope.row.thumbnailImageUrl")
+ img.friends-list-avatar(v-lazy="scope.row.imageUrl" style="height:500px;cursor:pointer" @click="openExternalLink(scope.row.imageUrl)")
+ el-table-column(label="Name" prop="name")
+ template(v-once #default="scope")
+ span.x-link(v-text="scope.row.name" @click="showAvatarDialog(scope.row.id)")
+ el-table-column(label="Author" width="120" prop="authorName")
+ template(v-once #default="scope")
+ span.x-link(v-text="scope.row.authorName" @click="showUserDialog(scope.row.authorId)")
+ el-table-column(label="Status" width="70" prop="releaseStatus")
+ template(v-once #default="scope")
+ span(v-text="scope.row.releaseStatus" v-if="scope.row.releaseStatus === 'public'" style="color:#67c23a")
+ span(v-text="scope.row.releaseStatus" v-else-if="scope.row.releaseStatus === 'private'" style="color:#f56c6c")
+ span(v-text="scope.row.releaseStatus" v-else)
+ el-table-column(label="Action" width="90" align="right")
+ template(v-once #default="scope")
+ el-button(type="text" icon="el-icon-close" size="mini" @click="deleteItemAvatarImport(scope.row)")
+
+ //- dialog: export friend list
+ el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="friendExportDialogRef" :visible.sync="friendExportDialogVisible" title="Friend Favorites Export" width="650px")
+ el-dropdown(@click.native.stop trigger="click" size="small")
+ el-button(size="mini")
+ span(v-if="friendExportFavoriteGroup") {{ friendExportFavoriteGroup.displayName }} ({{ friendExportFavoriteGroup.count }}/{{ friendExportFavoriteGroup.capacity }}) #[i.el-icon-arrow-down.el-icon--right]
+ span(v-else) All Favorites #[i.el-icon-arrow-down.el-icon--right]
+ el-dropdown-menu(#default="dropdown")
+ el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectFriendExportGroup(null)") All Favorites
+ template(v-for="groupAPI in API.favoriteFriendGroups" :key="groupAPI.name")
+ el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectFriendExportGroup(groupAPI)" :disabled="groupAPI.count >= groupAPI.capacity") {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
+ br
+ el-input(type="textarea" v-if="friendExportDialogVisible" v-model="friendExportContent" size="mini" rows="15" resize="none" readonly style="margin-top:15px" @click.native="$event.target.tagName === 'TEXTAREA' && $event.target.select()")
+
+ //- dialog: Friend import dialog
+ el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="friendImportDialog" :visible.sync="friendImportDialog.visible" title="Friend Favorites Import" width="650px")
+ div(style="font-size:12px")
+ | Enter a list of user IDs
+ el-input(type="textarea" v-model="friendImportDialog.input" size="mini" rows="10" resize="none" style="margin-top:15px")
+ el-button(size="small" @click="processFriendImportList" :disabled="!friendImportDialog.input") Process List
+ span(v-if="friendImportDialog.progress" style="margin-top:10px") #[i.el-icon-loading(style="margin-right:5px")] Progress: {{ friendImportDialog.progress }}/{{ friendImportDialog.progressTotal }}
+ br
+ el-dropdown(@click.native.stop trigger="click" size="small")
+ el-button(size="mini")
+ span(v-if="friendImportDialog.friendImportFavoriteGroup") {{ friendImportDialog.friendImportFavoriteGroup.displayName }} ({{ friendImportDialog.friendImportFavoriteGroup.count }}/{{ friendImportDialog.friendImportFavoriteGroup.capacity }}) #[i.el-icon-arrow-down.el-icon--right]
+ span(v-else) Select Group #[i.el-icon-arrow-down.el-icon--right]
+ el-dropdown-menu(#default="dropdown")
+ template(v-for="groupAPI in API.favoriteFriendGroups" :key="groupAPI.name")
+ el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectFriendImportGroup(groupAPI)" :disabled="groupAPI.count >= groupAPI.capacity") {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
+ el-button(size="small" @click="importFriendImportTable" style="margin:5px" :disabled="friendImportTable.data.length === 0 || !friendImportDialog.friendImportFavoriteGroup") Import Friends
+ el-button(v-if="friendImportDialog.loading" size="small" @click="cancelFriendImport" style="margin-top:10px") Cancel
+ span(v-if="friendImportDialog.friendImportFavoriteGroup") {{ friendImportTable.data.length }} / {{ friendImportDialog.friendImportFavoriteGroup.capacity - friendImportDialog.friendImportFavoriteGroup.count }}
+ span(v-if="friendImportDialog.importProgress" style="margin:10px") #[i.el-icon-loading(style="margin-right:5px")] Import Progress: {{ friendImportDialog.importProgress }}/{{ friendImportDialog.importProgressTotal }}
+ br
+ el-button(size="small" @click="clearFriendImportTable") Clear Table
+ template(v-if="friendImportDialog.errors")
+ el-button(size="small" @click="friendImportDialog.errors = ''" style="margin-left:5px") Clear Errors
+ h2(style="font-weight:bold;margin:0") Errors:
+ pre(v-text="friendImportDialog.errors" style="white-space:pre-wrap;font-size:12px")
+ data-tables(v-if="friendImportDialog.visible" v-bind="friendImportTable" v-loading="friendImportDialog.loading" style="margin-top:10px")
+ el-table-column(label="Image" width="70" prop="currentAvatarThumbnailImageUrl")
+ template(v-once #default="scope")
+ el-popover(placement="right" height="500px" trigger="hover")
+ img.friends-list-avatar(slot="reference" v-lazy="userImage(scope.row)")
+ img.friends-list-avatar(v-lazy="userImageFull(scope.row)" style="height:500px;cursor:pointer" @click="openExternalLink(userImageFull(scope.row))")
+ el-table-column(label="Name" prop="displayName")
+ template(v-once #default="scope")
+ span.x-link(v-text="scope.row.displayName" @click="showUserDialog(scope.row.id)")
+ el-table-column(label="Action" width="90" align="right")
+ template(v-once #default="scope")
+ el-button(type="text" icon="el-icon-close" size="mini" @click="deleteItemFriendImport(scope.row)")
+
//- dialog: Note export dialog
el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="noteExportDialog" :visible.sync="noteExportDialog.visible" title="Note Export" width="1000px")
div(style="font-size:12px")