2018-09-21 16:05:03 +02:00
class PeersUI {
constructor ( ) {
2023-10-31 18:32:23 +01:00
this . $xPeers = $$ ( 'x-peers' ) ;
this . $xNoPeers = $$ ( 'x-no-peers' ) ;
this . $xInstructions = $$ ( 'x-instructions' ) ;
2023-11-08 20:31:57 +01:00
this . $wsFallbackWarning = $ ( 'websocket-fallback' ) ;
2023-10-31 18:32:23 +01:00
2023-12-09 17:28:05 +01:00
this . $sharePanel = $$ ( '.shr-panel' ) ;
this . $shareModeImageThumb = $$ ( '.shr-panel .image-thumb' ) ;
this . $shareModeTextThumb = $$ ( '.shr-panel .text-thumb' ) ;
this . $shareModeFileThumb = $$ ( '.shr-panel .file-thumb' ) ;
this . $shareModeDescriptor = $$ ( '.shr-panel .share-descriptor' ) ;
this . $shareModeDescriptorItem = $$ ( '.shr-panel .descriptor-item' ) ;
this . $shareModeDescriptorOther = $$ ( '.shr-panel .descriptor-other' ) ;
this . $shareModeCancelBtn = $$ ( '.shr-panel .cancel-btn' ) ;
this . $shareModeEditBtn = $$ ( '.shr-panel .edit-btn' ) ;
2023-11-24 16:25:30 +01:00
2024-02-04 18:02:10 +01:00
this . peerUIs = { } ;
2023-10-31 18:32:23 +01:00
2023-11-24 16:25:30 +01:00
this . shareMode = { } ;
this . shareMode . active = false ;
this . shareMode . descriptor = "" ;
this . shareMode . files = [ ] ;
this . shareMode . text = "" ;
2023-11-08 20:31:57 +01:00
2024-02-04 18:02:10 +01:00
Events . on ( 'peer-joined' , e => this . _onPeerJoined ( e . detail . peer , e . detail . roomType , e . detail . roomId ) ) ;
2023-02-16 02:19:14 +01:00
Events . on ( 'peer-connected' , e => this . _onPeerConnected ( e . detail . peerId , e . detail . connectionHash ) ) ;
2024-02-04 18:02:10 +01:00
Events . on ( 'peer-connecting' , e => this . _onPeerConnecting ( e . detail ) ) ;
2022-12-22 22:41:26 +01:00
Events . on ( 'peer-disconnected' , e => this . _onPeerDisconnected ( e . detail ) ) ;
2018-09-21 16:05:03 +02:00
Events . on ( 'peers' , e => this . _onPeers ( e . detail ) ) ;
2023-01-17 10:41:50 +01:00
Events . on ( 'set-progress' , e => this . _onSetProgress ( e . detail ) ) ;
2023-10-31 18:32:23 +01:00
Events . on ( 'drop' , e => this . _onDrop ( e ) ) ;
Events . on ( 'keydown' , e => this . _onKeyDown ( e ) ) ;
2023-01-22 17:33:19 +01:00
Events . on ( 'dragover' , e => this . _onDragOver ( e ) ) ;
2023-01-22 16:14:27 +01:00
Events . on ( 'dragleave' , _ => this . _onDragEnd ( ) ) ;
Events . on ( 'dragend' , _ => this . _onDragEnd ( ) ) ;
2023-11-24 16:25:30 +01:00
Events . on ( 'resize' , _ => this . _evaluateOverflowingPeers ( ) ) ;
2023-12-12 13:39:55 +01:00
Events . on ( 'header-changed' , _ => this . _evaluateOverflowingPeers ( ) ) ;
2023-01-22 16:14:27 +01:00
2023-10-31 18:32:23 +01:00
Events . on ( 'paste' , e => this . _onPaste ( e ) ) ;
2023-11-24 16:25:30 +01:00
Events . on ( 'activate-share-mode' , e => this . _activateShareMode ( e . detail . files , e . detail . text ) ) ;
Events . on ( 'translation-loaded' , _ => this . _reloadShareMode ( ) ) ;
2023-10-31 18:32:23 +01:00
Events . on ( 'room-type-removed' , e => this . _onRoomTypeRemoved ( e . detail . peerId , e . detail . roomType ) ) ;
2024-02-04 18:05:11 +01:00
Events . on ( 'room-secret-added' , e => this . _onRoomSecretAdded ( e . detail . peerId , e . detail . roomSecret ) ) ;
2023-08-30 14:57:40 +02:00
2023-11-24 16:25:30 +01:00
this . $shareModeCancelBtn . addEventListener ( 'click' , _ => this . _deactivateShareMode ( ) ) ;
2023-03-01 21:35:00 +01:00
2024-02-04 18:02:10 +01:00
Events . on ( 'peer-display-name-changed' , e => this . _onPeerDisplayNameChanged ( e . detail . peerId , e . detail . displayName ) ) ;
2023-03-01 21:35:00 +01:00
2023-11-08 20:31:57 +01:00
Events . on ( 'ws-config' , e => this . _evaluateRtcSupport ( e . detail ) )
2023-10-11 23:22:10 +02:00
}
2023-11-08 20:31:57 +01:00
_evaluateRtcSupport ( wsConfig ) {
if ( wsConfig . wsFallback ) {
2024-02-04 18:02:10 +01:00
this . $wsFallbackWarning . removeAttribute ( "hidden" ) ;
2023-03-01 21:35:00 +01:00
}
2023-11-08 20:31:57 +01:00
else {
2024-02-04 18:02:10 +01:00
this . $wsFallbackWarning . setAttribute ( "hidden" , true ) ;
2023-11-08 20:31:57 +01:00
if ( ! window . isRtcSupported ) {
alert ( Localization . getTranslation ( "instructions.webrtc-requirement" ) ) ;
}
2023-03-01 21:35:00 +01:00
}
}
_changePeerDisplayName ( peerId , displayName ) {
2024-02-04 18:02:10 +01:00
const peerUI = this . peerUIs [ peerId ] ;
if ( ! peerUI ) return ;
peerUI . _setDisplayName ( displayName ) ;
2023-05-04 17:38:51 +02:00
}
2024-02-04 18:02:10 +01:00
_onPeerDisplayNameChanged ( peerId , displayName ) {
if ( ! peerId || ! displayName ) return ;
this . _changePeerDisplayName ( peerId , displayName ) ;
2023-01-17 10:41:50 +01:00
}
2023-11-24 16:25:30 +01:00
async _onKeyDown ( e ) {
if ( ! this . shareMode . active || Dialog . anyDialogShown ( ) ) return ;
2023-11-02 02:56:01 +01:00
2023-11-24 16:25:30 +01:00
if ( e . key === "Escape" ) {
await this . _deactivateShareMode ( ) ;
2023-01-17 10:41:50 +01:00
}
2023-10-11 23:22:10 +02:00
// close About PairDrop page on Escape
if ( e . key === "Escape" ) {
window . location . hash = '#' ;
}
2018-09-21 16:05:03 +02:00
}
2024-02-04 18:02:10 +01:00
_onPeerJoined ( peer , roomType , roomId ) {
this . _joinPeer ( peer , roomType , roomId ) ;
2023-01-10 05:07:57 +01:00
}
2023-09-13 18:15:01 +02:00
_joinPeer ( peer , roomType , roomId ) {
2024-02-04 18:02:10 +01:00
const existingPeerUI = this . peerUIs [ peer . id ] ;
if ( existingPeerUI ) {
// peerUI already exists. Abort but add roomType to GUI
existingPeerUI . _addRoomId ( roomType , roomId ) ;
2023-05-04 17:38:51 +02:00
return ;
}
2023-05-11 21:04:10 +02:00
2024-02-04 18:02:10 +01:00
const peerUI = new PeerUI ( peer , roomType , roomId , {
active : this . shareMode . active ,
descriptor : this . shareMode . descriptor ,
} ) ;
this . peerUIs [ peer . id ] = peerUI ;
2022-12-22 22:41:26 +01:00
}
2023-02-16 02:19:14 +01:00
_onPeerConnected ( peerId , connectionHash ) {
2024-02-04 18:02:10 +01:00
const peerUI = this . peerUIs [ peerId ] ;
2023-05-04 17:38:51 +02:00
2024-02-04 18:02:10 +01:00
if ( ! peerUI ) return ;
2023-05-04 17:38:51 +02:00
2024-02-04 18:02:10 +01:00
peerUI . _peerConnected ( true , connectionHash ) ;
this . _addPeerUIIfMissing ( peerUI ) ;
2018-09-21 16:05:03 +02:00
}
2024-02-04 18:02:10 +01:00
_addPeerUIIfMissing ( peerUI ) {
if ( this . $xPeers . contains ( peerUI . $el ) ) return ;
2023-09-13 18:15:01 +02:00
2024-02-04 18:02:10 +01:00
this . $xPeers . appendChild ( peerUI . $el ) ;
this . _evaluateOverflowingPeers ( ) ;
}
2023-09-13 18:15:01 +02:00
2024-02-04 18:02:10 +01:00
_onPeerConnecting ( peerId ) {
const peerUI = this . peerUIs [ peerId ] ;
2023-09-13 18:15:01 +02:00
2024-02-04 18:02:10 +01:00
if ( ! peerUI ) return ;
2023-09-13 18:15:01 +02:00
2024-02-04 18:02:10 +01:00
peerUI . _peerConnected ( false ) ;
2023-03-01 10:04:37 +01:00
}
2023-11-24 16:25:30 +01:00
_evaluateOverflowingPeers ( ) {
2023-03-01 10:04:37 +01:00
if ( this . $xPeers . clientHeight < this . $xPeers . scrollHeight ) {
this . $xPeers . classList . add ( 'overflowing' ) ;
2023-11-02 02:56:01 +01:00
}
else {
2023-03-01 10:04:37 +01:00
this . $xPeers . classList . remove ( 'overflowing' ) ;
}
2023-01-10 05:07:57 +01:00
}
_onPeers ( msg ) {
2023-09-13 18:15:01 +02:00
msg . peers . forEach ( peer => this . _joinPeer ( peer , msg . roomType , msg . roomId ) ) ;
2018-09-21 16:05:03 +02:00
}
2022-12-22 22:41:26 +01:00
_onPeerDisconnected ( peerId ) {
2024-02-04 18:02:10 +01:00
const peerUI = this . peerUIs [ peerId ] ;
if ( ! peerUI ) return ;
peerUI . _removeDom ( ) ;
delete this . peerUIs [ peerId ] ;
2023-11-24 16:25:30 +01:00
this . _evaluateOverflowingPeers ( ) ;
2018-09-21 16:05:03 +02:00
}
2023-09-13 18:15:01 +02:00
_onRoomTypeRemoved ( peerId , roomType ) {
2024-02-04 18:02:10 +01:00
const peerUI = this . peerUIs [ peerId ] ;
2023-09-13 18:15:01 +02:00
2024-02-04 18:02:10 +01:00
if ( ! peerUI ) return ;
2023-09-13 18:15:01 +02:00
2024-02-04 18:02:10 +01:00
peerUI . _removeRoomId ( roomType ) ;
2023-01-10 05:07:57 +01:00
}
2024-02-04 18:05:11 +01:00
_onRoomSecretAdded ( peerId , roomSecret ) {
// If a device is paired that is already connected we must update the displayName saved for the roomSecret
// here as the "display-name-changed" message has already been received
const peerUI = this . peerUIs [ peerId ] ;
if ( ! peerUI || ! peerUI . _connected ) return ;
const displayName = peerUI . _displayName ( ) ;
PersistentStorage
. updateRoomSecretDisplayName ( roomSecret , displayName )
. then ( roomSecretEntry => {
2024-02-05 15:42:27 +01:00
Logger . debug ( ` Successfully updated DisplayName for roomSecretEntry ${ roomSecretEntry . key } ` ) ;
2024-02-04 18:05:11 +01:00
} )
}
2023-01-17 10:41:50 +01:00
_onSetProgress ( progress ) {
2024-02-04 18:02:10 +01:00
const peerUI = this . peerUIs [ progress . peerId ] ;
if ( ! peerUI ) return ;
2024-02-07 23:58:15 +01:00
peerUI . setProgressOrQueue ( progress . progress , progress . status ) ;
2018-09-21 16:05:03 +02:00
}
2023-01-22 16:14:27 +01:00
_onDrop ( e ) {
e . preventDefault ( ) ;
2023-11-24 16:25:30 +01:00
if ( this . shareMode . active || Dialog . anyDialogShown ( ) ) return ;
2023-01-22 17:33:19 +01:00
if ( ! $$ ( 'x-peer' ) || ! $$ ( 'x-peer' ) . contains ( e . target ) ) {
2023-11-24 16:25:30 +01:00
if ( e . dataTransfer . files . length > 0 ) {
Events . fire ( 'activate-share-mode' , { files : e . dataTransfer . files } ) ;
} else {
for ( let i = 0 ; i < e . dataTransfer . items . length ; i ++ ) {
if ( e . dataTransfer . items [ i ] . type === "text/plain" ) {
e . dataTransfer . items [ i ] . getAsString ( text => {
Events . fire ( 'activate-share-mode' , { text : text } ) ;
} ) ;
}
}
}
2023-01-22 16:14:27 +01:00
}
this . _onDragEnd ( ) ;
}
2023-01-22 17:33:19 +01:00
_onDragOver ( e ) {
e . preventDefault ( ) ;
2023-11-24 16:25:30 +01:00
if ( this . shareMode . active || Dialog . anyDialogShown ( ) ) return ;
2023-11-08 20:36:46 +01:00
this . $xInstructions . setAttribute ( 'drop-bg' , true ) ;
this . $xNoPeers . setAttribute ( 'drop-bg' , true ) ;
2023-01-22 16:14:27 +01:00
}
_onDragEnd ( ) {
2023-11-08 20:36:46 +01:00
this . $xInstructions . removeAttribute ( 'drop-bg' ) ;
2023-01-22 17:33:19 +01:00
this . $xNoPeers . removeAttribute ( 'drop-bg' ) ;
2023-01-22 16:14:27 +01:00
}
2020-12-22 21:23:10 +01:00
_onPaste ( e ) {
2023-11-24 16:25:30 +01:00
// prevent send on paste when dialog is open
if ( this . shareMode . active || Dialog . anyDialogShown ( ) ) return ;
e . preventDefault ( )
const files = e . clipboardData . files ;
const text = e . clipboardData . getData ( "Text" ) ;
if ( files . length > 0 ) {
Events . fire ( 'activate-share-mode' , { files : files } ) ;
} else if ( text . length > 0 ) {
if ( ShareTextDialog . isApproveShareTextSet ( ) ) {
Events . fire ( 'share-text-dialog' , text ) ;
} else {
Events . fire ( 'activate-share-mode' , { text : text } ) ;
}
2022-11-09 17:43:27 +01:00
}
}
2023-11-24 16:25:30 +01:00
async _activateShareMode ( files = [ ] , text = "" ) {
if ( this . shareMode . active || ( files . length === 0 && text . length === 0 ) ) return ;
2022-11-09 17:43:27 +01:00
2023-11-24 16:25:30 +01:00
this . _activateCallback = e => this . _sendShareData ( e ) ;
this . _editShareTextCallback = _ => {
this . _deactivateShareMode ( ) ;
Events . fire ( 'share-text-dialog' , text ) ;
} ;
2023-07-06 21:29:36 +02:00
2023-11-24 16:25:30 +01:00
Events . on ( 'share-mode-pointerdown' , this . _activateCallback ) ;
2023-12-05 18:51:56 +01:00
const sharedText = Localization . getTranslation ( "instructions.activate-share-mode-shared-text" ) ;
2023-11-24 16:25:30 +01:00
const andOtherFilesPlural = Localization . getTranslation ( "instructions.activate-share-mode-and-other-files-plural" , null , { count : files . length - 1 } ) ;
const andOtherFiles = Localization . getTranslation ( "instructions.activate-share-mode-and-other-file" ) ;
let descriptorComplete , descriptorItem , descriptorOther , descriptorInstructions ;
if ( files . length > 2 ) {
// files shared
descriptorItem = files [ 0 ] . name ;
descriptorOther = andOtherFilesPlural ;
descriptorComplete = ` ${ descriptorItem } ${ descriptorOther } ` ;
}
else if ( files . length === 2 ) {
descriptorItem = files [ 0 ] . name ;
descriptorOther = andOtherFiles ;
descriptorComplete = ` ${ descriptorItem } ${ descriptorOther } ` ;
} else if ( files . length === 1 ) {
descriptorItem = files [ 0 ] . name ;
descriptorComplete = descriptorItem ;
}
else {
// text shared
descriptorItem = text . replace ( /\s/g , " " ) ;
2023-12-05 18:51:56 +01:00
descriptorComplete = sharedText ;
2023-11-24 16:25:30 +01:00
}
if ( files . length > 0 ) {
if ( descriptorOther ) {
this . $shareModeDescriptorOther . innerText = descriptorOther ;
this . $shareModeDescriptorOther . removeAttribute ( 'hidden' ) ;
2023-11-02 02:56:01 +01:00
}
2023-11-24 16:25:30 +01:00
if ( files . length > 1 ) {
descriptorInstructions = Localization . getTranslation ( "instructions.activate-share-mode-shared-files-plural" , null , { count : files . length } ) ;
2023-11-02 02:56:01 +01:00
}
else {
2023-11-24 16:25:30 +01:00
descriptorInstructions = Localization . getTranslation ( "instructions.activate-share-mode-shared-file" ) ;
2022-11-09 17:43:27 +01:00
}
2023-12-08 13:49:45 +01:00
files = await mime . addMissingMimeTypesToFiles ( files ) ;
2023-11-24 16:25:30 +01:00
if ( files [ 0 ] . type . split ( '/' ) [ 0 ] === 'image' ) {
2023-12-11 17:19:56 +01:00
try {
2023-12-12 14:07:15 +01:00
let imageUrl = await getThumbnailAsDataUrl ( files [ 0 ] , 80 , null , 0.9 ) ;
2023-12-11 17:19:56 +01:00
this . $shareModeImageThumb . style . backgroundImage = ` url( ${ imageUrl } ) ` ;
this . $shareModeImageThumb . removeAttribute ( 'hidden' ) ;
} catch ( e ) {
2024-02-05 15:42:27 +01:00
Logger . error ( e ) ;
2023-12-11 17:19:56 +01:00
this . $shareModeFileThumb . removeAttribute ( 'hidden' ) ;
}
2023-11-24 16:25:30 +01:00
} else {
this . $shareModeFileThumb . removeAttribute ( 'hidden' ) ;
}
}
else {
this . $shareModeTextThumb . removeAttribute ( 'hidden' ) ;
2022-11-09 17:43:27 +01:00
2023-11-24 16:25:30 +01:00
this . $shareModeEditBtn . addEventListener ( 'click' , this . _editShareTextCallback ) ;
this . $shareModeEditBtn . removeAttribute ( 'hidden' ) ;
2022-11-09 17:43:27 +01:00
2023-11-24 16:25:30 +01:00
descriptorInstructions = Localization . getTranslation ( "instructions.activate-share-mode-shared-text" ) ;
}
2023-11-08 20:31:57 +01:00
2023-11-24 16:25:30 +01:00
const desktop = Localization . getTranslation ( "instructions.x-instructions-share-mode_desktop" , null , { descriptor : descriptorInstructions } ) ;
const mobile = Localization . getTranslation ( "instructions.x-instructions-share-mode_mobile" , null , { descriptor : descriptorInstructions } ) ;
2022-11-09 17:43:27 +01:00
2023-11-24 16:25:30 +01:00
this . $xInstructions . setAttribute ( 'desktop' , desktop ) ;
this . $xInstructions . setAttribute ( 'mobile' , mobile ) ;
2022-11-09 17:43:27 +01:00
2023-11-24 16:25:30 +01:00
this . $sharePanel . removeAttribute ( 'hidden' ) ;
2022-11-09 17:43:27 +01:00
2023-11-24 16:25:30 +01:00
this . $shareModeDescriptor . removeAttribute ( 'hidden' ) ;
this . $shareModeDescriptorItem . innerText = descriptorItem ;
this . shareMode . active = true ;
2023-12-05 18:51:56 +01:00
this . shareMode . descriptor = descriptorComplete ;
2023-11-24 16:25:30 +01:00
this . shareMode . files = files ;
this . shareMode . text = text ;
2024-02-05 15:42:27 +01:00
Logger . debug ( 'Share mode activated.' ) ;
2023-11-24 16:25:30 +01:00
Events . fire ( 'share-mode-changed' , {
active : true ,
descriptor : descriptorComplete
} ) ;
2022-11-09 17:43:27 +01:00
}
2023-11-24 16:25:30 +01:00
async _reloadShareMode ( ) {
// If shareMode is active only
if ( ! this . shareMode . active ) return ;
let files = this . shareMode . files ;
let text = this . shareMode . text ;
await this . _deactivateShareMode ( ) ;
await this . _activateShareMode ( files , text ) ;
2022-11-09 17:43:27 +01:00
}
2023-11-24 16:25:30 +01:00
async _deactivateShareMode ( ) {
if ( ! this . shareMode . active ) return ;
2022-11-09 17:43:27 +01:00
2023-11-24 16:25:30 +01:00
this . shareMode . active = false ;
2023-12-05 18:51:56 +01:00
this . shareMode . descriptor = "" ;
2023-11-24 16:25:30 +01:00
this . shareMode . files = [ ] ;
this . shareMode . text = "" ;
2023-01-18 15:28:57 +01:00
2023-11-24 16:25:30 +01:00
Events . off ( 'share-mode-pointerdown' , this . _activateCallback ) ;
2023-01-18 15:28:57 +01:00
2023-11-24 16:25:30 +01:00
const desktop = Localization . getTranslation ( "instructions.x-instructions_desktop" ) ;
const mobile = Localization . getTranslation ( "instructions.x-instructions_mobile" ) ;
2023-01-18 15:28:57 +01:00
2023-11-24 16:25:30 +01:00
this . $xInstructions . setAttribute ( 'desktop' , desktop ) ;
this . $xInstructions . setAttribute ( 'mobile' , mobile ) ;
2022-11-09 17:43:27 +01:00
2023-11-24 16:25:30 +01:00
this . $sharePanel . setAttribute ( 'hidden' , true ) ;
this . $shareModeImageThumb . setAttribute ( 'hidden' , true ) ;
this . $shareModeFileThumb . setAttribute ( 'hidden' , true ) ;
this . $shareModeTextThumb . setAttribute ( 'hidden' , true ) ;
this . $shareModeDescriptorItem . innerHTML = "" ;
this . $shareModeDescriptorItem . classList . remove ( 'cursive' ) ;
this . $shareModeDescriptorOther . innerHTML = "" ;
this . $shareModeDescriptorOther . setAttribute ( 'hidden' , true ) ;
this . $shareModeEditBtn . removeEventListener ( 'click' , this . _editShareTextCallback ) ;
this . $shareModeEditBtn . setAttribute ( 'hidden' , true ) ;
2024-02-05 15:42:27 +01:00
Logger . debug ( 'Share mode deactivated.' )
2023-11-24 16:25:30 +01:00
Events . fire ( 'share-mode-changed' , { active : false } ) ;
2022-11-09 17:43:27 +01:00
}
2023-11-24 16:25:30 +01:00
_sendShareData ( e ) {
// send the shared file/text content
2022-11-09 17:43:27 +01:00
const peerId = e . detail . peerId ;
2023-11-24 16:25:30 +01:00
const files = this . shareMode . files ;
const text = this . shareMode . text ;
2022-11-09 17:43:27 +01:00
if ( files . length > 0 ) {
2020-03-03 09:15:32 -05:00
Events . fire ( 'files-selected' , {
files : files ,
2022-11-09 17:43:27 +01:00
to : peerId
} ) ;
2023-11-02 02:56:01 +01:00
}
else if ( text . length > 0 ) {
2022-11-09 17:43:27 +01:00
Events . fire ( 'send-text' , {
text : text ,
to : peerId
2020-03-03 09:15:32 -05:00
} ) ;
}
2018-09-21 16:05:03 +02:00
}
}
class PeerUI {
2023-11-20 05:23:21 +01:00
static _badgeClassNames = [ "badge-room-ip" , "badge-room-secret" , "badge-room-public-id" ] ;
2024-02-04 18:02:10 +01:00
constructor ( peer , roomType , roomId , shareMode = { active : false , descriptor : "" } ) {
2023-11-08 20:31:57 +01:00
this . $xInstructions = $$ ( 'x-instructions' ) ;
2023-03-01 10:04:37 +01:00
this . _peer = peer ;
2024-02-04 18:02:10 +01:00
this . _connectionHash = "" ;
this . _connected = false ;
2023-11-08 20:31:57 +01:00
2024-02-06 04:38:37 +01:00
this . _currentProgress = 0 ;
this . _currentStatus = null
this . _oldStatus = null ;
2024-02-07 23:58:15 +01:00
this . _progressQueue = [ ] ;
2024-02-04 18:02:10 +01:00
this . _roomIds = { }
this . _roomIds [ roomType ] = roomId ;
2023-11-08 20:31:57 +01:00
2024-02-04 18:02:10 +01:00
this . _shareMode = shareMode ;
2023-03-01 10:04:37 +01:00
2024-02-04 18:02:10 +01:00
this . _createCallbacks ( ) ;
this . _initDom ( ) ;
2023-11-24 16:25:30 +01:00
// ShareMode
Events . on ( 'share-mode-changed' , e => this . _onShareModeChanged ( e . detail . active , e . detail . descriptor ) ) ;
2023-03-01 10:04:37 +01:00
}
2024-02-04 18:02:10 +01:00
_initDom ( ) {
this . $el = document . createElement ( 'x-peer' ) ;
this . $el . id = this . _peer . id ;
this . $el . ui = this ;
this . $el . classList . add ( 'center' ) ;
this . html ( ) ;
2024-02-06 14:42:09 +01:00
this . $icon = this . $el . querySelector ( 'svg use' ) ;
this . $displayName = this . $el . querySelector ( '.name' ) ;
this . $deviceName = this . $el . querySelector ( '.device-name' ) ;
2024-02-04 18:02:10 +01:00
this . $label = this . $el . querySelector ( 'label' ) ;
this . $input = this . $el . querySelector ( 'input' ) ;
2024-02-06 04:38:37 +01:00
this . $progress = this . $el . querySelector ( '.progress' ) ;
2024-02-04 18:02:10 +01:00
2024-02-06 14:42:09 +01:00
this . $icon . setAttribute ( 'xlink:href' , this . _icon ( ) ) ;
this . $displayName . textContent = this . _displayName ( ) ;
this . $deviceName . textContent = this . _deviceName ( ) ;
2024-02-04 18:02:10 +01:00
this . updateTypesClassList ( ) ;
this . setStatus ( "connect" ) ;
this . _evaluateShareMode ( ) ;
this . _bindListeners ( ) ;
}
_removeDom ( ) {
this . $el . remove ( ) ;
}
2018-09-21 16:05:03 +02:00
html ( ) {
2024-02-04 18:02:10 +01:00
let title = Localization . getTranslation ( "peer-ui.click-to-send" ) ;
2023-11-24 16:25:30 +01:00
2023-01-18 15:28:57 +01:00
this . $el . innerHTML = `
2023-09-13 18:15:01 +02:00
< label class = "column center pointer" title = "${title}" >
2023-11-24 16:25:30 +01:00
< input type = "file" multiple / >
2023-03-01 10:04:37 +01:00
< x - icon >
< div class = "icon-wrapper" shadow = "1" >
< svg class = "icon" > < use xlink : href = "#" / > < / s v g >
< / d i v >
< div class = "highlight-wrapper center" >
2023-09-13 18:15:01 +02:00
< div class = "highlight highlight-room-ip" shadow = "1" > < / d i v >
< div class = "highlight highlight-room-secret" shadow = "1" > < / d i v >
< div class = "highlight highlight-room-public-id" shadow = "1" > < / d i v >
2023-03-01 10:04:37 +01:00
< / d i v >
2018-09-21 16:05:03 +02:00
< / x - i c o n >
< div class = "progress" >
< div class = "circle" > < / d i v >
< div class = "circle right" > < / d i v >
< / d i v >
2023-03-01 10:04:37 +01:00
< div class = "device-descriptor" >
< div class = "name font-subheading" > < / d i v >
< div class = "device-name font-body2" > < / d i v >
< div class = "status font-body2" > < / d i v >
< / d i v >
2022-11-09 17:43:27 +01:00
< / l a b e l > ` ;
2018-09-21 16:05:03 +02:00
}
2024-02-04 18:02:10 +01:00
updateTypesClassList ( ) {
// Remove all classes
this . $el . classList . remove ( 'type-ip' , 'type-secret' , 'type-public-id' , 'type-same-browser' , 'ws-peer' ) ;
// Add classes accordingly
Object . keys ( this . _roomIds ) . forEach ( roomType => this . $el . classList . add ( ` type- ${ roomType } ` ) ) ;
if ( BrowserTabsConnector . peerIsSameBrowser ( this . _peer . id ) ) {
2023-09-13 18:15:01 +02:00
this . $el . classList . add ( ` type-same-browser ` ) ;
}
2024-02-04 18:02:10 +01:00
if ( ! this . _peer . rtcSupported || ! window . isRtcSupported ) {
this . $el . classList . add ( 'ws-peer' ) ;
}
2023-09-13 18:15:01 +02:00
}
2024-02-04 18:02:10 +01:00
_addRoomId ( roomType , roomId ) {
this . _roomIds [ roomType ] = roomId ;
this . updateTypesClassList ( ) ;
}
2023-11-24 16:25:30 +01:00
2024-02-04 18:02:10 +01:00
_removeRoomId ( roomType ) {
delete this . _roomIds [ roomType ] ;
this . updateTypesClassList ( ) ;
2023-11-24 16:25:30 +01:00
}
_onShareModeChanged ( active = false , descriptor = "" ) {
2024-02-04 18:02:10 +01:00
this . _shareMode . active = active ;
this . _shareMode . descriptor = descriptor ;
2023-11-24 16:25:30 +01:00
this . _evaluateShareMode ( ) ;
2023-01-18 15:28:57 +01:00
this . _bindListeners ( ) ;
}
2023-11-24 16:25:30 +01:00
_evaluateShareMode ( ) {
let title ;
2024-02-04 18:02:10 +01:00
if ( ! this . _shareMode . active ) {
2023-11-24 16:25:30 +01:00
title = Localization . getTranslation ( "peer-ui.click-to-send" ) ;
this . $input . removeAttribute ( 'disabled' ) ;
}
else {
2024-02-04 18:02:10 +01:00
title = Localization . getTranslation ( "peer-ui.click-to-send-share-mode" , null , { descriptor : this . _shareMode . descriptor } ) ;
2023-11-24 16:25:30 +01:00
this . $input . setAttribute ( 'disabled' , true ) ;
}
this . $label . setAttribute ( 'title' , title ) ;
}
_createCallbacks ( ) {
this . _callbackInput = e => this . _onFilesSelected ( e ) ;
this . _callbackDrop = e => this . _onDrop ( e ) ;
this . _callbackDragEnd = e => this . _onDragEnd ( e ) ;
this . _callbackDragLeave = e => this . _onDragEnd ( e ) ;
this . _callbackDragOver = e => this . _onDragOver ( e ) ;
this . _callbackContextMenu = e => this . _onRightClick ( e ) ;
this . _callbackTouchStart = e => this . _onTouchStart ( e ) ;
this . _callbackTouchEnd = e => this . _onTouchEnd ( e ) ;
this . _callbackPointerDown = e => this . _onPointerDown ( e ) ;
}
2023-01-18 15:28:57 +01:00
_bindListeners ( ) {
2024-02-04 18:02:10 +01:00
if ( ! this . _shareMode . active ) {
2023-11-24 16:25:30 +01:00
// Remove Events Share mode
2023-01-18 15:28:57 +01:00
this . $el . removeEventListener ( 'pointerdown' , this . _callbackPointerDown ) ;
// Add Events Normal Mode
2024-02-06 14:42:09 +01:00
this . $input . addEventListener ( 'change' , this . _callbackInput ) ;
2023-01-18 15:28:57 +01:00
this . $el . addEventListener ( 'drop' , this . _callbackDrop ) ;
this . $el . addEventListener ( 'dragend' , this . _callbackDragEnd ) ;
this . $el . addEventListener ( 'dragleave' , this . _callbackDragLeave ) ;
this . $el . addEventListener ( 'dragover' , this . _callbackDragOver ) ;
this . $el . addEventListener ( 'contextmenu' , this . _callbackContextMenu ) ;
this . $el . addEventListener ( 'touchstart' , this . _callbackTouchStart ) ;
this . $el . addEventListener ( 'touchend' , this . _callbackTouchEnd ) ;
2023-11-02 02:56:01 +01:00
}
else {
2023-01-18 15:28:57 +01:00
// Remove Events Normal Mode
2024-02-06 14:42:09 +01:00
this . $input . removeEventListener ( 'change' , this . _callbackInput ) ;
2023-01-18 15:28:57 +01:00
this . $el . removeEventListener ( 'drop' , this . _callbackDrop ) ;
this . $el . removeEventListener ( 'dragend' , this . _callbackDragEnd ) ;
this . $el . removeEventListener ( 'dragleave' , this . _callbackDragLeave ) ;
this . $el . removeEventListener ( 'dragover' , this . _callbackDragOver ) ;
this . $el . removeEventListener ( 'contextmenu' , this . _callbackContextMenu ) ;
this . $el . removeEventListener ( 'touchstart' , this . _callbackTouchStart ) ;
this . $el . removeEventListener ( 'touchend' , this . _callbackTouchEnd ) ;
2023-11-24 16:25:30 +01:00
// Add Events Share mode
2023-01-18 15:28:57 +01:00
this . $el . addEventListener ( 'pointerdown' , this . _callbackPointerDown ) ;
2022-11-09 17:43:27 +01:00
}
}
_onPointerDown ( e ) {
// Prevents triggering of event twice on touch devices
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
2023-11-24 16:25:30 +01:00
Events . fire ( 'share-mode-pointerdown' , {
2022-11-09 17:43:27 +01:00
peerId : this . _peer . id
} ) ;
2018-09-21 16:05:03 +02:00
}
2024-02-04 18:02:10 +01:00
_peerConnected ( connected = true , connectionHash = "" ) {
if ( connected ) {
this . _connected = true ;
// on reconnect
2024-02-06 04:38:37 +01:00
this . setStatus ( this . _oldStatus ) ;
this . _oldStatus = null ;
2024-02-04 18:02:10 +01:00
this . _connectionHash = connectionHash ;
}
else {
this . _connected = false ;
2024-02-06 04:38:37 +01:00
if ( ! this . _oldStatus && this . _currentStatus !== "connect" ) {
2024-02-04 18:02:10 +01:00
// save old status when reconnecting
2024-02-06 04:38:37 +01:00
this . _oldStatus = this . _currentStatus ;
2024-02-04 18:02:10 +01:00
}
this . setStatus ( "connect" ) ;
this . _connectionHash = "" ;
}
}
getConnectionHashWithSpaces ( ) {
if ( this . _connectionHash . length !== 16 ) {
return ""
}
return ` ${ this . _connectionHash . substring ( 0 , 4 ) } ${ this . _connectionHash . substring ( 4 , 8 ) } ${ this . _connectionHash . substring ( 8 , 12 ) } ${ this . _connectionHash . substring ( 12 , 16 ) } ` ;
}
2020-12-19 21:05:48 +01:00
_displayName ( ) {
2019-08-28 20:16:04 +05:30
return this . _peer . name . displayName ;
2018-09-21 16:05:03 +02:00
}
2020-12-19 21:05:48 +01:00
_deviceName ( ) {
return this . _peer . name . deviceName ;
}
2024-02-04 18:02:10 +01:00
_setDisplayName ( displayName ) {
this . _peer . name . displayName = displayName ;
this . $displayName . textContent = displayName ;
}
_roomTypes ( ) {
return Object . keys ( this . _roomIds ) ;
}
2023-09-13 18:15:01 +02:00
_badgeClassName ( ) {
2024-02-04 18:02:10 +01:00
const roomTypes = this . _roomTypes ( ) ;
if ( roomTypes . includes ( 'secret' ) ) {
return 'badge-room-secret' ;
}
else if ( roomTypes . includes ( 'ip' ) ) {
return 'badge-room-ip' ;
}
else {
return 'badge-room-public-id' ;
}
2023-09-13 18:15:01 +02:00
}
2018-09-21 16:05:03 +02:00
_icon ( ) {
const device = this . _peer . name . device || this . _peer . name ;
if ( device . type === 'mobile' ) {
return '#phone-iphone' ;
}
if ( device . type === 'tablet' ) {
return '#tablet-mac' ;
}
return '#desktop-mac' ;
}
_onFilesSelected ( e ) {
const $input = e . target ;
const files = $input . files ;
2024-02-04 18:02:10 +01:00
2024-02-06 13:42:47 +01:00
if ( files . length === 0 ) return ;
2024-02-06 14:42:09 +01:00
let totalSize = 0 ;
for ( let i = 0 ; i < files . length ; i ++ ) {
totalSize += files [ i ] . size ;
}
// Prevent device from sleeping
NoSleepUI . enable ( ) ;
2018-09-21 16:05:03 +02:00
Events . fire ( 'files-selected' , {
files : files ,
to : this . _peer . id
} ) ;
2023-01-17 10:41:50 +01:00
$input . files = null ; // reset input
2018-09-21 16:05:03 +02:00
}
2024-02-07 23:58:15 +01:00
setProgressOrQueue ( progress , status ) {
if ( this . _progressQueue . length > 0 ) {
// add to queue
this . _progressQueue . push ( { progress : progress , status : status } ) ;
for ( let i = 0 ; i < this . _progressQueue . length ; i ++ ) {
if ( this . _progressQueue [ i ] . progress <= progress ) {
// if progress is higher than progress in queue -> overwrite in queue and cut queue at this position
this . _progressQueue [ i ] . progress = progress ;
this . _progressQueue [ i ] . status = status ;
this . _progressQueue = this . _progressQueue . slice ( 0 , i + 1 ) ;
break ;
}
}
return ;
}
2024-02-06 04:38:37 +01:00
2024-02-07 23:58:15 +01:00
this . setProgress ( progress , status ) ;
}
setNextProgress ( ) {
if ( this . _progressQueue . length > 0 ) {
setTimeout ( ( ) => {
let next = this . _progressQueue . shift ( )
this . setProgress ( next . progress , next . status ) ;
} , 250 ) ; // 200 ms animation + buffer
}
}
setProgress ( progress , status ) {
2024-02-06 04:38:37 +01:00
this . setStatus ( status ) ;
2024-02-04 18:02:10 +01:00
2024-02-07 23:58:15 +01:00
const progressSpillsOverHalf = this . _currentProgress < 0.5 && 0.5 < progress ; // 0.5 slips through
const progressSpillsOverFull = progress <= 0.5 && 0.5 <= this . _currentProgress && this . _currentProgress < 1 ;
2024-02-06 04:38:37 +01:00
2024-02-07 23:58:15 +01:00
if ( progressSpillsOverHalf ) {
this . _progressQueue . unshift ( { progress : progress , status : status } ) ;
this . setProgress ( 0.5 , status ) ;
return ;
} else if ( progressSpillsOverFull ) {
this . _progressQueue . unshift ( { progress : progress , status : status } ) ;
this . setProgress ( 1 , status ) ;
return ;
}
if ( progress === 0 ) {
this . _currentProgress = 0 ;
2024-02-06 04:38:37 +01:00
this . $progress . classList . remove ( 'animate' ) ;
2024-02-07 23:58:15 +01:00
this . $progress . classList . remove ( 'over50' ) ;
this . $progress . style . setProperty ( '--progress' , ` rotate( ${ 360 * progress } deg) ` ) ;
this . setNextProgress ( ) ;
return ;
2023-11-02 02:56:01 +01:00
}
2024-02-07 23:58:15 +01:00
if ( progress < this . _currentProgress && status !== this . _currentStatus ) {
// reset progress
this . _progressQueue . unshift ( { progress : progress , status : status } ) ;
this . setProgress ( 0 , status ) ;
return ;
2018-09-21 16:05:03 +02:00
}
2024-02-04 18:02:10 +01:00
2024-02-07 23:58:15 +01:00
if ( progress === 0 ) {
this . $progress . classList . remove ( 'animate' ) ;
this . $progress . classList . remove ( 'over50' ) ;
this . $progress . classList . add ( 'animate' ) ;
} else if ( this . _currentProgress === 0.5 ) {
this . $progress . classList . remove ( 'animate' ) ;
2024-02-06 04:38:37 +01:00
this . $progress . classList . add ( 'over50' ) ;
2024-02-07 23:58:15 +01:00
this . $progress . classList . add ( 'animate' ) ;
2024-02-06 04:38:37 +01:00
}
2024-02-07 23:58:15 +01:00
if ( this . _currentProgress < progress ) {
this . $progress . classList . add ( 'animate' ) ;
} else {
this . $progress . classList . remove ( 'animate' ) ;
2023-11-02 02:56:01 +01:00
}
2024-02-04 18:02:10 +01:00
2024-02-07 23:58:15 +01:00
this . $progress . style . setProperty ( '--progress' , ` rotate( ${ 360 * progress } deg) ` ) ;
2024-02-06 04:38:37 +01:00
this . _currentProgress = progress
if ( progress === 1 ) {
2024-02-07 23:58:15 +01:00
// reset progress
this . _progressQueue . unshift ( { progress : 0 , status : status } ) ;
2024-02-06 04:38:37 +01:00
}
2024-02-07 23:58:15 +01:00
this . setNextProgress ( ) ;
2024-02-04 18:02:10 +01:00
}
setStatus ( status ) {
2024-02-06 04:38:37 +01:00
if ( status === this . _currentStatus ) return ;
clearTimeout ( this . statusTimeout ) ;
2024-02-04 18:02:10 +01:00
if ( ! status ) {
2023-01-17 10:41:50 +01:00
this . $el . removeAttribute ( 'status' ) ;
2023-07-06 21:29:36 +02:00
this . $el . querySelector ( '.status' ) . innerHTML = '' ;
2024-02-06 04:38:37 +01:00
this . _currentStatus = null ;
2024-02-06 14:42:09 +01:00
NoSleepUI . disableIfIdle ( ) ;
2024-02-04 18:02:10 +01:00
return ;
2023-01-17 10:41:50 +01:00
}
2024-02-04 18:02:10 +01:00
let statusName = {
"connect" : Localization . getTranslation ( "peer-ui.connecting" ) ,
"prepare" : Localization . getTranslation ( "peer-ui.preparing" ) ,
"transfer" : Localization . getTranslation ( "peer-ui.transferring" ) ,
"receive" : Localization . getTranslation ( "peer-ui.receiving" ) ,
"process" : Localization . getTranslation ( "peer-ui.processing" ) ,
2024-02-06 04:36:52 +01:00
"wait" : Localization . getTranslation ( "peer-ui.waiting" ) ,
2024-02-07 23:58:15 +01:00
"transfer-complete" : Localization . getTranslation ( "peer-ui.transfer-complete" ) ,
"receive-complete" : Localization . getTranslation ( "peer-ui.receive-complete" )
2024-02-04 18:02:10 +01:00
} [ status ] ;
2024-02-07 23:58:15 +01:00
this . $el . setAttribute ( 'status' , status ) ;
this . $el . querySelector ( '.status' ) . innerText = statusName ;
this . _currentStatus = status ;
2024-02-09 02:09:53 +01:00
if ( status . indexOf ( "-complete" ) || status === "receive-complete" ) {
2024-02-06 04:38:37 +01:00
this . statusTimeout = setTimeout ( ( ) => {
this . setProgress ( 0 , null ) ;
} , 10000 ) ;
}
2018-09-21 16:05:03 +02:00
}
_onDrop ( e ) {
2024-02-04 18:02:10 +01:00
if ( this . _shareMode . active || Dialog . anyDialogShown ( ) ) return ;
2023-11-24 16:25:30 +01:00
2024-02-04 18:02:10 +01:00
e . preventDefault ( ) ;
2023-11-24 16:25:30 +01:00
if ( e . dataTransfer . files . length > 0 ) {
Events . fire ( 'files-selected' , {
files : e . dataTransfer . files ,
to : this . _peer . id
} ) ;
} else {
for ( let i = 0 ; i < e . dataTransfer . items . length ; i ++ ) {
if ( e . dataTransfer . items [ i ] . type === "text/plain" ) {
e . dataTransfer . items [ i ] . getAsString ( text => {
Events . fire ( 'send-text' , {
text : text ,
to : this . _peer . id
} ) ;
} ) ;
}
}
}
2018-09-21 16:05:03 +02:00
this . _onDragEnd ( ) ;
}
_onDragOver ( ) {
2023-11-08 20:36:46 +01:00
this . $el . setAttribute ( 'drop' , true ) ;
this . $xInstructions . setAttribute ( 'drop-peer' , true ) ;
2018-09-21 16:05:03 +02:00
}
_onDragEnd ( ) {
this . $el . removeAttribute ( 'drop' ) ;
2023-11-08 20:36:46 +01:00
this . $xInstructions . removeAttribute ( 'drop-peer' ) ;
2018-09-21 16:05:03 +02:00
}
_onRightClick ( e ) {
e . preventDefault ( ) ;
2023-03-01 10:04:37 +01:00
Events . fire ( 'text-recipient' , {
peerId : this . _peer . id ,
deviceName : e . target . closest ( 'x-peer' ) . querySelector ( '.name' ) . innerText
} ) ;
2018-09-21 16:05:03 +02:00
}
2023-03-01 10:04:37 +01:00
_onTouchStart ( e ) {
2018-09-21 16:05:03 +02:00
this . _touchStart = Date . now ( ) ;
2023-11-02 01:22:41 +01:00
this . _touchTimer = setTimeout ( ( ) => this . _onTouchEnd ( e ) , 610 ) ;
2018-09-21 16:05:03 +02:00
}
_onTouchEnd ( e ) {
if ( Date . now ( ) - this . _touchStart < 500 ) {
clearTimeout ( this . _touchTimer ) ;
2023-11-02 02:56:01 +01:00
}
else if ( this . _touchTimer ) { // this was a long tap
2023-03-01 10:04:37 +01:00
e . preventDefault ( ) ;
Events . fire ( 'text-recipient' , {
peerId : this . _peer . id ,
deviceName : e . target . closest ( 'x-peer' ) . querySelector ( '.name' ) . innerText
} ) ;
2018-09-21 16:05:03 +02:00
}
2023-03-01 10:04:37 +01:00
this . _touchTimer = null ;
2018-09-21 16:05:03 +02:00
}
}
class Dialog {
2023-02-08 12:55:28 +01:00
constructor ( id ) {
2018-09-21 16:05:03 +02:00
this . $el = $ ( id ) ;
2023-11-29 17:42:19 +01:00
this . $autoFocus = this . $el . querySelector ( '[autofocus]' ) ;
this . $xBackground = this . $el . querySelector ( 'x-background' ) ;
this . $closeBtns = this . $el . querySelectorAll ( '[close]' ) ;
this . $closeBtns . forEach ( el => {
2023-11-02 02:56:01 +01:00
el . addEventListener ( 'click' , _ => this . hide ( ) )
} ) ;
2023-10-11 17:56:40 +02:00
Events . on ( 'peer-disconnected' , e => this . _onPeerDisconnected ( e . detail ) ) ;
2018-09-21 16:05:03 +02:00
}
2023-11-24 16:25:30 +01:00
static anyDialogShown ( ) {
return document . querySelectorAll ( 'x-dialog[show]' ) . length > 0 ;
}
2018-09-21 16:05:03 +02:00
show ( ) {
2023-11-29 17:42:19 +01:00
if ( this . $xBackground ) {
this . $xBackground . scrollTop = 0 ;
}
2023-11-08 20:36:46 +01:00
this . $el . setAttribute ( 'show' , true ) ;
2023-11-29 17:42:19 +01:00
if ( ! window . isMobile && this . $autoFocus ) {
this . $autoFocus . focus ( ) ;
}
2018-09-21 16:05:03 +02:00
}
2023-05-09 03:21:10 +02:00
isShown ( ) {
return ! ! this . $el . attributes [ "show" ] ;
}
2018-09-21 16:05:03 +02:00
hide ( ) {
this . $el . removeAttribute ( 'show' ) ;
2023-12-12 14:19:41 +01:00
if ( ! window . isMobile ) {
2023-01-10 05:07:57 +01:00
document . activeElement . blur ( ) ;
window . blur ( ) ;
}
2023-11-20 03:53:43 +01:00
document . title = 'PairDrop | Transfer Files Cross-Platform. No Setup, No Signup.' ;
2023-10-11 23:22:10 +02:00
changeFavicon ( "images/favicon-96x96.png" ) ;
2023-05-09 03:21:10 +02:00
this . correspondingPeerId = undefined ;
2023-01-23 04:51:22 +01:00
}
_onPeerDisconnected ( peerId ) {
2023-05-09 03:21:10 +02:00
if ( this . isShown ( ) && this . correspondingPeerId === peerId ) {
2023-01-23 04:51:22 +01:00
this . hide ( ) ;
2023-07-06 21:29:36 +02:00
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.selected-peer-left" ) ) ;
2023-01-23 04:51:22 +01:00
}
2018-09-21 16:05:03 +02:00
}
2023-11-24 16:25:30 +01:00
_evaluateOverflowing ( element ) {
if ( element . clientHeight < element . scrollHeight ) {
element . classList . add ( 'overflowing' ) ;
}
else {
element . classList . remove ( 'overflowing' ) ;
}
}
2018-09-21 16:05:03 +02:00
}
2023-08-30 14:57:40 +02:00
class LanguageSelectDialog extends Dialog {
constructor ( ) {
super ( 'language-select-dialog' ) ;
this . $languageSelectBtn = $ ( 'language-selector' ) ;
this . $languageSelectBtn . addEventListener ( 'click' , _ => this . show ( ) ) ;
2023-12-11 21:44:13 +01:00
this . $languageButtons = this . $el . querySelectorAll ( ".language-buttons .btn" ) ;
2023-08-30 14:57:40 +02:00
this . $languageButtons . forEach ( $btn => {
$btn . addEventListener ( "click" , e => this . selectLanguage ( e ) ) ;
} )
Events . on ( 'keydown' , e => this . _onKeyDown ( e ) ) ;
}
_onKeyDown ( e ) {
2023-11-24 16:25:30 +01:00
if ( ! this . isShown ( ) ) return ;
if ( e . code === "Escape" ) {
2023-08-30 14:57:40 +02:00
this . hide ( ) ;
}
}
show ( ) {
2023-12-11 21:44:13 +01:00
let locale = Localization . getLocale ( ) ;
this . currentLanguageBtn = Localization . isSystemLocale ( )
? this . $languageButtons [ 0 ]
: this . $el . querySelector ( ` .btn[value=" ${ locale } "] ` ) ;
this . currentLanguageBtn . classList . add ( "current" ) ;
2023-08-30 14:57:40 +02:00
super . show ( ) ;
}
2023-12-11 21:44:13 +01:00
hide ( ) {
this . currentLanguageBtn . classList . remove ( "current" ) ;
super . hide ( ) ;
}
2023-08-30 14:57:40 +02:00
selectLanguage ( e ) {
e . preventDefault ( )
let languageCode = e . target . value ;
if ( languageCode ) {
2023-11-24 17:32:40 +01:00
localStorage . setItem ( 'language_code' , languageCode ) ;
2023-11-02 02:56:01 +01:00
}
else {
2023-11-24 17:32:40 +01:00
localStorage . removeItem ( 'language_code' ) ;
2023-08-30 14:57:40 +02:00
}
Localization . setTranslation ( languageCode )
. then ( _ => this . hide ( ) ) ;
}
}
2018-09-21 16:05:03 +02:00
class ReceiveDialog extends Dialog {
2023-02-08 12:55:28 +01:00
constructor ( id ) {
super ( id ) ;
2023-03-03 12:01:43 +01:00
this . $fileDescription = this . $el . querySelector ( '.file-description' ) ;
this . $displayName = this . $el . querySelector ( '.display-name' ) ;
this . $fileStem = this . $el . querySelector ( '.file-stem' ) ;
this . $fileExtension = this . $el . querySelector ( '.file-extension' ) ;
this . $fileOther = this . $el . querySelector ( '.file-other' ) ;
this . $fileSize = this . $el . querySelector ( '.file-size' ) ;
this . $previewBox = this . $el . querySelector ( '.file-preview' ) ;
this . $receiveTitle = this . $el . querySelector ( 'h2:first-of-type' ) ;
2023-01-17 10:41:50 +01:00
}
_formatFileSize ( bytes ) {
2023-10-12 20:13:40 +02:00
// 1 GB = 1e3 MB = 1e6 KB = 1e9 B
if ( bytes >= 1e9 ) {
const rndGigabytes = Math . round ( 10 * bytes / 1e9 ) / 10 ;
return ` ${ rndGigabytes } GB ` ;
2023-11-02 02:56:01 +01:00
}
2023-10-12 20:13:40 +02:00
else if ( bytes >= 1e6 ) {
const rndMegabytes = Math . round ( 10 * bytes / 1e6 ) / 10 ;
return ` ${ rndMegabytes } MB ` ;
2023-11-02 02:56:01 +01:00
}
2023-10-12 20:13:40 +02:00
else if ( bytes >= ( 1e3 ) ) {
const rndKilobytes = Math . round ( 10 * bytes / 1e3 ) / 10 ;
return ` ${ rndKilobytes } KB ` ;
2023-11-02 02:56:01 +01:00
}
else {
2023-10-12 20:13:40 +02:00
return ` ${ bytes } bytes ` ;
2023-01-17 10:41:50 +01:00
}
}
2023-03-03 12:01:43 +01:00
2023-09-13 18:15:01 +02:00
_parseFileData ( displayName , connectionHash , files , imagesOnly , totalSize , badgeClassName ) {
2023-09-13 17:44:49 +02:00
let fileOther = "" ;
if ( files . length === 2 ) {
fileOther = imagesOnly
? Localization . getTranslation ( "dialogs.file-other-description-image" )
: Localization . getTranslation ( "dialogs.file-other-description-file" ) ;
2023-11-02 02:56:01 +01:00
}
2024-02-05 20:39:36 +01:00
else if ( files . length > 2 ) {
2023-09-13 17:44:49 +02:00
fileOther = imagesOnly
? Localization . getTranslation ( "dialogs.file-other-description-image-plural" , null , { count : files . length - 1 } )
: Localization . getTranslation ( "dialogs.file-other-description-file-plural" , null , { count : files . length - 1 } ) ;
2023-03-03 12:01:43 +01:00
}
const fileName = files [ 0 ] . name ;
const fileNameSplit = fileName . split ( '.' ) ;
const fileExtension = '.' + fileNameSplit [ fileNameSplit . length - 1 ] ;
2024-02-05 20:39:36 +01:00
const fileStem = fileName . substring ( 0 , fileName . length - fileExtension . length ) ;
const fileSize = this . _formatFileSize ( totalSize ) ;
this . $fileOther . innerText = fileOther ;
this . $fileStem . innerText = fileStem ;
2023-03-03 12:01:43 +01:00
this . $fileExtension . innerText = fileExtension ;
2024-02-05 20:39:36 +01:00
this . $fileSize . innerText = fileSize ;
2023-03-03 12:01:43 +01:00
this . $displayName . innerText = displayName ;
2023-03-06 15:39:24 +01:00
this . $displayName . title = connectionHash ;
2023-09-13 18:15:01 +02:00
this . $displayName . classList . remove ( "badge-room-ip" , "badge-room-secret" , "badge-room-public-id" ) ;
this . $displayName . classList . add ( badgeClassName )
2023-03-03 12:01:43 +01:00
}
2023-01-17 10:41:50 +01:00
}
2023-01-17 14:19:51 +01:00
2023-01-17 10:41:50 +01:00
class ReceiveFileDialog extends ReceiveDialog {
2018-09-21 16:05:03 +02:00
constructor ( ) {
2023-03-01 10:44:57 +01:00
super ( 'receive-file-dialog' ) ;
2023-01-17 10:41:50 +01:00
2023-03-03 12:01:43 +01:00
this . $downloadBtn = this . $el . querySelector ( '#download-btn' ) ;
this . $shareBtn = this . $el . querySelector ( '#share-btn' ) ;
2023-01-17 10:41:50 +01:00
2023-09-13 18:15:01 +02:00
Events . on ( 'files-received' , e => this . _onFilesReceived ( e . detail . peerId , e . detail . files , e . detail . imagesOnly , e . detail . totalSize ) ) ;
2024-02-08 01:36:20 +01:00
this . _filesDataQueue = [ ] ;
2018-09-21 16:05:03 +02:00
}
2023-11-24 16:25:30 +01:00
async _onFilesReceived ( peerId , files , imagesOnly , totalSize ) {
2024-02-08 01:36:20 +01:00
const descriptor = this . _getDescriptor ( files , imagesOnly ) ;
2023-09-13 18:15:01 +02:00
const displayName = $ ( peerId ) . ui . _displayName ( ) ;
const connectionHash = $ ( peerId ) . ui . _connectionHash ;
const badgeClassName = $ ( peerId ) . ui . _badgeClassName ( ) ;
2024-02-08 01:36:20 +01:00
this . _filesDataQueue . push ( {
2023-09-13 18:15:01 +02:00
peerId : peerId ,
files : files ,
imagesOnly : imagesOnly ,
totalSize : totalSize ,
2024-02-08 01:36:20 +01:00
descriptor : descriptor ,
displayName : displayName ,
connectionHash : connectionHash ,
2023-09-13 18:15:01 +02:00
badgeClassName : badgeClassName
} ) ;
2024-02-05 02:09:40 +01:00
audioPlayer . playBlop ( ) ;
2023-11-24 16:25:30 +01:00
2024-02-08 01:36:20 +01:00
await this . _processFiles ( ) ;
2018-09-21 16:05:03 +02:00
}
2024-02-05 20:39:36 +01:00
canShareFilesViaMenu ( files ) {
return window . isMobile && ! ! navigator . share && navigator . canShare ( { files } ) ;
}
2024-02-08 01:36:20 +01:00
async _processFiles ( ) {
if ( this . _busy || ! this . _filesDataQueue . length ) return ;
this . _busy = true ;
Events . fire ( 'set-progress' , { peerId : this . _peerId , progress : 0 , status : 'process' } ) ;
this . _data = this . _filesDataQueue . shift ( ) ;
const documentTitleTranslation = this . _data . files . length === 1
2024-02-05 20:39:36 +01:00
? ` ${ Localization . getTranslation ( "document-titles.file-received" ) } - PairDrop `
2024-02-08 01:36:20 +01:00
: ` ${ Localization . getTranslation ( "document-titles.file-received-plural" , null , { count : this . _data . files . length } ) } - PairDrop ` ;
2024-02-05 20:39:36 +01:00
// If possible, share via menu - else download files
2024-02-08 01:36:20 +01:00
const shareViaMenu = this . canShareFilesViaMenu ( this . _data . files ) ;
2024-02-05 20:39:36 +01:00
2024-02-08 01:36:20 +01:00
this . _parseFileData (
this . _data . displayName ,
this . _data . connectionHash ,
this . _data . files ,
this . _data . imagesOnly ,
this . _data . totalSize ,
this . _data . badgeClassName
) ;
this . _setTitle ( this . _data . descriptor ) ;
2024-02-05 20:39:36 +01:00
2024-02-08 01:36:20 +01:00
await this . _addFileToPreviewBox ( this . _data . files [ 0 ] ) ;
2024-02-05 20:39:36 +01:00
document . title = documentTitleTranslation ;
changeFavicon ( "images/favicon-96x96-notification.png" ) ;
if ( shareViaMenu ) {
2024-02-08 01:36:20 +01:00
await this . _setupShareMenu ( ) ;
2024-02-05 20:39:36 +01:00
}
else {
2024-02-08 01:36:20 +01:00
await this . _setupDownload ( ) ;
2024-02-05 20:39:36 +01:00
}
2024-02-08 01:36:20 +01:00
Events . fire ( 'set-progress' , { peerId : this . _data . peerId , progress : 0 , status : "receive-complete" } ) ;
2024-02-05 20:39:36 +01:00
}
_getDescriptor ( files , imagesOnly ) {
let descriptor ;
if ( files . length === 1 ) {
descriptor = imagesOnly
? Localization . getTranslation ( "dialogs.title-image" )
: Localization . getTranslation ( "dialogs.title-file" ) ;
}
else {
descriptor = imagesOnly
? Localization . getTranslation ( "dialogs.title-image-plural" )
: Localization . getTranslation ( "dialogs.title-file-plural" ) ;
}
return descriptor ;
}
_setTitle ( descriptor ) {
this . $receiveTitle . innerText = Localization . getTranslation ( "dialogs.receive-title" , null , { descriptor : descriptor } ) ;
}
2024-02-08 01:36:20 +01:00
2023-01-17 10:41:50 +01:00
createPreviewElement ( file ) {
2023-01-21 18:21:58 +01:00
return new Promise ( ( resolve , reject ) => {
2023-03-13 00:04:48 +01:00
try {
let mime = file . type . split ( '/' ) [ 0 ]
let previewElement = {
image : 'img' ,
audio : 'audio' ,
video : 'video'
}
2023-01-17 10:41:50 +01:00
2023-03-13 00:04:48 +01:00
if ( Object . keys ( previewElement ) . indexOf ( mime ) === - 1 ) {
2024-02-05 20:39:36 +01:00
reject ( 'Preview is only supported for images, audio and video' ) ;
return ;
2023-03-13 00:04:48 +01:00
}
2024-02-05 20:39:36 +01:00
let element = document . createElement ( previewElement [ mime ] ) ;
let timeout = setTimeout ( _ => {
reject ( 'Preview could not be loaded: timeout' , file . type ) ;
} , 1000 ) ;
element . controls = true ;
element . onload = _ => {
clearTimeout ( timeout ) ;
resolve ( element ) ;
} ;
element . onloadeddata = _ => {
clearTimeout ( timeout ) ;
resolve ( element ) ;
} ;
element . onerror = _ => {
clearTimeout ( timeout ) ;
reject ( 'Preview could not be loaded from type' , file . type ) ;
} ;
element . src = URL . createObjectURL ( file ) ;
2023-03-13 00:04:48 +01:00
} catch ( e ) {
2024-02-05 20:39:36 +01:00
reject ( 'Preview could not be loaded from type' , file . type ) ;
2023-01-17 10:41:50 +01:00
}
} ) ;
}
2024-02-05 20:39:36 +01:00
_disableButton ( $button , duration ) {
$button . style . pointerEvents = "none" ;
2023-01-27 01:27:22 +01:00
2024-02-05 20:39:36 +01:00
setTimeout ( ( ) => {
$button . style . pointerEvents = "unset" ;
} , duration ) ;
}
2024-02-08 01:36:20 +01:00
async _setShareButton ( ) {
2024-02-05 20:39:36 +01:00
this . $shareBtn . onclick = _ => {
2024-02-08 01:36:20 +01:00
navigator . share ( { files : this . _data . files } )
. catch ( async err => {
2024-02-05 20:39:36 +01:00
Logger . error ( err ) ;
2024-02-08 01:36:20 +01:00
if ( err . name === 'AbortError' && err . message === 'Abort due to error while reading files.' ) {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.error-sharing-size" ) ) ;
}
else {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.error-sharing-default" ) ) ;
}
// Fallback to download
this . _tidyUpButtons ( ) ;
await this . _setupDownload ( )
2024-02-05 20:39:36 +01:00
} ) ;
// Prevent clicking the button multiple times
this . _disableButton ( this . $shareBtn , 2000 ) ;
2023-03-03 12:01:43 +01:00
}
2024-02-05 20:39:36 +01:00
this . $shareBtn . removeAttribute ( 'disabled' ) ;
this . $shareBtn . removeAttribute ( 'hidden' ) ;
}
2023-01-17 10:41:50 +01:00
2024-02-08 01:36:20 +01:00
async _processDataAsZip ( ) {
2024-02-13 18:23:27 +01:00
let zipObjectUrl = "" ;
let zipName = "" ;
2024-02-08 01:36:20 +01:00
let sendAsZip = false ;
2024-02-05 20:39:36 +01:00
2024-02-08 01:36:20 +01:00
const tooBigToZip = window . iOS && this . _data . totalSize > 256000000 ;
2024-02-13 18:23:27 +01:00
2024-02-08 01:36:20 +01:00
if ( this . _data . files . length > 1 && ! tooBigToZip ) {
2024-02-13 18:23:27 +01:00
Events . fire ( 'set-progress' , {
peerId : this . _data . peerId ,
progress : 0 ,
status : 'process'
2024-02-07 23:58:15 +01:00
} ) ;
2024-02-05 20:39:36 +01:00
2024-02-13 18:23:27 +01:00
zipObjectUrl = await zipper . getObjectUrlOfZipFile ( this . _data . files , zipProgress => {
Events . fire ( 'set-progress' , {
peerId : this . _data . peerId ,
progress : zipProgress / this . _data . totalSize ,
status : 'process'
} ) ;
} ) ;
zipName = this . _createZipFilename ( ) ;
sendAsZip = ! ! zipObjectUrl ;
2023-01-17 10:41:50 +01:00
}
2024-02-13 18:23:27 +01:00
return { sendAsZip , zipObjectUrl , zipName } ;
2024-02-05 20:39:36 +01:00
}
2023-01-17 10:41:50 +01:00
2024-02-08 01:36:20 +01:00
_setDownloadButtonToZip ( zipFileUrl , zipFileName ) {
const downloadSuccessfulTranslation = Localization . getTranslation ( "notifications.download-successful" , null , { descriptor : this . _data . descriptor } ) ;
2024-02-05 20:39:36 +01:00
this . downloadSuccessful = false ;
2023-03-03 12:01:43 +01:00
this . $downloadBtn . onclick = _ => {
2024-02-05 20:39:36 +01:00
this . _downloadFileFromUrl ( zipFileUrl , zipFileName )
2023-01-17 10:41:50 +01:00
2024-02-05 20:39:36 +01:00
Events . fire ( 'notify-user' , downloadSuccessfulTranslation ) ;
this . downloadSuccessful = true ;
2023-11-24 16:25:30 +01:00
2024-02-05 20:39:36 +01:00
this . hide ( ) ;
2023-03-03 12:01:43 +01:00
} ;
2024-02-05 20:39:36 +01:00
}
2023-01-17 10:41:50 +01:00
2024-02-08 01:36:20 +01:00
_setDownloadButtonToFiles ( files ) {
const downloadTranslation = Localization . getTranslation ( "dialogs.download" ) ;
const downloadSuccessfulTranslation = Localization . getTranslation ( "notifications.download-successful" , null , { descriptor : this . _data . descriptor } ) ;
this . $downloadBtn . innerText = files . length === 1
? downloadTranslation
: ` ${ downloadTranslation } 1/ ${ files . length } ` ;
2024-02-05 20:39:36 +01:00
this . downloadSuccessful = false ;
let i = 0 ;
2023-07-06 21:29:36 +02:00
2024-02-05 20:39:36 +01:00
this . $downloadBtn . onclick = _ => {
this . _disableButton ( this . $shareBtn , 2000 ) ;
this . _downloadFiles ( [ files [ i ] ] ) ;
if ( i < files . length - 1 ) {
i ++ ;
this . $downloadBtn . innerText = ` ${ downloadTranslation } ${ i + 1 } / ${ files . length } ` ;
return
2023-03-03 12:01:43 +01:00
}
2023-03-13 00:04:48 +01:00
2024-02-05 20:39:36 +01:00
Events . fire ( 'notify-user' , downloadSuccessfulTranslation ) ;
this . downloadSuccessful = true ;
this . hide ( )
} ;
}
async _addFileToPreviewBox ( file ) {
try {
const previewElement = await this . createPreviewElement ( file )
this . $previewBox . appendChild ( previewElement ) ;
}
catch ( e ) {
Logger . log ( e ) ;
}
2023-01-17 10:41:50 +01:00
}
2024-02-05 20:39:36 +01:00
_downloadFileFromUrl ( url , name ) {
let tmpZipBtn = document . createElement ( "a" ) ;
tmpZipBtn . download = name ;
tmpZipBtn . href = url ;
tmpZipBtn . click ( ) ;
}
_downloadFiles ( files ) {
2023-03-03 12:01:43 +01:00
let tmpBtn = document . createElement ( "a" ) ;
2024-02-05 20:39:36 +01:00
for ( let i = 0 ; i < files . length ; i ++ ) {
2023-03-03 12:01:43 +01:00
tmpBtn . download = files [ i ] . name ;
tmpBtn . href = URL . createObjectURL ( files [ i ] ) ;
tmpBtn . click ( ) ;
}
}
2024-02-05 20:39:36 +01:00
_createZipFilename ( ) {
let now = new Date ( Date . now ( ) ) ;
let year = now . getFullYear ( ) . toString ( ) ;
let month = ( now . getMonth ( ) + 1 ) . toString ( ) ;
let date = now . getDate ( ) . toString ( ) ;
let hours = now . getHours ( ) . toString ( ) ;
let minutes = now . getMinutes ( ) . toString ( ) ;
// Pad single letter strings with preceding "0"
month = month . length < 2 ? "0" + month : month ;
date = date . length < 2 ? "0" + date : date ;
hours = hours . length < 2 ? "0" + hours : hours ;
minutes = minutes . length < 2 ? "0" + minutes : minutes ;
return ` PairDrop_files_ ${ year } ${ month } ${ date } _ ${ hours } ${ minutes } .zip ` ;
}
2024-02-08 01:36:20 +01:00
async _setupShareMenu ( ) {
await this . _setShareButton ( ) ;
2024-02-05 20:39:36 +01:00
// always show dialog
this . show ( ) ;
2024-02-08 01:36:20 +01:00
2024-02-05 20:39:36 +01:00
// open share menu automatically
setTimeout ( ( ) => {
this . $shareBtn . click ( ) ;
} , 500 ) ;
}
2024-02-08 01:36:20 +01:00
async _setupDownload ( ) {
this . $downloadBtn . innerText = Localization . getTranslation ( "dialogs.download" ) ;
this . $downloadBtn . removeAttribute ( 'disabled' ) ;
this . $downloadBtn . removeAttribute ( 'hidden' ) ;
2024-02-13 18:23:27 +01:00
let { sendAsZip , zipObjectUrl , zipName } = await this . _processDataAsZip ( ) ;
2024-02-08 01:36:20 +01:00
// If single file or zipping failed -> download files individually -> else download zip
if ( sendAsZip ) {
2024-02-13 18:23:27 +01:00
this . _setDownloadButtonToZip ( zipObjectUrl , zipName ) ;
2024-02-08 01:36:20 +01:00
} else {
this . _setDownloadButtonToFiles ( this . _data . files ) ;
}
2024-02-05 20:39:36 +01:00
2024-02-08 01:36:20 +01:00
if ( ! sendAsZip ) {
2024-02-05 20:39:36 +01:00
this . show ( ) ;
return ;
}
// download automatically if zipped or if only one file is received
this . $downloadBtn . click ( ) ;
2024-02-07 23:58:15 +01:00
2024-02-08 01:36:20 +01:00
// if automatic download fails -> show dialog after 1 s
2024-02-05 20:39:36 +01:00
setTimeout ( ( ) => {
if ( ! this . downloadSuccessful ) {
this . show ( ) ;
}
} , 1000 ) ;
}
_tidyUpButtons ( ) {
this . $shareBtn . setAttribute ( 'disabled' , true ) ;
this . $shareBtn . setAttribute ( 'hidden' , true ) ;
this . $shareBtn . onclick = null ;
this . $downloadBtn . setAttribute ( 'disabled' , true ) ;
this . $downloadBtn . setAttribute ( 'hidden' , true ) ;
this . $downloadBtn . onclick = null ;
}
_tidyUpPreviewBox ( ) {
this . $previewBox . innerHTML = '' ;
}
2023-01-17 10:41:50 +01:00
hide ( ) {
super . hide ( ) ;
2023-11-24 16:25:30 +01:00
setTimeout ( async ( ) => {
2024-02-05 20:39:36 +01:00
this . _tidyUpButtons ( ) ;
this . _tidyUpPreviewBox ( ) ;
2023-11-24 16:25:30 +01:00
this . _busy = false ;
2024-02-05 20:39:36 +01:00
2024-02-08 01:36:20 +01:00
await this . _processFiles ( ) ;
2023-11-24 16:25:30 +01:00
} , 300 ) ;
2023-01-17 10:41:50 +01:00
}
}
class ReceiveRequestDialog extends ReceiveDialog {
2018-09-21 16:05:03 +02:00
2023-01-17 10:41:50 +01:00
constructor ( ) {
2023-03-01 10:44:57 +01:00
super ( 'receive-request-dialog' ) ;
2023-01-17 10:41:50 +01:00
2023-03-01 10:44:57 +01:00
this . $acceptRequestBtn = this . $el . querySelector ( '#accept-request' ) ;
this . $declineRequestBtn = this . $el . querySelector ( '#decline-request' ) ;
2023-01-17 10:41:50 +01:00
this . $acceptRequestBtn . addEventListener ( 'click' , _ => this . _respondToFileTransferRequest ( true ) ) ;
this . $declineRequestBtn . addEventListener ( 'click' , _ => this . _respondToFileTransferRequest ( false ) ) ;
2024-02-06 14:42:09 +01:00
this . _filesTransferRequestQueue = [ ] ;
this . _currentRequest = null ;
2023-01-17 10:41:50 +01:00
Events . on ( 'files-transfer-request' , e => this . _onRequestFileTransfer ( e . detail . request , e . detail . peerId ) )
Events . on ( 'keydown' , e => this . _onKeyDown ( e ) ) ;
}
_onKeyDown ( e ) {
2023-11-24 16:25:30 +01:00
if ( ! this . isShown ( ) ) return ;
if ( e . code === "Escape" ) {
2023-02-10 03:26:08 +01:00
this . _respondToFileTransferRequest ( false ) ;
2020-12-20 21:30:28 +01:00
}
2023-01-17 10:41:50 +01:00
}
2023-01-10 16:03:52 +01:00
2023-01-17 10:41:50 +01:00
_onRequestFileTransfer ( request , peerId ) {
2023-02-08 12:55:28 +01:00
this . _filesTransferRequestQueue . push ( { request : request , peerId : peerId } ) ;
2023-05-09 03:21:10 +02:00
if ( this . isShown ( ) ) return ;
2023-02-08 12:55:28 +01:00
this . _dequeueRequests ( ) ;
}
_dequeueRequests ( ) {
2024-02-06 14:42:09 +01:00
if ( ! this . _filesTransferRequestQueue . length ) {
this . _currentRequest = null ;
return ;
}
2023-02-08 12:55:28 +01:00
let { request , peerId } = this . _filesTransferRequestQueue . shift ( ) ;
2024-02-06 14:42:09 +01:00
this . _currentRequest = request ;
this . _showRequestDialog ( request , peerId ) ;
2023-02-08 12:55:28 +01:00
}
2024-02-05 20:39:36 +01:00
_addThumbnailToPreviewBox ( thumbnailData ) {
if ( thumbnailData && thumbnailData . substring ( 0 , 22 ) === "data:image/jpeg;base64" ) {
let element = document . createElement ( 'img' ) ;
element . src = thumbnailData ;
this . $previewBox . appendChild ( element )
}
}
2023-02-08 12:55:28 +01:00
_showRequestDialog ( request , peerId ) {
2023-01-23 04:51:22 +01:00
this . correspondingPeerId = peerId ;
2023-01-10 16:03:52 +01:00
2024-02-05 20:39:36 +01:00
const transferRequestTitleTranslation = request . imagesOnly
? Localization . getTranslation ( 'document-titles.image-transfer-requested' )
: Localization . getTranslation ( 'document-titles.file-transfer-requested' ) ;
2023-03-03 12:01:43 +01:00
const displayName = $ ( peerId ) . ui . _displayName ( ) ;
2023-03-06 15:39:24 +01:00
const connectionHash = $ ( peerId ) . ui . _connectionHash ;
2023-09-13 18:15:01 +02:00
const badgeClassName = $ ( peerId ) . ui . _badgeClassName ( ) ;
this . _parseFileData ( displayName , connectionHash , request . header , request . imagesOnly , request . totalSize , badgeClassName ) ;
2024-02-05 20:39:36 +01:00
this . _addThumbnailToPreviewBox ( request . thumbnailDataUrl ) ;
2023-01-17 10:41:50 +01:00
2024-02-05 20:39:36 +01:00
this . $receiveTitle . innerText = transferRequestTitleTranslation ;
2023-10-06 02:57:46 +02:00
2024-02-05 20:39:36 +01:00
document . title = ` ${ transferRequestTitleTranslation } - PairDrop ` ;
2023-10-11 23:22:10 +02:00
changeFavicon ( "images/favicon-96x96-notification.png" ) ;
2023-11-24 16:25:30 +01:00
this . $acceptRequestBtn . removeAttribute ( 'disabled' ) ;
2023-02-10 03:26:08 +01:00
this . show ( ) ;
2018-09-21 16:05:03 +02:00
}
2023-01-17 10:41:50 +01:00
_respondToFileTransferRequest ( accepted ) {
Events . fire ( 'respond-to-files-transfer-request' , {
2023-01-23 04:51:22 +01:00
to : this . correspondingPeerId ,
2023-01-17 10:41:50 +01:00
accepted : accepted
} )
if ( accepted ) {
2023-01-23 04:51:22 +01:00
Events . fire ( 'set-progress' , { peerId : this . correspondingPeerId , progress : 0 , status : 'wait' } ) ;
2024-02-06 14:42:09 +01:00
// Prevent device from sleeping
2023-01-17 14:19:51 +01:00
NoSleepUI . enable ( ) ;
2018-09-21 16:05:03 +02:00
}
2023-02-08 12:55:28 +01:00
this . hide ( ) ;
2018-09-21 16:05:03 +02:00
}
hide ( ) {
2023-05-04 17:38:51 +02:00
// clear previewBox after dialog is closed
2023-11-24 16:25:30 +01:00
setTimeout ( ( ) => {
this . $previewBox . innerHTML = '' ;
this . $acceptRequestBtn . setAttribute ( 'disabled' , true ) ;
} , 300 ) ;
2023-05-04 17:38:51 +02:00
2018-09-21 16:05:03 +02:00
super . hide ( ) ;
2023-05-04 17:38:51 +02:00
// show next request
2023-11-24 16:25:30 +01:00
setTimeout ( ( ) => this . _dequeueRequests ( ) , 300 ) ;
2020-12-20 21:30:28 +01:00
}
2018-09-21 16:05:03 +02:00
}
2023-09-13 18:15:01 +02:00
class InputKeyContainer {
constructor ( inputKeyContainer , evaluationRegex , onAllCharsFilled , onNoAllCharsFilled , onLastCharFilled ) {
2023-01-10 05:07:57 +01:00
2023-09-13 18:15:01 +02:00
this . $inputKeyContainer = inputKeyContainer ;
this . $inputKeyChars = inputKeyContainer . querySelectorAll ( 'input' ) ;
2023-01-10 05:07:57 +01:00
2023-09-13 18:15:01 +02:00
this . $inputKeyChars . forEach ( char => char . addEventListener ( 'input' , e => this . _onCharsInput ( e ) ) ) ;
this . $inputKeyChars . forEach ( char => char . addEventListener ( 'keydown' , e => this . _onCharsKeyDown ( e ) ) ) ;
this . $inputKeyChars . forEach ( char => char . addEventListener ( 'keyup' , e => this . _onCharsKeyUp ( e ) ) ) ;
this . $inputKeyChars . forEach ( char => char . addEventListener ( 'focus' , e => e . target . select ( ) ) ) ;
this . $inputKeyChars . forEach ( char => char . addEventListener ( 'click' , e => e . target . select ( ) ) ) ;
2023-05-10 21:20:11 +02:00
2023-09-13 18:15:01 +02:00
this . evalRgx = evaluationRegex
this . _onAllCharsFilled = onAllCharsFilled ;
this . _onNotAllCharsFilled = onNoAllCharsFilled ;
this . _onLastCharFilled = onLastCharFilled ;
}
_enableChars ( ) {
2023-11-08 20:36:46 +01:00
this . $inputKeyChars . forEach ( char => char . removeAttribute ( 'disabled' ) ) ;
2023-09-13 18:15:01 +02:00
}
_disableChars ( ) {
2023-11-08 20:36:46 +01:00
this . $inputKeyChars . forEach ( char => char . setAttribute ( 'disabled' , true ) ) ;
2023-09-13 18:15:01 +02:00
}
_clearChars ( ) {
this . $inputKeyChars . forEach ( char => char . value = '' ) ;
}
_cleanUp ( ) {
this . _clearChars ( ) ;
this . _disableChars ( ) ;
2023-01-10 05:07:57 +01:00
}
_onCharsInput ( e ) {
2023-09-13 18:15:01 +02:00
if ( ! e . target . value . match ( this . evalRgx ) ) {
e . target . value = '' ;
return ;
}
this . _evaluateKeyChars ( ) ;
2023-01-17 10:11:17 +01:00
2023-01-10 05:07:57 +01:00
let nextSibling = e . target . nextElementSibling ;
if ( nextSibling ) {
e . preventDefault ( ) ;
nextSibling . focus ( ) ;
}
}
_onCharsKeyDown ( e ) {
let previousSibling = e . target . previousElementSibling ;
let nextSibling = e . target . nextElementSibling ;
if ( e . key === "Backspace" && previousSibling && ! e . target . value ) {
previousSibling . value = '' ;
previousSibling . focus ( ) ;
2023-11-02 02:56:01 +01:00
}
else if ( e . key === "ArrowRight" && nextSibling ) {
2023-01-10 05:07:57 +01:00
e . preventDefault ( ) ;
nextSibling . focus ( ) ;
2023-11-02 02:56:01 +01:00
}
else if ( e . key === "ArrowLeft" && previousSibling ) {
2023-01-10 05:07:57 +01:00
e . preventDefault ( ) ;
previousSibling . focus ( ) ;
}
}
2023-09-13 18:15:01 +02:00
_onCharsKeyUp ( e ) {
// deactivate submit btn when e.g. using backspace to clear element
if ( ! e . target . value ) {
this . _evaluateKeyChars ( ) ;
}
}
_getInputKey ( ) {
let key = "" ;
this . $inputKeyChars . forEach ( char => {
key += char . value ;
} )
return key ;
}
_onPaste ( pastedKey ) {
let rgx = new RegExp ( "(?!" + this . evalRgx . source + ")." , "g" ) ;
pastedKey = pastedKey . replace ( rgx , '' ) . substring ( 0 , this . $inputKeyChars . length )
for ( let i = 0 ; i < pastedKey . length ; i ++ ) {
document . activeElement . value = pastedKey . charAt ( i ) ;
2023-01-10 05:07:57 +01:00
let nextSibling = document . activeElement . nextElementSibling ;
if ( ! nextSibling ) break ;
nextSibling . focus ( ) ;
}
2023-09-13 18:15:01 +02:00
this . _evaluateKeyChars ( ) ;
2023-01-10 05:07:57 +01:00
}
2023-09-13 18:15:01 +02:00
_evaluateKeyChars ( ) {
if ( this . $inputKeyContainer . querySelectorAll ( 'input:placeholder-shown' ) . length > 0 ) {
this . _onNotAllCharsFilled ( ) ;
2023-11-02 02:56:01 +01:00
}
else {
2023-09-13 18:15:01 +02:00
this . _onAllCharsFilled ( ) ;
const lastCharFocused = document . activeElement === this . $inputKeyChars [ this . $inputKeyChars . length - 1 ] ;
if ( lastCharFocused ) {
this . _onLastCharFilled ( ) ;
2023-01-17 10:11:17 +01:00
}
2023-01-10 05:07:57 +01:00
}
}
2023-09-13 18:15:01 +02:00
focusLastChar ( ) {
let lastChar = this . $inputKeyChars [ this . $inputKeyChars . length - 1 ] ;
lastChar . focus ( ) ;
}
}
class PairDeviceDialog extends Dialog {
constructor ( ) {
super ( 'pair-device-dialog' ) ;
this . $pairDeviceHeaderBtn = $ ( 'pair-device' ) ;
this . $editPairedDevicesHeaderBtn = $ ( 'edit-paired-devices' ) ;
this . $footerInstructionsPairedDevices = $$ ( '.discovery-wrapper .badge-room-secret' ) ;
this . $key = this . $el . querySelector ( '.key' ) ;
this . $qrCode = this . $el . querySelector ( '.key-qr-code' ) ;
this . $form = this . $el . querySelector ( 'form' ) ;
this . $closeBtn = this . $el . querySelector ( '[close]' )
this . $pairSubmitBtn = this . $el . querySelector ( 'button[type="submit"]' ) ;
this . inputKeyContainer = new InputKeyContainer (
this . $el . querySelector ( '.input-key-container' ) ,
/\d/ ,
2023-11-08 20:36:46 +01:00
( ) => this . $pairSubmitBtn . removeAttribute ( 'disabled' ) ,
( ) => this . $pairSubmitBtn . setAttribute ( 'disabled' , true ) ,
2023-09-13 18:15:01 +02:00
( ) => this . _submit ( )
) ;
this . $pairDeviceHeaderBtn . addEventListener ( 'click' , _ => this . _pairDeviceInitiate ( ) ) ;
this . $form . addEventListener ( 'submit' , e => this . _onSubmit ( e ) ) ;
this . $closeBtn . addEventListener ( 'click' , _ => this . _close ( ) ) ;
Events . on ( 'keydown' , e => this . _onKeyDown ( e ) ) ;
Events . on ( 'ws-disconnected' , _ => this . hide ( ) ) ;
Events . on ( 'pair-device-initiated' , e => this . _onPairDeviceInitiated ( e . detail ) ) ;
Events . on ( 'pair-device-joined' , e => this . _onPairDeviceJoined ( e . detail . peerId , e . detail . roomSecret ) ) ;
2024-02-04 18:02:10 +01:00
Events . on ( 'peers' , e => this . _onPeers ( e . detail . peers , e . detail . roomType , e . detail . roomId ) ) ;
Events . on ( 'peer-joined' , e => this . _onPeerJoined ( e . detail . peer , e . detail . roomType , e . detail . roomId ) ) ;
2023-09-13 18:15:01 +02:00
Events . on ( 'pair-device-join-key-invalid' , _ => this . _onPublicRoomJoinKeyInvalid ( ) ) ;
Events . on ( 'pair-device-canceled' , e => this . _onPairDeviceCanceled ( e . detail ) ) ;
Events . on ( 'evaluate-number-room-secrets' , _ => this . _evaluateNumberRoomSecrets ( ) )
Events . on ( 'secret-room-deleted' , e => this . _onSecretRoomDeleted ( e . detail ) ) ;
this . $el . addEventListener ( 'paste' , e => this . _onPaste ( e ) ) ;
2023-10-11 20:19:19 +02:00
this . $qrCode . addEventListener ( 'click' , _ => this . _copyPairUrl ( ) ) ;
2023-09-13 18:15:01 +02:00
this . pairPeer = { } ;
}
_onKeyDown ( e ) {
2023-11-24 16:25:30 +01:00
if ( ! this . isShown ( ) ) return ;
if ( e . code === "Escape" ) {
// Timeout to prevent share mode from getting cancelled simultaneously
2023-11-02 01:22:41 +01:00
setTimeout ( ( ) => this . _close ( ) , 50 ) ;
2023-09-13 18:15:01 +02:00
}
}
_onPaste ( e ) {
e . preventDefault ( ) ;
2023-11-02 02:56:01 +01:00
let pastedKey = e . clipboardData
. getData ( "Text" )
. replace ( /\D/g , '' )
. substring ( 0 , 6 ) ;
2023-09-13 18:15:01 +02:00
this . inputKeyContainer . _onPaste ( pastedKey ) ;
}
2023-01-10 05:07:57 +01:00
_pairDeviceInitiate ( ) {
Events . fire ( 'pair-device-initiate' ) ;
}
2023-09-13 18:15:01 +02:00
_onPairDeviceInitiated ( msg ) {
this . pairKey = msg . pairKey ;
2023-01-10 05:07:57 +01:00
this . roomSecret = msg . roomSecret ;
2023-12-05 19:26:17 +01:00
this . _setKeyAndQRCode ( ) ;
this . inputKeyContainer . _enableChars ( ) ;
this . show ( ) ;
}
_setKeyAndQRCode ( ) {
2023-09-13 18:15:01 +02:00
this . $key . innerText = ` ${ this . pairKey . substring ( 0 , 3 ) } ${ this . pairKey . substring ( 3 , 6 ) } `
2023-12-05 19:26:17 +01:00
2023-01-10 05:07:57 +01:00
// Display the QR code for the url
const qr = new QRCode ( {
2023-10-11 20:19:19 +02:00
content : this . _getPairUrl ( ) ,
2023-11-20 05:23:21 +01:00
width : 130 ,
height : 130 ,
2023-10-23 19:39:33 +02:00
padding : 1 ,
2023-11-29 18:53:57 +01:00
background : 'white' ,
2023-10-23 19:39:33 +02:00
color : 'rgb(18, 18, 18)' ,
2023-01-10 05:07:57 +01:00
ecl : "L" ,
join : true
} ) ;
this . $qrCode . innerHTML = qr . svg ( ) ;
}
2023-10-11 20:19:19 +02:00
_getPairUrl ( ) {
2023-01-10 05:07:57 +01:00
let url = new URL ( location . href ) ;
2023-09-13 18:15:01 +02:00
url . searchParams . append ( 'pair_key' , this . pairKey )
2023-01-10 05:07:57 +01:00
return url . href ;
}
2023-10-11 20:19:19 +02:00
_copyPairUrl ( ) {
navigator . clipboard . writeText ( this . _getPairUrl ( ) )
. then ( _ => {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.pair-url-copied-to-clipboard" ) ) ;
} )
. catch ( _ => {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.copied-to-clipboard-error" ) ) ;
} )
}
2023-03-02 16:30:47 +01:00
_onSubmit ( e ) {
e . preventDefault ( ) ;
2023-09-13 18:15:01 +02:00
this . _submit ( ) ;
}
_submit ( ) {
let inputKey = this . inputKeyContainer . _getInputKey ( ) ;
this . _pairDeviceJoin ( inputKey ) ;
2023-01-10 05:07:57 +01:00
}
2023-09-13 18:15:01 +02:00
_pairDeviceJoin ( pairKey ) {
if ( /^\d{6}$/g . test ( pairKey ) ) {
Events . fire ( 'pair-device-join' , pairKey ) ;
this . inputKeyContainer . focusLastChar ( ) ;
2023-01-10 05:07:57 +01:00
}
}
2023-09-13 18:15:01 +02:00
_onPairDeviceJoined ( peerId , roomSecret ) {
// abort if peer is another tab on the same browser and remove room-type from gui
2023-05-04 17:38:51 +02:00
if ( BrowserTabsConnector . peerIsSameBrowser ( peerId ) ) {
2023-02-08 04:06:42 +01:00
this . _cleanUp ( ) ;
2023-05-10 21:20:11 +02:00
this . hide ( ) ;
2023-09-13 18:15:01 +02:00
Events . fire ( 'room-secrets-deleted' , [ roomSecret ] ) ;
2023-07-06 21:29:36 +02:00
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.pairing-tabs-error" ) ) ;
2023-05-04 17:38:51 +02:00
return ;
}
2023-05-10 21:20:11 +02:00
// save pairPeer and wait for it to connect to ensure both devices have gotten the roomSecret
this . pairPeer = {
"peerId" : peerId ,
"roomSecret" : roomSecret
} ;
}
2024-02-04 18:02:10 +01:00
_onPeers ( peers , roomType , roomId ) {
peers . forEach ( messagePeer => {
this . _evaluateJoinedPeer ( messagePeer , roomType , roomId ) ;
2023-05-10 21:20:11 +02:00
} ) ;
}
2024-02-04 18:02:10 +01:00
_onPeerJoined ( peer , roomType , roomId ) {
this . _evaluateJoinedPeer ( peer , roomType , roomId ) ;
2023-05-10 21:20:11 +02:00
}
2024-02-04 18:02:10 +01:00
_evaluateJoinedPeer ( peer , roomType , roomId ) {
2023-09-13 18:15:01 +02:00
const noPairPeerSaved = ! Object . keys ( this . pairPeer ) ;
2024-02-04 18:02:10 +01:00
const peerId = peer . id ;
2023-09-13 18:15:01 +02:00
if ( ! peerId || ! roomType || ! roomId || noPairPeerSaved ) return ;
2023-05-10 21:20:11 +02:00
const samePeerId = peerId === this . pairPeer . peerId ;
2023-09-13 18:15:01 +02:00
const sameRoomSecret = roomId === this . pairPeer . roomSecret ;
const typeIsSecret = roomType === "secret" ;
2023-05-10 21:20:11 +02:00
2023-09-13 18:15:01 +02:00
if ( ! samePeerId || ! sameRoomSecret || ! typeIsSecret ) return ;
2023-05-10 21:20:11 +02:00
2024-02-04 18:02:10 +01:00
this . _onPairPeerJoined ( peer , roomId ) ;
2023-05-10 21:20:11 +02:00
this . pairPeer = { } ;
}
2024-02-04 18:02:10 +01:00
_onPairPeerJoined ( peer , roomSecret ) {
2024-02-04 18:05:11 +01:00
const displayName = peer . name . displayName ;
const deviceName = peer . name . deviceName ;
2023-05-04 17:38:51 +02:00
2023-11-02 02:56:01 +01:00
PersistentStorage
. addRoomSecret ( roomSecret , displayName , deviceName )
2023-05-04 17:38:51 +02:00
. then ( _ => {
2023-07-06 21:29:36 +02:00
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.pairing-success" ) ) ;
2023-05-04 17:38:51 +02:00
this . _evaluateNumberRoomSecrets ( ) ;
} )
2023-11-02 01:22:41 +01:00
. finally ( ( ) => {
2023-05-04 17:38:51 +02:00
this . _cleanUp ( ) ;
2023-05-10 21:20:11 +02:00
this . hide ( ) ;
2023-05-04 17:38:51 +02:00
} )
. catch ( _ => {
2023-07-06 21:29:36 +02:00
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.pairing-not-persistent" ) ) ;
2023-05-04 17:38:51 +02:00
PersistentStorage . logBrowserNotCapable ( ) ;
} ) ;
2024-02-04 18:05:11 +01:00
Events . fire ( 'room-secret-added' , { peerId : peer . id , roomSecret : roomSecret } )
2023-01-10 05:07:57 +01:00
}
2023-09-13 18:15:01 +02:00
_onPublicRoomJoinKeyInvalid ( ) {
2023-07-06 21:29:36 +02:00
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.pairing-key-invalid" ) ) ;
2023-01-10 05:07:57 +01:00
}
2023-09-13 18:15:01 +02:00
_close ( ) {
this . _pairDeviceCancel ( ) ;
}
2023-01-10 05:07:57 +01:00
_pairDeviceCancel ( ) {
this . hide ( ) ;
this . _cleanUp ( ) ;
Events . fire ( 'pair-device-cancel' ) ;
}
2023-09-13 18:15:01 +02:00
_onPairDeviceCanceled ( pairKey ) {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.pairing-key-invalidated" , null , { key : pairKey } ) ) ;
2023-01-10 05:07:57 +01:00
}
_cleanUp ( ) {
this . roomSecret = null ;
2023-09-13 18:15:01 +02:00
this . pairKey = null ;
this . inputKeyContainer . _cleanUp ( ) ;
2023-05-10 21:20:11 +02:00
this . pairPeer = { } ;
2023-01-10 05:07:57 +01:00
}
_onSecretRoomDeleted ( roomSecret ) {
2023-11-02 02:56:01 +01:00
PersistentStorage
. deleteRoomSecret ( roomSecret )
. then ( _ => {
this . _evaluateNumberRoomSecrets ( ) ;
} ) ;
2023-01-10 05:07:57 +01:00
}
_evaluateNumberRoomSecrets ( ) {
2023-11-02 02:56:01 +01:00
PersistentStorage
. getAllRoomSecrets ( )
2023-10-11 23:22:10 +02:00
. then ( roomSecrets => {
if ( roomSecrets . length > 0 ) {
this . $editPairedDevicesHeaderBtn . removeAttribute ( 'hidden' ) ;
this . $footerInstructionsPairedDevices . removeAttribute ( 'hidden' ) ;
2023-11-02 02:56:01 +01:00
}
else {
2023-11-08 20:36:46 +01:00
this . $editPairedDevicesHeaderBtn . setAttribute ( 'hidden' , true ) ;
this . $footerInstructionsPairedDevices . setAttribute ( 'hidden' , true ) ;
2023-10-11 23:22:10 +02:00
}
Events . fire ( 'evaluate-footer-badges' ) ;
} ) ;
2023-01-10 05:07:57 +01:00
}
}
2023-05-04 17:38:51 +02:00
class EditPairedDevicesDialog extends Dialog {
2023-01-10 05:07:57 +01:00
constructor ( ) {
2023-05-04 17:38:51 +02:00
super ( 'edit-paired-devices-dialog' ) ;
this . $pairedDevicesWrapper = this . $el . querySelector ( '.paired-devices-wrapper' ) ;
2023-10-11 23:22:10 +02:00
this . $footerBadgePairedDevices = $$ ( '.discovery-wrapper .badge-room-secret' ) ;
2024-02-04 18:02:10 +01:00
this . $editPairedDevices = $ ( 'edit-paired-devices' ) ;
2023-09-13 18:15:01 +02:00
2024-02-04 18:02:10 +01:00
this . $editPairedDevices . addEventListener ( 'click' , _ => this . _onEditPairedDevices ( ) ) ;
2023-10-11 23:22:10 +02:00
this . $footerBadgePairedDevices . addEventListener ( 'click' , _ => this . _onEditPairedDevices ( ) ) ;
2023-05-04 17:38:51 +02:00
Events . on ( 'keydown' , e => this . _onKeyDown ( e ) ) ;
2023-01-10 05:07:57 +01:00
}
2023-05-04 17:38:51 +02:00
_onKeyDown ( e ) {
2023-11-24 16:25:30 +01:00
if ( ! this . isShown ( ) ) return ;
if ( e . code === "Escape" ) {
2023-05-04 17:38:51 +02:00
this . hide ( ) ;
}
2023-01-10 05:07:57 +01:00
}
2023-05-04 17:38:51 +02:00
async _initDOM ( ) {
2023-12-09 17:37:00 +01:00
const pairedDeviceRemovedString = Localization . getTranslation ( "dialogs.paired-device-removed" ) ;
2023-09-14 18:15:07 +02:00
const unpairString = Localization . getTranslation ( "dialogs.unpair" ) . toUpperCase ( ) ;
const autoAcceptString = Localization . getTranslation ( "dialogs.auto-accept" ) . toLowerCase ( ) ;
2023-05-04 17:38:51 +02:00
const roomSecretsEntries = await PersistentStorage . getAllRoomSecretEntries ( ) ;
2023-09-14 18:15:07 +02:00
2023-11-02 02:56:01 +01:00
roomSecretsEntries
. forEach ( roomSecretsEntry => {
let $pairedDevice = document . createElement ( 'div' ) ;
2023-11-29 23:39:55 +01:00
$pairedDevice . classList . add ( "paired-device" ) ;
2023-12-09 17:37:00 +01:00
$pairedDevice . setAttribute ( 'placeholder' , pairedDeviceRemovedString ) ;
2023-05-04 17:38:51 +02:00
2023-11-02 02:56:01 +01:00
$pairedDevice . innerHTML = `
2023-11-29 23:39:55 +01:00
< div class = "display-name" >
< span class = "fw" >
$ { roomSecretsEntry . display _name }
< / s p a n >
< / d i v >
< div class = "device-name" >
< span class = "fw" >
$ { roomSecretsEntry . device _name }
< / s p a n >
< / d i v >
2023-12-09 17:37:00 +01:00
< div class = "button-wrapper row fw center wrap" >
< div class = "center grow" >
< span class = "center wrap" >
2023-11-29 23:39:55 +01:00
$ { autoAcceptString }
< / s p a n >
2023-12-12 13:39:55 +01:00
< label class = "auto-accept switch pointer m-1" >
2023-11-29 23:39:55 +01:00
< input type = "checkbox" $ { roomSecretsEntry . auto _accept ? "checked" : "" } >
< div class = "slider round" > < / d i v >
< / l a b e l >
< / d i v >
2023-12-09 17:37:00 +01:00
< button class = "btn grow" type = "button" > $ { unpairString } < / b u t t o n >
2023-11-29 23:39:55 +01:00
< / d i v > `
2023-11-02 02:56:01 +01:00
$pairedDevice
. querySelector ( 'input[type="checkbox"]' )
. addEventListener ( 'click' , e => {
2023-11-09 03:48:17 +01:00
PersistentStorage
. updateRoomSecretAutoAccept ( roomSecretsEntry . secret , e . target . checked )
2023-11-02 02:56:01 +01:00
. then ( roomSecretsEntry => {
Events . fire ( 'auto-accept-updated' , {
'roomSecret' : roomSecretsEntry . entry . secret ,
'autoAccept' : e . target . checked
} ) ;
} ) ;
} ) ;
2023-05-04 17:38:51 +02:00
2023-11-02 02:56:01 +01:00
$pairedDevice
. querySelector ( 'button' )
. addEventListener ( 'click' , e => {
2023-11-09 03:48:17 +01:00
PersistentStorage
. deleteRoomSecret ( roomSecretsEntry . secret )
2023-11-02 02:56:01 +01:00
. then ( roomSecret => {
Events . fire ( 'room-secrets-deleted' , [ roomSecret ] ) ;
Events . fire ( 'evaluate-number-room-secrets' ) ;
2023-12-09 17:37:00 +01:00
$pairedDevice . innerText = "" ;
2023-11-02 02:56:01 +01:00
} ) ;
} )
2023-05-04 17:38:51 +02:00
2023-11-02 02:56:01 +01:00
this . $pairedDevicesWrapper . appendChild ( $pairedDevice )
} )
2023-05-04 17:38:51 +02:00
}
hide ( ) {
super . hide ( ) ;
2023-11-02 01:22:41 +01:00
setTimeout ( ( ) => {
2023-05-04 17:38:51 +02:00
this . $pairedDevicesWrapper . innerHTML = ""
} , 300 ) ;
}
_onEditPairedDevices ( ) {
2023-11-20 05:23:21 +01:00
this . _initDOM ( )
. then ( _ => {
2023-11-24 16:25:30 +01:00
this . _evaluateOverflowing ( this . $pairedDevicesWrapper ) ;
2023-11-20 05:23:21 +01:00
this . show ( ) ;
} ) ;
}
2023-03-02 16:30:47 +01:00
_clearRoomSecrets ( ) {
2023-11-02 02:56:01 +01:00
PersistentStorage
. getAllRoomSecrets ( )
2023-05-04 17:38:51 +02:00
. then ( roomSecrets => {
2023-11-02 02:56:01 +01:00
PersistentStorage
. clearRoomSecrets ( )
. finally ( ( ) => {
Events . fire ( 'room-secrets-deleted' , roomSecrets ) ;
Events . fire ( 'evaluate-number-room-secrets' ) ;
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.pairing-cleared" ) ) ;
this . hide ( ) ;
} )
2023-05-04 17:38:51 +02:00
} ) ;
}
2023-01-10 05:07:57 +01:00
}
2023-09-13 18:15:01 +02:00
class PublicRoomDialog extends Dialog {
constructor ( ) {
super ( 'public-room-dialog' ) ;
this . $key = this . $el . querySelector ( '.key' ) ;
this . $qrCode = this . $el . querySelector ( '.key-qr-code' ) ;
this . $form = this . $el . querySelector ( 'form' ) ;
this . $closeBtn = this . $el . querySelector ( '[close]' ) ;
this . $leaveBtn = this . $el . querySelector ( '.leave-room' ) ;
this . $joinSubmitBtn = this . $el . querySelector ( 'button[type="submit"]' ) ;
this . $headerBtnJoinPublicRoom = $ ( 'join-public-room' ) ;
2023-10-11 23:22:10 +02:00
this . $footerBadgePublicRoomDevices = $$ ( '.discovery-wrapper .badge-room-public-id' ) ;
2023-09-13 18:15:01 +02:00
this . $form . addEventListener ( 'submit' , e => this . _onSubmit ( e ) ) ;
this . $closeBtn . addEventListener ( 'click' , _ => this . hide ( ) ) ;
this . $leaveBtn . addEventListener ( 'click' , _ => this . _leavePublicRoom ( ) )
this . $headerBtnJoinPublicRoom . addEventListener ( 'click' , _ => this . _onHeaderBtnClick ( ) ) ;
2023-10-11 23:22:10 +02:00
this . $footerBadgePublicRoomDevices . addEventListener ( 'click' , _ => this . _onHeaderBtnClick ( ) ) ;
2023-09-13 18:15:01 +02:00
this . inputKeyContainer = new InputKeyContainer (
this . $el . querySelector ( '.input-key-container' ) ,
/[a-z|A-Z]/ ,
2023-11-08 20:36:46 +01:00
( ) => this . $joinSubmitBtn . removeAttribute ( 'disabled' ) ,
( ) => this . $joinSubmitBtn . setAttribute ( 'disabled' , true ) ,
2023-09-13 18:15:01 +02:00
( ) => this . _submit ( )
) ;
Events . on ( 'keydown' , e => this . _onKeyDown ( e ) ) ;
Events . on ( 'public-room-created' , e => this . _onPublicRoomCreated ( e . detail ) ) ;
Events . on ( 'peers' , e => this . _onPeers ( e . detail ) ) ;
2024-02-04 18:02:10 +01:00
Events . on ( 'peer-joined' , e => this . _onPeerJoined ( e . detail . peer , e . detail . roomId ) ) ;
2023-09-13 18:15:01 +02:00
Events . on ( 'public-room-id-invalid' , e => this . _onPublicRoomIdInvalid ( e . detail ) ) ;
Events . on ( 'public-room-left' , _ => this . _onPublicRoomLeft ( ) ) ;
this . $el . addEventListener ( 'paste' , e => this . _onPaste ( e ) ) ;
2023-10-11 20:19:19 +02:00
this . $qrCode . addEventListener ( 'click' , _ => this . _copyShareRoomUrl ( ) ) ;
2023-09-13 18:15:01 +02:00
Events . on ( 'ws-connected' , _ => this . _onWsConnected ( ) ) ;
2023-09-14 20:06:17 +02:00
Events . on ( 'translation-loaded' , _ => this . setFooterBadge ( ) ) ;
2023-09-13 18:15:01 +02:00
}
_onKeyDown ( e ) {
2023-11-24 16:25:30 +01:00
if ( ! this . isShown ( ) ) return ;
if ( e . code === "Escape" ) {
2023-09-13 18:15:01 +02:00
this . hide ( ) ;
}
}
_onPaste ( e ) {
e . preventDefault ( ) ;
let pastedKey = e . clipboardData . getData ( "Text" ) ;
this . inputKeyContainer . _onPaste ( pastedKey ) ;
}
_onHeaderBtnClick ( ) {
if ( this . roomId ) {
this . show ( ) ;
2023-11-02 02:56:01 +01:00
}
else {
2023-09-13 18:15:01 +02:00
this . _createPublicRoom ( ) ;
}
}
_createPublicRoom ( ) {
Events . fire ( 'create-public-room' ) ;
}
_onPublicRoomCreated ( roomId ) {
this . roomId = roomId ;
2023-12-05 19:26:17 +01:00
this . _setKeyAndQrCode ( ) ;
2023-09-13 18:15:01 +02:00
this . show ( ) ;
sessionStorage . setItem ( 'public_room_id' , roomId ) ;
}
2023-12-05 19:26:17 +01:00
_setKeyAndQrCode ( ) {
2023-09-14 20:06:17 +02:00
if ( ! this . roomId ) return ;
2023-09-13 18:15:01 +02:00
this . $key . innerText = this . roomId . toUpperCase ( ) ;
// Display the QR code for the url
const qr = new QRCode ( {
2023-10-11 20:19:19 +02:00
content : this . _getShareRoomUrl ( ) ,
2023-11-20 05:23:21 +01:00
width : 130 ,
height : 130 ,
2023-10-23 19:39:33 +02:00
padding : 1 ,
2023-11-29 18:53:57 +01:00
background : 'white' ,
2023-10-23 19:39:33 +02:00
color : 'rgb(18, 18, 18)' ,
2023-09-13 18:15:01 +02:00
ecl : "L" ,
join : true
} ) ;
this . $qrCode . innerHTML = qr . svg ( ) ;
2023-09-14 20:06:17 +02:00
this . setFooterBadge ( ) ;
}
setFooterBadge ( ) {
if ( ! this . roomId ) return ;
2023-10-11 23:22:10 +02:00
this . $footerBadgePublicRoomDevices . innerText = Localization . getTranslation ( "footer.public-room-devices" , null , {
2023-09-13 18:15:01 +02:00
roomId : this . roomId . toUpperCase ( )
} ) ;
2023-10-11 23:22:10 +02:00
this . $footerBadgePublicRoomDevices . removeAttribute ( 'hidden' ) ;
2023-09-14 20:06:17 +02:00
2023-10-11 23:22:10 +02:00
Events . fire ( 'evaluate-footer-badges' ) ;
2023-09-13 18:15:01 +02:00
}
2023-10-11 20:19:19 +02:00
_getShareRoomUrl ( ) {
2023-09-13 18:15:01 +02:00
let url = new URL ( location . href ) ;
2023-10-11 20:19:19 +02:00
url . searchParams . append ( 'room_id' , this . roomId )
2023-09-13 18:15:01 +02:00
return url . href ;
}
2023-10-11 20:19:19 +02:00
_copyShareRoomUrl ( ) {
navigator . clipboard . writeText ( this . _getShareRoomUrl ( ) )
. then ( _ => {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.room-url-copied-to-clipboard" ) ) ;
} )
. catch ( _ => {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.copied-to-clipboard-error" ) ) ;
} )
}
2023-09-13 18:15:01 +02:00
_onWsConnected ( ) {
let roomId = sessionStorage . getItem ( 'public_room_id' ) ;
if ( ! roomId ) return ;
this . roomId = roomId ;
2023-12-05 19:26:17 +01:00
this . _setKeyAndQrCode ( ) ;
2023-09-13 18:15:01 +02:00
this . _joinPublicRoom ( roomId , true ) ;
}
_onSubmit ( e ) {
e . preventDefault ( ) ;
this . _submit ( ) ;
}
_submit ( ) {
let inputKey = this . inputKeyContainer . _getInputKey ( ) ;
this . _joinPublicRoom ( inputKey ) ;
}
_joinPublicRoom ( roomId , createIfInvalid = false ) {
roomId = roomId . toLowerCase ( ) ;
if ( /^[a-z]{5}$/g . test ( roomId ) ) {
this . roomIdJoin = roomId ;
this . inputKeyContainer . focusLastChar ( ) ;
Events . fire ( 'join-public-room' , {
roomId : roomId ,
createIfInvalid : createIfInvalid
} ) ;
}
}
_onPeers ( message ) {
message . peers . forEach ( messagePeer => {
this . _evaluateJoinedPeer ( messagePeer . id , message . roomId ) ;
} ) ;
}
2024-02-04 18:02:10 +01:00
_onPeerJoined ( peer , roomId ) {
this . _evaluateJoinedPeer ( peer . id , roomId ) ;
2023-09-13 18:15:01 +02:00
}
_evaluateJoinedPeer ( peerId , roomId ) {
const isInitiatedRoomId = roomId === this . roomId ;
const isJoinedRoomId = roomId === this . roomIdJoin ;
2024-02-04 18:02:10 +01:00
if ( ! peerId || ! roomId || ( ! isInitiatedRoomId && ! isJoinedRoomId ) ) return ;
2023-09-13 18:15:01 +02:00
this . hide ( ) ;
sessionStorage . setItem ( 'public_room_id' , roomId ) ;
if ( isJoinedRoomId ) {
this . roomId = roomId ;
this . roomIdJoin = false ;
2023-12-05 19:26:17 +01:00
this . _setKeyAndQrCode ( ) ;
2023-09-13 18:15:01 +02:00
}
}
_onPublicRoomIdInvalid ( roomId ) {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.public-room-id-invalid" ) ) ;
if ( roomId === sessionStorage . getItem ( 'public_room_id' ) ) {
sessionStorage . removeItem ( 'public_room_id' ) ;
}
}
_leavePublicRoom ( ) {
Events . fire ( 'leave-public-room' , this . roomId ) ;
}
_onPublicRoomLeft ( ) {
let publicRoomId = this . roomId . toUpperCase ( ) ;
this . hide ( ) ;
this . _cleanUp ( ) ;
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.public-room-left" , null , { publicRoomId : publicRoomId } ) ) ;
}
show ( ) {
this . inputKeyContainer . _enableChars ( ) ;
super . show ( ) ;
}
hide ( ) {
this . inputKeyContainer . _cleanUp ( ) ;
super . hide ( ) ;
}
_cleanUp ( ) {
this . roomId = null ;
this . inputKeyContainer . _cleanUp ( ) ;
sessionStorage . removeItem ( 'public_room_id' ) ;
2023-11-08 20:36:46 +01:00
this . $footerBadgePublicRoomDevices . setAttribute ( 'hidden' , true ) ;
2023-10-11 23:22:10 +02:00
Events . fire ( 'evaluate-footer-badges' ) ;
2023-09-13 18:15:01 +02:00
}
}
2018-09-21 16:05:03 +02:00
class SendTextDialog extends Dialog {
constructor ( ) {
2023-03-01 10:44:57 +01:00
super ( 'send-text-dialog' ) ;
2023-11-24 16:25:30 +01:00
this . $text = this . $el . querySelector ( '.textarea' ) ;
2023-03-03 12:01:43 +01:00
this . $peerDisplayName = this . $el . querySelector ( '.display-name' ) ;
2023-02-10 03:26:08 +01:00
this . $form = this . $el . querySelector ( 'form' ) ;
this . $submit = this . $el . querySelector ( 'button[type="submit"]' ) ;
2023-03-02 16:30:47 +01:00
this . $form . addEventListener ( 'submit' , e => this . _onSubmit ( e ) ) ;
2023-11-24 16:25:30 +01:00
this . $text . addEventListener ( 'input' , _ => this . _onInput ( ) ) ;
Events . on ( 'text-recipient' , e => this . _onRecipient ( e . detail . peerId , e . detail . deviceName ) ) ;
2023-09-14 20:06:17 +02:00
Events . on ( 'keydown' , e => this . _onKeyDown ( e ) ) ;
2022-12-30 20:34:54 +01:00
}
2023-11-24 16:25:30 +01:00
_onKeyDown ( e ) {
2023-05-27 01:13:49 +02:00
if ( ! this . isShown ( ) ) return ;
if ( e . code === "Escape" ) {
this . hide ( ) ;
2023-11-02 02:56:01 +01:00
}
else if ( e . code === "Enter" && ( e . ctrlKey || e . metaKey ) ) {
2023-11-24 16:25:30 +01:00
if ( this . _textEmpty ( ) ) return ;
2023-05-27 01:13:49 +02:00
this . _send ( ) ;
2022-12-30 20:34:54 +01:00
}
2018-09-21 16:05:03 +02:00
}
2023-11-24 16:25:30 +01:00
_textEmpty ( ) {
2023-10-11 18:56:07 +02:00
return ! this . $text . innerText || this . $text . innerText === "\n" ;
2023-02-10 03:26:08 +01:00
}
2023-11-24 16:25:30 +01:00
_onInput ( ) {
if ( this . _textEmpty ( ) ) {
2023-11-08 20:36:46 +01:00
this . $submit . setAttribute ( 'disabled' , true ) ;
2023-11-20 05:27:22 +01:00
// remove remaining whitespace on Firefox on text deletion
this . $text . innerText = "" ;
2023-11-02 02:56:01 +01:00
}
else {
2023-02-10 03:26:08 +01:00
this . $submit . removeAttribute ( 'disabled' ) ;
}
2023-11-24 16:25:30 +01:00
this . _evaluateOverflowing ( this . $text ) ;
2023-02-10 03:26:08 +01:00
}
2023-03-01 10:04:37 +01:00
_onRecipient ( peerId , deviceName ) {
2023-01-23 04:51:22 +01:00
this . correspondingPeerId = peerId ;
2023-03-01 10:04:37 +01:00
this . $peerDisplayName . innerText = deviceName ;
2023-11-20 05:23:21 +01:00
this . $peerDisplayName . classList . remove ( ... PeerUI . _badgeClassNames ) ;
2023-09-13 18:15:01 +02:00
this . $peerDisplayName . classList . add ( $ ( peerId ) . ui . _badgeClassName ( ) ) ;
2018-09-21 16:05:03 +02:00
this . show ( ) ;
2020-12-22 21:23:10 +01:00
const range = document . createRange ( ) ;
const sel = window . getSelection ( ) ;
2023-01-17 10:47:44 +01:00
range . selectNodeContents ( this . $text ) ;
sel . removeAllRanges ( ) ;
sel . addRange ( range ) ;
2018-09-21 16:05:03 +02:00
}
2023-03-02 16:30:47 +01:00
_onSubmit ( e ) {
e . preventDefault ( ) ;
this . _send ( ) ;
}
2023-01-14 01:43:44 +01:00
_send ( ) {
2018-09-21 16:05:03 +02:00
Events . fire ( 'send-text' , {
2023-01-23 04:51:22 +01:00
to : this . correspondingPeerId ,
2023-10-11 18:56:07 +02:00
text : this . $text . innerText
2018-09-21 16:05:03 +02:00
} ) ;
2023-02-10 03:26:08 +01:00
this . hide ( ) ;
2023-11-20 03:55:00 +01:00
setTimeout ( ( ) => this . $text . innerText = "" , 300 ) ;
2018-09-21 16:05:03 +02:00
}
}
class ReceiveTextDialog extends Dialog {
constructor ( ) {
2023-03-01 10:44:57 +01:00
super ( 'receive-text-dialog' ) ;
2023-02-10 03:26:08 +01:00
Events . on ( 'text-received' , e => this . _onText ( e . detail . text , e . detail . peerId ) ) ;
2018-09-21 16:05:03 +02:00
this . $text = this . $el . querySelector ( '#text' ) ;
2023-02-10 03:26:08 +01:00
this . $copy = this . $el . querySelector ( '#copy' ) ;
this . $close = this . $el . querySelector ( '#close' ) ;
this . $copy . addEventListener ( 'click' , _ => this . _onCopy ( ) ) ;
this . $close . addEventListener ( 'click' , _ => this . hide ( ) ) ;
2023-09-14 20:06:17 +02:00
Events . on ( 'keydown' , e => this . _onKeyDown ( e ) ) ;
2023-02-10 03:26:08 +01:00
2023-09-13 18:15:01 +02:00
this . $displayName = this . $el . querySelector ( '.display-name' ) ;
2023-02-10 03:26:08 +01:00
this . _receiveTextQueue = [ ] ;
2022-12-30 20:34:54 +01:00
}
2024-01-03 16:53:09 +01:00
selectionEmpty ( ) {
return ! window . getSelection ( ) . toString ( )
}
2022-12-30 20:34:54 +01:00
async _onKeyDown ( e ) {
2023-11-24 16:25:30 +01:00
if ( ! this . isShown ( ) ) return
2024-01-03 16:53:09 +01:00
if ( e . code === "KeyC" && ( e . ctrlKey || e . metaKey ) && this . selectionEmpty ( ) ) {
2023-11-24 16:25:30 +01:00
await this . _onCopy ( )
}
else if ( e . code === "Escape" ) {
this . hide ( ) ;
2022-12-30 20:34:54 +01:00
}
2018-09-21 16:05:03 +02:00
}
2023-02-10 03:26:08 +01:00
_onText ( text , peerId ) {
2024-02-05 02:09:40 +01:00
audioPlayer . playBlop ( ) ;
2023-02-10 03:26:08 +01:00
this . _receiveTextQueue . push ( { text : text , peerId : peerId } ) ;
2023-03-02 15:30:25 +01:00
this . _setDocumentTitleMessages ( ) ;
2023-05-09 03:21:10 +02:00
if ( this . isShown ( ) ) return ;
2023-02-10 03:26:08 +01:00
this . _dequeueRequests ( ) ;
}
_dequeueRequests ( ) {
if ( ! this . _receiveTextQueue . length ) return ;
let { text , peerId } = this . _receiveTextQueue . shift ( ) ;
this . _showReceiveTextDialog ( text , peerId ) ;
}
_showReceiveTextDialog ( text , peerId ) {
2023-09-13 18:15:01 +02:00
this . $displayName . innerText = $ ( peerId ) . ui . _displayName ( ) ;
2023-11-20 05:23:21 +01:00
this . $displayName . classList . remove ( ... PeerUI . _badgeClassNames ) ;
2023-09-13 18:15:01 +02:00
this . $displayName . classList . add ( $ ( peerId ) . ui . _badgeClassName ( ) ) ;
2023-02-10 03:26:08 +01:00
2023-03-03 12:28:50 +01:00
this . $text . innerText = text ;
this . $text . classList . remove ( 'text-center' ) ;
// Beautify text if text is short
if ( text . length < 2000 ) {
2024-01-03 13:54:13 +01:00
// replace URLs with actual links
this . $text . innerHTML = this . $text . innerHTML
2024-01-12 01:23:14 +01:00
. replace ( /(^|<br>|\s|")((https?:\/\/|www.)(([a-z]|[A-Z]|[0-9]|[\-_~:\/?#\[\]@!$&'()*+,;=%]){2,}\.)(([a-z]|[A-Z]|[0-9]|[\-_~:\/?#\[\]@!$&'()*+,;=%.]){2,}))/g ,
( match , whitespace , url ) => {
2024-01-03 13:54:13 +01:00
let link = url ;
// prefix www.example.com with http protocol to prevent it from being a relative link
if ( link . startsWith ( 'www' ) ) {
link = "http://" + link
}
2024-01-12 01:23:14 +01:00
// Check if link is valid
if ( isUrlValid ( link ) ) {
return ` ${ whitespace } <a href=" ${ link } " target="_blank"> ${ url } </a> ` ;
}
else {
return match ;
}
2024-01-03 13:54:13 +01:00
} ) ;
2018-09-21 16:05:03 +02:00
}
2023-03-03 12:28:50 +01:00
2023-11-24 16:25:30 +01:00
this . _evaluateOverflowing ( this . $text ) ;
2023-11-20 05:23:21 +01:00
2023-03-02 15:30:25 +01:00
this . _setDocumentTitleMessages ( ) ;
2023-10-11 23:22:10 +02:00
changeFavicon ( "images/favicon-96x96-notification.png" ) ;
2018-09-21 16:05:03 +02:00
this . show ( ) ;
}
2023-03-02 15:30:25 +01:00
_setDocumentTitleMessages ( ) {
document . title = ! this . _receiveTextQueue . length
2023-07-06 21:29:36 +02:00
? ` ${ Localization . getTranslation ( "document-titles.message-received" ) } - PairDrop `
: ` ${ Localization . getTranslation ( "document-titles.message-received-plural" , null , { count : this . _receiveTextQueue . length + 1 } ) } - PairDrop ` ;
2023-03-02 15:30:25 +01:00
}
2020-12-20 04:41:16 +01:00
async _onCopy ( ) {
2023-05-27 01:13:49 +02:00
const sanitizedText = this . $text . innerText . replace ( /\u00A0/gm , ' ' ) ;
2023-11-02 02:56:01 +01:00
navigator . clipboard
. writeText ( sanitizedText )
2023-09-13 17:50:46 +02:00
. then ( _ => {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.copied-to-clipboard" ) ) ;
this . hide ( ) ;
} )
. catch ( _ => {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.copied-to-clipboard-error" ) ) ;
} ) ;
2023-02-10 03:26:08 +01:00
}
hide ( ) {
super . hide ( ) ;
2024-01-03 16:52:26 +01:00
setTimeout ( ( ) => {
this . _dequeueRequests ( ) ;
this . $text . innerHTML = "" ;
} , 500 ) ;
2018-09-21 16:05:03 +02:00
}
}
2023-11-24 16:25:30 +01:00
class ShareTextDialog extends Dialog {
constructor ( ) {
super ( 'share-text-dialog' ) ;
this . $text = this . $el . querySelector ( '.textarea' ) ;
this . $approveMsgBtn = this . $el . querySelector ( 'button[type="submit"]' ) ;
this . $checkbox = this . $el . querySelector ( 'input[type="checkbox"]' )
this . $approveMsgBtn . addEventListener ( 'click' , _ => this . _approveShareText ( ) ) ;
// Only show this per default if user sets checkmark
2023-11-24 17:32:40 +01:00
this . $checkbox . checked = localStorage . getItem ( 'approve-share-text' )
2023-11-24 16:25:30 +01:00
? ShareTextDialog . isApproveShareTextSet ( )
: false ;
this . _setCheckboxValueToLocalStorage ( ) ;
this . $checkbox . addEventListener ( 'change' , _ => this . _setCheckboxValueToLocalStorage ( ) ) ;
Events . on ( 'share-text-dialog' , e => this . _onShareText ( e . detail ) ) ;
Events . on ( 'keydown' , e => this . _onKeyDown ( e ) ) ;
this . $text . addEventListener ( 'input' , _ => this . _evaluateEmptyText ( ) ) ;
}
static isApproveShareTextSet ( ) {
2023-11-24 17:32:40 +01:00
return localStorage . getItem ( 'approve-share-text' ) === "true" ;
2023-11-24 16:25:30 +01:00
}
_setCheckboxValueToLocalStorage ( ) {
2023-11-24 17:32:40 +01:00
localStorage . setItem ( 'approve-share-text' , this . $checkbox . checked ? "true" : "false" ) ;
2023-11-24 16:25:30 +01:00
}
_onKeyDown ( e ) {
if ( ! this . isShown ( ) ) return ;
if ( e . code === "Escape" ) {
this . _approveShareText ( ) ;
}
else if ( e . code === "Enter" && ( e . ctrlKey || e . metaKey ) ) {
if ( this . _textEmpty ( ) ) return ;
this . _approveShareText ( ) ;
}
}
_textEmpty ( ) {
return ! this . $text . innerText || this . $text . innerText === "\n" ;
}
_evaluateEmptyText ( ) {
if ( this . _textEmpty ( ) ) {
this . $approveMsgBtn . setAttribute ( 'disabled' , true ) ;
// remove remaining whitespace on Firefox on text deletion
this . $text . innerText = "" ;
}
else {
this . $approveMsgBtn . removeAttribute ( 'disabled' ) ;
}
this . _evaluateOverflowing ( this . $text ) ;
}
_onShareText ( text ) {
this . $text . innerText = text ;
this . _evaluateEmptyText ( ) ;
this . show ( ) ;
}
_approveShareText ( ) {
Events . fire ( 'activate-share-mode' , { text : this . $text . innerText } ) ;
this . hide ( ) ;
}
hide ( ) {
super . hide ( ) ;
setTimeout ( ( ) => this . $text . innerText = "" , 500 ) ;
}
}
2023-12-05 18:53:21 +01:00
class Base64Dialog extends Dialog {
2023-01-19 04:40:28 +01:00
constructor ( ) {
2023-03-01 10:44:57 +01:00
super ( 'base64-paste-dialog' ) ;
2023-02-20 17:42:02 +01:00
2023-12-05 18:57:31 +01:00
this . $title = this . $el . querySelector ( '.dialog-title' ) ;
2023-03-01 10:44:57 +01:00
this . $pasteBtn = this . $el . querySelector ( '#base64-paste-btn' ) ;
2023-03-06 02:18:07 +01:00
this . $fallbackTextarea = this . $el . querySelector ( '.textarea' ) ;
2023-12-05 18:57:31 +01:00
}
2023-01-25 09:43:32 +01:00
2023-12-05 18:57:31 +01:00
async evaluateBase64Text ( base64Text , hash ) {
this . $title . innerText = Localization . getTranslation ( 'dialogs.base64-title-text' ) ;
if ( base64Text === 'paste' ) {
// ?base64text=paste
// base64 encoded string is ready to be pasted from clipboard
this . preparePasting ( 'text' ) ;
2023-02-20 17:42:02 +01:00
this . show ( ) ;
2023-11-02 02:56:01 +01:00
}
2023-12-05 18:57:31 +01:00
else if ( base64Text === 'hash' ) {
// ?base64text=hash#BASE64ENCODED
// base64 encoded text is url hash which cannot be seen by the server and is faster (recommended)
2023-01-19 14:48:43 +01:00
this . show ( ) ;
2023-12-12 14:19:41 +01:00
await this . processBase64Text ( hash ) ;
2023-12-05 18:57:31 +01:00
}
else {
// ?base64text=BASE64ENCODED
// base64 encoded text is part of the url param. Seen by server and slow (not recommended)
this . show ( ) ;
2023-12-12 14:19:41 +01:00
await this . processBase64Text ( base64Text ) ;
2023-12-05 18:57:31 +01:00
}
}
async evaluateBase64Zip ( base64Zip , hash ) {
this . $title . innerText = Localization . getTranslation ( 'dialogs.base64-title-files' ) ;
if ( base64Zip === 'paste' ) {
// ?base64zip=paste || ?base64zip=true
this . preparePasting ( 'files' ) ;
this . show ( ) ;
}
else if ( base64Zip === 'hash' ) {
// ?base64zip=hash#BASE64ENCODED
// base64 encoded zip file is url hash which cannot be seen by the server
2023-12-12 14:19:41 +01:00
await this . processBase64Zip ( hash ) ;
2023-02-20 17:42:02 +01:00
}
}
_setPasteBtnToProcessing ( ) {
2023-03-03 12:01:43 +01:00
this . $pasteBtn . style . pointerEvents = "none" ;
2023-07-06 21:29:36 +02:00
this . $pasteBtn . innerText = Localization . getTranslation ( "dialogs.base64-processing" ) ;
2023-02-20 17:42:02 +01:00
}
2023-03-06 02:18:07 +01:00
preparePasting ( type ) {
2023-09-18 21:29:29 +02:00
const translateType = type === 'text'
? Localization . getTranslation ( "dialogs.base64-text" )
: Localization . getTranslation ( "dialogs.base64-files" ) ;
2023-03-06 02:18:07 +01:00
if ( navigator . clipboard . readText ) {
2023-09-18 21:29:29 +02:00
this . $pasteBtn . innerText = Localization . getTranslation ( "dialogs.base64-tap-to-paste" , null , { type : translateType } ) ;
2023-03-06 12:20:30 +01:00
this . _clickCallback = _ => this . processClipboard ( type ) ;
this . $pasteBtn . addEventListener ( 'click' , _ => this . _clickCallback ( ) ) ;
2023-11-02 02:56:01 +01:00
}
else {
2024-02-05 15:42:27 +01:00
Logger . log ( "`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience." )
2023-11-08 20:36:46 +01:00
this . $pasteBtn . setAttribute ( 'hidden' , true ) ;
2023-09-18 21:29:29 +02:00
this . $fallbackTextarea . setAttribute ( 'placeholder' , Localization . getTranslation ( "dialogs.base64-paste-to-send" , null , { type : translateType } ) ) ;
2023-03-06 02:18:07 +01:00
this . $fallbackTextarea . removeAttribute ( 'hidden' ) ;
2023-03-06 12:20:30 +01:00
this . _inputCallback = _ => this . processInput ( type ) ;
this . $fallbackTextarea . addEventListener ( 'input' , _ => this . _inputCallback ( ) ) ;
2023-03-06 02:18:07 +01:00
this . $fallbackTextarea . focus ( ) ;
2023-02-20 17:42:02 +01:00
}
2023-03-06 02:18:07 +01:00
}
2023-02-20 17:42:02 +01:00
2023-03-06 02:18:07 +01:00
async processInput ( type ) {
const base64 = this . $fallbackTextarea . textContent ;
this . $fallbackTextarea . textContent = '' ;
2023-12-05 18:57:31 +01:00
await this . processPastedBase64 ( type , base64 ) ;
2023-03-06 02:18:07 +01:00
}
2023-02-20 17:42:02 +01:00
2023-03-06 02:18:07 +01:00
async processClipboard ( type ) {
2023-02-20 17:42:02 +01:00
const base64 = await navigator . clipboard . readText ( ) ;
2023-12-05 18:57:31 +01:00
await this . processPastedBase64 ( type , base64 ) ;
2023-03-06 02:18:07 +01:00
}
2023-02-20 17:42:02 +01:00
2023-12-05 18:57:31 +01:00
async processPastedBase64 ( type , base64 ) {
2023-03-06 02:18:07 +01:00
try {
2023-09-18 21:29:29 +02:00
if ( type === 'text' ) {
2023-03-06 02:18:07 +01:00
await this . processBase64Text ( base64 ) ;
2023-11-02 02:56:01 +01:00
}
else {
2023-03-06 02:18:07 +01:00
await this . processBase64Zip ( base64 ) ;
}
2023-12-05 18:57:31 +01:00
}
catch ( e ) {
2023-07-06 21:29:36 +02:00
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.clipboard-content-incorrect" ) ) ;
2024-02-05 15:42:27 +01:00
Logger . warn ( "Clipboard content is incorrect." )
2023-01-19 14:48:43 +01:00
}
2023-03-06 02:18:07 +01:00
this . hide ( ) ;
2023-01-19 04:40:28 +01:00
}
2023-12-05 18:57:31 +01:00
async processBase64Text ( base64 ) {
this . _setPasteBtnToProcessing ( ) ;
try {
const decodedText = await decodeBase64Text ( base64 ) ;
2023-11-24 16:25:30 +01:00
if ( ShareTextDialog . isApproveShareTextSet ( ) ) {
Events . fire ( 'share-text-dialog' , decodedText ) ;
2023-12-05 18:57:31 +01:00
}
else {
2023-11-24 16:25:30 +01:00
Events . fire ( 'activate-share-mode' , { text : decodedText } ) ;
}
2023-12-05 18:57:31 +01:00
}
catch ( e ) {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.text-content-incorrect" ) ) ;
2024-02-05 15:42:27 +01:00
Logger . warn ( "Text content incorrect." ) ;
2023-12-05 18:57:31 +01:00
}
this . hide ( ) ;
2023-01-22 16:12:00 +01:00
}
2023-12-05 18:57:31 +01:00
async processBase64Zip ( base64 ) {
2023-02-20 17:42:02 +01:00
this . _setPasteBtnToProcessing ( ) ;
2023-01-19 04:40:28 +01:00
2023-12-05 18:57:31 +01:00
try {
const decodedFiles = await decodeBase64Files ( base64 ) ;
Events . fire ( 'activate-share-mode' , { files : decodedFiles } ) ;
}
catch ( e ) {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.file-content-incorrect" ) ) ;
2024-02-05 15:42:27 +01:00
Logger . warn ( "File content incorrect." ) ;
2023-01-19 04:40:28 +01:00
}
2023-01-25 09:43:32 +01:00
2023-12-05 18:57:31 +01:00
this . hide ( ) ;
2023-01-25 09:43:32 +01:00
}
2023-02-20 17:42:02 +01:00
hide ( ) {
2023-03-06 12:20:30 +01:00
this . $pasteBtn . removeEventListener ( 'click' , _ => this . _clickCallback ( ) ) ;
this . $fallbackTextarea . removeEventListener ( 'input' , _ => this . _inputCallback ( ) ) ;
2023-12-12 14:19:41 +01:00
this . $fallbackTextarea . setAttribute ( 'disabled' , true ) ;
this . $fallbackTextarea . blur ( ) ;
2023-02-20 17:42:02 +01:00
super . hide ( ) ;
}
2023-01-19 04:40:28 +01:00
}
2023-12-13 17:40:48 +01:00
class AboutUI {
constructor ( ) {
this . $donationBtn = $ ( 'donation-btn' ) ;
this . $twitterBtn = $ ( 'twitter-btn' ) ;
this . $mastodonBtn = $ ( 'mastodon-btn' ) ;
this . $blueskyBtn = $ ( 'bluesky-btn' ) ;
this . $customBtn = $ ( 'custom-btn' ) ;
this . $privacypolicyBtn = $ ( 'privacypolicy-btn' ) ;
2024-02-05 15:42:27 +01:00
Events . on ( 'config-loaded' , _ => this . _onConfigLoaded ( ) ) ;
2023-12-13 17:40:48 +01:00
}
2024-02-05 15:42:27 +01:00
async _onConfigLoaded ( ) {
const btnConfig = window . _config . buttons
2023-12-13 17:40:48 +01:00
await this . _evaluateBtnConfig ( this . $donationBtn , btnConfig . donation _button ) ;
await this . _evaluateBtnConfig ( this . $twitterBtn , btnConfig . twitter _button ) ;
await this . _evaluateBtnConfig ( this . $mastodonBtn , btnConfig . mastodon _button ) ;
await this . _evaluateBtnConfig ( this . $blueskyBtn , btnConfig . bluesky _button ) ;
await this . _evaluateBtnConfig ( this . $customBtn , btnConfig . custom _button ) ;
await this . _evaluateBtnConfig ( this . $privacypolicyBtn , btnConfig . privacypolicy _button ) ;
}
async _evaluateBtnConfig ( $btn , config ) {
// if config is not set leave everything as default
if ( ! Object . keys ( config ) . length ) return ;
if ( config . active === "false" ) {
$btn . setAttribute ( 'hidden' , true ) ;
} else {
if ( config . link ) {
$btn . setAttribute ( 'href' , config . link ) ;
}
if ( config . title ) {
$btn . setAttribute ( 'title' , config . title ) ;
// prevent overwriting of custom title when setting different language
$btn . removeAttribute ( 'data-i18n-key' ) ;
$btn . removeAttribute ( 'data-i18n-attrs' ) ;
}
if ( config . icon ) {
$btn . setAttribute ( 'title' , config . title ) ;
// prevent overwriting of custom title when setting different language
$btn . removeAttribute ( 'data-i18n-key' ) ;
$btn . removeAttribute ( 'data-i18n-attrs' ) ;
}
$btn . removeAttribute ( 'hidden' ) ;
}
}
}
2018-09-21 16:05:03 +02:00
class Toast extends Dialog {
constructor ( ) {
super ( 'toast' ) ;
2023-11-24 16:25:30 +01:00
this . $closeBtn = this . $el . querySelector ( '.icon-button' ) ;
this . $text = this . $el . querySelector ( 'span' ) ;
this . $closeBtn . addEventListener ( 'click' , _ => this . hide ( ) ) ;
2023-01-23 04:51:22 +01:00
Events . on ( 'notify-user' , e => this . _onNotify ( e . detail ) ) ;
2023-11-24 16:25:30 +01:00
Events . on ( 'share-mode-changed' , _ => this . hide ( ) ) ;
2018-09-21 16:05:03 +02:00
}
2023-01-23 04:51:22 +01:00
_onNotify ( message ) {
if ( this . hideTimeout ) clearTimeout ( this . hideTimeout ) ;
2023-11-24 16:25:30 +01:00
this . $text . innerText = typeof message === "object" ? message . message : message ;
2018-09-21 16:05:03 +02:00
this . show ( ) ;
2023-10-12 03:39:37 +02:00
if ( typeof message === "object" && message . persistent ) return ;
this . hideTimeout = setTimeout ( ( ) => this . hide ( ) , 5000 ) ;
2018-09-21 16:05:03 +02:00
}
2023-11-24 16:25:30 +01:00
hide ( ) {
if ( this . hideTimeout ) clearTimeout ( this . hideTimeout ) ;
super . hide ( ) ;
}
2018-09-21 16:05:03 +02:00
}
class Notifications {
constructor ( ) {
// Check if the browser supports notifications
2023-10-12 19:02:04 +02:00
if ( ! ( 'Notification' in window ) ) return ;
2018-09-21 22:31:46 +02:00
2023-11-09 03:48:17 +01:00
this . $headerNotificationButton = $ ( 'notification' ) ;
2023-11-24 16:25:30 +01:00
this . $downloadBtn = $ ( 'download-btn' ) ;
2023-11-09 03:48:17 +01:00
this . $headerNotificationButton . addEventListener ( 'click' , _ => this . _requestPermission ( ) ) ;
2023-10-06 02:57:46 +02:00
2023-10-11 23:22:10 +02:00
2023-02-10 03:26:08 +01:00
Events . on ( 'text-received' , e => this . _messageNotification ( e . detail . text , e . detail . peerId ) ) ;
2024-02-05 20:39:56 +01:00
Events . on ( 'files-received' , e => this . _downloadNotification ( e . detail . files , e . detail . imagesOnly ) ) ;
2023-03-06 15:05:13 +01:00
Events . on ( 'files-transfer-request' , e => this . _requestNotification ( e . detail . request , e . detail . peerId ) ) ;
2018-09-21 16:05:03 +02:00
}
2023-11-02 01:31:31 +01:00
async _requestPermission ( ) {
await Notification .
requestPermission ( permission => {
if ( permission !== 'granted' ) {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.notifications-permissions-error" ) ) ;
return ;
}
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.notifications-enabled" ) ) ;
2023-11-08 20:36:46 +01:00
this . $headerNotificationButton . setAttribute ( 'hidden' , true ) ;
2023-11-02 01:31:31 +01:00
} ) ;
2018-09-21 16:05:03 +02:00
}
2023-02-10 03:26:08 +01:00
_notify ( title , body ) {
2018-09-21 18:54:52 +02:00
const config = {
2018-09-21 16:05:03 +02:00
body : body ,
2018-09-21 18:54:52 +02:00
icon : '/images/logo_transparent_128x128.png' ,
}
2019-03-13 00:08:37 +01:00
let notification ;
2018-09-21 23:19:54 +02:00
try {
2023-02-10 03:26:08 +01:00
notification = new Notification ( title , config ) ;
2018-09-21 23:19:54 +02:00
} catch ( e ) {
2019-03-13 00:08:37 +01:00
// Android doesn't support "new Notification" if service worker is installed
2018-09-21 23:19:54 +02:00
if ( ! serviceWorker || ! serviceWorker . showNotification ) return ;
2023-02-10 03:26:08 +01:00
notification = serviceWorker . showNotification ( title , config ) ;
2018-09-21 18:54:52 +02:00
}
2018-09-21 23:19:54 +02:00
2019-03-13 00:08:37 +01:00
// Notification is persistent on Android. We have to close it manually
2022-12-22 22:41:26 +01:00
const visibilitychangeHandler = ( ) => {
if ( document . visibilityState === 'visible' ) {
2022-09-13 01:35:05 -04:00
notification . close ( ) ;
2022-09-13 08:34:44 -04:00
Events . off ( 'visibilitychange' , visibilitychangeHandler ) ;
2022-12-22 22:41:26 +01:00
}
} ;
2022-09-13 08:34:44 -04:00
Events . on ( 'visibilitychange' , visibilitychangeHandler ) ;
2019-03-13 00:08:37 +01:00
return notification ;
2018-09-21 16:05:03 +02:00
}
2023-02-10 03:26:08 +01:00
_messageNotification ( message , peerId ) {
2022-09-13 01:35:05 -04:00
if ( document . visibilityState !== 'visible' ) {
2023-02-10 03:26:08 +01:00
const peerDisplayName = $ ( peerId ) . ui . _displayName ( ) ;
2023-03-03 12:28:50 +01:00
if ( /^((https?:\/\/|www)[abcdefghijklmnopqrstuvwxyz0123456789\-._~:\/?#\[\]@!$&'()*+,;=]+)$/ . test ( message . toLowerCase ( ) ) ) {
2023-07-06 21:29:36 +02:00
const notification = this . _notify ( Localization . getTranslation ( "notifications.link-received" , null , { name : peerDisplayName } ) , message ) ;
2023-11-02 01:31:31 +01:00
this . _bind ( notification , _ => window . open ( message , '_blank' , "noreferrer" ) ) ;
2023-11-02 02:56:01 +01:00
}
else {
2023-07-06 21:29:36 +02:00
const notification = this . _notify ( Localization . getTranslation ( "notifications.message-received" , null , { name : peerDisplayName } ) , message ) ;
2023-01-10 05:07:57 +01:00
this . _bind ( notification , _ => this . _copyText ( message , notification ) ) ;
2022-09-13 01:35:05 -04:00
}
2018-09-21 16:05:03 +02:00
}
}
2024-02-05 20:39:56 +01:00
_downloadNotification ( files , imagesOnly ) {
if ( document . visibilityState === 'visible' ) return ;
let title , fileOther ;
const fileName = files [ 0 ] . name ;
2023-12-15 21:19:56 +01:00
2024-02-05 20:39:56 +01:00
if ( files . length === 1 ) {
title = ` ${ fileName } ` ;
}
else {
if ( files . length === 2 ) {
fileOther = imagesOnly
? Localization . getTranslation ( "dialogs.file-other-description-image" )
: Localization . getTranslation ( "dialogs.file-other-description-file" ) ;
2023-11-02 02:56:01 +01:00
}
else {
2024-02-05 20:39:56 +01:00
fileOther = imagesOnly
? Localization . getTranslation ( "dialogs.file-other-description-image-plural" , null , { count : files . length - 1 } )
: Localization . getTranslation ( "dialogs.file-other-description-file-plural" , null , { count : files . length - 1 } ) ;
2023-01-21 18:21:58 +01:00
}
2024-02-05 20:39:56 +01:00
title = ` ${ fileName } ${ fileOther } `
2022-09-13 01:35:05 -04:00
}
2024-02-05 20:39:56 +01:00
const notification = this . _notify ( title , Localization . getTranslation ( "notifications.click-to-download" ) ) ;
this . _bind ( notification , _ => this . _download ( notification ) ) ;
2018-09-21 22:31:46 +02:00
}
2023-03-06 15:05:13 +01:00
_requestNotification ( request , peerId ) {
if ( document . visibilityState !== 'visible' ) {
2023-12-15 21:19:56 +01:00
let imagesOnly = request . header . every ( header => header . mime . split ( '/' ) [ 0 ] === 'image' ) ;
let displayName = $ ( peerId ) . querySelector ( '.name' ) . textContent ;
2023-07-06 21:29:36 +02:00
2023-03-06 15:05:13 +01:00
let descriptor ;
2023-07-06 21:29:36 +02:00
if ( request . header . length === 1 ) {
descriptor = imagesOnly
? Localization . getTranslation ( "dialogs.title-image" )
: Localization . getTranslation ( "dialogs.title-file" ) ;
2023-11-02 02:56:01 +01:00
}
else {
2023-07-06 21:29:36 +02:00
descriptor = imagesOnly
? Localization . getTranslation ( "dialogs.title-image-plural" )
: Localization . getTranslation ( "dialogs.title-file-plural" ) ;
2023-03-06 15:05:13 +01:00
}
2023-07-06 21:29:36 +02:00
2023-11-02 02:56:01 +01:00
let title = Localization
. getTranslation ( "notifications.request-title" , null , {
name : displayName ,
count : request . header . length ,
descriptor : descriptor . toLowerCase ( )
} ) ;
2023-07-06 21:29:36 +02:00
const notification = this . _notify ( title , Localization . getTranslation ( "notifications.click-to-show" ) ) ;
2023-03-06 15:05:13 +01:00
}
}
2018-09-21 23:19:54 +02:00
_download ( notification ) {
2023-11-24 16:25:30 +01:00
this . $downloadBtn . click ( ) ;
2018-09-21 23:19:54 +02:00
notification . close ( ) ;
2018-09-21 16:05:03 +02:00
}
2023-11-02 01:31:31 +01:00
async _copyText ( message , notification ) {
if ( await navigator . clipboard . writeText ( message ) ) {
2023-01-10 05:07:57 +01:00
notification . close ( ) ;
2023-07-06 21:29:36 +02:00
this . _notify ( Localization . getTranslation ( "notifications.copied-text" ) ) ;
2023-11-02 02:56:01 +01:00
}
else {
2023-07-06 21:29:36 +02:00
this . _notify ( Localization . getTranslation ( "notifications.copied-text-error" ) ) ;
2023-01-10 05:07:57 +01:00
}
2018-09-21 23:19:54 +02:00
}
_bind ( notification , handler ) {
if ( notification . then ) {
2023-11-02 02:56:01 +01:00
notification . then ( _ => {
serviceWorker
. getNotifications ( )
. then ( _ => {
serviceWorker . addEventListener ( 'notificationclick' , handler ) ;
} )
} ) ;
}
else {
2018-09-21 23:19:54 +02:00
notification . onclick = handler ;
}
2018-09-21 22:31:46 +02:00
}
2018-09-21 16:05:03 +02:00
}
2019-03-13 01:21:44 +01:00
class NetworkStatusUI {
2019-03-12 23:37:50 +01:00
constructor ( ) {
2023-01-07 01:45:52 +01:00
Events . on ( 'offline' , _ => this . _showOfflineMessage ( ) ) ;
Events . on ( 'online' , _ => this . _showOnlineMessage ( ) ) ;
2019-03-13 01:21:44 +01:00
if ( ! navigator . onLine ) this . _showOfflineMessage ( ) ;
2019-03-12 23:37:50 +01:00
}
2019-03-13 01:21:44 +01:00
_showOfflineMessage ( ) {
2023-10-12 18:57:26 +02:00
Events . fire ( 'notify-user' , {
message : Localization . getTranslation ( "notifications.offline" ) ,
persistent : true
} ) ;
2019-03-12 23:37:50 +01:00
}
2019-03-13 01:21:44 +01:00
_showOnlineMessage ( ) {
2023-07-06 21:29:36 +02:00
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.online" ) ) ;
2019-03-12 23:37:50 +01:00
}
}
2019-03-13 01:48:53 +01:00
class WebShareTargetUI {
2023-01-18 21:01:29 +01:00
2023-12-05 18:57:31 +01:00
async evaluateShareTarget ( shareTargetType , title , text , url ) {
if ( shareTargetType === "text" ) {
let shareTargetText ;
if ( url ) {
shareTargetText = url ; // we share only the link - no text.
}
else if ( title && text ) {
shareTargetText = title + '\r\n' + text ;
}
else {
shareTargetText = title + text ;
2023-11-02 02:56:01 +01:00
}
2023-03-28 19:07:33 +02:00
2023-12-05 18:57:31 +01:00
if ( ShareTextDialog . isApproveShareTextSet ( ) ) {
Events . fire ( 'share-text-dialog' , shareTargetText ) ;
}
else {
Events . fire ( 'activate-share-mode' , { text : shareTargetText } ) ;
}
}
else if ( shareTargetType === "files" ) {
let openRequest = window . indexedDB . open ( 'pairdrop_store' )
openRequest . onsuccess = e => {
const db = e . target . result ;
const tx = db . transaction ( 'share_target_files' , 'readwrite' ) ;
const store = tx . objectStore ( 'share_target_files' ) ;
const request = store . getAll ( ) ;
request . onsuccess = _ => {
const fileObjects = request . result ;
let filesReceived = [ ] ;
for ( let i = 0 ; i < fileObjects . length ; i ++ ) {
filesReceived . push ( new File ( [ fileObjects [ i ] . buffer ] , fileObjects [ i ] . name ) ) ;
2023-01-19 01:28:35 +01:00
}
2023-12-05 18:57:31 +01:00
const clearRequest = store . clear ( )
clearRequest . onsuccess = _ => db . close ( ) ;
Events . fire ( 'activate-share-mode' , { files : filesReceived } )
2023-03-09 17:03:44 +01:00
}
2023-01-18 21:01:29 +01:00
}
}
2019-03-13 01:48:53 +01:00
}
}
2019-03-12 23:37:50 +01:00
2023-11-23 19:57:41 +01:00
// Keep for legacy reasons even though this is removed from new PWA installations
2023-01-18 15:45:53 +01:00
class WebFileHandlersUI {
2023-12-05 18:57:31 +01:00
async evaluateLaunchQueue ( ) {
if ( ! "launchQueue" in window ) return ;
2023-01-18 15:45:53 +01:00
2023-12-05 18:57:31 +01:00
launchQueue . setConsumer ( async launchParams => {
2024-02-05 15:42:27 +01:00
Logger . log ( "Launched with: " , launchParams ) ;
2023-12-05 18:57:31 +01:00
if ( ! launchParams . files . length ) return ;
let files = [ ] ;
for ( let i = 0 ; i < launchParams . files . length ; i ++ ) {
if ( i !== 0 && await launchParams . files [ i ] . isSameEntry ( launchParams . files [ i - 1 ] ) ) continue ;
const file = await launchParams . files [ i ] . getFile ( ) ;
files . push ( file ) ;
}
Events . fire ( 'activate-share-mode' , { files : files } )
} ) ;
2023-01-18 15:45:53 +01:00
}
}
2023-01-17 14:19:51 +01:00
class NoSleepUI {
constructor ( ) {
NoSleepUI . _nosleep = new NoSleep ( ) ;
2024-02-06 14:42:09 +01:00
NoSleepUI . _active = false ;
2023-01-17 14:19:51 +01:00
}
static enable ( ) {
2024-02-06 14:42:09 +01:00
if ( NoSleepUI . _active ) return ;
NoSleepUI . _nosleep . enable ( ) ;
NoSleepUI . _active = true ;
2023-01-17 14:19:51 +01:00
}
2024-02-06 14:42:09 +01:00
static disableIfIdle ( ) {
if ( $$ ( 'x-peer[status]' ) ) return ;
NoSleepUI . _nosleep . disable ( ) ;
NoSleepUI . _active = false ;
2023-01-17 14:19:51 +01:00
}
}