2023-11-13 11:59:36 +00:00
nopaque . corpus _analysis . ReaderExtension = class ReaderExtension {
2021-11-16 14:23:57 +00:00
name = 'Reader' ;
2023-08-08 14:00:05 +00:00
constructor ( app ) {
2021-11-16 14:23:57 +00:00
this . app = app ;
this . data = { } ;
this . elements = {
2023-08-08 14:00:05 +00:00
container : document . querySelector ( ` #corpus-analysis-reader-container ` ) ,
corpus : document . querySelector ( ` #corpus-analysis-reader-corpus ` ) ,
corpusPagination : document . querySelector ( ` #corpus-analysis-reader-corpus-pagination ` ) ,
error : document . querySelector ( ` #corpus-analysis-reader-error ` ) ,
progress : document . querySelector ( ` #corpus-analysis-reader-progress ` ) ,
userInterfaceForm : document . querySelector ( ` #corpus-analysis-reader-user-interface-form ` )
2021-11-16 14:23:57 +00:00
} ;
this . settings = {
2023-08-08 12:07:07 +00:00
perPage : parseInt ( this . elements . userInterfaceForm [ 'per-page' ] . value ) ,
textStyle : parseInt ( this . elements . userInterfaceForm [ 'text-style' ] . value ) ,
tokenRepresentation : this . elements . userInterfaceForm [ 'token-representation' ] . value ,
2023-07-13 13:27:49 +00:00
pagination : {
innerWindow : 5 ,
outerWindow : 1
}
2021-11-16 14:23:57 +00:00
}
this . app . registerExtension ( this ) ;
}
2023-07-17 08:40:34 +00:00
async submitForm ( ) {
this . app . disableActionElements ( ) ;
this . elements . error . innerText = '' ;
this . elements . error . classList . add ( 'hide' ) ;
this . elements . progress . classList . remove ( 'hide' ) ;
try {
const paginatedCorpus = await this . data . corpus . o . paginate ( 1 , this . settings . perPage ) ;
this . data . corpus . p = paginatedCorpus ;
this . renderCorpus ( ) ;
this . renderCorpusPagination ( ) ;
this . elements . progress . classList . add ( 'hide' ) ;
} catch ( error ) {
let errorString = '' ;
if ( 'code' in error ) { errorString += ` [ ${ error . code } ] ` ; }
errorString += ` ${ error . constructor . name } ` ;
if ( 'description' in error ) { errorString += ` : ${ error . description } ` ; }
this . elements . error . innerText = errorString ;
this . elements . error . classList . remove ( 'hide' ) ;
app . flash ( errorString , 'error' ) ;
this . elements . progress . classList . add ( 'hide' ) ;
}
this . app . enableActionElements ( ) ;
}
async init ( ) {
2021-11-16 14:23:57 +00:00
// Init data
this . data . corpus = this . app . data . corpus ;
// Add event listeners
2023-08-08 12:07:07 +00:00
this . elements . userInterfaceForm . addEventListener ( 'submit' , ( event ) => {
2021-11-16 14:23:57 +00:00
event . preventDefault ( ) ;
2023-07-17 08:40:34 +00:00
this . submitForm ( ) ;
2021-11-16 14:23:57 +00:00
} ) ;
2023-08-08 12:07:07 +00:00
this . elements . userInterfaceForm . addEventListener ( 'change' , ( event ) => {
if ( event . target === this . elements . userInterfaceForm [ 'per-page' ] ) {
this . settings . perPage = parseInt ( this . elements . userInterfaceForm [ 'per-page' ] . value ) ;
2023-07-17 08:40:34 +00:00
this . submitForm ( ) ;
2021-11-16 14:23:57 +00:00
}
2023-08-08 12:07:07 +00:00
if ( event . target === this . elements . userInterfaceForm [ 'text-style' ] ) {
this . settings . textStyle = parseInt ( this . elements . userInterfaceForm [ 'text-style' ] . value ) ;
2021-11-16 14:23:57 +00:00
this . setTextStyle ( ) ;
}
2023-08-08 12:07:07 +00:00
if ( event . target === this . elements . userInterfaceForm [ 'token-representation' ] ) {
this . settings . tokenRepresentation = this . elements . userInterfaceForm [ 'token-representation' ] . value ;
2021-11-16 14:23:57 +00:00
this . setTokenRepresentation ( ) ;
}
} ) ;
// Load initial data
2023-07-17 08:40:34 +00:00
await this . submitForm ( ) ;
2021-11-16 14:23:57 +00:00
}
clearCorpus ( ) {
// Destroy with .p-attr elements associated Materialize tooltips
2022-10-07 07:43:11 +00:00
let pAttrElements = this . elements . corpus . querySelectorAll ( '.p-attr.tooltipped' ) ;
for ( let pAttrElement of pAttrElements ) {
2021-11-16 14:23:57 +00:00
M . Tooltip . getInstance ( pAttrElement ) ? . destroy ( ) ;
}
this . elements . corpus . innerHTML = `
< p class = "show-if-only-child" >
< span class = "card-title" > < i class = "left material-icons" style = "font-size: inherit;" > search < / i > N o t h i n g h e r e . . . < / s p a n > < b r >
No text available .
< / p >
` .trim();
}
renderCorpus ( ) {
this . clearCorpus ( ) ;
2022-10-07 07:43:11 +00:00
let item = this . data . corpus . p . items [ 0 ] ;
2021-11-16 14:23:57 +00:00
this . elements . corpus . innerHTML += `
< p > $ { this . cposRange2HTML ( item [ 0 ] , item [ item . length - 1 ] ) } < / p >
` .trim();
this . setTextStyle ( ) ;
this . setTokenRepresentation ( ) ;
}
clearCorpusPagination ( ) {
this . elements . corpusPagination . innerHTML = '' ;
this . elements . corpusPagination . classList . add ( 'hide' ) ;
}
renderCorpusPagination ( ) {
this . clearCorpusPagination ( ) ;
if ( this . data . corpus . p . pages === 0 ) { return ; }
2023-03-09 12:18:39 +00:00
let pageElement ;
// First page button. Disables first page button if on first page
2023-11-09 13:29:01 +00:00
pageElement = nopaque . Utils . HTMLToElement (
2023-03-09 12:18:39 +00:00
`
< li class = "${this.data.corpus.p.page === 1 ? 'disabled' : 'waves-effect'}" >
< a class = "corpus-analysis-action pagination-trigger" $ { this . data . corpus . p . page === 1 ? '' : 'data-target="1"' } >
< i class = "material-icons" > first _page < / i >
< / a >
< / l i >
`
) ;
this . elements . corpusPagination . appendChild ( pageElement ) ;
// Previous page button. Disables previous page button if on first page
2023-11-09 13:29:01 +00:00
pageElement = nopaque . Utils . HTMLToElement (
2023-03-09 12:18:39 +00:00
`
< li class = "${this.data.corpus.p.has_prev ? 'waves-effect' : 'disabled'}" >
< a class = "corpus-analysis-action pagination-trigger" $ { this . data . corpus . p . has _prev ? 'data-target="' + this . data . corpus . p . prev _num + '"' : '' } >
< i class = "material-icons" > chevron _left < / i >
< / a >
2021-11-16 14:23:57 +00:00
< / l i >
2023-03-09 12:18:39 +00:00
`
) ;
this . elements . corpusPagination . appendChild ( pageElement ) ;
// First page as number. Hides first page button if on first page
if ( this . data . corpus . p . page > 6 ) {
2023-11-09 13:29:01 +00:00
pageElement = nopaque . Utils . HTMLToElement (
2023-03-09 12:18:39 +00:00
`
< li class = "waves-effect" >
< a class = "corpus-analysis-action pagination-trigger" data - target = "1" > 1 < / a >
< / l i >
`
) ;
this . elements . corpusPagination . appendChild ( pageElement ) ;
2023-11-09 13:29:01 +00:00
pageElement = nopaque . Utils . HTMLToElement ( "<li style='margin-top: 5px;'>…</li>" ) ;
2023-03-09 12:18:39 +00:00
this . elements . corpusPagination . appendChild ( pageElement ) ;
2021-11-16 14:23:57 +00:00
}
2023-03-09 12:18:39 +00:00
// render page buttons (5 before and 5 after current page)
2023-07-13 13:27:49 +00:00
for ( let i = this . data . corpus . p . page - this . settings . pagination . innerWindow ; i <= this . data . corpus . p . page ; i ++ ) {
2023-03-09 12:18:39 +00:00
if ( i <= 0 ) { continue ; }
2023-11-09 13:29:01 +00:00
pageElement = nopaque . Utils . HTMLToElement (
2023-03-09 12:18:39 +00:00
`
< li class = "${i === this.data.corpus.p.page ? 'active' : 'waves-effect'}" >
< a class = "corpus-analysis-action pagination-trigger" $ { i === this . data . corpus . p . page ? '' : 'data-target="' + i + '"' } > $ { i } < / a >
< / l i >
`
) ;
this . elements . corpusPagination . appendChild ( pageElement ) ;
} ;
2023-07-13 13:27:49 +00:00
for ( let i = this . data . corpus . p . page + 1 ; i <= this . data . corpus . p . page + this . settings . pagination . innerWindow ; i ++ ) {
2023-03-09 12:18:39 +00:00
if ( i > this . data . corpus . p . pages ) { break ; }
2023-11-09 13:29:01 +00:00
pageElement = nopaque . Utils . HTMLToElement (
2023-03-09 12:18:39 +00:00
`
< li class = "${i === this.data.corpus.p.page ? 'active' : 'waves-effect'}" >
< a class = "corpus-analysis-action pagination-trigger" $ { i === this . data . corpus . p . page ? '' : 'data-target="' + i + '"' } > $ { i } < / a >
< / l i >
`
) ;
this . elements . corpusPagination . appendChild ( pageElement ) ;
} ;
// Last page as number. Hides last page button if on last page
if ( this . data . corpus . p . page < this . data . corpus . p . pages - 6 ) {
2023-11-09 13:29:01 +00:00
pageElement = nopaque . Utils . HTMLToElement ( "<li style='margin-top: 5px;'>…</li>" ) ;
2023-03-09 12:18:39 +00:00
this . elements . corpusPagination . appendChild ( pageElement ) ;
2023-11-09 13:29:01 +00:00
pageElement = nopaque . Utils . HTMLToElement (
2023-03-09 12:18:39 +00:00
`
< li class = "waves-effect" >
< a class = "corpus-analysis-action pagination-trigger" data - target = "${this.data.corpus.p.pages}" > $ { this . data . corpus . p . pages } < / a >
< / l i >
`
) ;
this . elements . corpusPagination . appendChild ( pageElement ) ;
}
// Next page button. Disables next page button if on last page
2023-11-09 13:29:01 +00:00
pageElement = nopaque . Utils . HTMLToElement (
2023-03-09 12:18:39 +00:00
`
< li class = "${this.data.corpus.p.has_next ? 'waves-effect' : 'disabled'}" >
< a class = "corpus-analysis-action pagination-trigger" $ { this . data . corpus . p . has _next ? 'data-target="' + this . data . corpus . p . next _num + '"' : '' } >
< i class = "material-icons" > chevron _right < / i >
< / a >
< / l i >
`
) ;
this . elements . corpusPagination . appendChild ( pageElement ) ;
// Last page button. Disables last page button if on last page
2023-11-09 13:29:01 +00:00
pageElement = nopaque . Utils . HTMLToElement (
2023-03-09 12:18:39 +00:00
`
< li class = "${this.data.corpus.p.page === this.data.corpus.p.pages ? 'disabled' : 'waves-effect'}" >
< a class = "corpus-analysis-action pagination-trigger" $ { this . data . corpus . p . page === this . data . corpus . p . pages ? '' : 'data-target="' + this . data . corpus . p . pages + '"' } >
< i class = "material-icons" > last _page < / i >
< / a >
< / l i >
`
) ;
this . elements . corpusPagination . appendChild ( pageElement ) ;
2022-10-07 07:43:11 +00:00
for ( let paginateTriggerElement of this . elements . corpusPagination . querySelectorAll ( '.pagination-trigger[data-target]' ) ) {
2023-07-17 08:40:34 +00:00
paginateTriggerElement . addEventListener ( 'click' , ( event ) => {
2021-11-16 14:23:57 +00:00
event . preventDefault ( ) ;
2022-10-07 07:43:11 +00:00
let page = parseInt ( paginateTriggerElement . dataset . target ) ;
2021-11-16 14:23:57 +00:00
this . page ( page ) ;
} ) ;
}
this . elements . corpusPagination . classList . remove ( 'hide' ) ;
}
cposRange2HTML ( firstCpos , lastCpos ) {
let html = '' ;
2022-10-07 07:43:11 +00:00
for ( let cpos = firstCpos ; cpos <= lastCpos ; cpos ++ ) {
let prevPAttr = cpos > firstCpos ? this . data . corpus . p . lookups . cpos _lookup [ cpos - 1 ] : null ;
let pAttr = this . data . corpus . p . lookups . cpos _lookup [ cpos ] ;
let nextPAttr = cpos < lastCpos ? this . data . corpus . p . lookups . cpos _lookup [ cpos + 1 ] : null ;
let isEntityStart = 'ent' in pAttr && pAttr . ent !== prevPAttr ? . ent ;
let isEntityEnd = 'ent' in pAttr && pAttr . ent !== nextPAttr ? . ent ;
2021-11-16 14:23:57 +00:00
// Add a space before pAttr
if ( cpos !== firstCpos || pAttr . simple _pos !== 'PUNCT' ) { html += ' ' ; }
// Add entity start
if ( isEntityStart ) {
2022-10-07 07:43:11 +00:00
html += ` <span class="s-attr" data-cpos=" ${ cpos } " data-id=" ${ pAttr . ent } " data-s-attr-type="ent" data-s-attr-ent-type=" ${ this . data . corpus . p . lookups . ent _lookup [ pAttr . ent ] . type } "> ` ;
2021-11-16 14:23:57 +00:00
}
// Add pAttr
html += ` <span class="p-attr" data-cpos=" ${ cpos } "></span> ` ;
// Add entity end
if ( isEntityEnd ) {
2022-10-07 07:43:11 +00:00
html += ` <span class="badge black-text hide new white ent-indicator" data-badge-caption=""> ${ this . data . corpus . p . lookups . ent _lookup [ pAttr . ent ] . type } </span> ` ;
2021-11-16 14:23:57 +00:00
html += '</span>' ;
}
}
return html ;
}
page ( pageNum , callback ) {
if ( this . data . corpus . p . page === pageNum && typeof callback === 'function' ) {
callback ( ) ;
return ;
}
this . app . disableActionElements ( ) ;
2023-03-09 12:18:39 +00:00
window . scrollTo ( top ) ;
2021-11-16 14:23:57 +00:00
this . elements . progress . classList . remove ( 'hide' ) ;
this . data . corpus . o . paginate ( pageNum , this . settings . perPage )
. then (
2023-06-30 10:10:17 +00:00
( paginatedCorpus ) => {
2021-11-16 14:23:57 +00:00
this . data . corpus . p = paginatedCorpus ;
this . renderCorpus ( ) ;
this . renderCorpusPagination ( ) ;
this . elements . progress . classList . add ( 'hide' ) ;
this . app . enableActionElements ( ) ;
if ( typeof callback === 'function' ) { callback ( ) ; }
}
)
}
setTextStyle ( ) {
if ( this . settings . textStyle >= 0 ) {
// Destroy with .p-attr elements associated Materialize tooltips
for ( let pAttrElement of this . elements . corpus . querySelectorAll ( '.p-attr.tooltipped' ) ) {
M . Tooltip . getInstance ( pAttrElement ) ? . destroy ( ) ;
}
// Set basic styling on .p-attr elements
for ( let pAttrElement of this . elements . corpus . querySelectorAll ( '.p-attr' ) ) {
pAttrElement . setAttribute ( 'class' , 'p-attr' ) ;
}
// Set basic styling on .s-attr[data-type="ent"] elements
2022-10-07 07:43:11 +00:00
for ( let entElement of this . elements . corpus . querySelectorAll ( '.s-attr[data-s-attr-type="ent"]' ) ) {
entElement . querySelector ( '.ent-indicator' ) . classList . add ( 'hide' ) ;
2021-11-16 14:23:57 +00:00
entElement . removeAttribute ( 'style' ) ;
entElement . setAttribute ( 'class' , 's-attr' ) ;
}
}
if ( this . settings . textStyle >= 1 ) {
// Set advanced styling on .s-attr[data-type="ent"] elements
2022-10-07 07:43:11 +00:00
for ( let entElement of this . elements . corpus . querySelectorAll ( '.s-attr[data-s-attr-type="ent"]' ) ) {
2021-11-16 14:23:57 +00:00
entElement . classList . add ( 'chip' ) ;
2022-10-07 07:43:11 +00:00
entElement . querySelector ( '.ent-indicator' ) . classList . remove ( 'hide' ) ;
2021-11-16 14:23:57 +00:00
}
}
if ( this . settings . textStyle >= 2 ) {
// Set advanced styling on .p-attr elements
for ( let pAttrElement of this . elements . corpus . querySelectorAll ( '.p-attr' ) ) {
pAttrElement . classList . add ( 'chip' , 'hoverable' , 'tooltipped' ) ;
let cpos = pAttrElement . dataset . cpos ;
let pAttr = this . data . corpus . p . lookups . cpos _lookup [ cpos ] ;
let positionalPropertiesHTML = `
< p class = "left-align" >
< b > Positional properties < / b > < b r >
< span > Token : $ { cpos } < / s p a n >
` .trim();
let structuralPropertiesHTML = `
< p class = "left-align" >
< b > Structural properties < / b >
` .trim();
for ( let [ property , propertyValue ] of Object . entries ( pAttr ) ) {
if ( [ 'lemma' , 'ner' , 'pos' , 'simple_pos' , 'word' ] . includes ( property ) ) {
if ( propertyValue === 'None' ) { continue ; }
positionalPropertiesHTML += ` <br><i class="material-icons" style="font-size: inherit;">subdirectory_arrow_right</i> ${ property } : ${ propertyValue } ` ;
} else {
structuralPropertiesHTML += ` <br><span> ${ property } : ${ propertyValue } </span> ` ;
if ( ! ( ` ${ property } _lookup ` in this . data . corpus . p . lookups ) ) { continue ; }
for ( let [ subproperty , subpropertyValue ] of Object . entries ( this . data . corpus . p . lookups [ ` ${ property } _lookup ` ] [ propertyValue ] ) ) {
if ( subpropertyValue === 'NULL' ) { continue ; }
structuralPropertiesHTML += ` <br><i class="material-icons" style="font-size: inherit;">subdirectory_arrow_right</i> ${ subproperty } : ${ subpropertyValue } `
}
}
}
positionalPropertiesHTML += '</p>' ;
structuralPropertiesHTML += '</p>' ;
M . Tooltip . init (
pAttrElement ,
{ html : positionalPropertiesHTML + structuralPropertiesHTML }
) ;
}
}
}
setTokenRepresentation ( ) {
for ( let pAttrElement of this . elements . corpus . querySelectorAll ( '.p-attr' ) ) {
let pAttr = this . data . corpus . p . lookups . cpos _lookup [ pAttrElement . dataset . cpos ] ;
pAttrElement . innerText = pAttr [ this . settings . tokenRepresentation ] ;
}
}
}