refactor: Organize Project Structure (#1211)

* refactor: Organize Project Structure

* fix

* fix

* rm security

* fix
This commit is contained in:
pa
2025-04-18 15:04:03 +09:00
committed by GitHub
parent 59d3ead781
commit 6bda44be52
106 changed files with 172 additions and 165 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,130 @@
<template>
<el-dialog
ref="setAvatarStylesDialog"
class="x-dialog"
:before-close="beforeDialogClose"
:visible.sync="setAvatarStylesDialog.visible"
:title="t('dialog.set_avatar_styles.header')"
width="400px"
append-to-body
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp">
<template v-if="setAvatarStylesDialog.visible">
<div>
<span>{{ t('dialog.set_avatar_styles.primary_style') }}</span>
<el-select
v-model="setAvatarStylesDialog.primaryStyle"
:placeholder="t('dialog.set_avatar_styles.select_style')"
size="small"
clearable
style="display: inline-block">
<el-option
v-for="(style, index) in setAvatarStylesDialog.availableAvatarStyles"
:key="index"
:label="style"
:value="style"></el-option>
</el-select>
</div>
<br />
<div>
<span>{{ t('dialog.set_avatar_styles.secondary_style') }}</span>
<el-select
v-model="setAvatarStylesDialog.secondaryStyle"
:placeholder="t('dialog.set_avatar_styles.select_style')"
size="small"
clearable
style="display: inline-block">
<el-option
v-for="(style, index) in setAvatarStylesDialog.availableAvatarStyles"
:key="index"
:label="style"
:value="style"></el-option>
</el-select>
</div>
</template>
<template #footer>
<el-button size="small" @click="setAvatarStylesDialog.visible = false">{{
t('dialog.set_avatar_styles.cancel')
}}</el-button>
<el-button type="primary" size="small" @click="saveSetAvatarStylesDialog">{{
t('dialog.set_avatar_styles.save')
}}</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { inject, watch, getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { avatarRequest } from '../../../api';
const beforeDialogClose = inject('beforeDialogClose');
const dialogMouseDown = inject('dialogMouseDown');
const dialogMouseUp = inject('dialogMouseUp');
const { t } = useI18n();
const instance = getCurrentInstance();
const $message = instance.proxy.$message;
const props = defineProps({
setAvatarStylesDialog: {
type: Object,
required: true
}
});
watch(
() => props.setAvatarStylesDialog.visible,
(newVal) => {
if (newVal) {
getAvatarStyles();
}
}
);
async function getAvatarStyles() {
const ref = await avatarRequest.getAvailableAvatarStyles();
const styles = [];
const stylesMap = new Map();
for (const style of ref.json) {
styles.push(style.styleName);
stylesMap.set(style.styleName, style.id);
}
props.setAvatarStylesDialog.availableAvatarStyles = styles;
props.setAvatarStylesDialog.availableAvatarStylesMap = stylesMap;
}
function saveSetAvatarStylesDialog() {
if (
props.setAvatarStylesDialog.initialPrimaryStyle === props.setAvatarStylesDialog.primaryStyle &&
props.setAvatarStylesDialog.initialSecondaryStyle === props.setAvatarStylesDialog.secondaryStyle
) {
props.setAvatarStylesDialog.visible = false;
return;
}
const primaryStyleId =
props.setAvatarStylesDialog.availableAvatarStylesMap.get(props.setAvatarStylesDialog.primaryStyle) || '';
const secondaryStyleId =
props.setAvatarStylesDialog.availableAvatarStylesMap.get(props.setAvatarStylesDialog.secondaryStyle) || '';
const params = {
id: props.setAvatarStylesDialog.avatarId,
primaryStyle: primaryStyleId,
secondaryStyle: secondaryStyleId
};
avatarRequest
.saveAvatar(params)
.then(() => {
$message.success(t('dialog.set_avatar_styles.save_success'));
props.setAvatarStylesDialog.visible = false;
})
.catch((error) => {
$message.error(t('dialog.set_avatar_styles.save_failed'));
console.error('Error saving avatar styles:', error);
});
}
</script>
<style scoped></style>

View File

@@ -0,0 +1,289 @@
<template>
<el-dialog
ref="setAvatarTagsDialog"
class="x-dialog"
:before-close="beforeDialogClose"
:visible.sync="setAvatarTagsDialog.visible"
:title="t('dialog.set_avatar_tags.header')"
width="770px"
append-to-body
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp">
<template v-if="setAvatarTagsDialog.visible">
<el-checkbox v-model="setAvatarTagsDialog.contentHorror" @change="updateSelectedAvatarTags">{{
t('dialog.set_avatar_tags.content_horror')
}}</el-checkbox>
<br />
<el-checkbox v-model="setAvatarTagsDialog.contentGore" @change="updateSelectedAvatarTags">{{
t('dialog.set_avatar_tags.content_gore')
}}</el-checkbox>
<br />
<el-checkbox v-model="setAvatarTagsDialog.contentViolence" @change="updateSelectedAvatarTags">{{
t('dialog.set_avatar_tags.content_violence')
}}</el-checkbox>
<br />
<el-checkbox v-model="setAvatarTagsDialog.contentAdult" @change="updateSelectedAvatarTags">{{
t('dialog.set_avatar_tags.content_adult')
}}</el-checkbox>
<br />
<el-checkbox v-model="setAvatarTagsDialog.contentSex" @change="updateSelectedAvatarTags">{{
t('dialog.set_avatar_tags.content_sex')
}}</el-checkbox>
<br />
<el-input
v-model="setAvatarTagsDialog.selectedTagsCsv"
size="mini"
:autosize="{ minRows: 2, maxRows: 5 }"
:placeholder="t('dialog.set_avatar_tags.custom_tags_placeholder')"
style="margin-top: 10px"
@input="updateInputAvatarTags"></el-input>
<template v-if="setAvatarTagsDialog.ownAvatars.length === setAvatarTagsDialog.selectedCount">
<el-button size="small" @click="setAvatarTagsSelectToggle">{{
t('dialog.set_avatar_tags.select_none')
}}</el-button>
</template>
<template v-else>
<el-button size="small" @click="setAvatarTagsSelectToggle">{{
t('dialog.set_avatar_tags.select_all')
}}</el-button>
</template>
<span style="margin-left: 5px"
>{{ setAvatarTagsDialog.selectedCount }} / {{ setAvatarTagsDialog.ownAvatars.length }}</span
>
<span v-if="setAvatarTagsDialog.loading" style="margin-left: 5px">
<i class="el-icon-loading"></i>
</span>
<br />
<div class="x-friend-list" style="margin-top: 10px; min-height: 60px; max-height: 280px">
<div
v-for="avatar in setAvatarTagsDialog.ownAvatars"
:key="avatar.id"
class="x-friend-item x-friend-item-border"
style="width: 350px"
@click="showAvatarDialog(avatar.id)">
<div class="avatar">
<img v-if="avatar.thumbnailImageUrl" v-lazy="avatar.thumbnailImageUrl" />
</div>
<div class="detail">
<span class="name" v-text="avatar.name"></span>
<span
v-if="avatar.releaseStatus === 'public'"
class="extra"
style="color: #67c23a"
v-text="avatar.releaseStatus"></span>
<span
v-else-if="avatar.releaseStatus === 'private'"
class="extra"
style="color: #f56c6c"
v-text="avatar.releaseStatus"></span>
<span v-else class="extra" v-text="avatar.releaseStatus"></span>
<span class="extra" v-text="avatar.$tagString"></span>
</div>
<el-button type="text" size="mini" style="margin-left: 5px" @click.stop>
<el-checkbox v-model="avatar.$selected" @change="updateAvatarTagsSelection"></el-checkbox>
</el-button>
</div>
</div>
</template>
<template #footer>
<el-button size="small" @click="setAvatarTagsDialog.visible = false">{{
t('dialog.set_avatar_tags.cancel')
}}</el-button>
<el-button type="primary" size="small" @click="saveSetAvatarTagsDialog">{{
t('dialog.set_avatar_tags.save')
}}</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { inject, watch, getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { avatarRequest } from '../../../api';
const beforeDialogClose = inject('beforeDialogClose');
const dialogMouseDown = inject('dialogMouseDown');
const dialogMouseUp = inject('dialogMouseUp');
const showAvatarDialog = inject('showAvatarDialog');
const { t } = useI18n();
const instance = getCurrentInstance();
const $message = instance.proxy.$message;
const props = defineProps({
setAvatarTagsDialog: {
type: Object,
required: true
}
});
watch(
() => props.setAvatarTagsDialog.visible,
(newVal) => {
if (newVal) {
updateAvatarTagsSelection();
updateSelectedAvatarTags();
}
}
);
function updateSelectedAvatarTags() {
const D = props.setAvatarTagsDialog;
if (D.contentHorror) {
if (!D.selectedTags.includes('content_horror')) {
D.selectedTags.push('content_horror');
}
} else if (D.selectedTags.includes('content_horror')) {
D.selectedTags.splice(D.selectedTags.indexOf('content_horror'), 1);
}
if (D.contentGore) {
if (!D.selectedTags.includes('content_gore')) {
D.selectedTags.push('content_gore');
}
} else if (D.selectedTags.includes('content_gore')) {
D.selectedTags.splice(D.selectedTags.indexOf('content_gore'), 1);
}
if (D.contentViolence) {
if (!D.selectedTags.includes('content_violence')) {
D.selectedTags.push('content_violence');
}
} else if (D.selectedTags.includes('content_violence')) {
D.selectedTags.splice(D.selectedTags.indexOf('content_violence'), 1);
}
if (D.contentAdult) {
if (!D.selectedTags.includes('content_adult')) {
D.selectedTags.push('content_adult');
}
} else if (D.selectedTags.includes('content_adult')) {
D.selectedTags.splice(D.selectedTags.indexOf('content_adult'), 1);
}
if (D.contentSex) {
if (!D.selectedTags.includes('content_sex')) {
D.selectedTags.push('content_sex');
}
} else if (D.selectedTags.includes('content_sex')) {
D.selectedTags.splice(D.selectedTags.indexOf('content_sex'), 1);
}
D.selectedTagsCsv = D.selectedTags.join(',').replace(/content_/g, '');
}
function updateAvatarTagsSelection() {
const D = props.setAvatarTagsDialog;
D.selectedCount = 0;
for (const ref of D.ownAvatars) {
if (ref.$selected) {
D.selectedCount++;
}
ref.$tagString = '';
const contentTags = [];
ref.tags.forEach((tag) => {
if (tag.startsWith('content_')) {
contentTags.push(tag.substring(8));
}
});
for (let i = 0; i < contentTags.length; ++i) {
const tag = contentTags[i];
if (i < contentTags.length - 1) {
ref.$tagString += `${tag}, `;
} else {
ref.$tagString += tag;
}
}
}
// props.setAvatarTagsDialog.forceUpdate++;
}
function setAvatarTagsSelectToggle() {
const D = props.setAvatarTagsDialog;
const allSelected = D.ownAvatars.length === D.selectedCount;
for (const ref of D.ownAvatars) {
ref.$selected = !allSelected;
}
updateAvatarTagsSelection();
}
async function saveSetAvatarTagsDialog() {
const D = props.setAvatarTagsDialog;
if (D.loading) {
return;
}
D.loading = true;
try {
for (let i = D.ownAvatars.length - 1; i >= 0; --i) {
const ref = D.ownAvatars[i];
if (!D.visible) {
break;
}
if (!ref.$selected) {
continue;
}
const tags = [...D.selectedTags];
for (const tag of ref.tags) {
if (!tag.startsWith('content_')) {
tags.push(tag);
}
}
await avatarRequest.saveAvatar({
id: ref.id,
tags
});
D.selectedCount--;
}
} catch (err) {
console.error(err);
$message({
message: 'Error saving avatar tags',
type: 'error'
});
} finally {
D.loading = false;
D.visible = false;
}
}
function updateInputAvatarTags() {
const D = props.setAvatarTagsDialog;
D.contentHorror = false;
D.contentGore = false;
D.contentViolence = false;
D.contentAdult = false;
D.contentSex = false;
const tags = D.selectedTagsCsv.split(',');
D.selectedTags = [];
for (const tag of tags) {
switch (tag) {
case 'horror':
D.contentHorror = true;
break;
case 'gore':
D.contentGore = true;
break;
case 'violence':
D.contentViolence = true;
break;
case 'adult':
D.contentAdult = true;
break;
case 'sex':
D.contentSex = true;
break;
}
if (!D.selectedTags.includes(`content_${tag}`)) {
D.selectedTags.push(`content_${tag}`);
}
}
}
// useless
// $app.data.avatarContentTags = [
// 'content_horror',
// 'content_gore',
// 'content_violence',
// 'content_adult',
// 'content_sex'
// ];
</script>
<style scoped></style>

View File

@@ -0,0 +1,193 @@
<template>
<el-dialog
ref="favoriteDialog"
:before-close="beforeDialogClose"
:visible.sync="isVisible"
:title="$t('dialog.favorite.header')"
width="300px"
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp">
<div v-loading="loading">
<span style="display: block; text-align: center">{{ $t('dialog.favorite.vrchat_favorites') }}</span>
<template v-if="favoriteDialog.currentGroup && favoriteDialog.currentGroup.key">
<el-button
style="display: block; width: 100%; margin: 10px 0"
@click="deleteFavoriteNoConfirm(favoriteDialog.objectId)">
<i class="el-icon-check"></i>
{{ favoriteDialog.currentGroup.displayName }} ({{ favoriteDialog.currentGroup.count }} /
{{ favoriteDialog.currentGroup.capacity }})
</el-button>
</template>
<template v-else>
<el-button
v-for="group in groups"
:key="group.key"
style="display: block; width: 100%; margin: 10px 0"
@click="addFavorite(group)">
{{ group.displayName }} ({{ group.count }} / {{ group.capacity }})
</el-button>
</template>
</div>
<div v-if="favoriteDialog.type === 'world'" style="margin-top: 20px">
<span style="display: block; text-align: center">{{ $t('dialog.favorite.local_favorites') }}</span>
<template v-for="group in localWorldFavoriteGroups">
<el-button
v-if="hasLocalWorldFavorite(favoriteDialog.objectId, group)"
:key="group"
style="display: block; width: 100%; margin: 10px 0"
@click="removeLocalWorldFavorite(favoriteDialog.objectId, group)">
<i class="el-icon-check"></i>
{{ group }} ({{ getLocalWorldFavoriteGroupLength(group) }})
</el-button>
<el-button
v-else
:key="group"
style="display: block; width: 100%; margin: 10px 0"
@click="addLocalWorldFavorite(favoriteDialog.objectId, group)">
{{ group }} ({{ getLocalWorldFavoriteGroupLength(group) }})
</el-button>
</template>
</div>
<div v-if="favoriteDialog.type === 'avatar'" style="margin-top: 20px">
<span style="display: block; text-align: center">{{ $t('dialog.favorite.local_avatar_favorites') }}</span>
<template v-for="group in localAvatarFavoriteGroups">
<el-button
v-if="hasLocalAvatarFavorite(favoriteDialog.objectId, group)"
:key="group"
style="display: block; width: 100%; margin: 10px 0"
@click="removeLocalAvatarFavorite(favoriteDialog.objectId, group)">
<i class="el-icon-check"></i>
{{ group }} ({{ getLocalAvatarFavoriteGroupLength(group) }})
</el-button>
<el-button
v-else
:key="group"
style="display: block; width: 100%; margin: 10px 0"
:disabled="!isLocalUserVrcplusSupporter"
@click="addLocalAvatarFavorite(favoriteDialog.objectId, group)">
{{ group }} ({{ getLocalAvatarFavoriteGroupLength(group) }})
</el-button>
</template>
</div>
</el-dialog>
</template>
<script>
import { favoriteRequest } from '../../api';
import Noty from 'noty';
export default {
name: 'ChooseFavoriteGroupDialog',
inject: ['API', 'beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp', 'adjustDialogZ'],
props: {
favoriteDialog: {
type: Object,
default: () => ({
visible: false,
type: '',
objectId: '',
currentGroup: {}
})
},
localWorldFavoriteGroups: {
type: Array,
default: () => []
},
localAvatarFavoriteGroups: {
type: Array,
default: () => []
},
hasLocalWorldFavorite: {
type: Function,
default: () => () => false
},
getLocalWorldFavoriteGroupLength: {
type: Function,
default: () => () => 0
},
hasLocalAvatarFavorite: {
type: Function,
default: () => () => false
},
getLocalAvatarFavoriteGroupLength: {
type: Function,
default: () => () => 0
}
},
data() {
return {
groups: [],
loading: false
};
},
computed: {
isVisible: {
get() {
return this.favoriteDialog.visible;
},
set(value) {
this.$emit('update:favorite-dialog', { ...this.favoriteDialog, visible: value });
}
},
isLocalUserVrcplusSupporter() {
return this.API.currentUser.$isVRCPlus;
}
},
watch: {
'favoriteDialog.visible'(value) {
if (value) {
this.initFavoriteDialog();
this.$nextTick(() => {
this.adjustDialogZ(this.$refs.favoriteDialog.$el);
});
}
}
},
methods: {
initFavoriteDialog() {
if (this.favoriteDialog.type === 'friend') {
this.groups = this.API.favoriteFriendGroups;
} else if (this.favoriteDialog.type === 'world') {
this.groups = this.API.favoriteWorldGroups;
} else if (this.favoriteDialog.type === 'avatar') {
this.groups = this.API.favoriteAvatarGroups;
}
},
addFavorite(group) {
const D = this.favoriteDialog;
this.loading = true;
favoriteRequest
.addFavorite({
type: D.type,
favoriteId: D.objectId,
tags: group.name
})
.then(() => {
this.isVisible = false;
new Noty({
type: 'success',
text: 'Favorite added'
}).show();
})
.finally(() => {
this.loading = false;
});
},
addLocalWorldFavorite(...args) {
this.$emit('add-local-world-favorite', ...args);
},
removeLocalWorldFavorite(...args) {
this.$emit('remove-local-world-favorite', ...args);
},
addLocalAvatarFavorite(...args) {
this.$emit('add-local-avatar-favorite', ...args);
},
removeLocalAvatarFavorite(...args) {
this.$emit('remove-local-avatar-favorite', ...args);
},
deleteFavoriteNoConfirm(...args) {
this.$emit('delete-favorite-no-confirm', ...args);
}
}
};
</script>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,110 @@
<template>
<el-dialog
class="x-dialog"
:before-close="beforeDialogClose"
:visible="isGroupLogsExportDialogVisible"
:title="t('dialog.group_member_moderation.export_logs')"
width="650px"
append-to-body
@close="setIsGroupLogsExportDialogVisible"
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp">
<el-checkbox-group
v-model="checkedGroupLogsExportLogsOptions"
style="margin-bottom: 10px"
@change="updateGroupLogsExportContent">
<template v-for="option in checkGroupsLogsExportLogsOptions">
<el-checkbox :key="option.label" :label="option.label">
{{ t(option.text) }}
</el-checkbox>
</template>
</el-checkbox-group>
<br />
<el-input
v-model="groupLogsExportContent"
type="textarea"
size="mini"
rows="15"
resize="none"
readonly
style="margin-top: 15px"
@click.native="handleCopyGroupLogsExportContent" />
</el-dialog>
</template>
<script setup>
import { inject, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import utils from '../../../classes/utils';
const beforeDialogClose = inject('beforeDialogClose');
const dialogMouseDown = inject('dialogMouseDown');
const dialogMouseUp = inject('dialogMouseUp');
const { t } = useI18n();
const props = defineProps({
isGroupLogsExportDialogVisible: {
type: Boolean,
default: false
},
groupLogsModerationTable: {
type: Object,
default: () => {}
}
});
const emit = defineEmits(['update:isGroupLogsExportDialogVisible']);
watch(
() => props.isGroupLogsExportDialogVisible,
(newVal) => {
if (newVal) {
updateGroupLogsExportContent();
}
}
);
const groupLogsExportContent = ref('');
const checkGroupsLogsExportLogsOptions = [
{ label: 'created_at', text: 'dialog.group_member_moderation.created_at' },
{ label: 'eventType', text: 'dialog.group_member_moderation.type' },
{ label: 'actorDisplayName', text: 'dialog.group_member_moderation.display_name' },
{ label: 'description', text: 'dialog.group_member_moderation.description' },
{ label: 'data', text: 'dialog.group_member_moderation.data' }
];
const checkedGroupLogsExportLogsOptions = ref([
'created_at',
'eventType',
'actorDisplayName',
'description',
'data'
]);
function updateGroupLogsExportContent() {
const formatter = (str) => (/[\x00-\x1f,"]/.test(str) ? `"${str.replace(/"/g, '""')}"` : str);
const sortedCheckedOptions = checkGroupsLogsExportLogsOptions
.filter((option) => checkedGroupLogsExportLogsOptions.value.includes(option.label))
.map((option) => option.label);
const header = `${sortedCheckedOptions.join(',')}\n`;
const content = props.groupLogsModerationTable.data
.map((item) =>
sortedCheckedOptions
.map((key) => formatter(key === 'data' ? JSON.stringify(item[key]) : item[key]))
.join(',')
)
.join('\n');
groupLogsExportContent.value = header + content; // Update ref
}
function handleCopyGroupLogsExportContent() {
utils.copyToClipboard(groupLogsExportContent.value);
}
function setIsGroupLogsExportDialogVisible() {
emit('update:isGroupLogsExportDialogVisible', false);
}
</script>

View File

@@ -0,0 +1,200 @@
<template>
<el-dialog
ref="groupPostEditDialog"
:before-close="beforeDialogClose"
:visible.sync="groupPostEditDialog.visible"
:title="$t('dialog.group_post_edit.header')"
width="650px"
append-to-body
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp">
<div v-if="groupPostEditDialog.visible">
<h3 v-text="groupPostEditDialog.groupRef.name"></h3>
<el-form :model="groupPostEditDialog" label-width="150px">
<el-form-item :label="$t('dialog.group_post_edit.title')">
<el-input v-model="groupPostEditDialog.title" size="mini"></el-input>
</el-form-item>
<el-form-item :label="$t('dialog.group_post_edit.message')">
<el-input
v-model="groupPostEditDialog.text"
type="textarea"
:rows="4"
:autosize="{ minRows: 4, maxRows: 20 }"
style="margin-top: 10px"
resize="none"></el-input>
</el-form-item>
<el-form-item>
<el-checkbox
v-if="!groupPostEditDialog.postId"
v-model="groupPostEditDialog.sendNotification"
size="small">
{{ $t('dialog.group_post_edit.send_notification') }}
</el-checkbox>
</el-form-item>
<el-form-item :label="$t('dialog.group_post_edit.post_visibility')">
<el-radio-group v-model="groupPostEditDialog.visibility" size="small">
<el-radio label="public">
{{ $t('dialog.group_post_edit.visibility_public') }}
</el-radio>
<el-radio label="group">
{{ $t('dialog.group_post_edit.visibility_group') }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="groupPostEditDialog.visibility === 'group'"
:label="$t('dialog.new_instance.roles')">
<el-select
v-model="groupPostEditDialog.roleIds"
multiple
clearable
:placeholder="$t('dialog.new_instance.role_placeholder')"
style="width: 100%">
<el-option-group :label="$t('dialog.new_instance.role_placeholder')">
<el-option
v-for="role in groupPostEditDialog.groupRef?.roles"
:key="role.id"
:label="role.name"
:value="role.id"
style="height: auto; width: 478px">
<div class="detail">
<span class="name" v-text="role.name"></span>
</div>
</el-option>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item :label="$t('dialog.group_post_edit.image')">
<template v-if="gallerySelectDialog.selectedFileId">
<div style="display: inline-block; flex: none; margin-right: 5px">
<el-popover placement="right" width="500px" trigger="click">
<img
slot="reference"
v-lazy="gallerySelectDialog.selectedImageUrl"
style="
flex: none;
width: 60px;
height: 60px;
border-radius: 4px;
object-fit: cover;
" />
<img
v-lazy="gallerySelectDialog.selectedImageUrl"
style="height: 500px"
@click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)" />
</el-popover>
<el-button size="mini" style="vertical-align: top" @click="clearImageGallerySelect">
{{ $t('dialog.invite_message.clear_selected_image') }}
</el-button>
</div>
</template>
<template v-else>
<el-button size="mini" style="margin-right: 5px" @click="showGallerySelectDialog">
{{ $t('dialog.invite_message.select_image') }}
</el-button>
</template>
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button size="small" @click="groupPostEditDialog.visible = false">
{{ $t('dialog.group_post_edit.cancel') }}
</el-button>
<el-button v-if="groupPostEditDialog.postId" size="small" @click="editGroupPost">
{{ $t('dialog.group_post_edit.edit_post') }}
</el-button>
<el-button v-else size="small" @click="createGroupPost">
{{ $t('dialog.group_post_edit.create_post') }}
</el-button>
</template>
</el-dialog>
</template>
<script>
import { groupRequest } from '../../../api';
export default {
name: 'GroupPostEditDialog',
inject: [
'beforeDialogClose',
'showFullscreenImageDialog',
'dialogMouseDown',
'dialogMouseUp',
'showGallerySelectDialog'
],
props: {
dialogData: {
type: Object,
required: true
},
gallerySelectDialog: {
type: Object,
required: true
}
},
computed: {
groupPostEditDialog: {
get() {
return this.dialogData;
},
set(value) {
this.$emit('update:dialog-data', value);
}
}
},
methods: {
editGroupPost() {
const D = this.groupPostEditDialog;
if (!D.groupId || !D.postId) {
return;
}
const params = {
groupId: D.groupId,
postId: D.postId,
title: D.title,
text: D.text,
roleIds: D.roleIds,
visibility: D.visibility,
imageId: null
};
if (this.gallerySelectDialog.selectedFileId) {
params.imageId = this.gallerySelectDialog.selectedFileId;
}
groupRequest.editGroupPost(params).then((args) => {
this.$message({
message: 'Group post edited',
type: 'success'
});
return args;
});
D.visible = false;
},
createGroupPost() {
const D = this.groupPostEditDialog;
const params = {
groupId: D.groupId,
title: D.title,
text: D.text,
roleIds: D.roleIds,
visibility: D.visibility,
sendNotification: D.sendNotification,
imageId: null
};
if (this.gallerySelectDialog.selectedFileId) {
params.imageId = this.gallerySelectDialog.selectedFileId;
}
groupRequest.createGroupPost(params).then((args) => {
this.$message({
message: 'Group post created',
type: 'success'
});
return args;
});
D.visible = false;
},
clearImageGallerySelect() {
this.$emit('clear-image-gallery-select');
}
}
};
</script>

View File

@@ -0,0 +1,307 @@
<template>
<el-dialog
ref="inviteGroupDialog"
:visible.sync="inviteGroupDialog.visible"
:before-close="beforeDialogClose"
:title="$t('dialog.invite_to_group.header')"
width="450px"
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp">
<div v-if="inviteGroupDialog.visible" v-loading="inviteGroupDialog.loading">
<span>{{ $t('dialog.invite_to_group.description') }}</span>
<br />
<el-select
v-model="inviteGroupDialog.groupId"
clearable
:placeholder="$t('dialog.invite_to_group.choose_group_placeholder')"
filterable
:disabled="inviteGroupDialog.loading"
style="margin-top: 15px"
@change="isAllowedToInviteToGroup">
<el-option-group
v-if="API.currentUserGroups.size"
:label="$t('dialog.invite_to_group.groups')"
style="width: 410px">
<el-option
v-for="group in API.currentUserGroups.values()"
:key="group.id"
:label="group.name"
:value="group.id"
style="height: auto"
class="x-friend-item">
<div class="avatar">
<img v-lazy="group.iconUrl" />
</div>
<div class="detail">
<span class="name" v-text="group.name"></span>
</div>
</el-option>
</el-option-group>
</el-select>
<el-select
v-model="inviteGroupDialog.userIds"
multiple
clearable
:placeholder="$t('dialog.invite_to_group.choose_friends_placeholder')"
filterable
:disabled="inviteGroupDialog.loading"
style="width: 100%; margin-top: 15px">
<el-option-group v-if="inviteGroupDialog.userId" :label="$t('dialog.invite_to_group.selected_users')">
<el-option
:key="inviteGroupDialog.userObject.id"
:label="inviteGroupDialog.userObject.displayName"
:value="inviteGroupDialog.userObject.id"
class="x-friend-item">
<template v-if="inviteGroupDialog.userObject.id">
<div class="avatar" :class="userStatusClass(inviteGroupDialog.userObject)">
<img v-lazy="userImage(inviteGroupDialog.userObject)" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: inviteGroupDialog.userObject.$userColour }"
v-text="inviteGroupDialog.userObject.displayName"></span>
</div>
</template>
<span v-else v-text="inviteGroupDialog.userId"></span>
</el-option>
</el-option-group>
<el-option-group v-if="vipFriends.length" :label="$t('side_panel.favorite')">
<el-option
v-for="friend in vipFriends"
:key="friend.id"
:label="friend.name"
:value="friend.id"
style="height: auto"
class="x-friend-item">
<template v-if="friend.ref">
<div class="avatar" :class="userStatusClass(friend.ref)">
<img v-lazy="userImage(friend.ref)" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
<el-option-group v-if="onlineFriends.length" :label="$t('side_panel.online')">
<el-option
v-for="friend in onlineFriends"
:key="friend.id"
:label="friend.name"
:value="friend.id"
style="height: auto"
class="x-friend-item">
<template v-if="friend.ref">
<div class="avatar" :class="userStatusClass(friend.ref)">
<img v-lazy="userImage(friend.ref)" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
<el-option-group v-if="activeFriends.length" :label="$t('side_panel.active')">
<el-option
v-for="friend in activeFriends"
:key="friend.id"
:label="friend.name"
:value="friend.id"
style="height: auto"
class="x-friend-item">
<template v-if="friend.ref">
<div class="avatar">
<img v-lazy="userImage(friend.ref)" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
<el-option-group v-if="offlineFriends.length" :label="$t('side_panel.offline')">
<el-option
v-for="friend in offlineFriends"
:key="friend.id"
:label="friend.name"
:value="friend.id"
style="height: auto"
class="x-friend-item">
<template v-if="friend.ref">
<div class="avatar">
<img v-lazy="userImage(friend.ref)" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
</el-select>
</div>
<template #footer>
<el-button
type="primary"
size="small"
:disabled="inviteGroupDialog.loading || !inviteGroupDialog.userIds.length"
@click="sendGroupInvite">
Invite
</el-button>
</template>
</el-dialog>
</template>
<script>
import { groupRequest, userRequest } from '../../../api';
import utils from '../../../classes/utils';
export default {
name: 'InviteGroupDialog',
inject: [
'API',
'dialogMouseDown',
'dialogMouseUp',
'beforeDialogClose',
'userStatusClass',
'userImage',
'adjustDialogZ'
],
props: {
dialogData: {
type: Object,
required: true
},
vipFriends: {
type: Array,
required: true
},
onlineFriends: {
type: Array,
required: true
},
activeFriends: {
type: Array,
required: true
},
offlineFriends: {
type: Array,
required: true
}
},
computed: {
inviteGroupDialog: {
get() {
return this.dialogData;
},
set(value) {
this.$emit('update:dialog-data', value);
}
}
},
watch: {
'dialogData.visible'(value) {
if (value) {
this.initDialog();
}
}
},
methods: {
initDialog() {
this.$nextTick(() => this.adjustDialogZ(this.$refs.inviteGroupDialog.$el));
const D = this.inviteGroupDialog;
if (D.groupId) {
this.API.getCachedGroup({
groupId: D.groupId
})
.then((args) => {
D.groupName = args.ref.name;
})
.catch(() => {
D.groupId = '';
});
this.isAllowedToInviteToGroup();
}
if (D.userId) {
userRequest.getCachedUser({ userId: D.userId }).then((args) => {
D.userObject = args.ref;
D.userIds = [D.userId];
});
}
},
isAllowedToInviteToGroup() {
const D = this.inviteGroupDialog;
const groupId = D.groupId;
if (!groupId) {
return;
}
this.inviteGroupDialog.loading = true;
groupRequest
.getGroup({ groupId })
.then((args) => {
if (utils.hasGroupPermission(args.ref, 'group-invites-manage')) {
return args;
}
// not allowed to invite
this.inviteGroupDialog.groupId = '';
this.$message({
type: 'error',
message: 'You are not allowed to invite to this group'
});
return args;
})
.finally(() => {
this.inviteGroupDialog.loading = false;
});
},
sendGroupInvite() {
this.$confirm('Continue? Invite User(s) To Group', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
const D = this.inviteGroupDialog;
if (action !== 'confirm' || D.loading === true) {
return;
}
D.loading = true;
const inviteLoop = () => {
if (D.userIds.length === 0) {
D.loading = false;
return;
}
const receiverUserId = D.userIds.shift();
groupRequest
.sendGroupInvite({
groupId: D.groupId,
userId: receiverUserId
})
.then(inviteLoop)
.catch(() => {
D.loading = false;
});
};
inviteLoop();
}
});
}
}
};
</script>

View File

@@ -0,0 +1,877 @@
<template>
<el-dialog
ref="newInstanceDialog"
:before-close="beforeDialogClose"
:visible.sync="newInstanceDialog.visible"
:title="$t('dialog.new_instance.header')"
width="650px"
append-to-body
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp">
<el-tabs v-model="newInstanceDialog.selectedTab" type="card" @tab-click="newInstanceTabClick">
<el-tab-pane :label="$t('dialog.new_instance.normal')">
<el-form :model="newInstanceDialog" label-width="150px">
<el-form-item :label="$t('dialog.new_instance.access_type')">
<el-radio-group v-model="newInstanceDialog.accessType" size="mini" @change="buildInstance">
<el-radio-button label="public">{{
$t('dialog.new_instance.access_type_public')
}}</el-radio-button>
<el-radio-button label="group">{{
$t('dialog.new_instance.access_type_group')
}}</el-radio-button>
<el-radio-button label="friends+">{{
$t('dialog.new_instance.access_type_friend_plus')
}}</el-radio-button>
<el-radio-button label="friends">{{
$t('dialog.new_instance.access_type_friend')
}}</el-radio-button>
<el-radio-button label="invite+">{{
$t('dialog.new_instance.access_type_invite_plus')
}}</el-radio-button>
<el-radio-button label="invite">{{
$t('dialog.new_instance.access_type_invite')
}}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="newInstanceDialog.accessType === 'group'"
:label="$t('dialog.new_instance.group_access_type')">
<el-radio-group v-model="newInstanceDialog.groupAccessType" size="mini" @change="buildInstance">
<el-radio-button
label="members"
:disabled="
!hasGroupPermission(newInstanceDialog.groupRef, 'group-instance-open-create')
"
>{{ $t('dialog.new_instance.group_access_type_members') }}</el-radio-button
>
<el-radio-button
label="plus"
:disabled="
!hasGroupPermission(newInstanceDialog.groupRef, 'group-instance-plus-create')
"
>{{ $t('dialog.new_instance.group_access_type_plus') }}</el-radio-button
>
<el-radio-button
label="public"
:disabled="
!hasGroupPermission(newInstanceDialog.groupRef, 'group-instance-public-create') ||
newInstanceDialog.groupRef.privacy === 'private'
"
>{{ $t('dialog.new_instance.group_access_type_public') }}</el-radio-button
>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('dialog.new_instance.region')">
<el-radio-group v-model="newInstanceDialog.region" size="mini" @change="buildInstance">
<el-radio-button label="US West">{{
$t('dialog.new_instance.region_usw')
}}</el-radio-button>
<el-radio-button label="US East">{{
$t('dialog.new_instance.region_use')
}}</el-radio-button>
<el-radio-button label="Europe">{{ $t('dialog.new_instance.region_eu') }}</el-radio-button>
<el-radio-button label="Japan">{{ $t('dialog.new_instance.region_jp') }}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('dialog.new_instance.content_settings')">
<el-select
v-model="newInstanceDialog.selectedContentSettings"
multiple
:placeholder="$t('dialog.new_instance.content_placeholder')"
style="width: 100%"
@change="buildInstance">
<el-option-group :label="$t('dialog.new_instance.content_placeholder')">
<el-option
class="x-friend-item"
value="emoji"
:label="$t('dialog.new_instance.content_emoji')"></el-option>
<el-option
class="x-friend-item"
value="stickers"
:label="$t('dialog.new_instance.content_stickers')"></el-option>
<el-option
class="x-friend-item"
value="pedestals"
:label="$t('dialog.new_instance.content_pedestals')"></el-option>
<el-option
class="x-friend-item"
value="prints"
:label="$t('dialog.new_instance.content_prints')"></el-option>
<el-option
class="x-friend-item"
value="drones"
:label="$t('dialog.new_instance.content_drones')"></el-option>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item
v-if="newInstanceDialog.accessType === 'group'"
:label="$t('dialog.new_instance.queueEnabled')">
<el-checkbox v-model="newInstanceDialog.queueEnabled" @change="buildInstance"></el-checkbox>
</el-form-item>
<el-form-item
v-if="newInstanceDialog.accessType === 'group'"
:label="$t('dialog.new_instance.ageGate')">
<el-checkbox
v-model="newInstanceDialog.ageGate"
:disabled="
!hasGroupPermission(newInstanceDialog.groupRef, 'group-instance-age-gated-create')
"
@change="buildInstance"></el-checkbox>
</el-form-item>
<el-form-item :label="$t('dialog.new_instance.world_id')">
<el-input
v-model="newInstanceDialog.worldId"
size="mini"
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()"
@change="buildInstance"></el-input>
</el-form-item>
<el-form-item
v-if="newInstanceDialog.accessType === 'group'"
:label="$t('dialog.new_instance.group_id')">
<el-select
v-model="newInstanceDialog.groupId"
clearable
:placeholder="$t('dialog.new_instance.group_placeholder')"
filterable
style="width: 100%"
@change="buildInstance">
<el-option-group :label="$t('dialog.new_instance.group_placeholder')">
<el-option
v-for="group in API.currentUserGroups.values()"
v-if="
group &&
(hasGroupPermission(group, 'group-instance-public-create') ||
hasGroupPermission(group, 'group-instance-plus-create') ||
hasGroupPermission(group, 'group-instance-open-create'))
"
:key="group.id"
:label="group.name"
:value="group.id"
class="x-friend-item"
style="height: auto; width: 478px">
<div class="avatar">
<img v-lazy="group.iconUrl" />
</div>
<div class="detail">
<span class="name" v-text="group.name"></span>
</div>
</el-option>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item
v-if="
newInstanceDialog.accessType === 'group' && newInstanceDialog.groupAccessType === 'members'
"
:label="$t('dialog.new_instance.roles')">
<el-select
v-model="newInstanceDialog.roleIds"
multiple
clearable
:placeholder="$t('dialog.new_instance.role_placeholder')"
style="width: 100%"
@change="buildInstance">
<el-option-group :label="$t('dialog.new_instance.role_placeholder')">
<el-option
v-for="role in newInstanceDialog.selectedGroupRoles"
:key="role.id"
class="x-friend-item"
:label="role.name"
:value="role.id"
style="height: auto; width: 478px">
<div class="detail">
<span class="name" v-text="role.name"></span>
</div>
</el-option>
</el-option-group>
</el-select>
</el-form-item>
<template v-if="newInstanceDialog.instanceCreated">
<el-form-item :label="$t('dialog.new_instance.location')">
<el-input
v-model="newInstanceDialog.location"
size="mini"
readonly
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()"></el-input>
</el-form-item>
<el-form-item :label="$t('dialog.new_instance.url')">
<el-input v-model="newInstanceDialog.url" size="mini" readonly></el-input>
</el-form-item>
</template>
</el-form>
</el-tab-pane>
<el-tab-pane :label="$t('dialog.new_instance.legacy')">
<el-form :model="newInstanceDialog" label-width="150px">
<el-form-item :label="$t('dialog.new_instance.access_type')">
<el-radio-group
v-model="newInstanceDialog.accessType"
size="mini"
@change="buildLegacyInstance">
<el-radio-button label="public">{{
$t('dialog.new_instance.access_type_public')
}}</el-radio-button>
<el-radio-button label="group">{{
$t('dialog.new_instance.access_type_group')
}}</el-radio-button>
<el-radio-button label="friends+">{{
$t('dialog.new_instance.access_type_friend_plus')
}}</el-radio-button>
<el-radio-button label="friends">{{
$t('dialog.new_instance.access_type_friend')
}}</el-radio-button>
<el-radio-button label="invite+">{{
$t('dialog.new_instance.access_type_invite_plus')
}}</el-radio-button>
<el-radio-button label="invite">{{
$t('dialog.new_instance.access_type_invite')
}}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="newInstanceDialog.accessType === 'group'"
:label="$t('dialog.new_instance.group_access_type')">
<el-radio-group
v-model="newInstanceDialog.groupAccessType"
size="mini"
@change="buildLegacyInstance">
<el-radio-button label="members">{{
$t('dialog.new_instance.group_access_type_members')
}}</el-radio-button>
<el-radio-button label="plus">{{
$t('dialog.new_instance.group_access_type_plus')
}}</el-radio-button>
<el-radio-button label="public">{{
$t('dialog.new_instance.group_access_type_public')
}}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('dialog.new_instance.region')">
<el-radio-group v-model="newInstanceDialog.region" size="mini" @change="buildLegacyInstance">
<el-radio-button label="US West">{{
$t('dialog.new_instance.region_usw')
}}</el-radio-button>
<el-radio-button label="US East">{{
$t('dialog.new_instance.region_use')
}}</el-radio-button>
<el-radio-button label="Europe">{{ $t('dialog.new_instance.region_eu') }}</el-radio-button>
<el-radio-button label="Japan">{{ $t('dialog.new_instance.region_jp') }}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="newInstanceDialog.accessType === 'group'"
:label="$t('dialog.new_instance.ageGate')">
<el-checkbox v-model="newInstanceDialog.ageGate" @change="buildInstance"></el-checkbox>
</el-form-item>
<el-form-item :label="$t('dialog.new_instance.world_id')">
<el-input
v-model="newInstanceDialog.worldId"
size="mini"
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()"
@change="buildLegacyInstance"></el-input>
</el-form-item>
<el-form-item :label="$t('dialog.new_instance.instance_id')">
<el-input
v-model="newInstanceDialog.instanceName"
:placeholder="$t('dialog.new_instance.instance_id_placeholder')"
size="mini"
@change="buildLegacyInstance"></el-input>
</el-form-item>
<el-form-item
v-if="newInstanceDialog.accessType !== 'public' && newInstanceDialog.accessType !== 'group'"
:label="$t('dialog.new_instance.instance_creator')">
<el-select
v-model="newInstanceDialog.userId"
clearable
:placeholder="$t('dialog.new_instance.instance_creator_placeholder')"
filterable
style="width: 100%"
@change="buildLegacyInstance">
<el-option-group v-if="API.currentUser" :label="$t('side_panel.me')">
<el-option
class="x-friend-item"
:label="API.currentUser.displayName"
:value="API.currentUser.id"
style="height: auto">
<div class="avatar" :class="userStatusClass(API.currentUser)">
<img v-lazy="userImage(API.currentUser)" />
</div>
<div class="detail">
<span class="name" v-text="API.currentUser.displayName"></span>
</div>
</el-option>
</el-option-group>
<el-option-group v-if="vipFriends.length" :label="$t('side_panel.favorite')">
<el-option
v-for="friend in vipFriends"
:key="friend.id"
class="x-friend-item"
:label="friend.name"
:value="friend.id"
style="height: auto">
<template v-if="friend.ref">
<div class="avatar" :class="userStatusClass(friend.ref)">
<img v-lazy="userImage(friend.ref)" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
<el-option-group v-if="onlineFriends.length" :label="$t('side_panel.online')">
<el-option
v-for="friend in onlineFriends"
:key="friend.id"
class="x-friend-item"
:label="friend.name"
:value="friend.id"
style="height: auto">
<template v-if="friend.ref">
<div class="avatar" :class="userStatusClass(friend.ref)">
<img v-lazy="userImage(friend.ref)" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
<el-option-group v-if="activeFriends.length" :label="$t('side_panel.active')">
<el-option
v-for="friend in activeFriends"
:key="friend.id"
class="x-friend-item"
:label="friend.name"
:value="friend.id"
style="height: auto">
<template v-if="friend.ref">
<div class="avatar">
<img v-lazy="userImage(friend.ref)" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
<el-option-group v-if="offlineFriends.length" :label="$t('side_panel.offline')">
<el-option
v-for="friend in offlineFriends"
:key="friend.id"
class="x-friend-item"
:label="friend.name"
:value="friend.id"
style="height: auto">
<template v-if="friend.ref">
<div class="avatar">
<img v-lazy="userImage(friend.ref)" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item
v-if="newInstanceDialog.accessType === 'group'"
:label="$t('dialog.new_instance.group_id')">
<el-select
v-model="newInstanceDialog.groupId"
clearable
:placeholder="$t('dialog.new_instance.group_placeholder')"
filterable
style="width: 100%"
@change="buildLegacyInstance">
<el-option-group :label="$t('dialog.new_instance.group_placeholder')">
<el-option
v-for="group in API.currentUserGroups.values()"
v-if="group"
:key="group.id"
class="x-friend-item"
:label="group.name"
:value="group.id"
style="height: auto; width: 478px">
<div class="avatar">
<img v-lazy="group.iconUrl" />
</div>
<div class="detail">
<span class="name" v-text="group.name"></span>
</div>
</el-option>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item :label="$t('dialog.new_instance.location')">
<el-input
v-model="newInstanceDialog.location"
size="mini"
readonly
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()"></el-input>
</el-form-item>
<el-form-item :label="$t('dialog.new_instance.url')">
<el-input v-model="newInstanceDialog.url" size="mini" readonly></el-input>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
<template v-if="newInstanceDialog.selectedTab === '0'" #footer>
<template v-if="newInstanceDialog.instanceCreated">
<el-button size="small" @click="copyInstanceUrl(newInstanceDialog.location)">{{
$t('dialog.new_instance.copy_url')
}}</el-button>
<el-button size="small" @click="selfInvite(newInstanceDialog.location)">{{
$t('dialog.new_instance.self_invite')
}}</el-button>
<el-button
size="small"
:disabled="
(newInstanceDialog.accessType === 'friends' || newInstanceDialog.accessType === 'invite') &&
newInstanceDialog.userId !== API.currentUser.id
"
@click="showInviteDialog(newInstanceDialog.location)"
>{{ $t('dialog.new_instance.invite') }}</el-button
>
<el-button
type="primary"
size="small"
@click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)"
>{{ $t('dialog.new_instance.launch') }}</el-button
>
</template>
<template v-else>
<el-button type="primary" size="small" @click="handleCreateNewInstance">{{
$t('dialog.new_instance.create_instance')
}}</el-button>
</template>
</template>
<template v-else-if="newInstanceDialog.selectedTab === '1'" #footer>
<el-button size="small" @click="copyInstanceUrl(newInstanceDialog.location)">{{
$t('dialog.new_instance.copy_url')
}}</el-button>
<el-button size="small" @click="selfInvite(newInstanceDialog.location)">{{
$t('dialog.new_instance.self_invite')
}}</el-button>
<el-button
size="small"
:disabled="
(newInstanceDialog.accessType === 'friends' || newInstanceDialog.accessType === 'invite') &&
newInstanceDialog.userId !== API.currentUser.id
"
@click="showInviteDialog(newInstanceDialog.location)"
>{{ $t('dialog.new_instance.invite') }}</el-button
>
<el-button
type="primary"
size="small"
@click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)"
>{{ $t('dialog.new_instance.launch') }}</el-button
>
</template>
</el-dialog>
</template>
<script>
import { groupRequest, instanceRequest } from '../../api';
import utils from '../../classes/utils';
import configRepository from '../../service/config';
export default {
name: 'NewInstanceDialog',
inject: [
'API',
'userImage',
'userStatusClass',
'beforeDialogClose',
'dialogMouseDown',
'dialogMouseUp',
'showInviteDialog',
'showLaunchDialog',
'adjustDialogZ'
],
props: {
vipFriends: {
type: Array,
required: true
},
onlineFriends: {
type: Array,
required: true
},
activeFriends: {
type: Array,
required: true
},
offlineFriends: {
type: Array,
required: true
},
instanceContentSettings: {
type: Array,
required: true
},
createNewInstance: {
type: Function,
required: true
},
newInstanceDialogLocationTag: {
type: String,
required: true
}
},
data() {
return {
newInstanceDialog: {
visible: false,
// loading: false,
selectedTab: '0',
instanceCreated: false,
queueEnabled: false,
worldId: '',
instanceId: '',
instanceName: '',
userId: '',
accessType: 'public',
region: 'US West',
groupRegion: '',
groupId: '',
groupAccessType: 'plus',
ageGate: false,
strict: false,
location: '',
shortName: '',
url: '',
secureOrShortName: '',
lastSelectedGroupId: '',
selectedGroupRoles: [],
roleIds: [],
groupRef: {},
contentSettings: this.instanceContentSettings,
selectedContentSettings: []
}
};
},
watch: {
newInstanceDialogLocationTag(value) {
this.initNewInstanceDialog(value);
}
},
created() {
this.initializeNewInstanceDialog();
},
methods: {
initNewInstanceDialog(tag) {
if (!utils.isRealInstance(tag)) {
return;
}
this.$nextTick(() => this.adjustDialogZ(this.$refs.newInstanceDialog.$el));
const D = this.newInstanceDialog;
const L = utils.parseLocation(tag);
if (D.worldId === L.worldId) {
// reopening dialog, keep last open instance
D.visible = true;
return;
}
D.worldId = L.worldId;
D.instanceCreated = false;
D.lastSelectedGroupId = '';
D.selectedGroupRoles = [];
D.groupRef = {};
D.roleIds = [];
D.strict = false;
D.shortName = '';
D.secureOrShortName = '';
groupRequest.getGroupPermissions({ userId: this.API.currentUser.id });
this.buildInstance();
this.buildLegacyInstance();
this.updateNewInstanceDialog();
D.visible = true;
},
initializeNewInstanceDialog() {
configRepository
.getBool('instanceDialogQueueEnabled', true)
.then((value) => (this.newInstanceDialog.queueEnabled = value));
configRepository
.getString('instanceDialogInstanceName', '')
.then((value) => (this.newInstanceDialog.instanceName = value));
configRepository
.getString('instanceDialogUserId', '')
.then((value) => (this.newInstanceDialog.userId = value));
configRepository
.getString('instanceDialogAccessType', 'public')
.then((value) => (this.newInstanceDialog.accessType = value));
configRepository
.getString('instanceRegion', 'US West')
.then((value) => (this.newInstanceDialog.region = value));
configRepository
.getString('instanceDialogGroupId', '')
.then((value) => (this.newInstanceDialog.groupId = value));
configRepository
.getString('instanceDialogGroupAccessType', 'plus')
.then((value) => (this.newInstanceDialog.groupAccessType = value));
configRepository
.getBool('instanceDialogAgeGate', false)
.then((value) => (this.newInstanceDialog.ageGate = value));
configRepository
.getString('instanceDialogSelectedContentSettings', JSON.stringify(this.instanceContentSettings))
.then((value) => (this.newInstanceDialog.selectedContentSettings = JSON.parse(value)));
},
saveNewInstanceDialog() {
const {
accessType,
region,
instanceName,
userId,
groupId,
groupAccessType,
queueEnabled,
ageGate,
selectedContentSettings
} = this.newInstanceDialog;
configRepository.setString('instanceDialogAccessType', accessType);
configRepository.setString('instanceRegion', region);
configRepository.setString('instanceDialogInstanceName', instanceName);
configRepository.setString('instanceDialogUserId', userId === this.API.currentUser.id ? '' : userId);
configRepository.setString('instanceDialogGroupId', groupId);
configRepository.setString('instanceDialogGroupAccessType', groupAccessType);
configRepository.setBool('instanceDialogQueueEnabled', queueEnabled);
configRepository.setBool('instanceDialogAgeGate', ageGate);
configRepository.setString(
'instanceDialogSelectedContentSettings',
JSON.stringify(selectedContentSettings)
);
},
newInstanceTabClick(tab) {
if (tab === '1') {
this.buildInstance();
} else {
this.buildLegacyInstance();
}
},
updateNewInstanceDialog(noChanges) {
const D = this.newInstanceDialog;
if (D.instanceId) {
D.location = `${D.worldId}:${D.instanceId}`;
} else {
D.location = D.worldId;
}
const L = utils.parseLocation(D.location);
if (noChanges) {
L.shortName = D.shortName;
} else {
D.shortName = '';
}
D.url = utils.getLaunchURL(L);
},
selfInvite(location) {
const L = utils.parseLocation(location);
if (!L.isRealInstance) {
return;
}
instanceRequest
.selfInvite({
instanceId: L.instanceId,
worldId: L.worldId
})
.then((args) => {
this.$message({
message: 'Self invite sent',
type: 'success'
});
return args;
});
},
async handleCreateNewInstance() {
const args = await this.createNewInstance(this.newInstanceDialog.worldId, this.newInstanceDialog);
if (args) {
this.newInstanceDialog.location = args.json.location;
this.newInstanceDialog.instanceId = args.json.instanceId;
this.newInstanceDialog.secureOrShortName = args.json.shortName || args.json.secureName;
this.newInstanceDialog.instanceCreated = true;
this.updateNewInstanceDialog();
}
},
buildInstance() {
const D = this.newInstanceDialog;
D.instanceCreated = false;
D.instanceId = '';
D.shortName = '';
D.secureOrShortName = '';
if (!D.userId) {
D.userId = this.API.currentUser.id;
}
if (D.groupId && D.groupId !== D.lastSelectedGroupId) {
D.roleIds = [];
const ref = this.API.cachedGroups.get(D.groupId);
if (typeof ref !== 'undefined') {
D.groupRef = ref;
D.selectedGroupRoles = ref.roles;
groupRequest
.getGroupRoles({
groupId: D.groupId
})
.then((args) => {
D.lastSelectedGroupId = D.groupId;
D.selectedGroupRoles = args.json;
ref.roles = args.json;
});
}
}
if (!D.groupId) {
D.roleIds = [];
D.groupRef = {};
D.selectedGroupRoles = [];
D.lastSelectedGroupId = '';
}
this.saveNewInstanceDialog();
},
buildLegacyInstance() {
const D = this.newInstanceDialog;
D.instanceCreated = false;
D.shortName = '';
D.secureOrShortName = '';
const tags = [];
if (D.instanceName) {
D.instanceName = D.instanceName.replace(/[^A-Za-z0-9]/g, '');
tags.push(D.instanceName);
} else {
const randValue = (99999 * Math.random() + 1).toFixed(0);
tags.push(String(randValue).padStart(5, '0'));
}
if (!D.userId) {
D.userId = this.API.currentUser.id;
}
const userId = D.userId;
if (D.accessType !== 'public') {
if (D.accessType === 'friends+') {
tags.push(`~hidden(${userId})`);
} else if (D.accessType === 'friends') {
tags.push(`~friends(${userId})`);
} else if (D.accessType === 'group') {
tags.push(`~group(${D.groupId})`);
tags.push(`~groupAccessType(${D.groupAccessType})`);
} else {
tags.push(`~private(${userId})`);
}
if (D.accessType === 'invite+') {
tags.push('~canRequestInvite');
}
}
if (D.accessType === 'group' && D.ageGate) {
tags.push('~ageGate');
}
if (D.region === 'US West') {
tags.push(`~region(us)`);
} else if (D.region === 'US East') {
tags.push(`~region(use)`);
} else if (D.region === 'Europe') {
tags.push(`~region(eu)`);
} else if (D.region === 'Japan') {
tags.push(`~region(jp)`);
}
if (D.accessType !== 'invite' && D.accessType !== 'friends') {
D.strict = false;
}
if (D.strict) {
tags.push('~strict');
}
if (D.groupId && D.groupId !== D.lastSelectedGroupId) {
D.roleIds = [];
const ref = this.API.cachedGroups.get(D.groupId);
if (typeof ref !== 'undefined') {
D.groupRef = ref;
D.selectedGroupRoles = ref.roles;
groupRequest
.getGroupRoles({
groupId: D.groupId
})
.then((args) => {
D.lastSelectedGroupId = D.groupId;
D.selectedGroupRoles = args.json;
ref.roles = args.json;
});
}
}
if (!D.groupId) {
D.roleIds = [];
D.selectedGroupRoles = [];
D.groupRef = {};
D.lastSelectedGroupId = '';
}
D.instanceId = tags.join('');
this.updateNewInstanceDialog(false);
this.saveNewInstanceDialog();
},
async copyInstanceUrl(location) {
const L = utils.parseLocation(location);
const args = await instanceRequest.getInstanceShortName({
worldId: L.worldId,
instanceId: L.instanceId
});
if (args.json) {
if (args.json.shortName) {
L.shortName = args.json.shortName;
}
// NOTE:
// splitting the 'INSTANCE:SHORTNAME' event and put code here
const resLocation = `${args.instance.worldId}:${args.instance.instanceId}`;
if (resLocation === this.newInstanceDialog.location) {
const shortName = args.json.shortName;
const secureOrShortName = args.json.shortName || args.json.secureName;
this.newInstanceDialog.shortName = shortName;
this.newInstanceDialog.secureOrShortName = secureOrShortName;
this.updateNewInstanceDialog(true);
}
}
const newUrl = utils.getLaunchURL(L);
this.copyToClipboard(newUrl);
},
async copyToClipboard(newUrl) {
try {
await navigator.clipboard.writeText(newUrl);
this.$message({
message: 'Instance copied to clipboard',
type: 'success'
});
} catch (error) {
this.$message({
message: 'Instance copied failed',
type: 'error'
});
console.error(error.message);
}
},
hasGroupPermission(ref, permission) {
return utils.hasGroupPermission(ref, permission);
}
}
};
</script>

View File

@@ -0,0 +1,163 @@
<template>
<el-dialog
ref="dialog"
:before-close="beforeDialogClose"
:visible="visible"
:title="$t('dialog.previous_instances.info')"
width="800px"
:fullscreen="fullscreen"
destroy-on-close
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp"
@close="$emit('update:visible', false)">
<div style="display: flex; align-items: center; justify-content: space-between">
<location :location="location.tag" style="font-size: 14px"></location>
<el-input
v-model="dataTable.filters[0].value"
:placeholder="$t('dialog.previous_instances.search_placeholder')"
style="width: 150px"
clearable></el-input>
</div>
<data-tables v-loading="loading" v-bind="dataTable" style="margin-top: 10px">
<el-table-column :label="$t('table.previous_instances.date')" prop="created_at" sortable width="110">
<template slot-scope="scope">
<el-tooltip placement="left">
<template slot="content">
<span>{{ scope.row.created_at | formatDate('long') }}</span>
</template>
<span>{{ scope.row.created_at | formatDate('short') }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column :label="$t('table.gameLog.icon')" prop="isFriend" width="70" align="center">
<template slot-scope="scope">
<template v-if="gameLogIsFriend(scope.row)">
<el-tooltip v-if="gameLogIsFavorite(scope.row)" placement="top" content="Favorite">
<span></span>
</el-tooltip>
<el-tooltip v-else placement="top" content="Friend">
<span>💚</span>
</el-tooltip>
</template>
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.display_name')" prop="displayName" sortable>
<template slot-scope="scope">
<span class="x-link" @click="lookupUser(scope.row)">{{ scope.row.displayName }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.time')" prop="time" width="100" sortable>
<template slot-scope="scope">
<span>{{ scope.row.timer }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.count')" prop="count" width="100" sortable>
<template slot-scope="scope">
<span>{{ scope.row.count }}</span>
</template>
</el-table-column>
</data-tables>
</el-dialog>
</template>
<script>
import utils from '../../../classes/utils';
import database from '../../../service/database';
import dayjs from 'dayjs';
import Location from '../../Location.vue';
export default {
name: 'PreviousInstancesInfoDialog',
components: {
Location
},
inject: ['adjustDialogZ', 'beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp'],
props: {
visible: {
type: Boolean,
default: false
},
instanceId: { type: String, required: true },
gameLogIsFriend: { type: Function, required: true },
gameLogIsFavorite: { type: Function, required: true },
lookupUser: { type: Function, required: true },
isDarkMode: { type: Boolean, required: true }
},
data() {
return {
echarts: null,
echartsInstance: null,
loading: false,
location: {},
currentTab: 'table',
dataTable: {
data: [],
filters: [
{
prop: 'displayName',
value: ''
}
],
tableProps: {
stripe: true,
size: 'mini',
defaultSort: {
prop: 'created_at',
order: 'descending'
}
},
pageSize: 10,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 25, 50, 100]
}
},
fullscreen: false
};
},
computed: {
activityDetailData() {
return this.dataTable.data.map((item) => ({
displayName: item.displayName,
joinTime: dayjs(item.created_at),
leaveTime: dayjs(item.created_at).add(item.time, 'ms'),
time: item.time,
timer: item.timer
}));
}
},
watch: {
visible(value) {
if (value) {
this.$nextTick(() => {
this.init();
this.refreshPreviousInstancesInfoTable();
});
utils.loadEcharts().then((echarts) => {
this.echarts = echarts;
});
}
}
},
methods: {
init() {
this.adjustDialogZ(this.$refs.dialog.$el);
this.loading = true;
this.location = utils.parseLocation(this.instanceId);
},
refreshPreviousInstancesInfoTable() {
database.getPlayersFromInstance(this.location.tag).then((data) => {
const array = [];
for (const entry of Array.from(data.values())) {
entry.timer = utils.timeToText(entry.time);
array.push(entry);
}
array.sort(utils.compareByCreatedAt);
this.dataTable.data = array;
this.loading = false;
});
}
}
};
</script>

View File

@@ -0,0 +1,214 @@
<template>
<el-dialog
ref="previousInstancesUserDialog"
:before-close="beforeDialogClose"
:visible.sync="isVisible"
:title="$t('dialog.previous_instances.header')"
width="1000px"
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp">
<div style="display: flex; align-items: center; justify-content: space-between">
<span style="font-size: 14px" v-text="previousInstancesUserDialog.userRef.displayName"></span>
<el-input
v-model="previousInstancesUserDialogTable.filters[0].value"
:placeholder="$t('dialog.previous_instances.search_placeholder')"
style="display: block; width: 150px"></el-input>
</div>
<data-tables v-loading="loading" v-bind="previousInstancesUserDialogTable" style="margin-top: 10px">
<el-table-column :label="$t('table.previous_instances.date')" prop="created_at" sortable width="170">
<template slot-scope="scope">
<span>{{ scope.row.created_at | formatDate('long') }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.world')" prop="name" sortable>
<template slot-scope="scope">
<location
:location="scope.row.location"
:hint="scope.row.worldName"
:grouphint="scope.row.groupName"></location>
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.instance_creator')" prop="location" width="170">
<template slot-scope="scope">
<display-name
:userid="scope.row.$location.userId"
:location="scope.row.$location.tag"></display-name>
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.time')" prop="time" width="100" sortable>
<template slot-scope="scope">
<span v-text="scope.row.timer"></span>
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.action')" width="90" align="right">
<template slot-scope="scope">
<el-button
type="text"
icon="el-icon-switch-button"
size="mini"
@click="showLaunchDialog(scope.row.location)"></el-button>
<el-button
type="text"
icon="el-icon-s-data"
size="mini"
@click="showPreviousInstancesInfoDialog(scope.row.location)"></el-button>
<el-button
v-if="shiftHeld"
style="color: #f56c6c"
type="text"
icon="el-icon-close"
size="mini"
@click="deleteGameLogUserInstance(scope.row)"></el-button>
<el-button
v-else
type="text"
icon="el-icon-close"
size="mini"
@click="deleteGameLogUserInstancePrompt(scope.row)"></el-button>
</template>
</el-table-column>
</data-tables>
</el-dialog>
</template>
<script>
import utils from '../../../classes/utils';
import database from '../../../service/database';
import Location from '../../Location.vue';
export default {
name: 'PreviousInstancesUserDialog',
components: {
Location
},
inject: [
'beforeDialogClose',
'dialogMouseDown',
'dialogMouseUp',
'adjustDialogZ',
'showLaunchDialog',
'showPreviousInstancesInfoDialog'
],
props: {
previousInstancesUserDialog: {
type: Object,
default: () => ({
visible: false,
userRef: {},
loading: false,
forceUpdate: 0,
previousInstances: [],
previousInstancesTable: {
data: [],
filters: [
{
prop: 'displayName',
value: ''
}
],
tableProps: {
stripe: true,
size: 'mini',
height: '400px'
}
}
})
},
shiftHeld: {
type: Boolean,
default: false
}
},
data() {
return {
previousInstancesUserDialogTable: {
data: [],
filters: [
{
prop: 'worldName',
value: ''
}
],
tableProps: {
stripe: true,
size: 'mini',
defaultSort: {
prop: 'created_at',
order: 'descending'
}
},
pageSize: 10,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 25, 50, 100]
}
},
loading: false
};
},
computed: {
isVisible: {
get() {
return this.previousInstancesUserDialog.visible;
},
set(value) {
this.$emit('update:previous-instances-user-dialog', {
...this.previousInstancesUserDialog,
visible: value
});
}
}
},
watch: {
'previousInstancesUserDialog.openFlg'() {
if (this.previousInstancesUserDialog.visible) {
this.$nextTick(() => {
this.adjustDialogZ(this.$refs.previousInstancesUserDialog.$el);
});
this.refreshPreviousInstancesUserTable();
}
}
},
methods: {
refreshPreviousInstancesUserTable() {
this.loading = true;
database.getpreviousInstancesByUserId(this.previousInstancesUserDialog.userRef).then((data) => {
const array = [];
for (const ref of data.values()) {
ref.$location = utils.parseLocation(ref.location);
if (ref.time > 0) {
ref.timer = utils.timeToText(ref.time);
} else {
ref.timer = '';
}
array.push(ref);
}
array.sort(utils.compareByCreatedAt);
this.previousInstancesUserDialogTable.data = array;
this.loading = false;
});
},
deleteGameLogUserInstance(row) {
database.deleteGameLogInstance({
id: this.previousInstancesUserDialog.userRef.id,
displayName: this.previousInstancesUserDialog.userRef.displayName,
location: row.location
});
utils.removeFromArray(this.previousInstancesUserDialogTable.data, row);
},
deleteGameLogUserInstancePrompt(row) {
this.$confirm('Continue? Delete User From GameLog Instance', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') {
this.deleteGameLogUserInstance(row);
}
}
});
}
}
};
</script>

View File

@@ -0,0 +1,186 @@
<template>
<el-dialog
ref="previousInstancesWorldDialog"
:before-close="beforeDialogClose"
:visible.sync="isVisible"
:title="$t('dialog.previous_instances.header')"
width="1000px"
append-to-body
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp">
<div style="display: flex; align-items: center; justify-content: space-between">
<span style="font-size: 14px" v-text="previousInstancesWorldDialog.worldRef.name"></span>
<el-input
v-model="previousInstancesWorldDialogTable.filters[0].value"
:placeholder="$t('dialog.previous_instances.search_placeholder')"
style="display: block; width: 150px"></el-input>
</div>
<data-tables v-loading="loading" v-bind="previousInstancesWorldDialogTable" style="margin-top: 10px">
<el-table-column :label="$t('table.previous_instances.date')" prop="created_at" sortable width="170">
<template slot-scope="scope">
<span>{{ scope.row.created_at | formatDate('long') }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.instance_name')" prop="name">
<template slot-scope="scope">
<location-world
:locationobject="scope.row.$location"
:grouphint="scope.row.groupName"
:currentuserid="API.currentUser.id"
@show-launch-dialog="showLaunchDialog"></location-world>
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.instance_creator')" prop="location">
<template slot-scope="scope">
<display-name
:userid="scope.row.$location.userId"
:location="scope.row.$location.tag"
:force-update-key="previousInstancesWorldDialog.forceUpdate"></display-name>
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.time')" prop="time" width="100" sortable>
<template slot-scope="scope">
<span v-text="scope.row.timer"></span>
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.action')" width="90" align="right">
<template slot-scope="scope">
<el-button
type="text"
icon="el-icon-s-data"
size="mini"
@click="showPreviousInstancesInfoDialog(scope.row.location)"></el-button>
<el-button
v-if="shiftHeld"
style="color: #f56c6c"
type="text"
icon="el-icon-close"
size="mini"
@click="deleteGameLogWorldInstance(scope.row)"></el-button>
<el-button
v-else
type="text"
icon="el-icon-close"
size="mini"
@click="deleteGameLogWorldInstancePrompt(scope.row)"></el-button>
</template>
</el-table-column>
</data-tables>
</el-dialog>
</template>
<script>
import utils from '../../../classes/utils';
import database from '../../../service/database';
export default {
name: 'PreviousInstancesWorldDialog',
inject: [
'API',
'showLaunchDialog',
'showPreviousInstancesInfoDialog',
'adjustDialogZ',
'beforeDialogClose',
'dialogMouseDown',
'dialogMouseUp'
],
props: {
previousInstancesWorldDialog: {
type: Object,
required: true
},
shiftHeld: Boolean
},
data() {
return {
previousInstancesWorldDialogTable: {
data: [],
filters: [
{
prop: 'groupName',
value: ''
}
],
tableProps: {
stripe: true,
size: 'mini',
defaultSort: {
prop: 'created_at',
order: 'descending'
}
},
pageSize: 10,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 25, 50, 100]
}
},
loading: false
};
},
computed: {
isVisible: {
get() {
return this.previousInstancesWorldDialog.visible;
},
set(value) {
this.$emit('update:previous-instances-world-dialog', {
...this.previousInstancesWorldDialog,
visible: value
});
}
}
},
watch: {
'previousInstancesWorldDialog.openFlg'() {
if (this.previousInstancesWorldDialog.visible) {
this.$nextTick(() => {
this.adjustDialogZ(this.$refs.previousInstancesWorldDialog.$el);
});
this.refreshPreviousInstancesWorldTable();
}
}
},
methods: {
refreshPreviousInstancesWorldTable() {
this.loading = true;
const D = this.previousInstancesWorldDialog;
database.getpreviousInstancesByWorldId(D.worldRef).then((data) => {
const array = [];
for (const ref of data.values()) {
ref.$location = utils.parseLocation(ref.location);
if (ref.time > 0) {
ref.timer = utils.timeToText(ref.time);
} else {
ref.timer = '';
}
array.push(ref);
}
array.sort(utils.compareByCreatedAt);
this.previousInstancesWorldDialogTable.data = array;
this.loading = false;
});
},
deleteGameLogWorldInstance(row) {
database.deleteGameLogInstanceByInstanceId({
location: row.location
});
utils.removeFromArray(this.previousInstancesWorldDialogTable.data, row);
},
deleteGameLogWorldInstancePrompt(row) {
this.$confirm('Continue? Delete GameLog Instance', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') {
this.deleteGameLogWorldInstance(row);
}
}
});
}
}
};
</script>

View File

@@ -0,0 +1,297 @@
<template>
<el-dialog
:before-close="beforeDialogClose"
:visible.sync="isVisible"
:title="$t('dialog.set_world_tags.header')"
width="400px"
destroy-on-close
append-to-body
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp">
<el-checkbox v-model="setWorldTagsDialog.avatarScalingDisabled">
{{ $t('dialog.set_world_tags.avatar_scaling_disabled') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.focusViewDisabled">
{{ $t('dialog.set_world_tags.focus_view_disabled') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.debugAllowed">
{{ $t('dialog.set_world_tags.enable_debugging') }}
</el-checkbox>
<div style="font-size: 12px; margin-top: 10px">{{ $t('dialog.set_world_tags.author_tags') }}<br /></div>
<el-input
v-model="setWorldTagsDialog.authorTags"
type="textarea"
size="mini"
show-word-limit
:autosize="{ minRows: 2, maxRows: 5 }"
placeholder=""
style="margin-top: 10px"></el-input>
<div style="font-size: 12px; margin-top: 10px">{{ $t('dialog.set_world_tags.content_tags') }}<br /></div>
<el-checkbox v-model="setWorldTagsDialog.contentHorror">
{{ $t('dialog.set_world_tags.content_horror') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.contentGore">
{{ $t('dialog.set_world_tags.content_gore') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.contentViolence">
{{ $t('dialog.set_world_tags.content_violence') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.contentAdult">
{{ $t('dialog.set_world_tags.content_adult') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.contentSex">
{{ $t('dialog.set_world_tags.content_sex') }}
</el-checkbox>
<div style="font-size: 12px; margin-top: 10px">
{{ $t('dialog.set_world_tags.default_content_settings') }}<br />
</div>
<el-checkbox v-model="setWorldTagsDialog.emoji">
{{ $t('dialog.new_instance.content_emoji') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.stickers">
{{ $t('dialog.new_instance.content_stickers') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.pedestals">
{{ $t('dialog.new_instance.content_pedestals') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.prints">
{{ $t('dialog.new_instance.content_prints') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.drones">
{{ $t('dialog.new_instance.content_drones') }}
</el-checkbox>
<template #footer>
<div style="display: flex">
<el-button size="small" @click="setWorldTagsDialog.visible = false">
{{ $t('dialog.set_world_tags.cancel') }}
</el-button>
<el-button type="primary" size="small" @click="saveSetWorldTagsDialog">
{{ $t('dialog.set_world_tags.save') }}
</el-button>
</div>
</template>
</el-dialog>
</template>
<script>
import { worldRequest } from '../../../api';
export default {
name: 'SetWorldTagsDialog',
inject: ['beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp', 'showWorldDialog'],
props: {
oldTags: {
type: Array,
default: () => []
},
isSetWorldTagsDialogVisible: {
type: Boolean,
required: true
},
worldId: {
type: String,
required: true
},
isWorldDialogVisible: {
type: Boolean,
required: true
}
},
data() {
return {
setWorldTagsDialog: {
authorTags: [],
contentTags: [],
debugAllowed: false,
avatarScalingDisabled: false,
focusViewDisabled: false,
contentHorror: false,
contentGore: false,
contentViolence: false,
contentAdult: false,
contentSex: false,
emoji: true,
stickers: true,
pedestals: true,
prints: true,
drones: true
}
};
},
computed: {
isVisible: {
get() {
return this.isSetWorldTagsDialogVisible;
},
set(val) {
this.$emit('update:is-set-world-tags-dialog-visible', val);
}
}
},
watch: {
isSetWorldTagsDialogVisible(val) {
if (val) {
this.showSetWorldTagsDialog();
}
}
},
methods: {
showSetWorldTagsDialog() {
const D = this.setWorldTagsDialog;
D.visible = true;
D.debugAllowed = false;
D.avatarScalingDisabled = false;
D.focusViewDisabled = false;
D.contentHorror = false;
D.contentGore = false;
D.contentViolence = false;
D.contentAdult = false;
D.contentSex = false;
const authorTags = [];
const contentTags = [];
this.oldTags.forEach((tag) => {
if (tag.startsWith('author_tag_')) {
authorTags.unshift(tag.substring(11));
}
if (tag.startsWith('content_')) {
contentTags.unshift(tag.substring(8));
}
switch (tag) {
case 'content_horror':
D.contentHorror = true;
break;
case 'content_gore':
D.contentGore = true;
break;
case 'content_violence':
D.contentViolence = true;
break;
case 'content_adult':
D.contentAdult = true;
break;
case 'content_sex':
D.contentSex = true;
break;
case 'debug_allowed':
D.debugAllowed = true;
break;
case 'feature_avatar_scaling_disabled':
D.avatarScalingDisabled = true;
break;
case 'feature_focus_view_disabled':
D.focusViewDisabled = true;
break;
case 'feature_emoji_disabled':
D.emoji = false;
break;
case 'feature_stickers_disabled':
D.stickers = false;
break;
case 'feature_pedestals_disabled':
D.pedestals = false;
break;
case 'feature_prints_disabled':
D.prints = false;
break;
case 'feature_drones_disabled':
D.drones = false;
break;
}
});
D.authorTags = authorTags.toString();
D.contentTags = contentTags.toString();
},
saveSetWorldTagsDialog() {
const D = this.setWorldTagsDialog;
const authorTags = D.authorTags.trim().split(',');
const contentTags = D.contentTags.trim().split(',');
const tags = [];
authorTags.forEach((tag) => {
if (tag) {
tags.unshift(`author_tag_${tag}`);
}
});
// add back custom tags
contentTags.forEach((tag) => {
switch (tag) {
case 'horror':
case 'gore':
case 'violence':
case 'adult':
case 'sex':
case '':
break;
default:
tags.unshift(`content_${tag}`);
break;
}
});
if (D.contentHorror) {
tags.unshift('content_horror');
}
if (D.contentGore) {
tags.unshift('content_gore');
}
if (D.contentViolence) {
tags.unshift('content_violence');
}
if (D.contentAdult) {
tags.unshift('content_adult');
}
if (D.contentSex) {
tags.unshift('content_sex');
}
if (D.debugAllowed) {
tags.unshift('debug_allowed');
}
if (D.avatarScalingDisabled) {
tags.unshift('feature_avatar_scaling_disabled');
}
if (D.focusViewDisabled) {
tags.unshift('feature_focus_view_disabled');
}
if (!D.emoji) {
tags.unshift('feature_emoji_disabled');
}
if (!D.stickers) {
tags.unshift('feature_stickers_disabled');
}
if (!D.pedestals) {
tags.unshift('feature_pedestals_disabled');
}
if (!D.prints) {
tags.unshift('feature_prints_disabled');
}
if (!D.drones) {
tags.unshift('feature_drones_disabled');
}
worldRequest
.saveWorld({
id: this.worldId,
tags
})
.then((args) => {
this.$message({
message: 'Tags updated',
type: 'success'
});
this.$emit('update:is-set-world-tags-dialog-visible', false);
if (this.isWorldDialogVisible) {
this.showWorldDialog(args.json.id);
}
return args;
});
}
}
};
</script>

View File

@@ -0,0 +1,93 @@
<template>
<el-dialog
:before-close="beforeDialogClose"
:visible.sync="isVisible"
:title="$t('dialog.allowed_video_player_domains.header')"
width="600px"
destroy-on-close
append-to-body
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp">
<div>
<el-input
v-for="(domain, index) in urlList"
:key="index"
v-model="urlList[index]"
:value="domain"
size="small"
style="margin-top: 5px">
<el-button slot="append" icon="el-icon-delete" @click="urlList.splice(index, 1)"></el-button>
</el-input>
<el-button size="mini" style="margin-top: 5px" @click="urlList.push('')">
{{ $t('dialog.allowed_video_player_domains.add_domain') }}
</el-button>
</div>
<template #footer>
<el-button
type="primary"
size="small"
:disabled="!worldAllowedDomainsDialog.worldId"
@click="saveWorldAllowedDomains">
{{ $t('dialog.allowed_video_player_domains.save') }}
</el-button>
</template>
</el-dialog>
</template>
<script>
import { worldRequest } from '../../../api';
export default {
name: 'WorldAllowedDomainsDialog',
inject: ['beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp'],
props: {
worldAllowedDomainsDialog: {
type: Object,
required: true
}
},
data() {
return {
urlList: []
};
},
computed: {
isVisible: {
get() {
return this.worldAllowedDomainsDialog.visible;
},
set(val) {
this.$emit('update:world-allowed-domains-dialog', {
...this.worldAllowedDomainsDialog,
visible: val
});
}
}
},
watch: {
'worldAllowedDomainsDialog.visible'(val) {
if (val) {
this.urlList = this.worldAllowedDomainsDialog.urlList;
}
}
},
methods: {
saveWorldAllowedDomains() {
const D = this.worldAllowedDomainsDialog;
worldRequest
.saveWorld({
id: D.worldId,
urlList: D.urlList
})
.then((args) => {
this.$message({
message: 'Allowed Video Player Domains updated',
type: 'success'
});
return args;
});
D.visible = false;
}
}
};
</script>

File diff suppressed because it is too large Load Diff