2019-11-02 18:06:16 +00:00
/ *
2020-04-10 18:06:55 +00:00
* Copyright ( C ) 2019 - 2020 Yomichan Authors
2019-11-02 18:06:16 +00:00
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
2020-01-01 17:00:31 +00:00
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
2019-11-02 18:06:16 +00:00
* /
2020-03-11 02:30:36 +00:00
/ * g l o b a l
* PageExitPrevention
2020-05-24 17:30:40 +00:00
* api
2020-03-11 02:30:36 +00:00
* utilBackgroundIsolate
* /
2019-11-02 18:06:16 +00:00
2020-05-30 13:33:13 +00:00
class SettingsDictionaryListUI extends EventDispatcher {
2019-11-13 01:13:25 +00:00
constructor ( container , template , extraContainer , extraTemplate ) {
2020-05-30 13:33:13 +00:00
super ( ) ;
2019-11-02 18:06:16 +00:00
this . container = container ;
this . template = template ;
this . extraContainer = extraContainer ;
this . extraTemplate = extraTemplate ;
2019-11-13 01:13:25 +00:00
this . optionsDictionaries = null ;
this . dictionaries = null ;
2019-11-02 18:06:16 +00:00
this . dictionaryEntries = [ ] ;
this . extra = null ;
2019-11-02 20:21:06 +00:00
2020-02-27 02:01:40 +00:00
document . querySelector ( '#dict-delete-confirm' ) . addEventListener ( 'click' , this . onDictionaryConfirmDelete . bind ( this ) , false ) ;
2019-11-02 18:06:16 +00:00
}
2019-11-13 01:13:25 +00:00
setOptionsDictionaries ( optionsDictionaries ) {
this . optionsDictionaries = optionsDictionaries ;
if ( this . dictionaries !== null ) {
this . setDictionaries ( this . dictionaries ) ;
}
}
2019-11-02 18:06:16 +00:00
setDictionaries ( dictionaries ) {
for ( const dictionaryEntry of this . dictionaryEntries ) {
dictionaryEntry . cleanup ( ) ;
}
this . dictionaryEntries = [ ] ;
2019-11-13 01:13:25 +00:00
this . dictionaries = toIterable ( dictionaries ) ;
if ( this . optionsDictionaries === null ) {
return ;
}
2019-11-02 18:06:16 +00:00
let changed = false ;
2019-11-13 01:13:25 +00:00
for ( const dictionaryInfo of this . dictionaries ) {
2019-11-02 18:06:16 +00:00
if ( this . createEntry ( dictionaryInfo ) ) {
changed = true ;
}
}
2019-11-03 14:56:49 +00:00
this . updateDictionaryOrder ( ) ;
2019-11-27 03:01:54 +00:00
const titles = this . dictionaryEntries . map ( ( e ) => e . dictionaryInfo . title ) ;
const removeKeys = Object . keys ( this . optionsDictionaries ) . filter ( ( key ) => titles . indexOf ( key ) < 0 ) ;
2019-11-13 01:13:25 +00:00
if ( removeKeys . length > 0 ) {
2019-11-10 01:49:44 +00:00
for ( const key of toIterable ( removeKeys ) ) {
2019-11-02 18:06:16 +00:00
delete this . optionsDictionaries [ key ] ;
}
changed = true ;
}
if ( changed ) {
this . save ( ) ;
}
}
createEntry ( dictionaryInfo ) {
const title = dictionaryInfo . title ;
let changed = false ;
let optionsDictionary ;
const optionsDictionaries = this . optionsDictionaries ;
2019-11-25 19:19:18 +00:00
if ( hasOwn ( optionsDictionaries , title ) ) {
2019-11-02 18:06:16 +00:00
optionsDictionary = optionsDictionaries [ title ] ;
} else {
optionsDictionary = SettingsDictionaryListUI . createDictionaryOptions ( ) ;
optionsDictionaries [ title ] = optionsDictionary ;
changed = true ;
}
const content = document . importNode ( this . template . content , true ) . firstChild ;
this . dictionaryEntries . push ( new SettingsDictionaryEntryUI ( this , dictionaryInfo , content , optionsDictionary ) ) ;
return changed ;
}
static createDictionaryOptions ( ) {
return utilBackgroundIsolate ( {
priority : 0 ,
enabled : false ,
allowSecondarySearches : false
} ) ;
}
createExtra ( totalCounts , remainders , totalRemainder ) {
const content = document . importNode ( this . extraTemplate . content , true ) . firstChild ;
this . extraContainer . appendChild ( content ) ;
return new SettingsDictionaryExtraUI ( this , totalCounts , remainders , totalRemainder , content ) ;
}
setCounts ( dictionaryCounts , totalCounts ) {
const remainders = Object . assign ( { } , totalCounts ) ;
const keys = Object . keys ( remainders ) ;
for ( let i = 0 , ii = Math . min ( this . dictionaryEntries . length , dictionaryCounts . length ) ; i < ii ; ++ i ) {
const counts = dictionaryCounts [ i ] ;
this . dictionaryEntries [ i ] . setCounts ( counts ) ;
for ( const key of keys ) {
remainders [ key ] -= counts [ key ] ;
}
}
let totalRemainder = 0 ;
for ( const key of keys ) {
totalRemainder += remainders [ key ] ;
}
if ( this . extra !== null ) {
this . extra . cleanup ( ) ;
this . extra = null ;
}
if ( totalRemainder > 0 ) {
this . extra = this . createExtra ( totalCounts , remainders , totalRemainder ) ;
}
}
2019-11-03 14:56:49 +00:00
updateDictionaryOrder ( ) {
const sortInfo = this . dictionaryEntries . map ( ( e , i ) => [ e , i ] ) ;
sortInfo . sort ( ( a , b ) => {
const i = b [ 0 ] . optionsDictionary . priority - a [ 0 ] . optionsDictionary . priority ;
return ( i !== 0 ? i : a [ 1 ] - b [ 1 ] ) ;
} ) ;
for ( const [ e ] of sortInfo ) {
this . container . appendChild ( e . content ) ;
}
}
2019-11-02 18:06:16 +00:00
save ( ) {
// Overwrite
}
2019-11-02 20:21:06 +00:00
onDictionaryConfirmDelete ( e ) {
e . preventDefault ( ) ;
const n = document . querySelector ( '#dict-delete-modal' ) ;
const title = n . dataset . dict ;
delete n . dataset . dict ;
$ ( n ) . modal ( 'hide' ) ;
2020-02-17 20:21:30 +00:00
const index = this . dictionaryEntries . findIndex ( ( entry ) => entry . dictionaryInfo . title === title ) ;
2019-11-02 20:21:06 +00:00
if ( index >= 0 ) {
this . dictionaryEntries [ index ] . deleteDictionary ( ) ;
}
}
2019-11-02 18:06:16 +00:00
}
class SettingsDictionaryEntryUI {
constructor ( parent , dictionaryInfo , content , optionsDictionary ) {
this . parent = parent ;
this . dictionaryInfo = dictionaryInfo ;
this . optionsDictionary = optionsDictionary ;
this . counts = null ;
2020-02-16 21:33:48 +00:00
this . eventListeners = new EventListenerCollection ( ) ;
2019-11-02 20:21:06 +00:00
this . isDeleting = false ;
2019-11-02 18:06:16 +00:00
this . content = content ;
this . enabledCheckbox = this . content . querySelector ( '.dict-enabled' ) ;
this . allowSecondarySearchesCheckbox = this . content . querySelector ( '.dict-allow-secondary-searches' ) ;
this . priorityInput = this . content . querySelector ( '.dict-priority' ) ;
2019-11-02 20:21:06 +00:00
this . deleteButton = this . content . querySelector ( '.dict-delete-button' ) ;
2020-04-05 18:46:21 +00:00
this . detailsToggleLink = this . content . querySelector ( '.dict-details-toggle-link' ) ;
this . detailsContainer = this . content . querySelector ( '.dict-details' ) ;
this . detailsTable = this . content . querySelector ( '.dict-details-table' ) ;
2019-11-02 18:06:16 +00:00
2019-11-03 17:54:18 +00:00
if ( this . dictionaryInfo . version < 3 ) {
this . content . querySelector ( '.dict-outdated' ) . hidden = false ;
}
2020-04-05 18:46:21 +00:00
this . setupDetails ( dictionaryInfo ) ;
2019-11-02 18:06:16 +00:00
this . content . querySelector ( '.dict-title' ) . textContent = this . dictionaryInfo . title ;
this . content . querySelector ( '.dict-revision' ) . textContent = ` rev. ${ this . dictionaryInfo . revision } ` ;
2019-11-24 04:23:08 +00:00
this . content . querySelector ( '.dict-prefix-wildcard-searches-supported' ) . checked = ! ! this . dictionaryInfo . prefixWildcardsSupported ;
2019-11-02 18:06:16 +00:00
this . applyValues ( ) ;
2020-02-27 02:01:40 +00:00
this . eventListeners . addEventListener ( this . enabledCheckbox , 'change' , this . onEnabledChanged . bind ( this ) , false ) ;
this . eventListeners . addEventListener ( this . allowSecondarySearchesCheckbox , 'change' , this . onAllowSecondarySearchesChanged . bind ( this ) , false ) ;
this . eventListeners . addEventListener ( this . priorityInput , 'change' , this . onPriorityChanged . bind ( this ) , false ) ;
this . eventListeners . addEventListener ( this . deleteButton , 'click' , this . onDeleteButtonClicked . bind ( this ) , false ) ;
2020-04-05 18:46:21 +00:00
this . eventListeners . addEventListener ( this . detailsToggleLink , 'click' , this . onDetailsToggleLinkClicked . bind ( this ) , false ) ;
}
setupDetails ( dictionaryInfo ) {
const targets = [
[ 'Author' , 'author' ] ,
[ 'URL' , 'url' ] ,
[ 'Description' , 'description' ] ,
[ 'Attribution' , 'attribution' ]
] ;
let count = 0 ;
for ( const [ label , key ] of targets ) {
const info = dictionaryInfo [ key ] ;
if ( typeof info !== 'string' ) { continue ; }
const n1 = document . createElement ( 'div' ) ;
n1 . className = 'dict-details-entry' ;
n1 . dataset . type = key ;
const n2 = document . createElement ( 'span' ) ;
n2 . className = 'dict-details-entry-label' ;
n2 . textContent = ` ${ label } : ` ;
n1 . appendChild ( n2 ) ;
const n3 = document . createElement ( 'span' ) ;
n3 . className = 'dict-details-entry-info' ;
n3 . textContent = info ;
n1 . appendChild ( n3 ) ;
this . detailsTable . appendChild ( n1 ) ;
++ count ;
}
if ( count === 0 ) {
this . detailsContainer . hidden = true ;
this . detailsToggleLink . hidden = true ;
}
2019-11-02 18:06:16 +00:00
}
cleanup ( ) {
if ( this . content !== null ) {
if ( this . content . parentNode !== null ) {
this . content . parentNode . removeChild ( this . content ) ;
}
this . content = null ;
}
this . dictionaryInfo = null ;
2020-02-16 21:33:48 +00:00
this . eventListeners . removeAllEventListeners ( ) ;
2019-11-02 18:06:16 +00:00
}
setCounts ( counts ) {
this . counts = counts ;
const node = this . content . querySelector ( '.dict-counts' ) ;
node . textContent = JSON . stringify ( {
info : this . dictionaryInfo ,
counts
} , null , 4 ) ;
node . removeAttribute ( 'hidden' ) ;
}
save ( ) {
this . parent . save ( ) ;
}
applyValues ( ) {
this . enabledCheckbox . checked = this . optionsDictionary . enabled ;
this . allowSecondarySearchesCheckbox . checked = this . optionsDictionary . allowSecondarySearches ;
this . priorityInput . value = ` ${ this . optionsDictionary . priority } ` ;
}
2019-11-02 20:21:06 +00:00
async deleteDictionary ( ) {
if ( this . isDeleting ) {
return ;
}
const progress = this . content . querySelector ( '.progress' ) ;
progress . hidden = false ;
const progressBar = this . content . querySelector ( '.progress-bar' ) ;
this . isDeleting = true ;
2019-11-02 21:37:53 +00:00
const prevention = new PageExitPrevention ( ) ;
2019-11-02 20:21:06 +00:00
try {
2019-11-02 21:37:53 +00:00
prevention . start ( ) ;
2019-11-02 20:21:06 +00:00
const onProgress = ( { processed , count , storeCount , storesProcesed } ) => {
let percent = 0.0 ;
if ( count > 0 && storesProcesed > 0 ) {
percent = ( processed / count ) * ( storesProcesed / storeCount ) * 100.0 ;
}
progressBar . style . width = ` ${ percent } % ` ;
} ;
2020-05-24 17:30:40 +00:00
await api . deleteDictionary ( this . dictionaryInfo . title , onProgress ) ;
2019-11-02 20:21:06 +00:00
} catch ( e ) {
2020-05-30 00:25:22 +00:00
this . dictionaryErrorsShow ( [ e ] ) ;
2019-11-02 20:21:06 +00:00
} finally {
2019-11-02 21:37:53 +00:00
prevention . end ( ) ;
2019-11-02 20:21:06 +00:00
this . isDeleting = false ;
progress . hidden = true ;
2020-05-30 13:33:13 +00:00
this . parent . trigger ( 'databaseUpdated' ) ;
2019-11-02 20:21:06 +00:00
}
}
2019-11-02 18:06:16 +00:00
onEnabledChanged ( e ) {
this . optionsDictionary . enabled = ! ! e . target . checked ;
this . save ( ) ;
}
onAllowSecondarySearchesChanged ( e ) {
this . optionsDictionary . allowSecondarySearches = ! ! e . target . checked ;
this . save ( ) ;
}
onPriorityChanged ( e ) {
let value = Number . parseFloat ( e . target . value ) ;
if ( Number . isNaN ( value ) ) {
value = this . optionsDictionary . priority ;
} else {
this . optionsDictionary . priority = value ;
this . save ( ) ;
}
e . target . value = ` ${ value } ` ;
2019-11-03 14:56:49 +00:00
this . parent . updateDictionaryOrder ( ) ;
2019-11-02 18:06:16 +00:00
}
2019-11-02 20:21:06 +00:00
onDeleteButtonClicked ( e ) {
e . preventDefault ( ) ;
if ( this . isDeleting ) {
return ;
}
const title = this . dictionaryInfo . title ;
const n = document . querySelector ( '#dict-delete-modal' ) ;
n . dataset . dict = title ;
document . querySelector ( '#dict-remove-modal-dict-name' ) . textContent = title ;
$ ( n ) . modal ( 'show' ) ;
}
2020-04-05 18:46:21 +00:00
onDetailsToggleLinkClicked ( e ) {
e . preventDefault ( ) ;
this . detailsContainer . hidden = ! this . detailsContainer . hidden ;
}
2019-11-02 18:06:16 +00:00
}
class SettingsDictionaryExtraUI {
constructor ( parent , totalCounts , remainders , totalRemainder , content ) {
this . parent = parent ;
this . content = content ;
this . content . querySelector ( '.dict-total-count' ) . textContent = ` ${ totalRemainder } item ${ totalRemainder !== 1 ? 's' : '' } ` ;
const node = this . content . querySelector ( '.dict-counts' ) ;
node . textContent = JSON . stringify ( {
counts : totalCounts ,
remainders : remainders
} , null , 4 ) ;
node . removeAttribute ( 'hidden' ) ;
}
cleanup ( ) {
if ( this . content !== null ) {
if ( this . content . parentNode !== null ) {
this . content . parentNode . removeChild ( this . content ) ;
}
this . content = null ;
}
}
}
2020-05-30 00:25:22 +00:00
class DictionaryController {
2020-05-30 13:33:13 +00:00
constructor ( settingsController , storageController ) {
this . _settingsController = settingsController ;
2020-05-30 00:25:22 +00:00
this . _storageController = storageController ;
this . _dictionaryUI = null ;
this . _dictionaryErrorToStringOverrides = [
[
'A mutation operation was attempted on a database that did not allow mutations.' ,
'Access to IndexedDB appears to be restricted. Firefox seems to require that the history preference is set to "Remember history" before IndexedDB use of any kind is allowed.'
] ,
[
'The operation failed for reasons unrelated to the database itself and not covered by any other error code.' ,
'Unable to access IndexedDB due to a possibly corrupt user profile. Try using the "Refresh Firefox" feature to reset your user profile.'
] ,
[
'BulkError' ,
'Unable to finish importing dictionary data into IndexedDB. This may indicate that you do not have sufficient disk space available to complete this operation.'
]
] ;
}
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
async prepare ( ) {
this . _dictionaryUI = new SettingsDictionaryListUI (
document . querySelector ( '#dict-groups' ) ,
document . querySelector ( '#dict-template' ) ,
document . querySelector ( '#dict-groups-extra' ) ,
document . querySelector ( '#dict-extra-template' )
) ;
2020-05-30 13:33:13 +00:00
this . _dictionaryUI . save = ( ) => this . _settingsController . save ( ) ;
this . _dictionaryUI . on ( 'databaseUpdated' , this . _onDatabaseUpdated . bind ( this ) ) ;
2020-02-01 16:20:50 +00:00
2020-05-30 00:25:22 +00:00
document . querySelector ( '#dict-purge-button' ) . addEventListener ( 'click' , this . _onPurgeButtonClick . bind ( this ) , false ) ;
document . querySelector ( '#dict-purge-confirm' ) . addEventListener ( 'click' , this . _onPurgeConfirmButtonClick . bind ( this ) , false ) ;
document . querySelector ( '#dict-file-button' ) . addEventListener ( 'click' , this . _onImportButtonClick . bind ( this ) , false ) ;
document . querySelector ( '#dict-file' ) . addEventListener ( 'change' , this . _onImportFileChange . bind ( this ) , false ) ;
document . querySelector ( '#dict-main' ) . addEventListener ( 'change' , this . _onDictionaryMainChanged . bind ( this ) , false ) ;
document . querySelector ( '#database-enable-prefix-wildcard-searches' ) . addEventListener ( 'change' , this . _onDatabaseEnablePrefixWildcardSearchesChanged . bind ( this ) , false ) ;
2019-11-24 03:54:06 +00:00
2020-05-30 13:33:13 +00:00
this . _settingsController . on ( 'optionsChanged' , this . _onOptionsChanged . bind ( this ) ) ;
await this . _onOptionsChanged ( ) ;
2020-05-30 00:25:22 +00:00
await this . _onDatabaseUpdated ( ) ;
}
2020-02-01 16:48:24 +00:00
2020-05-30 13:33:13 +00:00
// Private
2019-11-13 01:13:25 +00:00
2020-05-30 13:33:13 +00:00
async _onOptionsChanged ( ) {
const options = await this . _settingsController . getOptionsMutable ( ) ;
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
this . _dictionaryUI . setOptionsDictionaries ( options . dictionaries ) ;
2019-11-02 18:39:37 +00:00
2020-05-30 13:33:13 +00:00
const optionsFull = await this . _settingsController . getOptionsFull ( ) ;
2020-05-30 00:25:22 +00:00
document . querySelector ( '#database-enable-prefix-wildcard-searches' ) . checked = optionsFull . global . database . prefixWildcardsSupported ;
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
await this . _updateMainDictionarySelectValue ( ) ;
2019-11-02 18:06:16 +00:00
}
2020-05-30 00:25:22 +00:00
_updateMainDictionarySelectOptions ( dictionaries ) {
const select = document . querySelector ( '#dict-main' ) ;
select . textContent = '' ; // Empty
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
let option = document . createElement ( 'option' ) ;
option . className = 'text-muted' ;
option . value = '' ;
option . textContent = 'Not selected' ;
2019-11-02 18:06:16 +00:00
select . appendChild ( option ) ;
2020-02-01 16:40:09 +00:00
2020-05-30 00:25:22 +00:00
for ( const { title , sequenced } of toIterable ( dictionaries ) ) {
if ( ! sequenced ) { continue ; }
2020-02-01 16:40:09 +00:00
2020-05-30 00:25:22 +00:00
option = document . createElement ( 'option' ) ;
option . value = title ;
option . textContent = title ;
select . appendChild ( option ) ;
2020-02-01 16:40:09 +00:00
}
}
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
async _updateMainDictionarySelectValue ( ) {
2020-05-30 13:33:13 +00:00
const options = await this . _settingsController . getOptions ( ) ;
2020-05-30 00:25:22 +00:00
const value = options . general . mainDictionary ;
const select = document . querySelector ( '#dict-main' ) ;
let selectValue = null ;
for ( const child of select . children ) {
if ( child . nodeName . toUpperCase ( ) === 'OPTION' && child . value === value ) {
selectValue = value ;
break ;
}
2020-02-01 16:40:09 +00:00
}
2020-05-30 00:25:22 +00:00
let missingNodeOption = select . querySelector ( 'option[data-not-installed=true]' ) ;
if ( selectValue === null ) {
if ( missingNodeOption === null ) {
missingNodeOption = document . createElement ( 'option' ) ;
missingNodeOption . className = 'text-muted' ;
missingNodeOption . value = value ;
missingNodeOption . textContent = ` ${ value } (Not installed) ` ;
missingNodeOption . dataset . notInstalled = 'true' ;
select . appendChild ( missingNodeOption ) ;
}
} else {
if ( missingNodeOption !== null ) {
missingNodeOption . parentNode . removeChild ( missingNodeOption ) ;
}
2019-11-02 18:06:16 +00:00
}
2020-05-30 00:25:22 +00:00
select . value = value ;
2019-11-02 18:06:16 +00:00
}
2020-05-30 00:25:22 +00:00
_dictionaryErrorToString ( error ) {
if ( error . toString ) {
error = error . toString ( ) ;
} else {
error = ` ${ error } ` ;
}
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
for ( const [ match , subst ] of this . _dictionaryErrorToStringOverrides ) {
if ( error . includes ( match ) ) {
error = subst ;
break ;
}
}
2020-02-01 16:42:27 +00:00
2020-05-30 00:25:22 +00:00
return error ;
2020-02-01 16:42:27 +00:00
}
2020-05-30 00:25:22 +00:00
_dictionaryErrorsShow ( errors ) {
const dialog = document . querySelector ( '#dict-error' ) ;
dialog . textContent = '' ;
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
if ( errors !== null && errors . length > 0 ) {
const uniqueErrors = new Map ( ) ;
for ( let e of errors ) {
yomichan . logError ( e ) ;
e = this . _dictionaryErrorToString ( e ) ;
let count = uniqueErrors . get ( e ) ;
if ( typeof count === 'undefined' ) {
count = 0 ;
}
uniqueErrors . set ( e , count + 1 ) ;
}
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
for ( const [ e , count ] of uniqueErrors . entries ( ) ) {
const div = document . createElement ( 'p' ) ;
if ( count > 1 ) {
div . textContent = ` ${ e } ` ;
const em = document . createElement ( 'em' ) ;
em . textContent = ` ( ${ count } ) ` ;
div . appendChild ( em ) ;
} else {
div . textContent = ` ${ e } ` ;
}
dialog . appendChild ( div ) ;
}
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
dialog . hidden = false ;
} else {
dialog . hidden = true ;
2019-11-02 18:06:16 +00:00
}
}
2020-05-30 00:25:22 +00:00
_dictionarySpinnerShow ( show ) {
const spinner = $ ( '#dict-spinner' ) ;
if ( show ) {
spinner . show ( ) ;
} else {
spinner . hide ( ) ;
2019-11-02 18:06:16 +00:00
}
2020-05-30 00:25:22 +00:00
}
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
_dictReadFile ( file ) {
return new Promise ( ( resolve , reject ) => {
const reader = new FileReader ( ) ;
reader . onload = ( ) => resolve ( reader . result ) ;
reader . onerror = ( ) => reject ( reader . error ) ;
reader . readAsBinaryString ( file ) ;
} ) ;
}
async _onDatabaseUpdated ( ) {
try {
const dictionaries = await api . getDictionaryInfo ( ) ;
this . _dictionaryUI . setDictionaries ( dictionaries ) ;
document . querySelector ( '#dict-warning' ) . hidden = ( dictionaries . length > 0 ) ;
this . _updateMainDictionarySelectOptions ( dictionaries ) ;
await this . _updateMainDictionarySelectValue ( ) ;
const { counts , total } = await api . getDictionaryCounts ( dictionaries . map ( ( v ) => v . title ) , true ) ;
this . _dictionaryUI . setCounts ( counts , total ) ;
} catch ( e ) {
this . _dictionaryErrorsShow ( [ e ] ) ;
}
2019-11-02 18:06:16 +00:00
}
2020-05-30 00:25:22 +00:00
async _onDictionaryMainChanged ( e ) {
const select = e . target ;
const value = select . value ;
const missingNodeOption = select . querySelector ( 'option[data-not-installed=true]' ) ;
if ( missingNodeOption !== null && missingNodeOption . value !== value ) {
missingNodeOption . parentNode . removeChild ( missingNodeOption ) ;
}
2019-11-02 18:06:16 +00:00
2020-05-30 13:33:13 +00:00
const options = await this . _settingsController . getOptionsMutable ( ) ;
2020-05-30 00:25:22 +00:00
options . general . mainDictionary = value ;
2020-05-30 13:33:13 +00:00
await this . _settingsController . save ( ) ;
2019-11-02 18:06:16 +00:00
}
2020-05-30 00:25:22 +00:00
_onImportButtonClick ( ) {
const dictFile = document . querySelector ( '#dict-file' ) ;
dictFile . click ( ) ;
}
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
_onPurgeButtonClick ( e ) {
e . preventDefault ( ) ;
$ ( '#dict-purge-modal' ) . modal ( 'show' ) ;
}
2019-11-02 18:30:17 +00:00
2020-05-30 00:25:22 +00:00
async _onPurgeConfirmButtonClick ( e ) {
e . preventDefault ( ) ;
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
$ ( '#dict-purge-modal' ) . modal ( 'hide' ) ;
2019-11-02 18:30:17 +00:00
2020-05-30 00:25:22 +00:00
const dictControls = $ ( '#dict-importer, #dict-groups, #dict-groups-extra, #dict-main-group' ) . hide ( ) ;
const dictProgress = document . querySelector ( '#dict-purge' ) ;
dictProgress . hidden = false ;
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
const prevention = new PageExitPrevention ( ) ;
2019-11-02 21:37:53 +00:00
2020-05-30 00:25:22 +00:00
try {
prevention . start ( ) ;
this . _dictionaryErrorsShow ( null ) ;
this . _dictionarySpinnerShow ( true ) ;
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
await api . purgeDatabase ( ) ;
2020-05-30 13:33:13 +00:00
const optionsFull = await this . _settingsController . getOptionsFullMutable ( ) ;
for ( const { options } of toIterable ( optionsFull . profiles ) ) {
2020-05-30 00:25:22 +00:00
options . dictionaries = utilBackgroundIsolate ( { } ) ;
options . general . mainDictionary = '' ;
}
2020-05-30 13:33:13 +00:00
await this . _settingsController . save ( ) ;
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
this . _onDatabaseUpdated ( ) ;
} catch ( err ) {
this . _dictionaryErrorsShow ( [ err ] ) ;
} finally {
prevention . end ( ) ;
2019-11-02 21:37:53 +00:00
2020-05-30 00:25:22 +00:00
this . _dictionarySpinnerShow ( false ) ;
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
dictControls . show ( ) ;
dictProgress . hidden = true ;
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
this . _storageController . updateStats ( ) ;
2019-11-02 18:06:16 +00:00
}
}
2020-05-30 00:25:22 +00:00
async _onImportFileChange ( e ) {
const files = [ ... e . target . files ] ;
e . target . value = null ;
2019-12-15 20:51:30 +00:00
2020-05-30 00:25:22 +00:00
const dictFile = $ ( '#dict-file' ) ;
const dictControls = $ ( '#dict-importer' ) . hide ( ) ;
const dictProgress = $ ( '#dict-import-progress' ) . show ( ) ;
const dictImportInfo = document . querySelector ( '#dict-import-info' ) ;
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
const prevention = new PageExitPrevention ( ) ;
2019-11-02 21:37:53 +00:00
2020-05-30 00:25:22 +00:00
try {
prevention . start ( ) ;
this . _dictionaryErrorsShow ( null ) ;
this . _dictionarySpinnerShow ( true ) ;
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
const setProgress = ( percent ) => dictProgress . find ( '.progress-bar' ) . css ( 'width' , ` ${ percent } % ` ) ;
const updateProgress = ( total , current ) => {
setProgress ( current / total * 100.0 ) ;
this . _storageController . updateStats ( ) ;
} ;
2019-11-02 18:06:16 +00:00
2020-05-30 13:33:13 +00:00
const optionsFull = await this . _settingsController . getOptionsFull ( ) ;
2019-11-24 03:54:06 +00:00
2020-05-30 00:25:22 +00:00
const importDetails = {
prefixWildcardsSupported : optionsFull . global . database . prefixWildcardsSupported
} ;
2019-11-24 03:54:06 +00:00
2020-05-30 00:25:22 +00:00
for ( let i = 0 , ii = files . length ; i < ii ; ++ i ) {
setProgress ( 0.0 ) ;
if ( ii > 1 ) {
dictImportInfo . hidden = false ;
dictImportInfo . textContent = ` ( ${ i + 1 } of ${ ii } ) ` ;
}
2019-11-02 18:06:16 +00:00
2020-05-30 00:25:22 +00:00
const archiveContent = await this . _dictReadFile ( files [ i ] ) ;
const { result , errors } = await api . importDictionaryArchive ( archiveContent , importDetails , updateProgress ) ;
2020-05-30 13:33:13 +00:00
const optionsFull2 = await this . _settingsController . getOptionsFullMutable ( ) ;
for ( const { options } of toIterable ( optionsFull2 . profiles ) ) {
2020-05-30 00:25:22 +00:00
const dictionaryOptions = SettingsDictionaryListUI . createDictionaryOptions ( ) ;
dictionaryOptions . enabled = true ;
options . dictionaries [ result . title ] = dictionaryOptions ;
if ( result . sequenced && options . general . mainDictionary === '' ) {
options . general . mainDictionary = result . title ;
}
2019-11-02 20:58:21 +00:00
}
2019-11-02 18:06:16 +00:00
2020-05-30 13:33:13 +00:00
await this . _settingsController . save ( ) ;
2020-05-30 00:25:22 +00:00
if ( errors . length > 0 ) {
const errors2 = errors . map ( ( error ) => jsonToError ( error ) ) ;
errors2 . push ( ` Dictionary may not have been imported properly: ${ errors2 . length } error ${ errors2 . length === 1 ? '' : 's' } reported. ` ) ;
this . _dictionaryErrorsShow ( errors2 ) ;
}
2019-11-02 20:58:21 +00:00
2020-05-30 00:25:22 +00:00
this . _onDatabaseUpdated ( ) ;
2019-11-02 20:58:21 +00:00
}
2020-05-30 00:25:22 +00:00
} catch ( err ) {
this . _dictionaryErrorsShow ( [ err ] ) ;
} finally {
prevention . end ( ) ;
this . _dictionarySpinnerShow ( false ) ;
2019-11-02 20:58:21 +00:00
2020-05-30 00:25:22 +00:00
dictImportInfo . hidden = false ;
dictImportInfo . textContent = '' ;
dictFile . val ( '' ) ;
dictControls . show ( ) ;
dictProgress . hide ( ) ;
2019-11-02 20:58:21 +00:00
}
2019-11-02 18:06:16 +00:00
}
2020-05-06 23:28:26 +00:00
2020-05-30 00:25:22 +00:00
async _onDatabaseEnablePrefixWildcardSearchesChanged ( e ) {
2020-05-30 13:33:13 +00:00
const optionsFull = await this . _settingsController . getOptionsFullMutable ( ) ;
2020-05-30 00:25:22 +00:00
const v = ! ! e . target . checked ;
if ( optionsFull . global . database . prefixWildcardsSupported === v ) { return ; }
optionsFull . global . database . prefixWildcardsSupported = ! ! e . target . checked ;
2020-05-30 13:33:13 +00:00
await this . _settingsController . save ( ) ;
2020-05-30 00:25:22 +00:00
}
2019-11-24 03:54:06 +00:00
}