2023-06-18 20:13:35 +02:00
|
|
|
import { h, Component, createRef } from 'preact'
|
2023-06-17 09:11:14 +02:00
|
|
|
import htm from 'htm'
|
|
|
|
import { signal } from 'preact/signals'
|
|
|
|
const html = htm.bind(h)
|
|
|
|
|
|
|
|
export class NodeUI extends Component {
|
|
|
|
constructor() {//{{{
|
|
|
|
super()
|
2023-06-18 22:05:10 +02:00
|
|
|
this.menu = signal(false)
|
2023-06-17 09:11:14 +02:00
|
|
|
this.node = signal(null)
|
2023-06-18 20:13:35 +02:00
|
|
|
this.nodeContent = createRef()
|
2023-06-21 23:52:21 +02:00
|
|
|
this.upload = signal(false)
|
2023-06-27 14:44:36 +02:00
|
|
|
|
2023-06-17 09:11:14 +02:00
|
|
|
window.addEventListener('popstate', evt=>{
|
|
|
|
if(evt.state && evt.state.hasOwnProperty('nodeID'))
|
|
|
|
this.goToNode(evt.state.nodeID, true)
|
|
|
|
else
|
|
|
|
this.goToNode(0, true)
|
|
|
|
})
|
2023-06-27 14:44:36 +02:00
|
|
|
|
2023-06-20 07:59:54 +02:00
|
|
|
window.addEventListener('keydown', evt=>this.keyHandler(evt))
|
2023-06-17 09:11:14 +02:00
|
|
|
}//}}}
|
|
|
|
render() {//{{{
|
|
|
|
if(this.node.value === null)
|
|
|
|
return
|
|
|
|
|
|
|
|
let node = this.node.value
|
|
|
|
|
|
|
|
let crumbs = [
|
|
|
|
html`<div class="crumb" onclick=${()=>this.goToNode(0)}>Start</div>`
|
|
|
|
]
|
|
|
|
|
2023-06-18 20:13:35 +02:00
|
|
|
crumbs = crumbs.concat(node.Crumbs.slice(0).map(node=>
|
2023-06-17 09:11:14 +02:00
|
|
|
html`<div class="crumb" onclick=${()=>this.goToNode(node.ID)}>${node.Name}</div>`
|
|
|
|
).reverse())
|
|
|
|
|
|
|
|
let children = node.Children.sort((a,b)=>{
|
|
|
|
if(a.Name.toLowerCase() > b.Name.toLowerCase()) return 1;
|
|
|
|
if(a.Name.toLowerCase() < b.Name.toLowerCase()) return -1;
|
|
|
|
return 0
|
|
|
|
}).map(child=>html`
|
|
|
|
<div class="child-node" onclick=${()=>this.goToNode(child.ID)}>${child.Name}</div>
|
|
|
|
`)
|
|
|
|
|
2023-06-18 20:13:35 +02:00
|
|
|
let modified = ''
|
|
|
|
if(this.props.app.nodeModified.value)
|
|
|
|
modified = 'modified';
|
|
|
|
|
2023-06-21 23:52:21 +02:00
|
|
|
let upload = '';
|
|
|
|
if(this.upload.value)
|
2023-06-22 06:52:27 +02:00
|
|
|
upload = html`<${UploadUI} nodeui=${this} />`
|
2023-06-21 23:52:21 +02:00
|
|
|
|
|
|
|
let menu = '';
|
|
|
|
if(this.menu.value)
|
2023-06-22 06:52:27 +02:00
|
|
|
upload = html`<${Menu} nodeui=${this} />`
|
2023-06-18 22:05:10 +02:00
|
|
|
|
2023-06-21 23:52:21 +02:00
|
|
|
return html`
|
|
|
|
${menu}
|
|
|
|
${upload}
|
2023-06-18 20:13:35 +02:00
|
|
|
<header class="${modified}" onclick=${()=>this.saveNode()}>
|
2023-06-27 15:08:48 +02:00
|
|
|
<div class="tree"><img src="/images/${window._VERSION}/tree.svg" onclick=${()=>document.getElementById('app').classList.toggle('toggle-tree')} /></div>
|
2023-06-18 20:13:35 +02:00
|
|
|
<div class="name">Notes</div>
|
2023-06-20 08:13:32 +02:00
|
|
|
<div class="add" onclick=${evt=>this.createNode(evt)}>+</div>
|
|
|
|
<div class="menu" onclick=${evt=>this.showMenu(evt)}>☰</div>
|
2023-06-18 20:13:35 +02:00
|
|
|
</header>
|
2023-06-18 22:05:10 +02:00
|
|
|
|
2023-06-27 14:44:36 +02:00
|
|
|
<div id="crumbs">
|
|
|
|
<div class="crumbs">${crumbs}</crumbs>
|
|
|
|
</div>
|
2023-06-18 22:05:10 +02:00
|
|
|
|
2023-06-17 09:11:14 +02:00
|
|
|
${children.length > 0 ? html`<div class="child-nodes">${children}</div>` : html``}
|
2023-06-18 22:05:10 +02:00
|
|
|
|
|
|
|
${node.ID > 0 ? html`
|
|
|
|
<div class="node-name">${node.Name}</div>
|
|
|
|
<${NodeContent} key=${node.ID} content=${node.Content} ref=${this.nodeContent} />
|
|
|
|
` : html``}
|
2023-06-22 08:28:51 +02:00
|
|
|
|
|
|
|
<${NodeFiles} node=${this.node.value} />
|
2023-06-17 09:11:14 +02:00
|
|
|
`
|
|
|
|
}//}}}
|
|
|
|
componentDidMount() {//{{{
|
2023-06-27 14:44:36 +02:00
|
|
|
this.props.app.startNode.retrieve(node=>{
|
2023-06-17 09:11:14 +02:00
|
|
|
this.node.value = node
|
2023-06-27 14:44:36 +02:00
|
|
|
|
|
|
|
// The tree isn't guaranteed to have loaded yet. This is also run from
|
|
|
|
// the tree code, in case the node hasn't loaded.
|
|
|
|
this.props.app.tree.crumbsUpdateNodes(node)
|
2023-06-17 09:11:14 +02:00
|
|
|
})
|
|
|
|
}//}}}
|
2023-06-18 22:05:10 +02:00
|
|
|
|
2023-06-20 07:59:54 +02:00
|
|
|
keyHandler(evt) {//{{{
|
|
|
|
let handled = true
|
|
|
|
switch(evt.key.toUpperCase()) {
|
|
|
|
case 'S':
|
2023-06-21 23:52:21 +02:00
|
|
|
if(evt.ctrlKey || (evt.shiftKey && evt.altKey))
|
|
|
|
this.saveNode()
|
2023-06-22 07:10:26 +02:00
|
|
|
else
|
|
|
|
handled = false
|
2023-06-20 07:59:54 +02:00
|
|
|
break
|
|
|
|
|
|
|
|
case 'N':
|
2023-06-21 23:52:21 +02:00
|
|
|
if((evt.ctrlKey && evt.AltKey) || (evt.shiftKey && evt.altKey))
|
|
|
|
this.createNode()
|
2023-06-22 07:10:26 +02:00
|
|
|
else
|
|
|
|
handled = false
|
2023-06-20 07:59:54 +02:00
|
|
|
break
|
|
|
|
|
2023-06-21 23:52:21 +02:00
|
|
|
case 'U':
|
|
|
|
if((evt.ctrlKey && evt.altKey) || (evt.shiftKey && evt.altKey))
|
|
|
|
this.upload.value = true
|
2023-06-22 07:10:26 +02:00
|
|
|
else
|
|
|
|
handled = false
|
2023-06-21 23:52:21 +02:00
|
|
|
|
2023-06-20 07:59:54 +02:00
|
|
|
default:
|
|
|
|
handled = false
|
|
|
|
}
|
|
|
|
|
|
|
|
if(handled) {
|
|
|
|
evt.preventDefault()
|
|
|
|
evt.stopPropagation()
|
|
|
|
}
|
|
|
|
}//}}}
|
2023-06-20 08:13:32 +02:00
|
|
|
showMenu(evt) {//{{{
|
|
|
|
evt.stopPropagation()
|
2023-06-18 22:05:10 +02:00
|
|
|
this.menu.value = true
|
|
|
|
}//}}}
|
2023-06-20 08:13:32 +02:00
|
|
|
logout() {//{{{
|
|
|
|
window.localStorage.removeItem('session.UUID')
|
|
|
|
location.href = '/'
|
|
|
|
}//}}}
|
2023-06-18 22:05:10 +02:00
|
|
|
|
2023-06-17 09:11:14 +02:00
|
|
|
goToNode(nodeID, dontPush) {//{{{
|
2023-06-18 20:13:35 +02:00
|
|
|
if(this.props.app.nodeModified.value) {
|
|
|
|
if(!confirm("Changes not saved. Do you want to discard changes?"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-17 09:11:14 +02:00
|
|
|
if(!dontPush)
|
2023-06-18 20:13:35 +02:00
|
|
|
history.pushState({ nodeID }, '', `/?node=${nodeID}`)
|
2023-06-27 14:44:36 +02:00
|
|
|
|
|
|
|
// New node is fetched in order to retrieve content and files.
|
|
|
|
// Such data is unnecessary to transfer for tree/navigational purposes.
|
2023-06-17 09:11:14 +02:00
|
|
|
let node = new Node(this.props.app, nodeID)
|
|
|
|
node.retrieve(node=>{
|
2023-06-18 20:13:35 +02:00
|
|
|
this.props.app.nodeModified.value = false
|
2023-06-17 09:11:14 +02:00
|
|
|
this.node.value = node
|
2023-06-27 14:44:36 +02:00
|
|
|
|
|
|
|
// Tree needs to know another node is selected, in order to render any
|
|
|
|
// previously selected node not selected.
|
|
|
|
this.props.app.tree.setSelected(node)
|
2023-06-27 15:08:48 +02:00
|
|
|
|
|
|
|
// Hide tree toggle, as this would be the next natural action to do manually anyway.
|
|
|
|
// At least in mobile mode.
|
|
|
|
document.getElementById('app').classList.remove('toggle-tree');
|
2023-06-17 09:11:14 +02:00
|
|
|
})
|
|
|
|
}//}}}
|
2023-06-20 08:13:32 +02:00
|
|
|
createNode(evt) {//{{{
|
2023-06-21 23:52:21 +02:00
|
|
|
if(evt)
|
|
|
|
evt.stopPropagation()
|
2023-06-18 20:13:35 +02:00
|
|
|
let name = prompt("Name")
|
|
|
|
if(!name)
|
|
|
|
return
|
2023-06-22 07:10:26 +02:00
|
|
|
this.node.value.create(name, nodeID=>this.goToNode(nodeID))
|
2023-06-18 20:13:35 +02:00
|
|
|
}//}}}
|
2023-06-18 22:05:10 +02:00
|
|
|
saveNode() {//{{{
|
2023-06-20 07:35:34 +02:00
|
|
|
let content = this.nodeContent.current.contentDiv.current.value
|
2023-06-22 07:10:26 +02:00
|
|
|
this.node.value.save(content, ()=>this.props.app.nodeModified.value = false)
|
2023-06-18 22:05:10 +02:00
|
|
|
}//}}}
|
|
|
|
renameNode() {//{{{
|
|
|
|
let name = prompt("New name")
|
|
|
|
if(!name)
|
|
|
|
return
|
|
|
|
|
2023-06-22 07:10:26 +02:00
|
|
|
this.node.value.rename(name, ()=>{
|
2023-06-18 22:05:10 +02:00
|
|
|
this.goToNode(this.node.value.ID)
|
|
|
|
this.menu.value = false
|
|
|
|
})
|
|
|
|
}//}}}
|
|
|
|
deleteNode() {//{{{
|
|
|
|
if(!confirm("Do you want to delete this note and all sub-notes?"))
|
|
|
|
return
|
2023-06-22 07:10:26 +02:00
|
|
|
this.node.value.delete(()=>{
|
2023-06-18 22:05:10 +02:00
|
|
|
this.goToNode(this.node.value.ParentID)
|
|
|
|
this.menu.value = false
|
|
|
|
})
|
|
|
|
}//}}}
|
2023-06-18 20:13:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class NodeContent extends Component {
|
|
|
|
constructor(props) {//{{{
|
|
|
|
super(props)
|
|
|
|
this.contentDiv = createRef()
|
|
|
|
this.state = {
|
|
|
|
modified: false,
|
|
|
|
//content: props.content,
|
|
|
|
}
|
|
|
|
}//}}}
|
|
|
|
render({ content }) {//{{{
|
2023-06-20 07:35:34 +02:00
|
|
|
return html`
|
2023-06-27 14:44:36 +02:00
|
|
|
<div class="grow-wrap">
|
|
|
|
<textarea id="node-content" class="node-content" ref=${this.contentDiv} oninput=${()=>this.contentChanged()} required rows=1>${content}</textarea>
|
|
|
|
</div>
|
2023-06-20 07:35:34 +02:00
|
|
|
`
|
|
|
|
}//}}}
|
|
|
|
componentDidMount() {//{{{
|
|
|
|
this.resize()
|
2023-06-27 14:44:36 +02:00
|
|
|
window.addEventListener('resize', ()=>this.resize())
|
2023-06-20 07:35:34 +02:00
|
|
|
}//}}}
|
|
|
|
componentDidUpdate() {//{{{
|
|
|
|
this.resize()
|
|
|
|
}//}}}
|
|
|
|
contentChanged() {//{{{
|
|
|
|
window._app.current.nodeModified.value = true
|
|
|
|
this.resize()
|
|
|
|
}//}}}
|
|
|
|
resize() {//{{{
|
2023-06-27 14:44:36 +02:00
|
|
|
let textarea = document.getElementById('node-content')
|
|
|
|
if(textarea)
|
|
|
|
textarea.parentNode.dataset.replicatedValue = textarea.value
|
2023-06-18 20:13:35 +02:00
|
|
|
}//}}}
|
2023-06-17 09:11:14 +02:00
|
|
|
}
|
|
|
|
|
2023-06-22 06:52:27 +02:00
|
|
|
class NodeFiles extends Component {
|
2023-06-22 08:28:51 +02:00
|
|
|
render({ node }) {//{{{
|
|
|
|
if(node.Files === null || node.Files.length == 0)
|
|
|
|
return
|
|
|
|
|
|
|
|
let files = node.Files
|
|
|
|
.sort((a, b)=>{
|
|
|
|
if(a.Filename.toUpperCase() < b.Filename.toUpperCase()) return -1
|
|
|
|
if(a.Filename.toUpperCase() > b.Filename.toUpperCase()) return 1
|
|
|
|
return 0
|
|
|
|
})
|
|
|
|
.map(file=>
|
|
|
|
html`
|
2023-06-22 16:48:31 +02:00
|
|
|
<div class="filename" onclick=${()=>node.download(file.ID)}>${file.Filename}</div>
|
2023-06-22 08:28:51 +02:00
|
|
|
<div class="size">${this.formatSize(file.Size)}</div>
|
|
|
|
`
|
|
|
|
)
|
|
|
|
|
|
|
|
return html`
|
|
|
|
<div id="file-section">
|
|
|
|
<div class="header">Files</div>
|
|
|
|
<div class="files">
|
|
|
|
${files}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`
|
|
|
|
}//}}}
|
|
|
|
formatSize(size) {//{{{
|
|
|
|
if(size < 1048576) {
|
|
|
|
return `${Math.round(size / 1024)} KiB`
|
|
|
|
} else {
|
|
|
|
return `${Math.round(size / 1048576)} MiB`
|
|
|
|
}
|
|
|
|
}//}}}
|
2023-06-22 06:52:27 +02:00
|
|
|
}
|
|
|
|
|
2023-06-27 14:44:36 +02:00
|
|
|
export class Node {
|
2023-06-17 09:11:14 +02:00
|
|
|
constructor(app, nodeID) {//{{{
|
2023-06-27 14:44:36 +02:00
|
|
|
this.app = app
|
|
|
|
this.ID = nodeID
|
|
|
|
this.ParentID = 0
|
|
|
|
this.UserID = 0
|
|
|
|
this.Name = ''
|
|
|
|
this.Content = ''
|
|
|
|
this.Children = []
|
|
|
|
this.Crumbs = []
|
|
|
|
this.Files = []
|
|
|
|
this._expanded = false // start value for the TreeNode component,
|
|
|
|
// it doesn't control it afterwards.
|
|
|
|
// Used to expand the crumbs upon site loading.
|
2023-06-17 09:11:14 +02:00
|
|
|
}//}}}
|
|
|
|
retrieve(callback) {//{{{
|
|
|
|
this.app.request('/node/retrieve', { ID: this.ID })
|
|
|
|
.then(res=>{
|
|
|
|
this.ParentID = res.Node.ParentID
|
|
|
|
this.UserID = res.Node.UserID
|
|
|
|
this.Name = res.Node.Name
|
|
|
|
this.Content = res.Node.Content
|
|
|
|
this.Children = res.Node.Children
|
|
|
|
this.Crumbs = res.Node.Crumbs
|
2023-06-22 08:28:51 +02:00
|
|
|
this.Files = res.Node.Files
|
2023-06-17 09:11:14 +02:00
|
|
|
callback(this)
|
|
|
|
})
|
|
|
|
.catch(this.app.responseError)
|
|
|
|
}//}}}
|
2023-06-22 07:10:26 +02:00
|
|
|
delete(callback) {//{{{
|
|
|
|
this.app.request('/node/delete', {
|
|
|
|
NodeID: this.ID,
|
|
|
|
})
|
|
|
|
.then(callback)
|
|
|
|
.catch(this.app.responseError)
|
|
|
|
}//}}}
|
|
|
|
create(name, callback) {//{{{
|
|
|
|
this.app.request('/node/create', {
|
|
|
|
Name: name.trim(),
|
|
|
|
ParentID: this.ID,
|
|
|
|
})
|
|
|
|
.then(res=>{
|
|
|
|
callback(res.Node.ID)
|
|
|
|
})
|
|
|
|
.catch(this.app.responseError)
|
|
|
|
}//}}}
|
|
|
|
save(content, callback) {//{{{
|
|
|
|
this.app.request('/node/update', {
|
|
|
|
NodeID: this.ID,
|
|
|
|
Content: content,
|
|
|
|
})
|
|
|
|
.then(callback)
|
|
|
|
.catch(this.app.responseError)
|
|
|
|
}//}}}
|
|
|
|
rename(name, callback) {//{{{
|
|
|
|
this.app.request('/node/rename', {
|
|
|
|
Name: name.trim(),
|
|
|
|
NodeID: this.ID,
|
|
|
|
})
|
|
|
|
.then(callback)
|
|
|
|
.catch(this.app.responseError)
|
|
|
|
}//}}}
|
2023-06-22 16:48:31 +02:00
|
|
|
download(fileID) {//{{{
|
|
|
|
let headers = {
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
}
|
|
|
|
|
|
|
|
if(this.app.session.UUID !== '')
|
|
|
|
headers['X-Session-Id'] = this.app.session.UUID
|
|
|
|
|
|
|
|
let fname = ""
|
|
|
|
fetch("/node/download", {
|
|
|
|
method: 'POST',
|
|
|
|
headers,
|
|
|
|
body: JSON.stringify({
|
|
|
|
NodeID: this.ID,
|
|
|
|
FileID: fileID,
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
.then(response=>{
|
2023-06-22 17:42:34 +02:00
|
|
|
let match = response.headers.get('content-disposition').match(/filename="([^"]*)"/)
|
|
|
|
fname = match[1]
|
2023-06-22 16:48:31 +02:00
|
|
|
return response.blob()
|
|
|
|
})
|
|
|
|
.then(blob=>{
|
|
|
|
let url = window.URL.createObjectURL(blob)
|
|
|
|
let a = document.createElement('a');
|
|
|
|
a.href = url;
|
2023-06-22 17:42:34 +02:00
|
|
|
a.download = fname;
|
2023-06-22 16:48:31 +02:00
|
|
|
document.body.appendChild(a); // we need to append the element to the dom -> otherwise it will not work in firefox
|
|
|
|
a.click();
|
|
|
|
a.remove(); //afterwards we remove the element again
|
|
|
|
})
|
|
|
|
}//}}}
|
2023-06-17 09:11:14 +02:00
|
|
|
}
|
|
|
|
|
2023-06-21 23:52:21 +02:00
|
|
|
class Menu extends Component {
|
2023-06-22 06:52:27 +02:00
|
|
|
render({ nodeui }) {//{{{
|
2023-06-21 23:52:21 +02:00
|
|
|
return html`
|
2023-06-22 06:52:27 +02:00
|
|
|
<div id="blackout" onclick=${()=>nodeui.menu.value = false}></div>
|
|
|
|
<div id="menu">
|
|
|
|
<div class="item" onclick=${()=>{ nodeui.renameNode(); nodeui.menu.value = false }}>Rename</div>
|
|
|
|
<div class="item separator" onclick=${()=>{ nodeui.deleteNode(); nodeui.menu.value = false }}>Delete</div>
|
|
|
|
<div class="item separator" onclick=${()=>{ nodeui.upload.value = true; nodeui.menu.value = false }}>Upload</div>
|
|
|
|
<div class="item" onclick=${()=>{ nodeui.logout(); nodeui.menu.value = false }}>Log out</div>
|
2023-06-21 23:52:21 +02:00
|
|
|
</div>
|
|
|
|
`
|
|
|
|
}//}}}
|
|
|
|
}
|
|
|
|
class UploadUI extends Component {
|
2023-06-22 08:28:51 +02:00
|
|
|
constructor(props) {//{{{
|
|
|
|
super(props)
|
2023-06-21 23:52:21 +02:00
|
|
|
this.file = createRef()
|
|
|
|
this.filelist = signal([])
|
|
|
|
this.fileRefs = []
|
|
|
|
this.progressRefs = []
|
|
|
|
}//}}}
|
|
|
|
render({ nodeui }) {//{{{
|
|
|
|
let filelist = this.filelist.value
|
|
|
|
let files = []
|
|
|
|
for(let i = 0; i < filelist.length; i++) {
|
|
|
|
files.push(html`<div key=file_${i} ref=${this.fileRefs[i]} class="file">${filelist.item(i).name}</div><div class="progress" ref=${this.progressRefs[i]}></div>`)
|
|
|
|
}
|
|
|
|
|
|
|
|
return html`
|
|
|
|
<div id="blackout" onclick=${()=>nodeui.upload.value = false}></div>
|
|
|
|
<div id="upload">
|
|
|
|
<input type="file" ref=${this.file} onchange=${()=>this.upload()} multiple />
|
|
|
|
<div class="files">
|
|
|
|
${files}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`
|
|
|
|
}//}}}
|
|
|
|
componentDidMount() {//{{{
|
|
|
|
this.file.current.focus()
|
|
|
|
}//}}}
|
|
|
|
|
|
|
|
upload() {//{{{
|
2023-06-22 06:52:27 +02:00
|
|
|
let nodeID = this.props.nodeui.node.value.ID
|
2023-06-21 23:52:21 +02:00
|
|
|
this.fileRefs = []
|
|
|
|
this.progressRefs = []
|
|
|
|
|
|
|
|
let input = this.file.current
|
|
|
|
this.filelist.value = input.files
|
|
|
|
for(let i = 0; i < input.files.length; i++) {
|
|
|
|
this.fileRefs.push(createRef())
|
|
|
|
this.progressRefs.push(createRef())
|
2023-06-22 06:52:27 +02:00
|
|
|
|
|
|
|
this.postFile(
|
|
|
|
input.files[i],
|
|
|
|
nodeID,
|
|
|
|
progress=>{
|
|
|
|
this.progressRefs[i].current.innerHTML = `${progress}%`
|
|
|
|
},
|
2023-06-22 08:28:51 +02:00
|
|
|
res=>{
|
|
|
|
this.props.nodeui.node.value.Files.push(res.File)
|
|
|
|
this.props.nodeui.forceUpdate()
|
|
|
|
|
2023-06-22 06:52:27 +02:00
|
|
|
this.fileRefs[i].current.classList.add("done")
|
|
|
|
this.progressRefs[i].current.classList.add("done")
|
2023-06-22 08:28:51 +02:00
|
|
|
|
|
|
|
this.props.nodeui.upload.value = false
|
2023-06-22 06:52:27 +02:00
|
|
|
})
|
2023-06-21 23:52:21 +02:00
|
|
|
}
|
|
|
|
}//}}}
|
2023-06-22 06:52:27 +02:00
|
|
|
postFile(file, nodeID, progressCallback, doneCallback) {//{{{
|
2023-06-21 23:52:21 +02:00
|
|
|
var formdata = new FormData()
|
|
|
|
formdata.append('file', file)
|
2023-06-22 06:52:27 +02:00
|
|
|
formdata.append('NodeID', nodeID)
|
2023-06-21 23:52:21 +02:00
|
|
|
|
|
|
|
var request = new XMLHttpRequest()
|
|
|
|
|
|
|
|
request.addEventListener("error", ()=>{
|
|
|
|
window._app.current.responseError({ upload: "An unknown error occured" })
|
|
|
|
})
|
|
|
|
|
|
|
|
request.addEventListener("loadend", ()=>{
|
|
|
|
if(request.status != 200) {
|
|
|
|
window._app.current.responseError({ upload: request.statusText })
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let response = JSON.parse(request.response)
|
|
|
|
if(!response.OK) {
|
|
|
|
window._app.current.responseError({ upload: response.Error })
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-22 08:28:51 +02:00
|
|
|
doneCallback(response)
|
2023-06-21 23:52:21 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
request.upload.addEventListener('progress', evt=>{
|
|
|
|
var fileSize = file.size
|
|
|
|
|
|
|
|
if(evt.loaded <= fileSize)
|
|
|
|
progressCallback(Math.round(evt.loaded / fileSize * 100))
|
|
|
|
if(evt.loaded == evt.total)
|
|
|
|
progressCallback(100)
|
|
|
|
})
|
|
|
|
|
|
|
|
request.open('post', '/node/upload')
|
|
|
|
request.setRequestHeader("X-Session-Id", window._app.current.session.UUID)
|
|
|
|
//request.timeout = 45000
|
|
|
|
request.send(formdata)
|
|
|
|
}//}}}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-06-17 09:11:14 +02:00
|
|
|
// vim: foldmethod=marker
|