158 lines
4.4 KiB
JavaScript
158 lines
4.4 KiB
JavaScript
import { CustomHTMLElement } from "./lib/custom_html_element.mjs";
|
|
import { API } from 'api'
|
|
|
|
export class N2File extends CustomHTMLElement {
|
|
static {// {{{
|
|
this.tmpl = document.createElement('template')
|
|
this.tmpl.innerHTML = `
|
|
<style>
|
|
:host {
|
|
display: inline-grid;
|
|
grid-template-columns: min-content min-content;
|
|
align-items: end;
|
|
white-space: nowrap;
|
|
cursor: pointer;
|
|
|
|
.el-image {
|
|
max-width: var(--thumbnail-width);
|
|
max-height: var(--thumbnail-height);
|
|
}
|
|
|
|
.el-filename {
|
|
display: none;
|
|
font-weight: bold;
|
|
background-color: #ddd;
|
|
border-radius: 4px;
|
|
padding: 4px 8px;
|
|
margin-left: 8px;
|
|
}
|
|
}
|
|
</style>
|
|
<img data-el="image" src="/images/${_VERSION}/file_icons/generic.svg">
|
|
<div data-el="filename"></div>
|
|
`
|
|
}// }}}
|
|
constructor() {// {{{
|
|
super(true)
|
|
|
|
this.streamLocalFile = false
|
|
this.uuid = null
|
|
|
|
this.addEventListener('click', event => {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
|
|
// Download to disk.
|
|
if (event.shiftKey) {
|
|
this.downloadToDisk()
|
|
return
|
|
}
|
|
|
|
// Open in browser.
|
|
this.openInBrowser(event)
|
|
})
|
|
|
|
this.render()
|
|
}// }}}
|
|
openInBrowser(event) {// {{{
|
|
if (this.streamLocalFile)
|
|
window.open(
|
|
`/stream-local?uuid=${this.metadata.UUID}`,
|
|
event.ctrlKey ? '_blank' : '_self',
|
|
)
|
|
else {
|
|
const jwt = localStorage.getItem('token')
|
|
window.open(
|
|
`/file/uuid/${this.uuid}?jwt=${jwt}`,
|
|
event.ctrlKey ? '_blank' : '_self',
|
|
)
|
|
}
|
|
}// }}}
|
|
async downloadToDisk() {// {{{
|
|
try {
|
|
// TODO: implement download of remote content.
|
|
if (this.streamLocalFile === false)
|
|
throw new Error('This file is only available online. Download not implemented yet.')
|
|
|
|
const handle = await window.showSaveFilePicker({
|
|
suggestedName: this.metadata.name,
|
|
})
|
|
|
|
const writable = await handle.createWritable()
|
|
const blobStream = await globalThis.nodeStore.fileSegments.getStream(this.uuid)
|
|
await blobStream.pipeTo(writable)
|
|
} catch (err) {
|
|
if (err.name == 'AbortError')
|
|
return
|
|
console.error(err)
|
|
alert(err.message)
|
|
}
|
|
}// }}}
|
|
async render() {// {{{
|
|
const src = this.getAttribute('src')
|
|
|
|
// N2's db:// URLs are fetched from IndexedDB.
|
|
if (src.toLowerCase().match('^db://[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$')) {
|
|
// image population has to happen asynchronously,
|
|
// while Marked lib has to be returned a string when exiting this function.
|
|
// populateImg makes sure this returned img element exists and then populates it
|
|
// with the image from IndexedDB.
|
|
const fileUUID = src.slice(5)
|
|
|
|
this.uuid = fileUUID
|
|
const md = await globalThis.nodeStore.filesMetadata.get(fileUUID)
|
|
|
|
if (md) {
|
|
this.metadata = md
|
|
this.streamLocalFile = true
|
|
this.renderLocalFile(md)
|
|
} else {
|
|
/* <file> can be undefined when a node is retrieved but files aren't synced.
|
|
* Try to show from server.
|
|
*
|
|
* Metadata is needed to know whether to display an image or the file representation. */
|
|
try {
|
|
const res = await API.query('GET', `/file/uuid/${fileUUID}/metadata`)
|
|
this.renderRemoteFile(res.Metadata)
|
|
} catch (err) {
|
|
console.error(err)
|
|
}
|
|
}
|
|
|
|
} else
|
|
// Probably an external URL.
|
|
this.elImage.src = src
|
|
}// }}}
|
|
async renderLocalFile(fileMetadata) {// {{{
|
|
if (fileMetadata.type.startsWith('image/'))
|
|
this.elImage.src = `/stream-local?uuid=${fileMetadata.UUID}`
|
|
else {
|
|
// Check for and use an existing MIME type icon.
|
|
// Place them in static/images/file_icons/ and replace the slash with an underscore.
|
|
const url = `/images/${_VERSION}/file_icons/${fileMetadata.type.replaceAll('/', '_')}.svg`
|
|
const res = await fetch(url)
|
|
if (res.ok)
|
|
this.elImage.src = url
|
|
|
|
this.elFilename.innerText = fileMetadata.name
|
|
this.elFilename.style.display = 'block'
|
|
}
|
|
}// }}}
|
|
async renderRemoteFile(md) {// {{{
|
|
if (md.Type.startsWith('image/')) {
|
|
const jwt = localStorage.getItem('token')
|
|
this.elImage.src = `/file/uuid/${md.UUID}?jwt=${jwt}`
|
|
} else {
|
|
// Check for and use an existing MIME type icon.
|
|
// Place them in static/images/file_icons/ and replace the slash with an underscore.
|
|
const url = `/images/${_VERSION}/file_icons/${md.Type.replaceAll('/', '_')}.svg`
|
|
const res = await fetch(url)
|
|
if (res.ok)
|
|
this.elImage.src = url
|
|
|
|
this.elFilename.innerText = md.Name
|
|
this.elFilename.style.display = 'block'
|
|
}
|
|
}// }}}
|
|
}
|
|
customElements.define('n2-file', N2File)
|