Sync recently used reactions list across sessions
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>pull/21833/head
							parent
							
								
									113a0f1b5c
								
							
						
					
					
						commit
						a7f92f35f5
					
				|  | @ -1,35 +0,0 @@ | |||
| /* | ||||
| Copyright 2019 Tulir Asokan <tulir@maunium.net> | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| const REACTION_COUNT = JSON.parse(window.localStorage.mx_reaction_count || '{}'); | ||||
| let sorted = null; | ||||
| 
 | ||||
| export function add(emoji) { | ||||
|     const [count] = REACTION_COUNT[emoji] || [0]; | ||||
|     REACTION_COUNT[emoji] = [count + 1, Date.now()]; | ||||
|     window.localStorage.mx_reaction_count = JSON.stringify(REACTION_COUNT); | ||||
|     sorted = null; | ||||
| } | ||||
| 
 | ||||
| export function get(limit = 24) { | ||||
|     if (sorted === null) { | ||||
|         sorted = Object.entries(REACTION_COUNT) | ||||
|             .sort(([, [count1, date1]], [, [count2, date2]]) => | ||||
|                 count2 === count1 ? date2 - date1 : count2 - count1) | ||||
|             .map(([emoji, count]) => emoji); | ||||
|     } | ||||
|     return sorted.slice(0, limit); | ||||
| } | ||||
|  | @ -0,0 +1,73 @@ | |||
| /* | ||||
| Copyright 2019 Tulir Asokan <tulir@maunium.net> | ||||
| Copyright 2020 The Matrix.org Foundation C.I.C. | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import SettingsStore, {SettingLevel} from "../settings/SettingsStore"; | ||||
| import {sortBy} from "lodash"; | ||||
| 
 | ||||
| interface ILegacyFormat { | ||||
|     [emoji: string]: [number, number]; // [count, date]
 | ||||
| } | ||||
| 
 | ||||
| // New format tries to be more space efficient for synchronization. Ordered by Date descending.
 | ||||
| type Format = [string, number][]; // [emoji, count]
 | ||||
| 
 | ||||
| const SETTING_NAME = "recent_emoji"; | ||||
| 
 | ||||
| // we store more recents than we typically query but this lets us sort by weighted usage
 | ||||
| // even if you haven't used your typically favourite emoji for a little while.
 | ||||
| const STORAGE_LIMIT = 100; | ||||
| 
 | ||||
| // TODO remove this after some time
 | ||||
| function migrate() { | ||||
|     const data: ILegacyFormat = JSON.parse(window.localStorage.mx_reaction_count || '{}'); | ||||
|     const sorted = Object.entries(data).sort(([, [count1, date1]], [, [count2, date2]]) => date2 - date1); | ||||
|     const newFormat = sorted.map(([emoji, [count, date]]) => [emoji, count]); | ||||
|     SettingsStore.setValue(SETTING_NAME, null, SettingLevel.ACCOUNT, newFormat.slice(0, STORAGE_LIMIT)); | ||||
| } | ||||
| 
 | ||||
| function getRecentEmoji(): Format { | ||||
|     return SettingsStore.getValue(SETTING_NAME) || []; | ||||
| } | ||||
| 
 | ||||
| export function add(emoji: string) { | ||||
|     const recents = getRecentEmoji(); | ||||
|     const i = recents.findIndex(([e]) => e === emoji); | ||||
| 
 | ||||
|     let newEntry; | ||||
|     if (i >= 0) { | ||||
|         // first remove the existing tuple so that we can increment it and push it to the front
 | ||||
|         [newEntry] = recents.splice(i, 1); | ||||
|         newEntry[1]++; // increment the usage count
 | ||||
|     } else { | ||||
|         newEntry = [emoji, 1]; | ||||
|     } | ||||
| 
 | ||||
|     SettingsStore.setValue(SETTING_NAME, null, SettingLevel.ACCOUNT, [newEntry, ...recents].slice(0, STORAGE_LIMIT)); | ||||
| } | ||||
| 
 | ||||
| export function get(limit = 24) { | ||||
|     let recents = getRecentEmoji(); | ||||
| 
 | ||||
|     if (recents.length < 1) { | ||||
|         migrate(); | ||||
|         recents = getRecentEmoji(); | ||||
|     } | ||||
| 
 | ||||
|     // perform a stable sort on `count` to keep the recent (date) order as a secondary sort factor
 | ||||
|     const sorted = sortBy(recents, "1"); | ||||
|     return sorted.slice(0, limit).map(([emoji]) => emoji); | ||||
| } | ||||
|  | @ -351,6 +351,12 @@ export const SETTINGS = { | |||
|         default: "en", | ||||
|     }, | ||||
|     "breadcrumb_rooms": { | ||||
|         // not really a setting
 | ||||
|         supportedLevels: ['account'], | ||||
|         default: [], | ||||
|     }, | ||||
|     "recent_emoji": { | ||||
|         // not really a setting
 | ||||
|         supportedLevels: ['account'], | ||||
|         default: [], | ||||
|     }, | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ import {objectClone, objectKeyChanges} from "../../utils/objects"; | |||
| const BREADCRUMBS_LEGACY_EVENT_TYPE = "im.vector.riot.breadcrumb_rooms"; | ||||
| const BREADCRUMBS_EVENT_TYPE = "im.vector.setting.breadcrumbs"; | ||||
| const BREADCRUMBS_EVENT_TYPES = [BREADCRUMBS_LEGACY_EVENT_TYPE, BREADCRUMBS_EVENT_TYPE]; | ||||
| const RECENT_EMOJI_EVENT_TYPE = "io.element.recent_emoji"; | ||||
| 
 | ||||
| const INTEG_PROVISIONING_EVENT_TYPE = "im.vector.setting.integration_provisioning"; | ||||
| 
 | ||||
|  | @ -69,6 +70,9 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa | |||
|         } else if (event.getType() === INTEG_PROVISIONING_EVENT_TYPE) { | ||||
|             const val = event.getContent()['enabled']; | ||||
|             this._watchers.notifyUpdate("integrationProvisioning", null, SettingLevel.ACCOUNT, val); | ||||
|         } else if (event.getType() === RECENT_EMOJI_EVENT_TYPE) { | ||||
|             const val = event.getContent()['enabled']; | ||||
|             this._watchers.notifyUpdate("recent_emoji", null, SettingLevel.ACCOUNT, val); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -95,6 +99,12 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa | |||
|             return content && content['recent_rooms'] ? content['recent_rooms'] : []; | ||||
|         } | ||||
| 
 | ||||
|         // Special case recent emoji
 | ||||
|         if (settingName === "recent_emoji") { | ||||
|             const content = this._getSettings(RECENT_EMOJI_EVENT_TYPE); | ||||
|             return content ? content["recent_emoji"] : null; | ||||
|         } | ||||
| 
 | ||||
|         // Special case integration manager provisioning
 | ||||
|         if (settingName === "integrationProvisioning") { | ||||
|             const content = this._getSettings(INTEG_PROVISIONING_EVENT_TYPE); | ||||
|  | @ -135,6 +145,13 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa | |||
|             return MatrixClientPeg.get().setAccountData(BREADCRUMBS_EVENT_TYPE, content); | ||||
|         } | ||||
| 
 | ||||
|         // Special case recent emoji
 | ||||
|         if (settingName === "recent_emoji") { | ||||
|             const content = this._getSettings(RECENT_EMOJI_EVENT_TYPE) || {}; | ||||
|             content["recent_emoji"] = newValue; | ||||
|             return MatrixClientPeg.get().setAccountData(RECENT_EMOJI_EVENT_TYPE, content); | ||||
|         } | ||||
| 
 | ||||
|         // Special case integration manager provisioning
 | ||||
|         if (settingName === "integrationProvisioning") { | ||||
|             const content = this._getSettings(INTEG_PROVISIONING_EVENT_TYPE) || {}; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Michael Telatynski
						Michael Telatynski