File uploads

This commit is contained in:
Magnus Åhall 2023-06-22 08:28:51 +02:00
parent 33b10e6527
commit 8a3970645f
5 changed files with 126 additions and 18 deletions

View File

@ -12,7 +12,7 @@ import (
type File struct { type File struct {
ID int ID int
UserID int `db:"user_id"` UserID int `db:"user_id"`
NodeID int NodeID int `db:"node_id"`
Filename string Filename string
Size int64 Size int64
MIME string MIME string
@ -49,7 +49,7 @@ func (session Session) AddFile(file *File) (err error) { // {{{
func (session Session) Files(nodeID int) (files []File, err error) { // {{{ func (session Session) Files(nodeID int) (files []File, err error) { // {{{
var rows *sqlx.Rows var rows *sqlx.Rows
rows, err = db.Queryx( rows, err = db.Queryx(
`SELECT * FROM files WHERE user_id = $1 AND node_id = $2`, `SELECT * FROM file WHERE user_id = $1 AND node_id = $2`,
session.UserID, session.UserID,
nodeID, nodeID,
) )
@ -58,6 +58,7 @@ func (session Session) Files(nodeID int) (files []File, err error) { // {{{
} }
defer rows.Close() defer rows.Close()
files = []File{}
for rows.Next() { for rows.Next() {
file := File{} file := File{}
if err = rows.StructScan(&file); err != nil { if err = rows.StructScan(&file); err != nil {

View File

@ -17,6 +17,7 @@ type Node struct {
Updated time.Time Updated time.Time
Children []Node Children []Node
Crumbs []Node Crumbs []Node
Files []File
Complete bool Complete bool
Level int Level int
} }
@ -108,6 +109,7 @@ func (session Session) RootNode() (node Node, err error) {// {{{
node.Complete = true node.Complete = true
node.Children = []Node{} node.Children = []Node{}
node.Crumbs = []Node{} node.Crumbs = []Node{}
node.Files = []File{}
for rows.Next() { for rows.Next() {
row := Node{} row := Node{}
if err = rows.StructScan(&row); err != nil { if err = rows.StructScan(&row); err != nil {
@ -202,6 +204,7 @@ func (session Session) Node(nodeID int) (node Node, err error) {// {{{
} }
node.Crumbs, err = session.NodeCrumbs(node.ID) node.Crumbs, err = session.NodeCrumbs(node.ID)
node.Files, err = session.Files(node.ID)
return return
}// }}} }// }}}
@ -271,6 +274,7 @@ func (session Session) CreateNode(parentID int, name string) (node Node, err err
return return
} }
node.Children = []Node{} node.Children = []Node{}
node.Files = []File{}
node.Complete = true node.Complete = true
} }

View File

@ -193,17 +193,41 @@ header .menu {
font-family: monospace; font-family: monospace;
font-size: 0.85em; font-size: 0.85em;
color: #333; color: #333;
width: 100ex; width: 900px;
resize: none; resize: none;
border: none; border: none;
outline: none; outline: none;
} }
.node-content[contenteditable] {
outline: 0px solid transparent;
}
.node-content:invalid { .node-content:invalid {
border-bottom: 1px solid #eee; background: #f5f5f5;
border-radius: 8px; }
#file-section {
justify-self: center;
width: 900px;
margin-top: 32px;
padding: 32px;
background: #f5f5f5;
}
#file-section .header {
font-weight: bold;
color: #000;
margin-bottom: 16px;
}
#file-section .files {
display: grid;
grid-template-columns: 1fr min-content;
grid-gap: 8px 16px;
color: #444;
font-size: 0.85em;
}
#file-section .files .filename {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#file-section .files .size {
white-space: nowrap;
text-align: right;
} }
.tree { .tree {
padding: 16px; padding: 16px;
@ -213,4 +237,8 @@ header .menu {
width: 100%; width: 100%;
justify-self: start; justify-self: start;
} }
#file-section {
width: 100%;
justify-self: start;
}
} }

View File

@ -75,6 +75,8 @@ export class NodeUI extends Component {
<div class="node-name">${node.Name}</div> <div class="node-name">${node.Name}</div>
<${NodeContent} key=${node.ID} content=${node.Content} ref=${this.nodeContent} /> <${NodeContent} key=${node.ID} content=${node.Content} ref=${this.nodeContent} />
` : html``} ` : html``}
<${NodeFiles} node=${this.node.value} />
` `
}//}}} }//}}}
componentDidMount() {//{{{ componentDidMount() {//{{{
@ -212,6 +214,39 @@ class NodeContent extends Component {
} }
class NodeFiles extends Component { class NodeFiles extends Component {
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`
<div class="filename">${file.Filename}</div>
<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`
}
}//}}}
} }
class Node { class Node {
@ -224,6 +259,7 @@ class Node {
this.Content = '' this.Content = ''
this.Children = [] this.Children = []
this.Crumbs = [] this.Crumbs = []
this.Files = []
}//}}} }//}}}
retrieve(callback) {//{{{ retrieve(callback) {//{{{
this.app.request('/node/retrieve', { ID: this.ID }) this.app.request('/node/retrieve', { ID: this.ID })
@ -234,6 +270,7 @@ class Node {
this.Content = res.Node.Content this.Content = res.Node.Content
this.Children = res.Node.Children this.Children = res.Node.Children
this.Crumbs = res.Node.Crumbs this.Crumbs = res.Node.Crumbs
this.Files = res.Node.Files
callback(this) callback(this)
}) })
.catch(this.app.responseError) .catch(this.app.responseError)
@ -294,8 +331,8 @@ class Menu extends Component {
}//}}} }//}}}
} }
class UploadUI extends Component { class UploadUI extends Component {
constructor() {//{{{ constructor(props) {//{{{
super() super(props)
this.file = createRef() this.file = createRef()
this.filelist = signal([]) this.filelist = signal([])
this.fileRefs = [] this.fileRefs = []
@ -339,9 +376,14 @@ class UploadUI extends Component {
progress=>{ progress=>{
this.progressRefs[i].current.innerHTML = `${progress}%` this.progressRefs[i].current.innerHTML = `${progress}%`
}, },
()=>{ res=>{
this.props.nodeui.node.value.Files.push(res.File)
this.props.nodeui.forceUpdate()
this.fileRefs[i].current.classList.add("done") this.fileRefs[i].current.classList.add("done")
this.progressRefs[i].current.classList.add("done") this.progressRefs[i].current.classList.add("done")
this.props.nodeui.upload.value = false
}) })
} }
}//}}} }//}}}
@ -368,7 +410,7 @@ class UploadUI extends Component {
return return
} }
doneCallback() doneCallback(response)
}) })
request.upload.addEventListener('progress', evt=>{ request.upload.addEventListener('progress', evt=>{

View File

@ -227,18 +227,46 @@ header {
font-family: monospace; font-family: monospace;
font-size: 0.85em; font-size: 0.85em;
color: #333; color: #333;
width: 100ex; width: 900px;
resize: none; resize: none;
border: none; border: none;
outline: none; outline: none;
&[contenteditable] { &:invalid {
outline: 0px solid transparent; background: #f5f5f5;
}
} }
&:invalid { #file-section {
border-bottom: 1px solid #eee; justify-self: center;
border-radius: 8px; width: 900px;
margin-top: 32px;
padding: 32px;
background: #f5f5f5;
.header {
font-weight: bold;
color: #000;
margin-bottom: 16px;
}
.files {
display: grid;
grid-template-columns: 1fr min-content;
grid-gap: 8px 16px;
color: #444;
font-size: 0.85em;
.filename {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.size {
white-space: nowrap;
text-align: right;
}
} }
} }
@ -251,4 +279,9 @@ header {
width: 100%; width: 100%;
justify-self: start; justify-self: start;
} }
#file-section {
width: 100%;
justify-self: start;
}
} }