@@ -20,6 +20,7 @@ import * as ajvSchemas0 from '../../../lib/validate-schemas.js';
20
20
import { EventListenerCollection } from '../../core/event-listener-collection.js' ;
21
21
import { readResponseJson } from '../../core/json.js' ;
22
22
import { log } from '../../core/log.js' ;
23
+ import { deferPromise } from '../../core/utilities.js' ;
23
24
import { compareRevisions } from '../../dictionary/dictionary-data-util.js' ;
24
25
import { DictionaryWorker } from '../../dictionary/dictionary-worker.js' ;
25
26
import { querySelectorNotNull } from '../../dom/query-selector.js' ;
@@ -32,14 +33,17 @@ class DictionaryEntry {
32
33
* @param {DocumentFragment } fragment
33
34
* @param {number } index
34
35
* @param {import('dictionary-importer').Summary } dictionaryInfo
36
+ * @param {string | null } updateDownloadUrl
35
37
*/
36
- constructor ( dictionaryController , fragment , index , dictionaryInfo ) {
38
+ constructor ( dictionaryController , fragment , index , dictionaryInfo , updateDownloadUrl ) {
37
39
/** @type {DictionaryController } */
38
40
this . _dictionaryController = dictionaryController ;
39
41
/** @type {number } */
40
42
this . _index = index ;
41
43
/** @type {import('dictionary-importer').Summary } */
42
44
this . _dictionaryInfo = dictionaryInfo ;
45
+ /** @type {string | null } */
46
+ this . _updateDownloadUrl = updateDownloadUrl ;
43
47
/** @type {EventListenerCollection } */
44
48
this . _eventListeners = new EventListenerCollection ( ) ;
45
49
/** @type {?import('dictionary-database').DictionaryCountGroup } */
@@ -86,6 +90,7 @@ class DictionaryEntry {
86
90
this . _outdatedButton . hidden = ( version >= 3 ) ;
87
91
this . _priorityInput . dataset . setting = `dictionaries[${ index } ].priority` ;
88
92
this . _enabledCheckbox . dataset . setting = `dictionaries[${ index } ].enabled` ;
93
+ this . _showUpdatesAvailableButton ( ) ;
89
94
this . _eventListeners . addEventListener ( this . _enabledCheckbox , 'settingChanged' , this . _onEnabledChanged . bind ( this ) , false ) ;
90
95
this . _eventListeners . addEventListener ( this . _menuButton , 'menuOpen' , this . _onMenuOpen . bind ( this ) , false ) ;
91
96
this . _eventListeners . addEventListener ( this . _menuButton , 'menuClose' , this . _onMenuClose . bind ( this ) , false ) ;
@@ -122,6 +127,11 @@ class DictionaryEntry {
122
127
this . _enabledCheckbox . checked = value ;
123
128
}
124
129
130
+ /** */
131
+ hideUpdatesAvailableButton ( ) {
132
+ this . _updatesAvailable . hidden = true ;
133
+ }
134
+
125
135
/**
126
136
* @returns {Promise<boolean> }
127
137
*/
@@ -147,21 +157,38 @@ class DictionaryEntry {
147
157
148
158
const downloadUrl = latestDownloadUrl ?? currentDownloadUrl ;
149
159
150
- this . _updatesAvailable . dataset . downloadUrl = downloadUrl ;
151
- this . _updatesAvailable . hidden = false ;
160
+ this . _updateDownloadUrl = downloadUrl ;
161
+ this . _showUpdatesAvailableButton ( ) ;
152
162
return true ;
153
163
}
154
164
165
+ /**
166
+ * @returns {string | null }
167
+ */
168
+ get updateDownloadUrl ( ) {
169
+ return this . _updateDownloadUrl ;
170
+ }
171
+
155
172
// Private
156
173
174
+ /** */
175
+ _showUpdatesAvailableButton ( ) {
176
+ if ( this . _updateDownloadUrl === null || this . _dictionaryController . isDictionaryInTaskQueue ( this . dictionaryTitle ) ) {
177
+ return ;
178
+ }
179
+ this . _updatesAvailable . dataset . downloadUrl = this . _updateDownloadUrl ;
180
+ this . _updatesAvailable . hidden = false ;
181
+ }
182
+
157
183
/**
158
184
* @param {import('popup-menu').MenuOpenEvent } e
159
185
*/
160
186
_onMenuOpen ( e ) {
161
187
const bodyNode = e . detail . menu . bodyNode ;
162
188
const count = this . _dictionaryController . dictionaryOptionCount ;
163
189
this . _setMenuActionEnabled ( bodyNode , 'moveTo' , count > 1 ) ;
164
- this . _setMenuActionEnabled ( bodyNode , 'delete' , ! this . _dictionaryController . isDictionaryInDeleteQueue ( this . dictionaryTitle ) ) ;
190
+ const deleteDisabled = this . _dictionaryController . isDictionaryInTaskQueue ( this . dictionaryTitle ) ;
191
+ this . _setMenuActionEnabled ( bodyNode , 'delete' , ! deleteDisabled ) ;
165
192
}
166
193
167
194
/**
@@ -504,10 +531,12 @@ export class DictionaryController {
504
531
this . _allCheckbox = querySelectorNotNull ( document , '#all-dictionaries-enabled' ) ;
505
532
/** @type {?DictionaryExtraInfo } */
506
533
this . _extraInfo = null ;
534
+ /** @type {import('dictionary-controller.js').DictionaryTask[] } */
535
+ this . _dictionaryTaskQueue = [ ] ;
507
536
/** @type {boolean } */
508
- this . _isDeleting = false ;
509
- /** @type {string[] } */
510
- this . _dictionaryDeleteQueue = [ ] ;
537
+ this . _isTaskQueueRunning = false ;
538
+ /** @type {(() => void) | null } */
539
+ this . _onDictionariesUpdate = null ;
511
540
}
512
541
513
542
/** @type {import('./modal-controller.js').ModalController } */
@@ -738,6 +767,10 @@ export class DictionaryController {
738
767
this . _dictionaries = dictionaries ;
739
768
740
769
await this . _updateEntries ( ) ;
770
+
771
+ if ( this . _onDictionariesUpdate ) {
772
+ this . _onDictionariesUpdate ( ) ;
773
+ }
741
774
}
742
775
743
776
/** */
@@ -754,7 +787,10 @@ export class DictionaryController {
754
787
if ( dictionaries === null ) { return ; }
755
788
this . _updateMainDictionarySelectOptions ( dictionaries ) ;
756
789
790
+ /** @type {Map<string, string | null> } */
791
+ const dictionaryUpdateDownloadUrlMap = new Map ( ) ;
757
792
for ( const entry of this . _dictionaryEntries ) {
793
+ dictionaryUpdateDownloadUrlMap . set ( entry . dictionaryTitle , entry . updateDownloadUrl ) ;
758
794
entry . cleanup ( ) ;
759
795
}
760
796
this . _dictionaryEntries = [ ] ;
@@ -784,8 +820,9 @@ export class DictionaryController {
784
820
for ( let i = 0 , ii = dictionaryOptionsArray . length ; i < ii ; ++ i ) {
785
821
const { name} = dictionaryOptionsArray [ i ] ;
786
822
const dictionaryInfo = dictionaryInfoMap . get ( name ) ;
823
+ const updateDownloadUrl = dictionaryUpdateDownloadUrlMap . get ( name ) ?? null ;
787
824
if ( typeof dictionaryInfo === 'undefined' ) { continue ; }
788
- this . _createDictionaryEntry ( i , dictionaryInfo ) ;
825
+ this . _createDictionaryEntry ( i , dictionaryInfo , updateDownloadUrl ) ;
789
826
}
790
827
}
791
828
@@ -842,11 +879,12 @@ export class DictionaryController {
842
879
const modal = /** @type {import('./modal.js').Modal } */ ( this . _deleteDictionaryModal ) ;
843
880
modal . setVisible ( false ) ;
844
881
845
- const title = modal . node . dataset . dictionaryTitle ;
846
- if ( typeof title !== 'string' ) { return ; }
882
+ const dictionaryTitle = modal . node . dataset . dictionaryTitle ;
883
+ if ( typeof dictionaryTitle !== 'string' ) { return ; }
847
884
delete modal . node . dataset . dictionaryTitle ;
848
885
849
- void this . _enqueueDictionaryDelete ( title ) ;
886
+ void this . _enqueueTask ( { type : 'delete' , dictionaryTitle} ) ;
887
+ this . _hideUpdatesAvailableButton ( dictionaryTitle ) ;
850
888
}
851
889
852
890
/**
@@ -858,12 +896,25 @@ export class DictionaryController {
858
896
const modal = /** @type {import('./modal.js').Modal } */ ( this . _updateDictionaryModal ) ;
859
897
modal . setVisible ( false ) ;
860
898
861
- const title = modal . node . dataset . dictionaryTitle ;
899
+ const dictionaryTitle = modal . node . dataset . dictionaryTitle ;
862
900
const downloadUrl = modal . node . dataset . downloadUrl ;
863
- if ( typeof title !== 'string' ) { return ; }
901
+ if ( typeof dictionaryTitle !== 'string' ) { return ; }
864
902
delete modal . node . dataset . dictionaryTitle ;
865
903
866
- void this . _updateDictionary ( title , downloadUrl ) ;
904
+ void this . _enqueueTask ( { type : 'update' , dictionaryTitle, downloadUrl} ) ;
905
+ this . _hideUpdatesAvailableButton ( dictionaryTitle ) ;
906
+ }
907
+
908
+ /**
909
+ * @param {string } dictionaryTitle
910
+ */
911
+ _hideUpdatesAvailableButton ( dictionaryTitle ) {
912
+ for ( const entry of this . _dictionaryEntries ) {
913
+ if ( entry . dictionaryTitle === dictionaryTitle ) {
914
+ entry . hideUpdatesAvailableButton ( ) ;
915
+ break ;
916
+ }
917
+ }
867
918
}
868
919
869
920
/**
@@ -954,7 +1005,7 @@ export class DictionaryController {
954
1005
955
1006
/** */
956
1007
async _checkForUpdates ( ) {
957
- if ( this . _dictionaries === null || this . _checkingIntegrity || this . _checkingUpdates || this . _isDeleting ) { return ; }
1008
+ if ( this . _dictionaries === null || this . _checkingIntegrity || this . _checkingUpdates || this . _isTaskQueueRunning ) { return ; }
958
1009
let hasUpdates ;
959
1010
try {
960
1011
this . _checkingUpdates = true ;
@@ -977,7 +1028,7 @@ export class DictionaryController {
977
1028
978
1029
/** */
979
1030
async _checkIntegrity ( ) {
980
- if ( this . _dictionaries === null || this . _checkingIntegrity || this . _checkingUpdates || this . _isDeleting ) { return ; }
1031
+ if ( this . _dictionaries === null || this . _checkingIntegrity || this . _checkingUpdates || this . _isTaskQueueRunning ) { return ; }
981
1032
982
1033
try {
983
1034
this . _checkingIntegrity = true ;
@@ -1033,11 +1084,12 @@ export class DictionaryController {
1033
1084
/**
1034
1085
* @param {number } index
1035
1086
* @param {import('dictionary-importer').Summary } dictionaryInfo
1087
+ * @param {string|null } updateDownloadUrl
1036
1088
*/
1037
- _createDictionaryEntry ( index , dictionaryInfo ) {
1089
+ _createDictionaryEntry ( index , dictionaryInfo , updateDownloadUrl ) {
1038
1090
const fragment = this . instantiateTemplateFragment ( 'dictionary' ) ;
1039
1091
1040
- const entry = new DictionaryEntry ( this , fragment , index , dictionaryInfo ) ;
1092
+ const entry = new DictionaryEntry ( this , fragment , index , dictionaryInfo , updateDownloadUrl ) ;
1041
1093
this . _dictionaryEntries . push ( entry ) ;
1042
1094
entry . prepare ( ) ;
1043
1095
@@ -1053,30 +1105,41 @@ export class DictionaryController {
1053
1105
* @param {string } dictionaryTitle
1054
1106
* @returns {boolean }
1055
1107
*/
1056
- isDictionaryInDeleteQueue ( dictionaryTitle ) {
1057
- return this . _dictionaryDeleteQueue . includes ( dictionaryTitle ) ;
1108
+ isDictionaryInTaskQueue ( dictionaryTitle ) {
1109
+ return this . _dictionaryTaskQueue . some ( ( task ) => task . dictionaryTitle === dictionaryTitle ) ;
1058
1110
}
1059
1111
1060
1112
/**
1061
- * @param {string } dictionaryTitle
1113
+ * @param {import('dictionary-controller.js').DictionaryTask } task
1062
1114
*/
1063
- async _enqueueDictionaryDelete ( dictionaryTitle ) {
1064
- if ( this . isDictionaryInDeleteQueue ( dictionaryTitle ) ) { return ; }
1065
- this . _dictionaryDeleteQueue . push ( dictionaryTitle ) ;
1066
- if ( this . _isDeleting ) { return ; }
1067
- while ( this . _dictionaryDeleteQueue . length > 0 ) {
1068
- const title = this . _dictionaryDeleteQueue [ 0 ] ;
1069
- if ( ! title ) { continue ; }
1070
- await this . _deleteDictionary ( title ) ;
1071
- void this . _dictionaryDeleteQueue . shift ( ) ;
1115
+ _enqueueTask ( task ) {
1116
+ if ( this . isDictionaryInTaskQueue ( task . dictionaryTitle ) ) { return ; }
1117
+ this . _dictionaryTaskQueue . push ( task ) ;
1118
+ void this . _runTaskQueue ( ) ;
1119
+ }
1120
+
1121
+
1122
+ /** */
1123
+ async _runTaskQueue ( ) {
1124
+ if ( this . _isTaskQueueRunning ) { return ; }
1125
+ this . _isTaskQueueRunning = true ;
1126
+ while ( this . _dictionaryTaskQueue . length > 0 ) {
1127
+ const task = this . _dictionaryTaskQueue [ 0 ] ;
1128
+ if ( task . type === 'delete' ) {
1129
+ await this . _deleteDictionary ( task . dictionaryTitle ) ;
1130
+ } else if ( task . type === 'update' ) {
1131
+ await this . _updateDictionary ( task . dictionaryTitle , task . downloadUrl ) ;
1132
+ }
1133
+ void this . _dictionaryTaskQueue . shift ( ) ;
1072
1134
}
1135
+ this . _isTaskQueueRunning = false ;
1073
1136
}
1074
1137
1075
1138
/**
1076
1139
* @param {string } dictionaryTitle
1077
1140
*/
1078
1141
async _deleteDictionary ( dictionaryTitle ) {
1079
- if ( this . _isDeleting || this . _checkingIntegrity ) { return ; }
1142
+ if ( this . _checkingIntegrity ) { return ; }
1080
1143
1081
1144
const index = this . _dictionaryEntries . findIndex ( ( entry ) => entry . dictionaryTitle === dictionaryTitle ) ;
1082
1145
if ( index < 0 ) { return ; }
@@ -1089,7 +1152,6 @@ export class DictionaryController {
1089
1152
const statusLabels = /** @type {NodeListOf<HTMLElement> } */ ( document . querySelectorAll ( `${ progressSelector } .progress-status` ) ) ;
1090
1153
const prevention = this . _settingsController . preventPageExit ( ) ;
1091
1154
try {
1092
- this . _isDeleting = true ;
1093
1155
this . _setButtonsEnabled ( false ) ;
1094
1156
1095
1157
/**
@@ -1115,14 +1177,14 @@ export class DictionaryController {
1115
1177
1116
1178
await this . _deleteDictionaryInternal ( dictionaryTitle , onProgress ) ;
1117
1179
await this . _deleteDictionarySettings ( dictionaryTitle ) ;
1180
+ this . _onDictionariesUpdate = null ;
1118
1181
} catch ( e ) {
1119
1182
log . error ( e ) ;
1120
1183
} finally {
1121
1184
prevention . end ( ) ;
1122
1185
for ( const progress of progressContainers ) { progress . hidden = true ; }
1123
1186
if ( statusFooter !== null ) { statusFooter . setTaskActive ( progressSelector , false ) ; }
1124
1187
this . _setButtonsEnabled ( true ) ;
1125
- this . _isDeleting = false ;
1126
1188
this . _triggerStorageChanged ( ) ;
1127
1189
}
1128
1190
}
@@ -1132,7 +1194,7 @@ export class DictionaryController {
1132
1194
* @param {string|undefined } downloadUrl
1133
1195
*/
1134
1196
async _updateDictionary ( dictionaryTitle , downloadUrl ) {
1135
- if ( this . _checkingIntegrity || this . _checkingUpdates || this . _isDeleting || this . _dictionaries === null ) { return ; }
1197
+ if ( this . _checkingIntegrity || this . _checkingUpdates || this . _dictionaries === null ) { return ; }
1136
1198
1137
1199
const dictionaryInfo = this . _dictionaries . find ( ( entry ) => entry . title === dictionaryTitle ) ;
1138
1200
if ( typeof dictionaryInfo === 'undefined' ) { throw new Error ( 'Dictionary not found' ) ; }
@@ -1156,7 +1218,10 @@ export class DictionaryController {
1156
1218
}
1157
1219
1158
1220
await this . _deleteDictionary ( dictionaryTitle ) ;
1159
- this . _settingsController . trigger ( 'importDictionaryFromUrl' , { url : downloadUrl , profilesDictionarySettings} ) ;
1221
+ /** @type {import('core').DeferredPromiseDetails<void> } */
1222
+ const { promise : importPromise , resolve} = deferPromise ( ) ;
1223
+ this . _settingsController . trigger ( 'importDictionaryFromUrl' , { url : downloadUrl , profilesDictionarySettings, onImportDone : resolve } ) ;
1224
+ await importPromise ;
1160
1225
}
1161
1226
1162
1227
/**
@@ -1175,7 +1240,12 @@ export class DictionaryController {
1175
1240
*/
1176
1241
async _deleteDictionaryInternal ( dictionaryTitle , onProgress ) {
1177
1242
await new DictionaryWorker ( ) . deleteDictionary ( dictionaryTitle , onProgress ) ;
1243
+ /** @type {import('core').DeferredPromiseDetails<void> } */
1244
+ const { promise : dictionariesUpdatePromise , resolve} = deferPromise ( ) ;
1245
+ this . _onDictionariesUpdate = resolve ;
1178
1246
void this . _settingsController . application . api . triggerDatabaseUpdated ( 'dictionary' , 'delete' ) ;
1247
+ await dictionariesUpdatePromise ;
1248
+ this . _onDictionariesUpdate = null ;
1179
1249
}
1180
1250
1181
1251
/**
0 commit comments