Group add last visited (#1351)

* Fixed compilation/project files on Visual Studio.

* Add group instance last visit (#1286)

* Fixed erroneous translation + remove x-link
This commit is contained in:
Thomas
2025-08-24 01:00:08 +01:00
committed by GitHub
parent ced458229b
commit f63b1bdca2
24 changed files with 300 additions and 39 deletions

View File

@@ -5,7 +5,9 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9</TargetFramework>
<Platform>x64</Platform>
<Configurations>Debug;Release</Configurations>
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<AssemblyTitle>DBMerger</AssemblyTitle>

View File

@@ -5,7 +5,9 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9-windows10.0.19041.0</TargetFramework>
<Platform>x64</Platform>
<Configurations>Debug;Release</Configurations>
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<UseWindowsForms>true</UseWindowsForms>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>

View File

@@ -7,7 +7,9 @@
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Library</OutputType>
<Platform>x64</Platform>
<Configurations>Debug;Release</Configurations>
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<AssemblyTitle>VRCX</AssemblyTitle>

View File

@@ -1,16 +1,10 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34309.116
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VRCX-Cef", "Dotnet\VRCX-Cef.csproj", "{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8612F19B-3C1F-4B17-8679-A2747A53EC6B}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRCX-Electron", "Dotnet\VRCX-Electron.csproj", "{B0275E4A-FE0F-410A-96F1-1D73DF2535FA}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VRCX-Electron", "Dotnet\VRCX-Electron.csproj", "{B0275E4A-FE0F-410A-96F1-1D73DF2535FA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DBMerger", "DBMerger\DBMerger.csproj", "{9BE1DD2F-CABC-4CF9-A53E-C62923760887}"
EndProject
@@ -24,14 +18,16 @@ Global
{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Debug|x64.Build.0 = Debug|x64
{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Release|x64.ActiveCfg = Release|x64
{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Release|x64.Build.0 = Release|x64
{B0275E4A-FE0F-410A-96F1-1D73DF2535FA}.Debug|x64.ActiveCfg = Debug|x64
{B0275E4A-FE0F-410A-96F1-1D73DF2535FA}.Debug|x64.Build.0 = Debug|x64
{B0275E4A-FE0F-410A-96F1-1D73DF2535FA}.Release|x64.ActiveCfg = Release|x64
{B0275E4A-FE0F-410A-96F1-1D73DF2535FA}.Release|x64.Build.0 = Release|x64
{9BE1DD2F-CABC-4CF9-A53E-C62923760887}.Debug|x64.ActiveCfg = Debug|Any CPU
{9BE1DD2F-CABC-4CF9-A53E-C62923760887}.Debug|x64.Build.0 = Debug|Any CPU
{9BE1DD2F-CABC-4CF9-A53E-C62923760887}.Release|x64.ActiveCfg = Release|Any CPU
{9BE1DD2F-CABC-4CF9-A53E-C62923760887}.Release|x64.Build.0 = Release|Any CPU
{9BE1DD2F-CABC-4CF9-A53E-C62923760887}.Debug|x64.ActiveCfg = Debug|x64
{9BE1DD2F-CABC-4CF9-A53E-C62923760887}.Debug|x64.Build.0 = Debug|x64
{9BE1DD2F-CABC-4CF9-A53E-C62923760887}.Release|x64.ActiveCfg = Release|x64
{9BE1DD2F-CABC-4CF9-A53E-C62923760887}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -39,4 +35,4 @@ Global
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {797D384F-D118-4CBB-9450-17949F9EFCA4}
EndGlobalSection
EndGlobal
EndGlobal

View File

@@ -611,6 +611,14 @@
<span class="extra">{{ formatDateFilter(groupDialog.ref.createdAt, 'long') }}</span>
</div>
</div>
<el-tooltip :disabled="hideTooltips" placement="top" :content="t('dialog.user.info.open_previous_instance')">
<div class="x-friend-item" @click="showPreviousInstancesGroupDialog(groupDialog.ref)">
<div class="detail">
<span class="name">{{ t('dialog.group.info.last_visited') }}</span>
<span class="extra">{{formatDateFilter(groupDialog.lastVisit,'long') }}</span>
</div>
</div>
</el-tooltip>
<div class="x-friend-item" style="cursor: default">
<div class="detail">
<span class="name">{{ t('dialog.group.info.links') }}</span>
@@ -1166,6 +1174,10 @@
<!--Nested-->
<GroupPostEditDialog :dialog-data.sync="groupPostEditDialog" :selected-gallery-file="selectedGalleryFile" />
<InviteGroupDialog />
<PreviousInstancesGroupDialog
:previous-instances-group-dialog.sync="previousInstancesGroupDialog"
:current-user="currentUser"
/>
</safe-dialog>
</template>
@@ -1203,6 +1215,7 @@
} from '../../../stores';
import InviteGroupDialog from '../InviteGroupDialog.vue';
import GroupPostEditDialog from './GroupPostEditDialog.vue';
import PreviousInstancesGroupDialog from "../PreviousInstancesDialog/PreviousInstancesGroupDialog.vue";
const { t } = useI18n();
@@ -1253,6 +1266,12 @@
groupId: ''
});
const previousInstancesGroupDialog = ref({
visible: false,
openFlg: false,
groupRef: {}
});
let loadMoreGroupMembersParams = ref({
n: 100,
offset: 0,
@@ -1285,6 +1304,14 @@
inviteGroupDialog.value.visible = true;
}
function showPreviousInstancesGroupDialog(groupRef) {
const D = previousInstancesGroupDialog.value;
D.groupRef = groupRef;
D.visible = true;
D.openFlg = true;
nextTick(() => (D.openFlg = false));
}
function setGroupRepresentation(groupId) {
handleGroupRepresentationChange(groupId, true);
}

View File

@@ -0,0 +1,161 @@
<template>
<safe-dialog
ref="previousInstancesGroupDialogRef"
:visible.sync="isVisible"
:title="t('dialog.previous_instances.header')"
width="1000px"
append-to-body
>
<div style="display: flex; align-items: center; justify-content: space-between">
<span style="font-size: 14px" v-text="previousInstancesGroupDialog.groupRef.name"></span>
<el-input
v-model="previousInstancesGroupDialogTable.filters[0].value"
:placeholder="t('dialog.previous_instances.search_placeholder')"
style="width: 150px"
/>
</div>
<data-tables v-loading="loading" v-bind="previousInstancesGroupDialogTable" style="margin-top: 10px">
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="170">
<template #default="scope">
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
</template>
</el-table-column>
<el-table-column :label="t('table.previous_instances.instance_name')" prop="name">
<template #default="scope">
<LocationWorld
:locationobject="scope.row.$location"
:grouphint="scope.row.groupName"
:currentuserid="currentUser.id"
/>
</template>
</el-table-column>
<el-table-column :label="t('table.previous_instances.instance_creator')" prop="location">
<template #default="scope">
<DisplayName
:userid="scope.row.$location.userId"
:location="scope.row.$location.tag"
:force-update-key="previousInstancesGroupDialog.forceUpdate"
/>
</template>
</el-table-column>
<el-table-column :label="t('table.previous_instances.time')" prop="time" width="100" sortable>
<template #default="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 #default="scope">
<el-button
type="text"
icon="el-icon-s-data"
size="mini"
@click="showPreviousInstancesInfoDialog(scope.row.location)"
/>
<el-button
v-if="shiftHeld"
style="color: #f56c6c"
type="text"
icon="el-icon-close"
size="mini"
@click="deleteGameLogGroupInstance(scope.row)"
/>
<el-button
v-else
type="text"
icon="el-icon-close"
size="mini"
@click="deleteGameLogGroupInstancePrompt(scope.row)"
/>
</template>
</el-table-column>
</data-tables>
</safe-dialog>
</template>
<script setup>
import { ref, reactive, computed, watch, nextTick, getCurrentInstance } from 'vue';
import { parseLocation, compareByCreatedAt, timeToText, removeFromArray, adjustDialogZ, formatDateFilter } from '../../../shared/utils';
import { database } from '../../../service/database';
import { useI18n } from 'vue-i18n-bridge';
import { useInstanceStore, useUiStore, useUserStore } from '../../../stores';
const { proxy } = getCurrentInstance();
const { showPreviousInstancesInfoDialog } = useInstanceStore();
const { shiftHeld } = useUiStore();
const { currentUser } = useUserStore();
const { t } = useI18n();
const previousInstancesGroupDialogRef = ref(null);
const loading = ref(false);
const previousInstancesGroupDialogTable = reactive({
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] }
});
const props = defineProps({
previousInstancesGroupDialog: { type: Object, required: true },
});
const emit = defineEmits(['update:previousInstancesGroupDialog']);
const isVisible = computed({
get: () => props.previousInstancesGroupDialog.visible,
set: (value) => {
emit('update:previousInstancesGroupDialog', {
...props.previousInstancesGroupDialog,
visible: value
});
}
});
watch(() => props.previousInstancesGroupDialog.openFlg, () => {
if (props.previousInstancesGroupDialog.visible) {
nextTick(() => {
adjustDialogZ(previousInstancesGroupDialogRef.value.$el);
});
refreshPreviousInstancesGroupTable();
}
});
function refreshPreviousInstancesGroupTable() {
loading.value = true;
const D = props.previousInstancesGroupDialog;
database.getPreviousInstancesByGroupName(D.groupRef.name).then((data) => {
const array = [];
for (const ref of data.values()) {
ref.$location = parseLocation(ref.location);
ref.timer = ref.time > 0 ? timeToText(ref.time) : '';
array.push(ref);
}
array.sort(compareByCreatedAt);
previousInstancesGroupDialogTable.data = array;
loading.value = false;
});
}
function deleteGameLogGroupInstance(row) {
database.deleteGameLogInstanceByInstanceId({ location: row.location });
removeFromArray(previousInstancesGroupDialogTable.data, row);
}
function deleteGameLogGroupInstancePrompt(row) {
proxy.$confirm('Continue? Delete GameLog Instance', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => { if (action === 'confirm') deleteGameLogGroupInstance(row); }
});
}
</script>

View File

@@ -847,7 +847,7 @@
<el-tooltip
:disabled="hideTooltips"
placement="top"
:content="t('dialog.user.info.open_previouse_instance')">
:content="t('dialog.user.info.open_previous_instance')">
<div class="x-friend-item" @click="showPreviousInstancesUserDialog(userDialog.ref)">
<div class="detail">
<span class="name">
@@ -886,7 +886,7 @@
<el-tooltip
:disabled="hideTooltips || currentUser.id !== userDialog.id"
placement="top"
:content="t('dialog.user.info.open_previouse_instance')">
:content="t('dialog.user.info.open_previous_instance')">
<div class="x-friend-item" @click="showPreviousInstancesUserDialog(userDialog.ref)">
<div class="detail">
<span class="name">

View File

@@ -682,7 +682,7 @@
<el-tooltip
:disabled="hideTooltips"
placement="top"
:content="t('dialog.user.info.open_previouse_instance')">
:content="t('dialog.user.info.open_previous_instance')">
<div class="x-friend-item" @click="showPreviousInstancesWorldDialog(worldDialog.ref)">
<div class="detail">
<span class="name">

View File

@@ -915,7 +915,8 @@
"role_description": "Description:",
"role_updated_at": "Updated At:",
"role_created_at": "Created At:",
"role_permissions": "Permissions:"
"role_permissions": "Permissions:",
"last_visited": "Last Visited"
},
"posts": {
"header": "Posts",

View File

@@ -829,7 +829,7 @@
"instance_hard_closed": "hard closed",
"close_instance": "Close Instance",
"instance_age_gated": "age gated",
"open_previouse_instance": "Open Previous Instance"
"open_previous_instance": "Open Previous Instance"
},
"groups": {
"header": "Groups",
@@ -1091,7 +1091,8 @@
"role_description": "Description:",
"role_updated_at": "Updated At:",
"role_created_at": "Created At:",
"role_permissions": "Permissions:"
"role_permissions": "Permissions:",
"last_visited": "Last Visited"
},
"posts": {
"header": "Posts",

View File

@@ -1035,7 +1035,8 @@
"role_description": "Descripción:",
"role_updated_at": "Actualizado el:",
"role_created_at": "Creado el:",
"role_permissions": "Permisos:"
"role_permissions": "Permisos:",
"last_visited": "Última visita"
},
"posts": {
"header": "Publicaciones",

View File

@@ -985,7 +985,8 @@
"role_description": "Description :",
"role_updated_at": "Mis à jour le :",
"role_created_at": "Créé le :",
"role_permissions": "Permissions :"
"role_permissions": "Permissions :",
"last_visited": "Dernière visite"
},
"posts": {
"header": "Publications",

View File

@@ -869,7 +869,8 @@
"role_description": "Description:",
"role_updated_at": "Updated At:",
"role_created_at": "Created At:",
"role_permissions": "Permissions:"
"role_permissions": "Permissions:",
"last_visited": "Last Visited"
},
"posts": {
"header": "Posts",

View File

@@ -798,7 +798,7 @@
"instance_hard_closed": "hard closed",
"close_instance": "インスタンスを閉じる",
"instance_age_gated": "年齢制限あり",
"open_previouse_instance": "前回のインスタンスを開く"
"open_previous_instance": "前回のインスタンスを開く"
},
"groups": {
"header": "グループ",
@@ -1058,7 +1058,8 @@
"role_description": "説明:",
"role_updated_at": "更新日:",
"role_created_at": "作成日:",
"role_permissions": "権限:"
"role_permissions": "権限:",
"last_visited": "最終訪問"
},
"posts": {
"header": "投稿",

View File

@@ -869,7 +869,8 @@
"role_description": "설명:",
"role_updated_at": "업데이트:",
"role_created_at": "생성일:",
"role_permissions": "권한:"
"role_permissions": "권한:",
"last_visited": "마지막 방문"
},
"posts": {
"header": "Posts",

View File

@@ -869,7 +869,8 @@
"role_description": "Opis:",
"role_updated_at": "Zaktualizowano:",
"role_created_at": "Data utworzenia:",
"role_permissions": "Uprawnienia:"
"role_permissions": "Uprawnienia:",
"last_visited": "Ostatnia wizyta"
},
"posts": {
"header": "Posty",

View File

@@ -869,7 +869,8 @@
"role_description": "Descrição:",
"role_updated_at": "Atualizado Em:",
"role_created_at": "Criado Em:",
"role_permissions": "Permissões:"
"role_permissions": "Permissões:",
"last_visited": "Última visita"
},
"posts": {
"header": "Publicações",

View File

@@ -783,7 +783,7 @@
"instance_hard_closed": "жёстко закрыт",
"close_instance": "Закрыть инстанс",
"instance_age_gated": "ограничение по возросту",
"open_previouse_instance": "Открыть предыдущий инстанс"
"open_previous_instance": "Открыть предыдущий инстанс"
},
"groups": {
"header": "Группы",
@@ -1039,7 +1039,8 @@
"role_description": "Описание:",
"role_updated_at": "Обновлена:",
"role_created_at": "Создана:",
"role_permissions": "Права доступа:"
"role_permissions": "Права доступа:",
"last_visited": "Последний визит"
},
"posts": {
"header": "Объявления",

View File

@@ -802,7 +802,7 @@
"instance_hard_closed": "ปิดถาวร",
"close_instance": "ปิดอินสแตนซ์",
"instance_age_gated": "จำกัดอายุ",
"open_previouse_instance": "เปิดอินสแตนซ์ก่อนหน้า"
"open_previous_instance": "เปิดอินสแตนซ์ก่อนหน้า"
},
"groups": {
"header": "กลุ่ม",
@@ -1064,7 +1064,8 @@
"role_description": "คำอธิบาย:",
"role_updated_at": "อัปเดตเมื่อ:",
"role_created_at": "สร้างเมื่อ:",
"role_permissions": "สิทธิ์:"
"role_permissions": "สิทธิ์:",
"last_visited": "เข้าชมล่าสุด"
},
"posts": {
"header": "โพสต์",

View File

@@ -869,7 +869,8 @@
"role_description": "Mô tả:",
"role_updated_at": "Cập nhật lúc:",
"role_created_at": "Tạo lúc:",
"role_permissions": "Quyền:"
"role_permissions": "Quyền:",
"last_visited": "Lần truy cập cuối"
},
"posts": {
"header": "Posts",

View File

@@ -804,7 +804,7 @@
"instance_hard_closed": "房间已关闭",
"close_instance": "关闭该房间",
"instance_age_gated": "房间具有年龄限制",
"open_previouse_instance": "打开上次加入的房间"
"open_previous_instance": "打开上次加入的房间"
},
"groups": {
"header": "群组",
@@ -1066,7 +1066,8 @@
"role_description": "描述:",
"role_updated_at": "更新时间:",
"role_created_at": "创建时间:",
"role_permissions": "权限:"
"role_permissions": "权限:",
"last_visited": "上次访问"
},
"posts": {
"header": "帖子",

View File

@@ -823,7 +823,7 @@
"instance_hard_closed": "強制關閉",
"close_instance": "關閉房間",
"instance_age_gated": "年齡限制",
"open_previouse_instance": "開啟上個房間"
"open_previous_instance": "開啟上個房間"
},
"groups": {
"header": "群組",
@@ -1085,7 +1085,8 @@
"role_description": "敘述:",
"role_updated_at": "更新時間:",
"role_created_at": "創建時間:",
"role_permissions": "權限:"
"role_permissions": "權限:",
"last_visited": "上次造訪"
},
"posts": {
"header": "貼文",

View File

@@ -304,6 +304,55 @@ const gameLog = {
return ref;
},
async getLastGroupVisit(groupName) {
var ref = {
created_at: '',
groupName: ''
};
await sqliteService.execute(
(row) => {
ref = {
created_at: row[0],
groupName: row[1]
};
},
`SELECT created_at, group_name FROM gamelog_location WHERE group_name = @groupName ORDER BY id DESC LIMIT 1`,
{
'@groupName': groupName
}
);
return ref;
},
async getPreviousInstancesByGroupName(groupName) {
var data = new Map();
await sqliteService.execute(
(dbRow) => {
var time = 0;
if (dbRow[2]) {
time = dbRow[2];
}
var ref = data.get(dbRow[1]);
if (typeof ref !== 'undefined') {
time += ref.time;
}
var row = {
created_at: dbRow[0],
location: dbRow[1],
time,
worldName: dbRow[3],
groupName: dbRow[4]
};
data.set(row.location, row);
},
`SELECT created_at, location, time, world_name, group_name FROM gamelog_location WHERE group_name = @groupName ORDER BY id DESC`,
{
'@groupName': groupName
}
);
return data;
},
async getLastSeen(input, inCurrentWorld) {
if (inCurrentWorld) {
var count = 2;

View File

@@ -10,6 +10,7 @@ import {
import { $app } from '../app';
import configRepository from '../service/config';
import { watchState } from '../service/watchState';
import { database } from '../service/database.js';
import { groupDialogFilterOptions } from '../shared/constants/';
import {
replaceBioSymbols,
@@ -45,6 +46,7 @@ export const useGroupStore = defineStore('Group', () => {
memberSearchResults: [],
instances: [],
memberRoles: [],
lastVisit: '',
memberFilter: {
name: 'dialog.group.members.filters.everyone',
id: null
@@ -183,6 +185,7 @@ export const useGroupStore = defineStore('Group', () => {
D.postsFiltered = [];
D.instances = [];
D.memberRoles = [];
D.lastVisit = '';
D.memberSearch = '';
D.memberSearchResults = [];
D.galleries = {};
@@ -215,6 +218,11 @@ export const useGroupStore = defineStore('Group', () => {
D.ownerDisplayName = args1.ref.displayName;
return args1;
});
database.getLastGroupVisit(D.ref.name).then((r) => {
if (D.id === args.ref.id) {
D.lastVisit = r.created_at;
}
});
instanceStore.applyGroupDialogInstances();
getGroupDialogGroup(groupId);
}