diff --git a/html/src/app.js b/html/src/app.js
index eb71e7ba..1b24ee59 100644
--- a/html/src/app.js
+++ b/html/src/app.js
@@ -476,6 +476,13 @@ speechSynthesis.getVoices();
if (status === 404 && endpoint.substring(0, 6) === 'users/') {
throw new Error("404: Can't find user!");
}
+ if (
+ status === 404 &&
+ endpoint.substring(0, 7) === 'invite/' &&
+ init.inviteId
+ ) {
+ this.expireNotification(init.inviteId);
+ }
if (data.error === Object(data.error)) {
this.$throw(
data.error.status_code || status,
@@ -2218,11 +2225,9 @@ speechSynthesis.getVoices();
// API: Notification
- API.cachedNotifications = new Map();
API.isNotificationsLoading = false;
API.$on('LOGIN', function () {
- this.cachedNotifications.clear();
this.isNotificationsLoading = false;
});
@@ -2254,13 +2259,19 @@ speechSynthesis.getVoices();
});
API.$on('NOTIFICATION:ACCEPT', function (args) {
- var ref = this.cachedNotifications.get(args.params.notificationId);
- if (typeof ref === 'undefined' || ref.$isDeleted) {
+ var array = $app.notificationTable.data;
+ for (var i = array.length - 1; i >= 0; i--) {
+ if (array[i].id === args.params.notificationId) {
+ var ref = array[i];
+ break;
+ }
+ }
+ if (typeof ref === 'undefined') {
return;
}
+ ref.$isExpired = true;
args.ref = ref;
- ref.$isDeleted = true;
- this.$emit('NOTIFICATION:@DELETE', {
+ this.$emit('NOTIFICATION:EXPIRE', {
ref,
params: {
notificationId: ref.id
@@ -2274,13 +2285,32 @@ speechSynthesis.getVoices();
});
API.$on('NOTIFICATION:HIDE', function (args) {
- var ref = this.cachedNotifications.get(args.params.notificationId);
- if (typeof ref === 'undefined' && ref.$isDeleted) {
+ var array = $app.notificationTable.data;
+ for (var i = array.length - 1; i >= 0; i--) {
+ if (array[i].id === args.params.notificationId) {
+ var ref = array[i];
+ break;
+ }
+ }
+ if (typeof ref === 'undefined') {
return;
}
args.ref = ref;
- ref.$isDeleted = true;
- this.$emit('NOTIFICATION:@DELETE', {
+ if (
+ ref.type === 'friendRequest' ||
+ ref.type === 'hiddenFriendRequest'
+ ) {
+ for (var i = array.length - 1; i >= 0; i--) {
+ if (array[i].id === ref.id) {
+ array.splice(i, 1);
+ break;
+ }
+ }
+ } else {
+ ref.$isExpired = true;
+ database.updateNotificationExpired(ref);
+ }
+ this.$emit('NOTIFICATION:EXPIRE', {
ref,
params: {
notificationId: ref.id
@@ -2289,7 +2319,13 @@ speechSynthesis.getVoices();
});
API.applyNotification = function (json) {
- var ref = this.cachedNotifications.get(json.id);
+ var array = $app.notificationTable.data;
+ for (var i = array.length - 1; i >= 0; i--) {
+ if (array[i].id === json.id) {
+ var ref = array[i];
+ break;
+ }
+ }
if (typeof ref === 'undefined') {
ref = {
id: '',
@@ -2301,12 +2337,10 @@ speechSynthesis.getVoices();
seen: false,
created_at: '',
// VRCX
- $isDeleted: false,
$isExpired: false,
//
...json
};
- this.cachedNotifications.set(ref.id, ref);
} else {
Object.assign(ref, json);
ref.$isExpired = false;
@@ -2326,36 +2360,42 @@ speechSynthesis.getVoices();
return ref;
};
- API.expireNotifications = function () {
- for (var ref of this.cachedNotifications.values()) {
- ref.$isExpired = true;
+ API.expireFriendRequestNotifications = function () {
+ var array = $app.notificationTable.data;
+ for (var i = array.length - 1; i >= 0; i--) {
if (
- ref.type === 'friendRequest' ||
- ref.type === 'hiddenFriendRequest'
+ array[i].type === 'friendRequest' ||
+ array[i].type === 'hiddenFriendRequest'
) {
- this.cachedNotifications.delete(ref.id);
+ array.splice(i, 1);
}
}
};
- API.deleteExpiredNotifcations = function () {
- for (var ref of this.cachedNotifications.values()) {
- if (ref.$isDeleted || ref.$isExpired === false) {
- continue;
+ API.expireNotification = function (notificationId) {
+ var array = $app.notificationTable.data;
+ for (var i = array.length - 1; i >= 0; i--) {
+ if (array[i].id === notificationId) {
+ var ref = array[i];
+ break;
}
- ref.$isDeleted = true;
- this.$emit('NOTIFICATION:@DELETE', {
- ref,
- params: {
- notificationId: ref.id
- }
- });
}
+ if (typeof ref === 'undefined') {
+ return;
+ }
+ ref.$isExpired = true;
+ database.updateNotificationExpired(ref);
+ this.$emit('NOTIFICATION:EXPIRE', {
+ ref,
+ params: {
+ notificationId: ref.id
+ }
+ });
};
API.refreshNotifications = async function () {
this.isNotificationsLoading = true;
- this.expireNotifications();
+ this.expireFriendRequestNotifications();
var params = {
n: 100,
offset: 0
@@ -2382,7 +2422,6 @@ speechSynthesis.getVoices();
break;
}
}
- this.deleteExpiredNotifcations();
this.isNotificationsLoading = false;
};
@@ -2512,31 +2551,33 @@ speechSynthesis.getVoices();
});
};
- API.sendInviteResponse = function (params, inviteID) {
- return this.call(`invite/${inviteID}/response`, {
+ API.sendInviteResponse = function (params, inviteId) {
+ return this.call(`invite/${inviteId}/response`, {
method: 'POST',
- params
+ params,
+ inviteId
}).then((json) => {
var args = {
json,
params,
- inviteID
+ inviteId
};
this.$emit('INVITE:RESPONSE:SEND', args);
return args;
});
};
- API.sendInviteResponsePhoto = function (params, inviteID) {
- return this.call(`invite/${inviteID}/response/photo`, {
+ API.sendInviteResponsePhoto = function (params, inviteId) {
+ return this.call(`invite/${inviteId}/response/photo`, {
uploadImage: true,
postData: JSON.stringify(params),
- imageData: $app.uploadImage
+ imageData: $app.uploadImage,
+ inviteId
}).then((json) => {
var args = {
json,
params,
- inviteID
+ inviteId
};
this.$emit('INVITE:RESPONSE:PHOTO:SEND', args);
return args;
@@ -2586,13 +2627,13 @@ speechSynthesis.getVoices();
};
API.getFriendRequest = function (userId) {
- for (var ref of this.cachedNotifications.values()) {
+ var array = $app.notificationTable.data;
+ for (var i = array.length - 1; i >= 0; i--) {
if (
- ref.$isDeleted === false &&
- ref.type === 'friendRequest' &&
- ref.senderUserId === userId
+ array[i].type === 'friendRequest' &&
+ array[i].senderUserId === userId
) {
- return ref.id;
+ return array[i].id;
}
}
return '';
@@ -3506,6 +3547,11 @@ speechSynthesis.getVoices();
break;
case 'hide-notification':
+ this.$emit('NOTIFICATION:HIDE', {
+ params: {
+ notificationId: content
+ }
+ });
this.$emit('NOTIFICATION:SEE', {
params: {
notificationId: content
@@ -4508,9 +4554,6 @@ speechSynthesis.getVoices();
};
$app.methods.queueNotificationNoty = function (noty) {
- if (noty.senderUserId === API.currentUser.id) {
- return;
- }
noty.isFriend = this.friends.has(noty.senderUserId);
noty.isFavorite = API.cachedFavoritesByObjectId.has(noty.senderUserId);
var notyFilter = this.sharedFeedFilters.noty;
@@ -6875,6 +6918,8 @@ speechSynthesis.getVoices();
await database.initUserTables(args.json.id);
$app.feedTable.data = await database.getFeedDatabase();
$app.sweepFeed();
+ // eslint-disable-next-line require-atomic-updates
+ $app.notificationTable.data = await database.getNotifications();
if (configRepository.getBool(`friendLogInit_${args.json.id}`)) {
await $app.getFriendLog();
} else {
@@ -8615,6 +8660,20 @@ speechSynthesis.getVoices();
this.friendLog.set(id, friendLogCurrent);
database.setFriendLogCurrent(friendLogCurrent);
this.notifyMenu('friendLog');
+ this.deleteFriendRequest(id);
+ }
+ };
+
+ $app.methods.deleteFriendRequest = function (userId) {
+ var array = $app.notificationTable.data;
+ for (var i = array.length - 1; i >= 0; i--) {
+ if (
+ array[i].type === 'friendRequest' &&
+ array[i].senderUserId === userId
+ ) {
+ array.splice(i, 1);
+ return;
+ }
}
};
@@ -8859,22 +8918,22 @@ speechSynthesis.getVoices();
var {length} = array;
for (var i = 0; i < length; ++i) {
if (array[i].id === ref.id) {
- if (ref.$isDeleted) {
- array.splice(i, 1);
- } else {
- Vue.set(array, i, ref);
- }
+ Vue.set(array, i, ref);
return;
}
}
- if (ref.$isDeleted === false) {
- $app.notificationTable.data.push(ref);
- if (ref.senderUserId !== this.currentUser.id) {
- $app.notifyMenu('notification');
- $app.unseenNotifications.push(ref.id);
+ if (ref.senderUserId !== this.currentUser.id) {
+ if (
+ ref.type !== 'friendRequest' &&
+ ref.type !== 'hiddenFriendRequest'
+ ) {
+ database.addNotificationToDatabase(ref);
}
+ $app.notifyMenu('notification');
+ $app.unseenNotifications.push(ref.id);
$app.queueNotificationNoty(ref);
}
+ $app.notificationTable.data.push(ref);
$app.updateSharedFeed(true);
});
@@ -8886,18 +8945,6 @@ speechSynthesis.getVoices();
}
});
- API.$on('NOTIFICATION:@DELETE', function (args) {
- var {ref} = args;
- var array = $app.notificationTable.data;
- var {length} = array;
- for (var i = 0; i < length; ++i) {
- if (array[i].id === ref.id) {
- array.splice(i, 1);
- return;
- }
- }
- });
-
$app.methods.acceptNotification = function (row) {
// FIXME: 메시지 수정
this.$confirm('Continue? Accept Friend Request', 'Confirm', {
@@ -8915,8 +8962,7 @@ speechSynthesis.getVoices();
};
$app.methods.hideNotification = function (row) {
- // FIXME: 메시지 수정
- this.$confirm('Continue? Delete Notification', 'Confirm', {
+ this.$confirm(`Continue? Decline ${row.type}`, 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
@@ -8939,6 +8985,25 @@ speechSynthesis.getVoices();
});
};
+ $app.methods.deleteNotificationLog = function (row) {
+ this.$confirm(`Continue? Delete ${row.type}`, 'Confirm', {
+ confirmButtonText: 'Confirm',
+ cancelButtonText: 'Cancel',
+ type: 'info',
+ callback: (action) => {
+ if (action === 'confirm') {
+ removeFromArray(this.notificationTable.data, row);
+ if (
+ row.type !== 'friendRequest' &&
+ row.type !== 'hiddenFriendRequest'
+ ) {
+ database.deleteNotification(row.id);
+ }
+ }
+ }
+ });
+ };
+
$app.methods.acceptRequestInvite = function (row) {
this.$confirm('Continue? Send Invite', 'Confirm', {
confirmButtonText: 'Confirm',
@@ -10146,7 +10211,7 @@ speechSynthesis.getVoices();
D.isFriend = true;
});
- API.$on('NOTIFICATION:@DELETE', function (args) {
+ API.$on('NOTIFICATION:EXPIRE', function (args) {
var {ref} = args;
var D = $app.userDialog;
if (
diff --git a/html/src/index.pug b/html/src/index.pug
index 3249d38b..af091f23 100644
--- a/html/src/index.pug
+++ b/html/src/index.pug
@@ -470,15 +470,26 @@ html
span(v-else-if='scope.row.details && scope.row.details.inviteMessage' v-text="scope.row.details.inviteMessage")
span(v-else-if='scope.row.details && scope.row.details.requestMessage' v-text="scope.row.details.requestMessage")
span(v-else-if='scope.row.details && scope.row.details.responseMessage' v-text="scope.row.details.responseMessage")
- el-table-column(label="Action" width="80" align="right")
+ el-table-column(label="Action" width="100" align="right")
template(v-once #default="scope")
- template(v-if="scope.row.senderUserId !== API.currentUser.id")
- el-button(v-if="scope.row.type === 'friendRequest'" type="text" icon="el-icon-check" size="mini" @click="acceptNotification(scope.row)")
- el-button(v-else-if="scope.row.type === 'invite'" type="text" icon="el-icon-chat-line-square" size="mini" @click="showSendInviteResponseDialog(scope.row)")
+ template(v-if="scope.row.senderUserId !== API.currentUser.id && !scope.row.$isExpired")
+ template(v-if="scope.row.type === 'friendRequest'")
+ el-tooltip(placement="top" content="Accept" :disabled="hideTooltips")
+ el-button(type="text" icon="el-icon-check" size="mini" @click="acceptNotification(scope.row)")
+ template(v-else-if="scope.row.type === 'invite'")
+ el-tooltip(placement="top" content="Decline with message" :disabled="hideTooltips")
+ el-button(type="text" icon="el-icon-chat-line-square" size="mini" @click="showSendInviteResponseDialog(scope.row)")
template(v-else-if="scope.row.type === 'requestInvite'")
- el-button(v-if="lastLocation.location && isGameRunning && !checkCanInvite(lastLocation.location)" type="text" icon="el-icon-check" size="mini" @click="acceptRequestInvite(scope.row)")
- el-button(type="text" icon="el-icon-chat-line-square" size="mini" style="margin-left:5px" @click="showSendInviteRequestResponseDialog(scope.row)")
- el-button(type="text" icon="el-icon-close" size="mini" style="margin-left:5px" @click="hideNotification(scope.row)")
+ template(v-if="lastLocation.location && isGameRunning && !checkCanInvite(lastLocation.location)")
+ el-tooltip(placement="top" content="Invite" :disabled="hideTooltips")
+ el-button(type="text" icon="el-icon-check" size="mini" @click="acceptRequestInvite(scope.row)")
+ el-tooltip(placement="top" content="Decline with message" :disabled="hideTooltips")
+ el-button(type="text" icon="el-icon-chat-line-square" size="mini" style="margin-left:5px" @click="showSendInviteRequestResponseDialog(scope.row)")
+ template(v-if="scope.row.type !== 'requestInviteResponse' && scope.row.type !== 'inviteResponse' && scope.row.type !== 'message'")
+ el-tooltip(placement="top" content="Decline" :disabled="hideTooltips")
+ el-button(type="text" icon="el-icon-close" size="mini" style="margin-left:5px" @click="hideNotification(scope.row)")
+ el-tooltip(placement="top" content="Delete log" :disabled="hideTooltips")
+ el-button(type="text" icon="el-icon-delete" size="mini" style="margin-left:5px" @click="deleteNotificationLog(scope.row)")
//- profile
.x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'profile'")
diff --git a/html/src/repository/database.js b/html/src/repository/database.js
index 6ed5a500..b145567e 100644
--- a/html/src/repository/database.js
+++ b/html/src/repository/database.js
@@ -21,6 +21,9 @@ class Database {
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS ${Database.userId}_friend_log_history (id INTEGER PRIMARY KEY, created_at TEXT, type TEXT, user_id TEXT, display_name TEXT, previous_display_name TEXT, trust_level TEXT, previous_trust_level TEXT)`
);
+ await sqliteService.executeNonQuery(
+ `CREATE TABLE IF NOT EXISTS ${Database.userId}_notifications (id TEXT PRIMARY KEY, created_at TEXT, type TEXT, sender_user_id TEXT, sender_username TEXT, receiver_user_id TEXT, message TEXT, world_id TEXT, world_name TEXT, image_url TEXT, invite_message TEXT, request_message TEXT, response_message TEXT, expired INTEGER)`
+ );
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS memos (user_id TEXT PRIMARY KEY, edited_at TEXT, memo TEXT)`
);
@@ -523,6 +526,103 @@ class Database {
}
);
}
+
+ async getNotifications() {
+ var notifications = [];
+ await sqliteService.execute((dbRow) => {
+ var row = {
+ id: dbRow[0],
+ created_at: dbRow[1],
+ type: dbRow[2],
+ senderUserId: dbRow[3],
+ senderUsername: dbRow[4],
+ receiverUserId: dbRow[5],
+ message: dbRow[6],
+ details: {
+ worldId: dbRow[7],
+ worldName: dbRow[8],
+ imageUrl: dbRow[9],
+ inviteMessage: dbRow[10],
+ requestMessage: dbRow[11],
+ responseMessage: dbRow[12]
+ }
+ };
+ row.$isExpired = false;
+ if (dbRow[13] === 1) {
+ row.$isExpired = true;
+ }
+ notifications.unshift(row);
+ }, `SELECT * FROM ${Database.userId}_notifications LIMIT 5000`);
+ return notifications;
+ }
+
+ addNotificationToDatabase(row) {
+ var entry = {
+ id: '',
+ created_at: '',
+ type: '',
+ senderUserId: '',
+ senderUsername: '',
+ receiverUserId: '',
+ message: '',
+ ...row,
+ details: {
+ worldId: '',
+ worldName: '',
+ imageUrl: '',
+ inviteMessage: '',
+ requestMessage: '',
+ responseMessage: '',
+ ...row.details
+ }
+ };
+ var expired = 0;
+ if (row.$isExpired) {
+ expired = 1;
+ }
+ sqliteService.executeNonQuery(
+ `INSERT OR IGNORE INTO ${Database.userId}_notifications (id, created_at, type, sender_user_id, sender_username, receiver_user_id, message, world_id, world_name, image_url, invite_message, request_message, response_message, expired) VALUES (@id, @created_at, @type, @sender_user_id, @sender_username, @receiver_user_id, @message, @world_id, @world_name, @image_url, @invite_message, @request_message, @response_message, @expired)`,
+ {
+ '@id': entry.id,
+ '@created_at': entry.created_at,
+ '@type': entry.type,
+ '@sender_user_id': entry.senderUserId,
+ '@sender_username': entry.senderUsername,
+ '@receiver_user_id': entry.receiverUserId,
+ '@message': entry.message,
+ '@world_id': entry.details.worldId,
+ '@world_name': entry.details.worldName,
+ '@image_url': entry.details.imageUrl,
+ '@invite_message': entry.details.inviteMessage,
+ '@request_message': entry.details.requestMessage,
+ '@response_message': entry.details.responseMessage,
+ '@expired': expired
+ }
+ );
+ }
+
+ deleteNotification(rowId) {
+ sqliteService.executeNonQuery(
+ `DELETE FROM ${Database.userId}_notifications WHERE id = @row_id`,
+ {
+ '@row_id': rowId
+ }
+ );
+ }
+
+ updateNotificationExpired(entry) {
+ var expired = 0;
+ if (entry.$isExpired) {
+ expired = 1;
+ }
+ sqliteService.executeNonQuery(
+ `UPDATE ${Database.userId}_notifications SET expired = @expired WHERE id = @id`,
+ {
+ '@id': entry.id,
+ '@expired': expired
+ }
+ );
+ }
}
var self = new Database();