bug fixes, accessiblity improvements, and more
This commit is contained in:
@@ -20,5 +20,5 @@ DB_HOST=localhost:5432 DB_NAME=filething DB_USER=postgres STORAGE_PATH=data ./fi
|
|||||||
To run filething in dev mode with a hot reloading Ui server and auto rebuilding backend server, run
|
To run filething in dev mode with a hot reloading Ui server and auto rebuilding backend server, run
|
||||||
|
|
||||||
```BASH
|
```BASH
|
||||||
DB_HOST=localhost:5432 DB_NAME=filething DB_USER=postgres STORAGE_PATH=data CompileDaemon --build="go build -tags netgo,dev -ldflags=-s" --command=./filething --exclude-dir=data/ --graceful-kill
|
DB_HOST=localhost:5432 DB_NAME=filething DB_USER=postgres STORAGE_PATH=data CompileDaemon --build="go build -tags netgo,dev -ldflags=-s" --command=./filething --exclude-dir=data/ --exclude-dir=ui. --graceful-kill
|
||||||
```
|
```
|
||||||
|
|||||||
12
go.mod
12
go.mod
@@ -2,13 +2,7 @@ module filething
|
|||||||
|
|
||||||
go 1.23.0
|
go 1.23.0
|
||||||
|
|
||||||
require github.com/go-pg/pg v8.0.7+incompatible
|
require github.com/google/uuid v1.6.0
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/onsi/ginkgo v1.14.2 // indirect
|
|
||||||
github.com/onsi/gomega v1.10.3 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||||
@@ -26,8 +20,8 @@ require (
|
|||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
golang.org/x/crypto v0.26.0 // indirect
|
golang.org/x/crypto v0.26.0
|
||||||
golang.org/x/net v0.24.0 // indirect
|
golang.org/x/net v0.25.0 // indirect
|
||||||
golang.org/x/sys v0.24.0 // indirect
|
golang.org/x/sys v0.24.0 // indirect
|
||||||
golang.org/x/text v0.17.0 // indirect
|
golang.org/x/text v0.17.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
|
|||||||
65
go.sum
65
go.sum
@@ -1,25 +1,9 @@
|
|||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
|
||||||
github.com/go-pg/pg v8.0.7+incompatible h1:ty/sXL1OZLo+47KK9N8llRcmbA9tZasqbQ/OO4ld53g=
|
|
||||||
github.com/go-pg/pg v8.0.7+incompatible/go.mod h1:a2oXow+aFOrvwcKs3eIA0lNFmMilrxK2sOkB5NWe0vA=
|
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
||||||
@@ -31,16 +15,6 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
|||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
|
||||||
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
|
|
||||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
|
||||||
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
|
|
||||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
||||||
@@ -63,53 +37,18 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU
|
|||||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
|
||||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
|
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
|
||||||
|
|||||||
15
main.go
15
main.go
@@ -10,7 +10,6 @@ import (
|
|||||||
"filething/routes"
|
"filething/routes"
|
||||||
"filething/ui"
|
"filething/ui"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -25,7 +24,6 @@ import (
|
|||||||
var initUi func(e *echo.Echo)
|
var initUi func(e *echo.Echo)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
dbHost := os.Getenv("DB_HOST")
|
dbHost := os.Getenv("DB_HOST")
|
||||||
dbName := os.Getenv("DB_NAME")
|
dbName := os.Getenv("DB_NAME")
|
||||||
dbUser := os.Getenv("DB_USER")
|
dbUser := os.Getenv("DB_USER")
|
||||||
@@ -35,6 +33,7 @@ func main() {
|
|||||||
panic("Missing database environment variabled!")
|
panic("Missing database environment variabled!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: retry connection or only connect at the first moment that we need the db
|
||||||
dbUrl := fmt.Sprintf("postgres://%s:%s@%s/%s?dial_timeout=10s&sslmode=disable", dbUser, dbPasswd, dbHost, dbName)
|
dbUrl := fmt.Sprintf("postgres://%s:%s@%s/%s?dial_timeout=10s&sslmode=disable", dbUser, dbPasswd, dbHost, dbName)
|
||||||
|
|
||||||
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dbUrl)))
|
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dbUrl)))
|
||||||
@@ -52,6 +51,7 @@ func main() {
|
|||||||
|
|
||||||
e := echo.New()
|
e := echo.New()
|
||||||
|
|
||||||
|
// insert the db into the echo context so it is easily accessible in routes and middleware
|
||||||
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
c.Set("db", db)
|
c.Set("db", db)
|
||||||
@@ -68,6 +68,7 @@ func main() {
|
|||||||
CookieHTTPOnly: true,
|
CookieHTTPOnly: true,
|
||||||
CookieSameSite: http.SameSiteStrictMode,
|
CookieSameSite: http.SameSiteStrictMode,
|
||||||
}))
|
}))
|
||||||
|
e.Use(echoMiddleware.Secure())
|
||||||
|
|
||||||
api := e.Group("/api")
|
api := e.Group("/api")
|
||||||
{
|
{
|
||||||
@@ -76,6 +77,7 @@ func main() {
|
|||||||
|
|
||||||
// everything past this needs auth
|
// everything past this needs auth
|
||||||
api.Use(middleware.SessionMiddleware(db))
|
api.Use(middleware.SessionMiddleware(db))
|
||||||
|
api.POST("/logout", routes.LogoutHandler)
|
||||||
api.GET("/user", routes.GetUser)
|
api.GET("/user", routes.GetUser)
|
||||||
|
|
||||||
api.POST("/files/upload*", routes.UploadFile)
|
api.POST("/files/upload*", routes.UploadFile)
|
||||||
@@ -88,6 +90,9 @@ func main() {
|
|||||||
// this isnt explicitly required, but it provides a better experience than doing this same thing clientside
|
// this isnt explicitly required, but it provides a better experience than doing this same thing clientside
|
||||||
e.Use(middleware.AuthCheckMiddleware)
|
e.Use(middleware.AuthCheckMiddleware)
|
||||||
|
|
||||||
|
// calls out to a function set by either server.go server_dev.go based on the value of the dev tag, and hosts
|
||||||
|
// either the static files that get embedded into the binary in ui/embed.go or proxies the dev server that gets
|
||||||
|
// run in the provided function
|
||||||
initUi(e)
|
initUi(e)
|
||||||
|
|
||||||
e.HTTPErrorHandler = customHTTPErrorHandler
|
e.HTTPErrorHandler = customHTTPErrorHandler
|
||||||
@@ -97,6 +102,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom Error handling since Nuxt relies on the 404 page for dynamic pages we still want api routes to use the default
|
||||||
|
// error handling built into echo
|
||||||
func customHTTPErrorHandler(err error, c echo.Context) {
|
func customHTTPErrorHandler(err error, c echo.Context) {
|
||||||
if he, ok := err.(*echo.HTTPError); ok && he.Code == http.StatusNotFound {
|
if he, ok := err.(*echo.HTTPError); ok && he.Code == http.StatusNotFound {
|
||||||
path := c.Request().URL.Path
|
path := c.Request().URL.Path
|
||||||
@@ -132,6 +139,7 @@ func customHTTPErrorHandler(err error, c echo.Context) {
|
|||||||
c.Echo().DefaultHTTPErrorHandler(err, c)
|
c.Echo().DefaultHTTPErrorHandler(err, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// creates tables in the db if they dont already exist
|
||||||
func createSchema(db *bun.DB) error {
|
func createSchema(db *bun.DB) error {
|
||||||
models := []interface{}{
|
models := []interface{}{
|
||||||
(*models.User)(nil),
|
(*models.User)(nil),
|
||||||
@@ -149,6 +157,7 @@ func createSchema(db *bun.DB) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// seeds the storage plans into the database
|
||||||
func seedPlans(db *bun.DB) error {
|
func seedPlans(db *bun.DB) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
count, err := db.NewSelect().Model((*models.Plan)(nil)).Count(ctx)
|
count, err := db.NewSelect().Model((*models.Plan)(nil)).Count(ctx)
|
||||||
@@ -161,6 +170,7 @@ func seedPlans(db *bun.DB) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: read this from a config
|
||||||
plans := []models.Plan{
|
plans := []models.Plan{
|
||||||
{MaxStorage: 10 * 1024 * 1024 * 1024}, // 10GB
|
{MaxStorage: 10 * 1024 * 1024 * 1024}, // 10GB
|
||||||
{MaxStorage: 50 * 1024 * 1024 * 1024}, // 50GB
|
{MaxStorage: 50 * 1024 * 1024 * 1024}, // 50GB
|
||||||
@@ -172,6 +182,5 @@ func seedPlans(db *bun.DB) error {
|
|||||||
return fmt.Errorf("failed to seed plans: %w", err)
|
return fmt.Errorf("failed to seed plans: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Successfully seeded the plans table")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,14 +29,22 @@ func LoginHandler(c echo.Context) error {
|
|||||||
db := c.Get("db").(*bun.DB)
|
db := c.Get("db").(*bun.DB)
|
||||||
|
|
||||||
user := new(models.User)
|
user := new(models.User)
|
||||||
err := db.NewSelect().Model(user).Where("email = ?", loginData.UsernameOrEmail).Scan(context.Background())
|
err := db.NewSelect().Model(user).Where("email = ?", loginData.UsernameOrEmail).Relation("Plan").Scan(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := db.NewSelect().Model(user).Where("username = ?", loginData.UsernameOrEmail).Scan(context.Background())
|
err := db.NewSelect().Model(user).Where("username = ?", loginData.UsernameOrEmail).Relation("Plan").Scan(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusNotFound, map[string]string{"message": "User with that username or email not found!"})
|
return c.JSON(http.StatusNotFound, map[string]string{"message": "User with that username or email not found!"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
basePath := fmt.Sprintf("%s/%s/", os.Getenv("STORAGE_PATH"), user.ID)
|
||||||
|
storageUsage, err := calculateStorageUsage(basePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Usage = storageUsage
|
||||||
|
|
||||||
session, err := GenerateSessionToken(db, user.ID)
|
session, err := GenerateSessionToken(db, user.ID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -53,7 +61,7 @@ func LoginHandler(c echo.Context) error {
|
|||||||
Path: "/",
|
Path: "/",
|
||||||
})
|
})
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, map[string]string{"message": "Login successful!"})
|
return c.JSON(http.StatusOK, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SignupHandler(c echo.Context) error {
|
func SignupHandler(c echo.Context) error {
|
||||||
@@ -91,16 +99,17 @@ func SignupHandler(c echo.Context) error {
|
|||||||
return c.JSON(http.StatusConflict, map[string]string{"message": "A user with that email or username already exists!"})
|
return c.JSON(http.StatusConflict, map[string]string{"message": "A user with that email or username already exists!"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = db.NewSelect().Model(user).WherePK().Relation("Plan").Scan(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusNotFound, map[string]string{"message": "An unknown error occoured!"})
|
||||||
|
}
|
||||||
|
|
||||||
err = os.Mkdir(fmt.Sprintf("%s/%s", os.Getenv("STORAGE_PATH"), user.ID), os.ModePerm)
|
err = os.Mkdir(fmt.Sprintf("%s/%s", os.Getenv("STORAGE_PATH"), user.ID), os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"})
|
|
||||||
}
|
|
||||||
|
|
||||||
session, err := GenerateSessionToken(db, user.ID)
|
session, err := GenerateSessionToken(db, user.ID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -117,7 +126,7 @@ func SignupHandler(c echo.Context) error {
|
|||||||
Path: "/",
|
Path: "/",
|
||||||
})
|
})
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, map[string]string{"message": "Signup successful!"})
|
return c.JSON(http.StatusOK, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateSessionToken(db *bun.DB, userId uuid.UUID) (*models.Session, error) {
|
func GenerateSessionToken(db *bun.DB, userId uuid.UUID) (*models.Session, error) {
|
||||||
@@ -146,3 +155,32 @@ func GetUser(c echo.Context) error {
|
|||||||
|
|
||||||
return c.JSON(http.StatusOK, user.(*models.User))
|
return c.JSON(http.StatusOK, user.(*models.User))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LogoutHandler(c echo.Context) error {
|
||||||
|
db := c.Get("db").(*bun.DB)
|
||||||
|
|
||||||
|
cookie, err := c.Cookie("sessionToken")
|
||||||
|
if err != nil {
|
||||||
|
if err == http.ErrNoCookie {
|
||||||
|
return echo.NewHTTPError(http.StatusUnauthorized, "Session token missing")
|
||||||
|
}
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Bad request")
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionId, err := uuid.Parse(cookie.Value)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Bad request")
|
||||||
|
}
|
||||||
|
|
||||||
|
session := &models.Session{
|
||||||
|
ID: sessionId,
|
||||||
|
}
|
||||||
|
_, err = db.NewDelete().Model(session).WherePK().Exec(context.Background())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, map[string]string{"message": "Succesfully logged out"})
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func UploadFile(c echo.Context) error {
|
|||||||
Name: entry.Name(),
|
Name: entry.Name(),
|
||||||
IsDir: entry.IsDir(),
|
IsDir: entry.IsDir(),
|
||||||
Size: entry.Size(),
|
Size: entry.Size(),
|
||||||
LastModified: entry.ModTime().Format("1/2/2006"),
|
LastModified: entry.ModTime().Format("2 Jan 06"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ func UploadFile(c echo.Context) error {
|
|||||||
Name: entry.Name(),
|
Name: entry.Name(),
|
||||||
IsDir: entry.IsDir(),
|
IsDir: entry.IsDir(),
|
||||||
Size: entry.Size(),
|
Size: entry.Size(),
|
||||||
LastModified: entry.ModTime().Format("1/2/2006"),
|
LastModified: entry.ModTime().Format("2 Jan 06"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
--color-accent: 136 57 239;
|
--color-accent: 136 57 239;
|
||||||
--color-accent-20: #dac9f1;
|
--color-accent-20: #dac9f1;
|
||||||
|
|
||||||
|
|
||||||
--nav-height: 48px;
|
--nav-height: 48px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +37,7 @@
|
|||||||
--color-love: 235 111 146;
|
--color-love: 235 111 146;
|
||||||
--color-pine: 49 116 143;
|
--color-pine: 49 116 143;
|
||||||
--color-accent: 154 87 237;
|
--color-accent: 154 87 237;
|
||||||
--color-accent-20: #342c3f;
|
--color-accent-20: #2a1c3d;
|
||||||
|
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
@@ -48,7 +47,8 @@
|
|||||||
@apply ease-[cubic-bezier(0.25,_1,_0.5,_1)];
|
@apply ease-[cubic-bezier(0.25,_1,_0.5,_1)];
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
html, body {
|
||||||
|
overflow: hidden !important;
|
||||||
background-color: rgb(var(--color-base));
|
background-color: rgb(var(--color-base));
|
||||||
color: rgb(var(--color-text));
|
color: rgb(var(--color-text));
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
ui/bun.lockb
BIN
ui/bun.lockb
Binary file not shown.
@@ -25,7 +25,8 @@ const crumbs = computed(() => {
|
|||||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
d="m9 6l6 6l-6 6" />
|
d="m9 6l6 6l-6 6" />
|
||||||
</svg>
|
</svg>
|
||||||
<NuxtLink :class="index === crumbs.length - 1 ? 'text-foam' : 'text-subtle hover:text-text'"
|
<NuxtLink class="focus:outline-none focus:ring focus:ring-inset"
|
||||||
|
:class="index === crumbs.length - 1 ? 'text-foam' : 'text-subtle hover:text-text focus:text-text'"
|
||||||
:to="crumb.link">{{
|
:to="crumb.link">{{
|
||||||
crumb.name }}</NuxtLink>
|
crumb.name }}</NuxtLink>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-on:click="toggle()" class="w-5 h-5 border rounded cursor-pointer flex items-center justify-center"
|
<div v-on:click="toggle()" v-on:keypress.enter="toggle()" v-on:keypress.space="toggle()" tabindex="0"
|
||||||
|
class="w-5 h-5 border rounded cursor-pointer flex items-center justify-center focus:outline-none focus:ring focus:ring-inset"
|
||||||
:class="state === 'unchecked' ? 'hover:bg-muted/5 active:bg-muted/15' : 'bg-accent/10 hover:bg-accent/15 active:bg-accent/25 text-accent'">
|
:class="state === 'unchecked' ? 'hover:bg-muted/5 active:bg-muted/15' : 'bg-accent/10 hover:bg-accent/15 active:bg-accent/25 text-accent'">
|
||||||
<div v-if="state === 'some'" class="w-8/12 h-0.5 bg-current rounded-full"></div>
|
<div v-if="state === 'some'" class="w-8/12 h-0.5 bg-current rounded-full"></div>
|
||||||
<span v-else-if="state === 'checked'">
|
<span v-else-if="state === 'checked'">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useUser } from '~/composables/useUser'
|
|||||||
const { getUser } = useUser()
|
const { getUser } = useUser()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
usageBytes: Number
|
usageBytes: Number,
|
||||||
})
|
})
|
||||||
|
|
||||||
const user = await getUser()
|
const user = await getUser()
|
||||||
@@ -35,7 +35,11 @@ const isInFolder = computed(() => route.path.startsWith('/home/') && route.path
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<aside class="h-screen flex flex-col w-56 pt-3 bg-surface border-r">
|
<aside class="h-screen flex flex-col w-56 pt-3 bg-surface border-r z-50 md:z-20">
|
||||||
|
<a href="#main"
|
||||||
|
class="absolute w-fit -translate-x-full top-0 px-2 py-4 bg-surface border opacity-0 focus-within:translate-x-0 focus-within:opacity-100">
|
||||||
|
Skip to content
|
||||||
|
</a>
|
||||||
<div class="pl-9 h-14 flex items-center">
|
<div class="pl-9 h-14 flex items-center">
|
||||||
<h2>Home</h2>
|
<h2>Home</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +47,7 @@ const isInFolder = computed(() => route.path.startsWith('/home/') && route.path
|
|||||||
<ul class="flex flex-col gap-y-2">
|
<ul class="flex flex-col gap-y-2">
|
||||||
<li>
|
<li>
|
||||||
<NuxtLink to="/home"
|
<NuxtLink to="/home"
|
||||||
class="flex py-1.5 px-4 rounded-lg transition-bg duration-300 hover:bg-muted/10"
|
class="flex py-1.5 px-4 rounded-lg transition-bg duration-300 hover:bg-muted/10 focus:outline-none focus:ring focus:ring-inset"
|
||||||
:class="{ 'bg-muted/10': isAllFilesActive }">
|
:class="{ 'bg-muted/10': isAllFilesActive }">
|
||||||
<div class="flex relative">
|
<div class="flex relative">
|
||||||
<svg class="m-0.5 mr-2" xmlns="http://www.w3.org/2000/svg" width="20" height="20"
|
<svg class="m-0.5 mr-2" xmlns="http://www.w3.org/2000/svg" width="20" height="20"
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
let colorMode = useColorMode();
|
|
||||||
|
|
||||||
const changeTheme = () => {
|
|
||||||
if (colorMode.preference === "dark") {
|
|
||||||
// from dark => light
|
|
||||||
colorMode.preference = "light"
|
|
||||||
} else if (colorMode.preference === "light") {
|
|
||||||
// from light => system
|
|
||||||
colorMode.preference = "system";
|
|
||||||
} else {
|
|
||||||
// from system => dark
|
|
||||||
colorMode.preference = "dark";
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<header class="flex h-[var(--nav-height)] px-4 justify-center sticky top-0 z-20 border-b bg-base">
|
|
||||||
<div class="flex w-full items-center justify-between space-x-2.5">
|
|
||||||
<p
|
|
||||||
class="-ml-2.5 flex shrink-0 items-center px-2.5 py-1.5 focus:outline-none focus:ring rounded-m font-semiboldd">
|
|
||||||
filething
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<nav class="hidden md:flex" aria-label="Main">
|
|
||||||
<ul class="flex items-center gap-3" role="list">
|
|
||||||
<li>
|
|
||||||
<a href="#"
|
|
||||||
class="px-2.5 py-1.5 text-[15px] font-semibold transition-bg duration-300 hover:bg-muted/10 focus:outline-none focus:ring focus:ring-inset rounded-md">Link</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#"
|
|
||||||
class="px-2.5 py-1.5 text-[15px] font-semibold transition-bg duration-300 hover:bg-muted/10 focus:outline-none focus:ring focus:ring-inset rounded-md">Link</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#"
|
|
||||||
class="px-2.5 py-1.5 text-[15px] font-semibold transition-bg duration-300 hover:bg-muted/10 focus:outline-none focus:ring focus:ring-inset rounded-md">Link</a>
|
|
||||||
</li>
|
|
||||||
<li class="h-6 border-r"></li>
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
class="flex items-center px-3 h-8 text-[15px] font-semibold transition-bg duration-300 hover:bg-muted/10 focus:outline-none focus:ring focus:ring-inset rounded-md"
|
|
||||||
@click="changeTheme">
|
|
||||||
<span class="inline-block">
|
|
||||||
<svg v-if="$colorMode.preference === 'dark'" xmlns="http://www.w3.org/2000/svg" width="22"
|
|
||||||
height="22" viewBox="0 0 24 24">
|
|
||||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
stroke-width="2" d="M12 3h.393a7.5 7.5 0 0 0 7.92 12.446A9 9 0 1 1 12 2.992z" />
|
|
||||||
</svg>
|
|
||||||
<svg v-else-if="$colorMode.preference === 'light'" xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="22" height="22" viewBox="0 0 24 24">
|
|
||||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M14.828 14.828a4 4 0 1 0-5.656-5.656a4 4 0 0 0 5.656 5.656m-8.485 2.829l-1.414 1.414M6.343 6.343L4.929 4.929m12.728 1.414l1.414-1.414m-1.414 12.728l1.414 1.414M4 12H2m10-8V2m8 10h2m-10 8v2" />
|
|
||||||
</svg>
|
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 256 256">
|
|
||||||
<path fill="currentColor"
|
|
||||||
d="M208 36H48a28 28 0 0 0-28 28v112a28 28 0 0 0 28 28h160a28 28 0 0 0 28-28V64a28 28 0 0 0-28-28Zm4 140a4 4 0 0 1-4 4H48a4 4 0 0 1-4-4V64a4 4 0 0 1 4-4h160a4 4 0 0 1 4 4Zm-40 52a12 12 0 0 1-12 12H96a12 12 0 0 1 0-24h64a12 12 0 0 1 12 12Z" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { formatBytes } from '~/utils/formatBytes';
|
import { formatBytes } from '~/utils/formatBytes';
|
||||||
import type { FileUpload } from '~/types/user';
|
import type { FileUpload } from '~/types/file';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
uploadingFiles: {
|
uploadingFiles: {
|
||||||
@@ -45,12 +45,21 @@ const formatRemainingTime = (seconds: number): string => {
|
|||||||
const truncateFilenameToFitWidth = (filename: string, maxWidthPx: number, font = '18px ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji') => {
|
const truncateFilenameToFitWidth = (filename: string, maxWidthPx: number, font = '18px ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji') => {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
const context = canvas.getContext('2d');
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
if (context === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
context.font = font;
|
context.font = font;
|
||||||
|
|
||||||
const name = filename.substring(0, filename.lastIndexOf('.'));
|
const name = filename.substring(0, filename.lastIndexOf('.'));
|
||||||
const extension = filename.substring(filename.lastIndexOf('.'));
|
const extension = filename.substring(filename.lastIndexOf('.'));
|
||||||
|
|
||||||
function getTextWidth(text) {
|
function getTextWidth(text: string): number {
|
||||||
|
if (context === null) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
return context.measureText(text).width;
|
return context.measureText(text).width;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +108,7 @@ let uploadFailed = computed(() => props.uploadingFiles.filter(x => x.status.erro
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="absolute bottom-0 right-0 m-3 rounded-2xl border flex flex-col sm:w-[440px] w-[calc(100%-24px)] shadow-md bg-surface"
|
<div class="absolute bottom-0 right-0 m-3 rounded-2xl border flex flex-col sm:w-[440px] w-[calc(100%-24px)] shadow-md bg-surface z-20"
|
||||||
:class="{ 'h-[510px]': !collapsed, 'hidden': closed }">
|
:class="{ 'h-[510px]': !collapsed, 'hidden': closed }">
|
||||||
<div class="flex flex-row justify-between h-14 items-center mb-3 px-4" :class="{ 'hidden': collapsed }">
|
<div class="flex flex-row justify-between h-14 items-center mb-3 px-4" :class="{ 'hidden': collapsed }">
|
||||||
<h3 class="text-xl font-semibold">Upload</h3>
|
<h3 class="text-xl font-semibold">Upload</h3>
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "nuxt-app",
|
"name": "nuxt-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt build",
|
"build": "nuxt build",
|
||||||
"dev": "nuxt dev",
|
"dev": "nuxt dev",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
"preview": "nuxt preview",
|
"preview": "nuxt preview",
|
||||||
"postinstall": "nuxt prepare"
|
"postinstall": "nuxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/color-mode": "^3.4.4",
|
"@nuxtjs/color-mode": "^3.4.4",
|
||||||
"nuxt": "^3.13.1",
|
"nuxt": "^3.13.1",
|
||||||
"vue": "latest"
|
"vue": "latest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"postcss": "^8.4.45",
|
"postcss": "^8.4.45",
|
||||||
"tailwindcss": "^3.4.10"
|
"tailwindcss": "^3.4.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { NuxtError } from '#app';
|
||||||
import { useUser } from '~/composables/useUser'
|
import { useUser } from '~/composables/useUser'
|
||||||
import type { File } from '~/types/file';
|
import type { File, UploadResponse, FileUpload } from '~/types/file';
|
||||||
import type { FileUpload } from '~/types/user';
|
|
||||||
const { getUser } = useUser()
|
const { getUser } = useUser()
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
@@ -11,7 +11,7 @@ definePageMeta({
|
|||||||
const user = await getUser()
|
const user = await getUser()
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
let { data: files } = await useFetch<[File]>('/api/files/get/' + route.path.replace(/^\/home/, ''))
|
let { data: files } = await useFetch<File[]>('/api/files/get/' + route.path.replace(/^\/home/, ''))
|
||||||
|
|
||||||
const sortedFiles = computed(() => {
|
const sortedFiles = computed(() => {
|
||||||
files.value?.forEach(file => file.toggled === undefined ? file.toggled = 'unchecked' : {})
|
files.value?.forEach(file => file.toggled === undefined ? file.toggled = 'unchecked' : {})
|
||||||
@@ -23,16 +23,20 @@ const sortedFiles = computed(() => {
|
|||||||
return ('' + a.name).localeCompare(b.name);
|
return ('' + a.name).localeCompare(b.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
return folders?.concat(archives)
|
if (folders === undefined || archives === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return folders.concat(archives)
|
||||||
})
|
})
|
||||||
|
|
||||||
let selectAll = ref('unchecked');
|
let selectAll: Ref<"unchecked" | "some" | "checked"> = ref('unchecked');
|
||||||
let selectedFiles = computed(() => sortedFiles.value?.filter(file => file.toggled === 'checked'))
|
let selectedFiles = computed(() => sortedFiles.value?.filter(file => file.toggled === 'checked'))
|
||||||
|
|
||||||
watch(sortedFiles, (newVal, oldVal) => {
|
watch(sortedFiles, (newVal, oldVal) => {
|
||||||
let checkedFilesLength = newVal?.filter(file => file.toggled === 'checked').length;
|
let checkedFilesLength = newVal?.filter(file => file.toggled === 'checked').length;
|
||||||
if (checkedFilesLength > 0) {
|
if (newVal !== undefined && checkedFilesLength !== undefined && checkedFilesLength > 0) {
|
||||||
if (checkedFilesLength < newVal?.length) {
|
if (checkedFilesLength < newVal.length) {
|
||||||
selectAll.value = 'some';
|
selectAll.value = 'some';
|
||||||
} else {
|
} else {
|
||||||
selectAll.value = 'checked';
|
selectAll.value = 'checked';
|
||||||
@@ -58,6 +62,8 @@ let folderError = ref('');
|
|||||||
let popupVisable = ref(false);
|
let popupVisable = ref(false);
|
||||||
let uploadPaneClosed = ref(true);
|
let uploadPaneClosed = ref(true);
|
||||||
|
|
||||||
|
let fileNavClosed = ref(true);
|
||||||
|
|
||||||
if (typeof route.params.name == "object") {
|
if (typeof route.params.name == "object") {
|
||||||
folder.value = route.params.name.join("/");
|
folder.value = route.params.name.join("/");
|
||||||
}
|
}
|
||||||
@@ -82,12 +88,12 @@ const handleFileChange = (event: Event) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileInput.value.files.length > 0) {
|
if (fileInput.value.files && fileInput.value.files.length > 0) {
|
||||||
fileInput.value.value = "";
|
fileInput.value.value = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadFile = (file: File) => {
|
const uploadFile = (file: globalThis.File) => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
let id = `${file.name}-${Math.floor(Math.random() * 1000)}`;
|
let id = `${file.name}-${Math.floor(Math.random() * 1000)}`;
|
||||||
@@ -97,7 +103,15 @@ const uploadFile = (file: File) => {
|
|||||||
uploading: true,
|
uploading: true,
|
||||||
controller: xhr,
|
controller: xhr,
|
||||||
startTime,
|
startTime,
|
||||||
file: file,
|
speed: 0,
|
||||||
|
remainingTime: Infinity,
|
||||||
|
file: {
|
||||||
|
name: file.name,
|
||||||
|
is_dir: false,
|
||||||
|
size: file.size,
|
||||||
|
last_modified: "",
|
||||||
|
toggled: "unchecked"
|
||||||
|
},
|
||||||
length: {},
|
length: {},
|
||||||
status: {}
|
status: {}
|
||||||
}
|
}
|
||||||
@@ -208,7 +222,7 @@ const openFilePicker = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createFolder = async () => {
|
const createFolder = async () => {
|
||||||
const { data, error } = await useAsyncData(
|
const { data, error } = await useAsyncData<UploadResponse, NuxtError<{ message: string }>>(
|
||||||
() => $fetch('/api/files/upload' + route.path.replace(/^\/home/, '') + '/' + folderName.value, {
|
() => $fetch('/api/files/upload' + route.path.replace(/^\/home/, '') + '/' + folderName.value, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: {
|
body: {
|
||||||
@@ -217,14 +231,16 @@ const createFolder = async () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
if (error.value != null) {
|
console.log(error.value)
|
||||||
folderError.value = error.value.data.message;
|
|
||||||
} else {
|
if (data.value != null) {
|
||||||
user.usage = data.value.usage
|
user.usage = data.value.usage
|
||||||
files.value?.push(data.value.file)
|
files.value?.push(data.value.file)
|
||||||
|
|
||||||
popupVisable.value = false;
|
popupVisable.value = false;
|
||||||
navigateTo(route.path + '/' + folderName.value);
|
navigateTo(route.path + '/' + folderName.value);
|
||||||
|
} else if (error.value != null && error.value.data != undefined) {
|
||||||
|
folderError.value = error.value.data.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,10 +252,14 @@ const deleteFiles = async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (files.value === null) {
|
||||||
|
throw new Error("Files are null!")
|
||||||
|
}
|
||||||
|
|
||||||
files.value = files.value?.filter(file => !selectedFiles.value?.includes(file))
|
files.value = files.value?.filter(file => !selectedFiles.value?.includes(file))
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadFile = (file) => {
|
const downloadFile = (file: File) => {
|
||||||
const anchor = document.createElement('a');
|
const anchor = document.createElement('a');
|
||||||
anchor.href = '/api/files/download/' + file.name;
|
anchor.href = '/api/files/download/' + file.name;
|
||||||
anchor.download = file.name;
|
anchor.download = file.name;
|
||||||
@@ -253,13 +273,17 @@ const downloadFiles = async () => {
|
|||||||
let filenames = ""
|
let filenames = ""
|
||||||
|
|
||||||
selectedFiles.value?.forEach((file, i) => {
|
selectedFiles.value?.forEach((file, i) => {
|
||||||
|
if (selectedFiles.value === undefined) {
|
||||||
|
throw new Error("selected files is undefined")
|
||||||
|
}
|
||||||
|
|
||||||
filenames += encodeURIComponent(file.name)
|
filenames += encodeURIComponent(file.name)
|
||||||
if (i != selectedFiles.value?.length - 1) {
|
if (i != selectedFiles.value.length - 1) {
|
||||||
filenames += ",";
|
filenames += ",";
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let { data, error } = await useAsyncData(
|
let { data, error } = await useAsyncData<Blob, NuxtError<{ message: string }>>(
|
||||||
() => $fetch('/api/files/download', {
|
() => $fetch('/api/files/download', {
|
||||||
params: {
|
params: {
|
||||||
"filenames": filenames
|
"filenames": filenames
|
||||||
@@ -267,9 +291,7 @@ const downloadFiles = async () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log("DATA", data.value)
|
if (data.value !== null) {
|
||||||
|
|
||||||
if (error.value == null) {
|
|
||||||
const anchor = document.createElement('a');
|
const anchor = document.createElement('a');
|
||||||
anchor.href = window.URL.createObjectURL(data.value)
|
anchor.href = window.URL.createObjectURL(data.value)
|
||||||
anchor.download = "filething.zip";
|
anchor.download = "filething.zip";
|
||||||
@@ -283,7 +305,11 @@ const downloadFiles = async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex relative min-h-[100dvh]">
|
<div class="flex relative min-h-[100dvh]">
|
||||||
<div class="fixed md:relative -translate-x-full md:translate-x-0">
|
<div v-if="!fileNavClosed" v-on:click="fileNavClosed = !fileNavClosed"
|
||||||
|
class="absolute top-0 left-0 bottom-0 right-0 bg-base/40 z-40 block md:hidden">
|
||||||
|
</div>
|
||||||
|
<div class="fixed md:relative -translate-x-full md:translate-x-0 transition-transform z-50 md:z-20"
|
||||||
|
:class="{ 'translate-x-0': !fileNavClosed }">
|
||||||
<FileNav :usageBytes="user.usage" />
|
<FileNav :usageBytes="user.usage" />
|
||||||
</div>
|
</div>
|
||||||
<UploadPane :closed="uploadPaneClosed" v-on:update:closed="(newValue) => uploadPaneClosed = newValue"
|
<UploadPane :closed="uploadPaneClosed" v-on:update:closed="(newValue) => uploadPaneClosed = newValue"
|
||||||
@@ -306,13 +332,13 @@ const downloadFiles = async () => {
|
|||||||
</Popup>
|
</Popup>
|
||||||
|
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<Nav />
|
<Nav v-on:update:filenav="(e) => fileNavClosed = e" :filenav="fileNavClosed" />
|
||||||
<div class="pt-6 pl-12 overflow-auto max-h-[calc(100vh-var(--nav-height))]">
|
<div class="pt-6 pl-12 overflow-y-auto max-h-[calc(100vh-var(--nav-height))]" id="main">
|
||||||
<div class="flex gap-x-4 flex-col">
|
<div class="flex gap-x-4 flex-col">
|
||||||
<div class="py-5 flex flex-row gap-x-4">
|
<div class="py-5 flex flex-row gap-x-4">
|
||||||
<input type="file" ref="fileInput" @change="handleFileChange" multiple class="hidden" />
|
<input type="file" ref="fileInput" @change="handleFileChange" multiple class="hidden" />
|
||||||
<button v-on:click="openFilePicker"
|
<button v-on:click="openFilePicker"
|
||||||
class="rounded-xl border-2 border-surface flex flex-col gap-y-2 px-2 py-3 w-40 justify-center items-center hover:bg-muted/10 active:bg-muted/20 transition-bg">
|
class="focus:outline-none focus:ring focus:ring-inset rounded-xl border-2 border-surface flex flex-col gap-y-2 px-2 py-3 w-40 justify-center items-center hover:bg-muted/10 active:bg-muted/20 transition-bg">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
stroke-width="2">
|
stroke-width="2">
|
||||||
@@ -323,7 +349,7 @@ const downloadFiles = async () => {
|
|||||||
Upload
|
Upload
|
||||||
</button>
|
</button>
|
||||||
<button v-on:click="popupVisable = !popupVisable"
|
<button v-on:click="popupVisable = !popupVisable"
|
||||||
class="rounded-xl border-2 border-surface flex flex-col gap-y-2 px-2 py-3 w-40 justify-center items-center hover:bg-muted/10 active:bg-muted/20 transition-bg">
|
class="focus:outline-none focus:ring focus:ring-inset rounded-xl border-2 border-surface flex flex-col gap-y-2 px-2 py-3 w-40 justify-center items-center hover:bg-muted/10 active:bg-muted/20 transition-bg">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
stroke-width="2">
|
stroke-width="2">
|
||||||
@@ -342,9 +368,10 @@ const downloadFiles = async () => {
|
|||||||
<Breadcrumbs :path="route.path" />
|
<Breadcrumbs :path="route.path" />
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<div class="flex flex-row gap-x-2" v-if="selectedFiles?.length > 0">
|
<div class="flex flex-row gap-x-2"
|
||||||
|
v-if="selectedFiles !== undefined && selectedFiles.length > 0">
|
||||||
<button v-on:click="downloadFiles"
|
<button v-on:click="downloadFiles"
|
||||||
class="flex flex-row px-2 py-1 rounded-md transition-bg text-xs border hover:bg-muted/10 active:bg-muted/20 items-center">
|
class="flex flex-row px-2 py-1 rounded-md transition-bg text-xs border hover:bg-muted/10 active:bg-muted/20 items-center focus:outline-none focus:ring focus:ring-inset">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
||||||
<path fill="none" stroke="currentColor" stroke-linecap="round"
|
<path fill="none" stroke="currentColor" stroke-linecap="round"
|
||||||
stroke-linejoin="round" stroke-width="2"
|
stroke-linejoin="round" stroke-width="2"
|
||||||
@@ -353,7 +380,7 @@ const downloadFiles = async () => {
|
|||||||
Download
|
Download
|
||||||
</button>
|
</button>
|
||||||
<button v-on:click="deleteFiles"
|
<button v-on:click="deleteFiles"
|
||||||
class="flex flex-row px-2 py-1 rounded-md transition-bg text-xs border hover:bg-love/10 active:bg-love/20 hover:text-love active:text-love items-center">
|
class="flex flex-row px-2 py-1 rounded-md transition-bg text-xs border hover:bg-love/10 active:bg-love/20 hover:text-love active:text-love items-center focus:outline-none focus:ring focus:ring-inset">
|
||||||
<svg class="mr-1" xmlns="http://www.w3.org/2000/svg" width="16" height="16"
|
<svg class="mr-1" xmlns="http://www.w3.org/2000/svg" width="16" height="16"
|
||||||
viewBox="0 0 24 24">
|
viewBox="0 0 24 24">
|
||||||
<path fill="none" stroke="currentColor" stroke-linecap="round"
|
<path fill="none" stroke="currentColor" stroke-linecap="round"
|
||||||
@@ -389,7 +416,7 @@ const downloadFiles = async () => {
|
|||||||
<tr class="flex border-l-2 flex-row h-10 group items-center border-b active:bg-surface/45 transition-bg relative"
|
<tr class="flex border-l-2 flex-row h-10 group items-center border-b active:bg-surface/45 transition-bg relative"
|
||||||
v-for="file in sortedFiles"
|
v-for="file in sortedFiles"
|
||||||
:class="file.toggled === 'checked' ? 'bg-accent/20 border-l-accent' : 'border-l-transparent hover:bg-surface'">
|
:class="file.toggled === 'checked' ? 'bg-accent/20 border-l-accent' : 'border-l-transparent hover:bg-surface'">
|
||||||
<td class="-ml-7 pr-4 flex-shrink-0">
|
<td class="-ml-7 flex-shrink-0">
|
||||||
<div class="w-5 h-5">
|
<div class="w-5 h-5">
|
||||||
<Checkbox class="group-hover:flex"
|
<Checkbox class="group-hover:flex"
|
||||||
:class="{ 'hidden': file.toggled === 'unchecked' }"
|
:class="{ 'hidden': file.toggled === 'unchecked' }"
|
||||||
@@ -397,7 +424,10 @@ const downloadFiles = async () => {
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td v-on:click="file.toggled === 'unchecked' ? file.toggled = 'checked' : file.toggled = 'unchecked'"
|
<td v-on:click="file.toggled === 'unchecked' ? file.toggled = 'checked' : file.toggled = 'unchecked'"
|
||||||
class="flex-grow text-start flex items-center h-full min-w-40">
|
v-on:keypress.enter="file.toggled === 'unchecked' ? file.toggled = 'checked' : file.toggled = 'unchecked'"
|
||||||
|
v-on:keypress.space="file.toggled === 'unchecked' ? file.toggled = 'checked' : file.toggled = 'unchecked'"
|
||||||
|
class="flex-grow text-start flex items-center h-full min-w-40 focus:outline-none focus:ring focus:ring-inset pl-4"
|
||||||
|
tabindex="0">
|
||||||
<div class="flex items-center min-w-40">
|
<div class="flex items-center min-w-40">
|
||||||
<svg v-if="!file.is_dir" class="mr-2 flex-shrink-0"
|
<svg v-if="!file.is_dir" class="mr-2 flex-shrink-0"
|
||||||
xmlns="http://www.w3.org/2000/svg" width="16" height="16"
|
xmlns="http://www.w3.org/2000/svg" width="16" height="16"
|
||||||
@@ -416,7 +446,8 @@ const downloadFiles = async () => {
|
|||||||
d="M5 4h4l3 3h7a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2" />
|
d="M5 4h4l3 3h7a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2" />
|
||||||
</svg>
|
</svg>
|
||||||
<span class="overflow-hidden whitespace-nowrap text-ellipsis">
|
<span class="overflow-hidden whitespace-nowrap text-ellipsis">
|
||||||
<NuxtLink v-if="file.is_dir" :to="`${route.path}/${file.name}`">
|
<NuxtLink v-if="file.is_dir" class="hover:underline focus:underline"
|
||||||
|
:to="`${route.path}/${file.name}`">
|
||||||
{{ file.name }}
|
{{ file.name }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<span v-else>{{ file.name }}</span>
|
<span v-else>{{ file.name }}</span>
|
||||||
@@ -430,9 +461,9 @@ const downloadFiles = async () => {
|
|||||||
{{ file.last_modified }}
|
{{ file.last_modified }}
|
||||||
</td>
|
</td>
|
||||||
<td :class="file.toggled === 'checked' ? 'context-active' : 'context'"
|
<td :class="file.toggled === 'checked' ? 'context-active' : 'context'"
|
||||||
class="absolute pl-6 top-0 bottom-0 right-0 hidden group-hover:flex items-center pr-8">
|
class="absolute pl-6 top-0 bottom-0 right-0 hidden group-hover:flex group-focus-within:flex items-center pr-8">
|
||||||
<button v-on:click="downloadFile(file)"
|
<button v-on:click="downloadFile(file)"
|
||||||
class="p-2 rounded hover:bg-muted/10 active:bg-muted/20">
|
class="p-2 rounded hover:bg-muted/10 active:bg-muted/20 focus:outline-none focus:ring focus:ring-inset">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"
|
||||||
viewBox="0 0 24 24">
|
viewBox="0 0 24 24">
|
||||||
<path fill="none" stroke="currentColor" stroke-linecap="round"
|
<path fill="none" stroke="currentColor" stroke-linecap="round"
|
||||||
@@ -464,6 +495,6 @@ th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.context {
|
.context {
|
||||||
background: linear-gradient(to right, transparent, rgb(var(--color-surface)) 16px, rgb(var(--color-surface)) 100%);
|
background: linear-gradient(to right, transparent, rgb(var(--color-base)) 16px, rgb(var(--color-base)) 100%);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { NuxtError } from '#app';
|
||||||
import type { User } from '~/types/user';
|
import type { User } from '~/types/user';
|
||||||
const { fetchUser } = useUser()
|
const { setUser } = useUser()
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: "unauth"
|
middleware: "unauth"
|
||||||
@@ -12,19 +13,21 @@ let password = ref('')
|
|||||||
let error = ref('')
|
let error = ref('')
|
||||||
|
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
let response = await useFetch<User>('/api/login', {
|
let { data, error: fetchError } = await useAsyncData<User, NuxtError<{ message: string }>>(
|
||||||
method: 'POST',
|
() => $fetch('/api/login', {
|
||||||
body: {
|
method: 'POST',
|
||||||
"username_or_email": username_or_email.value,
|
body: {
|
||||||
"password": password.value,
|
"username_or_email": username_or_email.value,
|
||||||
}
|
"password": password.value,
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
if (response.error.value != null) {
|
if (fetchError.value !== null && fetchError.value.data !== undefined) {
|
||||||
error.value = response.error.value.data.message
|
error.value = fetchError.value.data.message
|
||||||
setTimeout(() => error.value = "", 15000)
|
setTimeout(() => error.value = "", 15000)
|
||||||
} else {
|
} else if (data.value !== null) {
|
||||||
await fetchUser()
|
setUser(data.value)
|
||||||
await navigateTo('/home')
|
await navigateTo('/home')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,8 +41,9 @@ const submitForm = async () => {
|
|||||||
<Input v-model="password" type="password" placeholder="Password..." />
|
<Input v-model="password" type="password" placeholder="Password..." />
|
||||||
<p class="text-love">{{ error }}</p>
|
<p class="text-love">{{ error }}</p>
|
||||||
<button @click="submitForm"
|
<button @click="submitForm"
|
||||||
class="py-2 px-4 my-2 bg-pine/10 text-pine rounded-md transition-colors hover:bg-pine/15 active:bg-pine/25">Login</button>
|
class="py-2 px-4 my-2 bg-pine/10 text-pine rounded-md transition-colors hover:bg-pine/15 active:bg-pine/25 focus:outline-none focus:ring focus:ring-inset">Login</button>
|
||||||
<p>Or <NuxtLink to="/signup" class="text-foam hover:underline">Sign up</NuxtLink>
|
<p>Or <NuxtLink to="/signup"
|
||||||
|
class="text-foam hover:underline focus:outline-none focus:ring focus:ring-inset">Sign up</NuxtLink>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { NuxtError } from '#app';
|
||||||
import type { User } from '~/types/user'
|
import type { User } from '~/types/user'
|
||||||
const { fetchUser } = useUser()
|
const { setUser } = useUser()
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: "unauth"
|
middleware: "unauth"
|
||||||
@@ -13,20 +14,22 @@ let password = ref('')
|
|||||||
let error = ref('')
|
let error = ref('')
|
||||||
|
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
const response = await useFetch<User>('/api/signup', {
|
let { data, error: fetchError } = await useAsyncData<User, NuxtError<{ message: string }>>(
|
||||||
method: 'POST',
|
() => $fetch('/api/signup', {
|
||||||
body: {
|
method: 'POST',
|
||||||
"username": username.value,
|
body: {
|
||||||
"email": email.value,
|
"username": username.value,
|
||||||
"password": password.value,
|
"email": email.value,
|
||||||
}
|
"password": password.value,
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
if (response.error.value != null) {
|
if (fetchError.value != null && fetchError.value.data !== undefined) {
|
||||||
error.value = response.error.value.data.message
|
error.value = fetchError.value.data.message
|
||||||
setTimeout(() => error.value = "", 15000)
|
setTimeout(() => error.value = "", 15000)
|
||||||
} else {
|
} else if (data.value !== null) {
|
||||||
await fetchUser()
|
setUser(data.value)
|
||||||
await navigateTo('/home')
|
await navigateTo('/home')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,8 +45,9 @@ const submitForm = async () => {
|
|||||||
<Input v-model="password" type="password" placeholder="Password..." />
|
<Input v-model="password" type="password" placeholder="Password..." />
|
||||||
<p class="text-love">{{ error }}</p>
|
<p class="text-love">{{ error }}</p>
|
||||||
<button @click="submitForm"
|
<button @click="submitForm"
|
||||||
class="py-2 px-4 my-2 bg-pine/10 text-pine rounded-md transition-colors hover:bg-pine/15 active:bg-pine/25">Login</button>
|
class="py-2 px-4 my-2 bg-pine/10 text-pine rounded-md transition-colors hover:bg-pine/15 active:bg-pine/25 focus:outline-none focus:ring focus:ring-inset">Login</button>
|
||||||
<p>Or <NuxtLink to="/login" class="text-foam hover:underline">Log in</NuxtLink>
|
<p>Or <NuxtLink to="/login"
|
||||||
|
class="text-foam hover:underline focus:outline-none focus:ring focus:ring-inset">Log in</NuxtLink>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,5 +3,30 @@ export interface File {
|
|||||||
is_dir: boolean,
|
is_dir: boolean,
|
||||||
size: number,
|
size: number,
|
||||||
last_modified: string,
|
last_modified: string,
|
||||||
toggled: string,
|
toggled: "checked" | "some" | "unchecked",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileUpload {
|
||||||
|
id: string,
|
||||||
|
uploading: boolean,
|
||||||
|
file: File,
|
||||||
|
startTime: number,
|
||||||
|
speed: number,
|
||||||
|
remainingTime: number,
|
||||||
|
controller: XMLHttpRequest,
|
||||||
|
length: {
|
||||||
|
total: number,
|
||||||
|
loaded: number,
|
||||||
|
} | {},
|
||||||
|
status: {
|
||||||
|
error: boolean,
|
||||||
|
aborted: boolean,
|
||||||
|
code: number,
|
||||||
|
message: string
|
||||||
|
} | {},
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UploadResponse {
|
||||||
|
usage: number,
|
||||||
|
file: File
|
||||||
}
|
}
|
||||||
@@ -8,23 +8,3 @@ export interface User {
|
|||||||
},
|
},
|
||||||
usage: number,
|
usage: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileUpload {
|
|
||||||
id: string,
|
|
||||||
uploading: boolean,
|
|
||||||
file: File,
|
|
||||||
startTime: number,
|
|
||||||
speed: number,
|
|
||||||
remainingTime: number,
|
|
||||||
controller: XMLHttpRequest,
|
|
||||||
length: {
|
|
||||||
total: number,
|
|
||||||
loaded: number,
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
error: boolean,
|
|
||||||
aborted: boolean,
|
|
||||||
code: number,
|
|
||||||
message: string
|
|
||||||
},
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user