mirror of
https://github.com/mollersuite/monofile.git
synced 2024-11-25 07:06:25 -08:00
work on the
This commit is contained in:
parent
74e18d3d9c
commit
c911896f6d
4
.vscode/tasks.json
vendored
4
.vscode/tasks.json
vendored
|
@ -3,7 +3,7 @@
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command":"npx tsc",
|
"command":"tsc\nsass src/style:out/style\nrollup -c",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command":"npx tsc\nnode ./out/index.js\ndel ./out/* -Recurse",
|
"command":"tsc\nsass src/style:out/style\nrollup -c\nnpx tsc\nnode ./out/index.js\ndel ./out/* -Recurse",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
|
|
|
@ -22,7 +22,8 @@ TOKEN=KILL-YOURSELF.NOW
|
||||||
- [X] 1.2.2 clean up this shitty code
|
- [X] 1.2.2 clean up this shitty code
|
||||||
- [X] 1.2.3 bugfixes
|
- [X] 1.2.3 bugfixes
|
||||||
- [ ] 1.3.0 new ui; accounts
|
- [ ] 1.3.0 new ui; accounts
|
||||||
- [ ] 1.3.1 disable cloning of local ips
|
- [ ] 1.3.1 add utility endpoints: `/embed/:fileId` for discord, `/:fileId` for quick access
|
||||||
|
- [ ] 1.3.3 disable cloning of local ips
|
||||||
- [ ] 1.4.0 admin panel
|
- [ ] 1.4.0 admin panel
|
||||||
- [ ] 2.0.0 rewrite using theUnfunny's code as a base/rewrite using monofile-core
|
- [ ] 2.0.0 rewrite using theUnfunny's code as a base/rewrite using monofile-core
|
||||||
|
|
||||||
|
|
747
package-lock.json
generated
747
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -24,5 +24,12 @@
|
||||||
"express": "^4.18.1",
|
"express": "^4.18.1",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"typescript": "^4.8.3"
|
"typescript": "^4.8.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
|
"rollup": "^3.11.0",
|
||||||
|
"rollup-plugin-svelte": "^7.1.0",
|
||||||
|
"sass": "^1.57.1",
|
||||||
|
"svelte": "^3.55.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,72 +1,34 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
|
|
||||||
<meta name="og:site_name" content="monofile $Version">
|
<link
|
||||||
<meta name="application-name" content="$ErrorCode">
|
rel="stylesheet"
|
||||||
<meta name="description" content="$ErrorMessage">
|
href="/static/style/error.css"
|
||||||
|
>
|
||||||
|
|
||||||
<title>monofile</title>
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
href="/static/assets/monofile-circ.png"
|
||||||
|
>
|
||||||
|
|
||||||
<style>
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, user-scalable=0"
|
||||||
|
>
|
||||||
|
|
||||||
* {
|
<title>$code</title>
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
h1,h2 {
|
|
||||||
text-align:center;
|
|
||||||
}
|
|
||||||
#content {
|
|
||||||
position:fixed;
|
|
||||||
left:50%;
|
|
||||||
top:50%;
|
|
||||||
transform:translate(-50%,-50%);
|
|
||||||
background-color:white;
|
|
||||||
width:450px;
|
|
||||||
/*height:100%;*/
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
background-color: darkgray;
|
|
||||||
}
|
|
||||||
.note {
|
|
||||||
padding:5px;
|
|
||||||
position:relative;
|
|
||||||
width:calc( 100% - 60px );
|
|
||||||
left:25px;
|
|
||||||
border:1px solid #AAAAAAFF;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: #AAAAAA66;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 450px) {
|
|
||||||
#content {
|
|
||||||
position:fixed;
|
|
||||||
left:0%;
|
|
||||||
top:0%;
|
|
||||||
transform:translate(0%,0%);
|
|
||||||
background-color:white;
|
|
||||||
width:100%;
|
|
||||||
height:100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="content">
|
<p class="error">
|
||||||
<h1>
|
<span class="code">$code</span>
|
||||||
monofile<span style="font-style:italic;font-weight:bold;font-size:16px;color:#999999"> $Version</span>
|
$text
|
||||||
</h1>
|
</p>
|
||||||
<h2><em>Discord-based file sharing</em></h2>
|
|
||||||
<div class="note" style="border-color:#FFAAAAFF;background-color:#FFAAAA66">
|
|
||||||
<h1>$ErrorCode</h1>
|
|
||||||
<p style="width:calc( 100% - 50px );left:25px;position:relative;">$ErrorMessage</p>
|
|
||||||
</div>
|
|
||||||
<div style="width:100%;height:25px"></div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
15
rollup.config.mjs
Normal file
15
rollup.config.mjs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import svelte from 'rollup-plugin-svelte'
|
||||||
|
import resolve from "@rollup/plugin-node-resolve"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: "src/script/client/main.js",
|
||||||
|
output: {
|
||||||
|
file: 'out/script/client/bundle.js',
|
||||||
|
format: 'esm',
|
||||||
|
sourcemap:true
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
resolve(),
|
||||||
|
svelte({})
|
||||||
|
]
|
||||||
|
}
|
0
src/client/main.js
Normal file
0
src/client/main.js
Normal file
|
@ -8,6 +8,7 @@ import Discord, { IntentsBitField, Client } from "discord.js"
|
||||||
import express from "express"
|
import express from "express"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import axios, { AxiosResponse } from "axios"
|
import axios, { AxiosResponse } from "axios"
|
||||||
|
import ServeError from "./lib/errors"
|
||||||
|
|
||||||
import Files from "./lib/files"
|
import Files from "./lib/files"
|
||||||
require("dotenv").config()
|
require("dotenv").config()
|
||||||
|
@ -21,14 +22,6 @@ app.use("/static",express.static("assets"))
|
||||||
app.use(bodyParser.text({limit:(config.maxDiscordFileSize*config.maxDiscordFiles)+1048576,type:["application/json","text/plain"]}))
|
app.use(bodyParser.text({limit:(config.maxDiscordFileSize*config.maxDiscordFiles)+1048576,type:["application/json","text/plain"]}))
|
||||||
// funcs
|
// funcs
|
||||||
|
|
||||||
function ThrowError(response:express.Response,code:number,errorMessage:string) {
|
|
||||||
fs.readFile(__dirname+"/../pages/error.html",(err,buf) => {
|
|
||||||
if (err) {response.sendStatus(500);console.log(err);return}
|
|
||||||
response.status(code)
|
|
||||||
response.send(buf.toString().replace(/\$ErrorCode/g,code.toString()).replace(/\$ErrorMessage/g,errorMessage).replace(/\$Version/g,pkg.version))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// init data
|
// init data
|
||||||
|
|
||||||
if (!fs.existsSync(__dirname+"/../.data/")) fs.mkdirSync(__dirname+"/../.data/")
|
if (!fs.existsSync(__dirname+"/../.data/")) fs.mkdirSync(__dirname+"/../.data/")
|
||||||
|
@ -87,7 +80,10 @@ app.post("/upload",multerSetup.single('file'),async (req,res) => {
|
||||||
try {
|
try {
|
||||||
files.uploadFile({name:req.file.originalname,mime:req.file.mimetype,uploadId:req.header("monofile-upload-id")},req.file.buffer)
|
files.uploadFile({name:req.file.originalname,mime:req.file.mimetype,uploadId:req.header("monofile-upload-id")},req.file.buffer)
|
||||||
.then((uID) => res.send(uID))
|
.then((uID) => res.send(uID))
|
||||||
.catch((stat) => {res.status(stat.status);res.send(`[err] ${stat.message}`)})
|
.catch((stat) => {
|
||||||
|
res.status(stat.status);
|
||||||
|
res.send(`[err] ${stat.message}`)
|
||||||
|
})
|
||||||
} catch {
|
} catch {
|
||||||
res.status(400)
|
res.status(400)
|
||||||
res.send("[err] bad request")
|
res.send("[err] bad request")
|
||||||
|
@ -108,7 +104,10 @@ app.post("/clone",(req,res) => {
|
||||||
axios.get(j.url,{responseType:"arraybuffer"}).then((data:AxiosResponse) => {
|
axios.get(j.url,{responseType:"arraybuffer"}).then((data:AxiosResponse) => {
|
||||||
files.uploadFile({name:j.url.split("/")[req.body.split("/").length-1] || "generic",mime:data.headers["content-type"],uploadId:j.uploadId},Buffer.from(data.data))
|
files.uploadFile({name:j.url.split("/")[req.body.split("/").length-1] || "generic",mime:data.headers["content-type"],uploadId:j.uploadId},Buffer.from(data.data))
|
||||||
.then((uID) => res.send(uID))
|
.then((uID) => res.send(uID))
|
||||||
.catch((stat) => {res.status(stat.status);res.send(`[err] ${stat.message}`)})
|
.catch((stat) => {
|
||||||
|
res.status(stat.status);
|
||||||
|
res.send(`[err] ${stat.message}`)
|
||||||
|
})
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
res.status(400)
|
res.status(400)
|
||||||
|
@ -141,7 +140,7 @@ app.get("/download/:fileId",(req,res) => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
ThrowError(res,404,"File not found.")
|
ServeError(res,404,"File not found.")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -151,12 +150,12 @@ app.get("/file/:fileId",async (req,res) => {
|
||||||
res.status(200)
|
res.status(200)
|
||||||
f.dataStream.pipe(res)
|
f.dataStream.pipe(res)
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
ThrowError(res,err.status,err.message)
|
ServeError(res,err.status,err.message)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get("*",(req,res) => {
|
app.get("*",(req,res) => {
|
||||||
ThrowError(res,404,"Page not found.")
|
ServeError(res,404,"Page not found.")
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get("/server",(req,res) => {
|
app.get("/server",(req,res) => {
|
131
src/server/lib/accounts.ts
Normal file
131
src/server/lib/accounts.ts
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
import crypto from "crypto"
|
||||||
|
import * as auth from "./auth";
|
||||||
|
import { readFile, writeFile } from "fs/promises"
|
||||||
|
|
||||||
|
// this is probably horrible
|
||||||
|
// but i don't even care anymore
|
||||||
|
|
||||||
|
export let Accounts: Account[] = []
|
||||||
|
|
||||||
|
export interface Account {
|
||||||
|
id : string
|
||||||
|
username: string
|
||||||
|
password: {
|
||||||
|
hash: string
|
||||||
|
salt: string
|
||||||
|
}
|
||||||
|
accounts: string[]
|
||||||
|
admin : boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function create(username:string,pwd:string,admin:boolean=false) {
|
||||||
|
let accId = crypto.randomBytes(12).toString("hex")
|
||||||
|
|
||||||
|
Accounts.push(
|
||||||
|
{
|
||||||
|
id: accId,
|
||||||
|
username: username,
|
||||||
|
password: password.hash(pwd),
|
||||||
|
accounts: [],
|
||||||
|
admin: admin
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
save()
|
||||||
|
|
||||||
|
return accId
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFromUsername(username:string) {
|
||||||
|
return Accounts.find(e => e.username == username)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFromId(id:string) {
|
||||||
|
return Accounts.find(e => e.id == id)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFromToken(token:string) {
|
||||||
|
let accId = auth.validate(token)
|
||||||
|
if (!accId) return
|
||||||
|
return getFromId(accId)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteAccount(id:string) {
|
||||||
|
Accounts.splice(Accounts.findIndex(e => e.id == id),1)
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace password {
|
||||||
|
export function hash(password:string,_salt?:string) {
|
||||||
|
let salt = _salt || crypto.randomBytes(12).toString('base64')
|
||||||
|
let hash = crypto.createHash('sha256').update(`${salt}${password}`).digest('hex')
|
||||||
|
|
||||||
|
return {
|
||||||
|
salt:salt,
|
||||||
|
hash:hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function set(id:string,password:string) {
|
||||||
|
let acc = Accounts.find(e => e.id == id)
|
||||||
|
if (!acc) return
|
||||||
|
|
||||||
|
acc.password = hash(password)
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function check(id:string,password:string) {
|
||||||
|
let acc = Accounts.find(e => e.id == id)
|
||||||
|
if (!acc) return
|
||||||
|
|
||||||
|
return acc.password.hash == hash(password,acc.password.salt).hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace rbxaccounts {
|
||||||
|
export function add(id:string,name:string) {
|
||||||
|
let acc = getFromId(id)
|
||||||
|
if (!acc) return
|
||||||
|
|
||||||
|
/* check for account that already has name */
|
||||||
|
let idx = acc.accounts.findIndex(e=>e==name)
|
||||||
|
if (idx > -1) return
|
||||||
|
|
||||||
|
acc.accounts = [...acc.accounts,name]
|
||||||
|
save()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remove(id:string,name:string) {
|
||||||
|
let acc = getFromId(id)
|
||||||
|
if (!acc) return
|
||||||
|
let idx = acc.accounts.findIndex(e=>e==name)
|
||||||
|
if (idx < 0) return
|
||||||
|
acc.accounts.splice(idx,1)
|
||||||
|
save()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clear(id:string) {
|
||||||
|
let acc = getFromId(id)
|
||||||
|
if (!acc) return
|
||||||
|
acc.accounts = []
|
||||||
|
save()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function save() {
|
||||||
|
writeFile(`${process.cwd()}/.data/accounts.json`,JSON.stringify(Accounts))
|
||||||
|
.catch((err) => console.error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
readFile(`${process.cwd()}/.data/accounts.json`)
|
||||||
|
.then((buf) => {
|
||||||
|
Accounts = JSON.parse(buf.toString())
|
||||||
|
}).catch(err => console.error(err))
|
||||||
|
.finally(() => {
|
||||||
|
if (!Accounts.find(e => e.admin)) {
|
||||||
|
create("admin","admin",true)
|
||||||
|
}
|
||||||
|
})
|
58
src/server/lib/auth.ts
Normal file
58
src/server/lib/auth.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import crypto from "crypto"
|
||||||
|
import { readFile, writeFile } from "fs/promises"
|
||||||
|
export let AuthTokens: AuthToken[] = []
|
||||||
|
export let AuthTokenTO:{[key:string]:NodeJS.Timeout} = {}
|
||||||
|
|
||||||
|
export interface AuthToken {
|
||||||
|
account: string,
|
||||||
|
token: string,
|
||||||
|
expire: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function create(id:string,expire:number=(24*60*60*1000)) {
|
||||||
|
let token = {
|
||||||
|
account:id,
|
||||||
|
token:crypto.randomBytes(12).toString('hex'),
|
||||||
|
expire:Date.now()+expire
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthTokens.push(token)
|
||||||
|
tokenTimer(token)
|
||||||
|
|
||||||
|
save()
|
||||||
|
|
||||||
|
return token.token
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validate(token:string) {
|
||||||
|
return AuthTokens.find(e => e.token == token && Date.now() < e.expire)?.account
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tokenTimer(token:AuthToken) {
|
||||||
|
if (Date.now() >= token.expire) {
|
||||||
|
invalidate(token.token)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthTokenTO[token.token] = setTimeout(() => invalidate(token.token),token.expire-Date.now())
|
||||||
|
}
|
||||||
|
|
||||||
|
export function invalidate(token:string) {
|
||||||
|
if (AuthTokenTO[token]) {
|
||||||
|
clearTimeout(AuthTokenTO[token])
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthTokens.splice(AuthTokens.findIndex(e => e.token == token),1)
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function save() {
|
||||||
|
writeFile(`${process.cwd()}/.data/tokens.json`,JSON.stringify(AuthTokens))
|
||||||
|
.catch((err) => console.error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
readFile(`${process.cwd()}/.data/tokens.json`)
|
||||||
|
.then((buf) => {
|
||||||
|
AuthTokens = JSON.parse(buf.toString())
|
||||||
|
AuthTokens.forEach(e => tokenTimer(e))
|
||||||
|
}).catch(err => console.error(err))
|
35
src/server/lib/errors.ts
Normal file
35
src/server/lib/errors.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { Response } from "express";
|
||||||
|
import { readFile } from "fs/promises"
|
||||||
|
|
||||||
|
let errorPage:string
|
||||||
|
|
||||||
|
export default async function ServeError(
|
||||||
|
res:Response,
|
||||||
|
code:number,
|
||||||
|
reason:string
|
||||||
|
) {
|
||||||
|
// fetch error page if not cached
|
||||||
|
if (!errorPage) {
|
||||||
|
errorPage =
|
||||||
|
(
|
||||||
|
await readFile(`${process.cwd()}/src/pages/error.html`)
|
||||||
|
.catch(() => {res.header("Content-Type","text/plain")})
|
||||||
|
|| "<pre>$code $text</pre>"
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// serve error
|
||||||
|
res.status(code)
|
||||||
|
res.send(
|
||||||
|
errorPage
|
||||||
|
.replace(/\$code/g,code.toString())
|
||||||
|
.replace(/\$text/g,reason)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Redirect(res:Response,url:string) {
|
||||||
|
res.status(302)
|
||||||
|
res.header("Location",url)
|
||||||
|
res.send()
|
||||||
|
}
|
50
src/style/_base.scss
Normal file
50
src/style/_base.scss
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
could probably replace this with fonts served directly
|
||||||
|
from the server but it's fine for now
|
||||||
|
*/
|
||||||
|
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Fira+Code&family=Inconsolata&family=Source+Sans+Pro:wght@300;400;600;700;900&display=swap');
|
||||||
|
|
||||||
|
$FallbackFonts:
|
||||||
|
-apple-system,
|
||||||
|
system-ui,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
"Segoe UI",
|
||||||
|
Roboto,
|
||||||
|
sans-serif;
|
||||||
|
|
||||||
|
%normal {
|
||||||
|
font-family: "Source Sans Pro", $FallbackFonts
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
everything that's not a span
|
||||||
|
and/or has the normal class
|
||||||
|
(it's just in case)
|
||||||
|
*/
|
||||||
|
|
||||||
|
*:not(span), .normal { @extend %normal; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
for code blocks / terminal
|
||||||
|
*/
|
||||||
|
|
||||||
|
.monospace {
|
||||||
|
font-family: "Fira Code", monospace
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
colors
|
||||||
|
*/
|
||||||
|
|
||||||
|
$Background: #252525;
|
||||||
|
/* hsl(210,12.9,24.3) */
|
||||||
|
$darkish: rgb(54, 62, 70);
|
||||||
|
|
||||||
|
/*
|
||||||
|
then other stuff
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: $Background
|
||||||
|
}
|
34
src/style/app/topbar.scss
Normal file
34
src/style/app/topbar.scss
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
@use "../base";
|
||||||
|
|
||||||
|
#topbar {
|
||||||
|
position:absolute;
|
||||||
|
left:0px;
|
||||||
|
top:0px;
|
||||||
|
|
||||||
|
width:100%;
|
||||||
|
height:40px;
|
||||||
|
|
||||||
|
/* hsl(210,9.1,12.9) */
|
||||||
|
background-color: rgb(30, 33, 36);
|
||||||
|
|
||||||
|
display:flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
column-gap:5px;
|
||||||
|
|
||||||
|
.monofile_ico {
|
||||||
|
width:26px;
|
||||||
|
height:26px;
|
||||||
|
border:2px solid base.$darkish;
|
||||||
|
border-radius:100%;
|
||||||
|
transition-duration: 100ms;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border:2px solid #999999;
|
||||||
|
transition-duration: 100ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
src/style/error.scss
Normal file
20
src/style/error.scss
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
@use "_base";
|
||||||
|
|
||||||
|
.error {
|
||||||
|
font-size:20px;
|
||||||
|
color: lightslategray;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%,-50%);
|
||||||
|
|
||||||
|
text-align:center;
|
||||||
|
|
||||||
|
.code {
|
||||||
|
font-size:25px;
|
||||||
|
font-family: "Inconsolata", monospace;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
0
src/svelte/App.svelte
Normal file
0
src/svelte/App.svelte
Normal file
16
src/svelte/elem/Topbar.svelte
Normal file
16
src/svelte/elem/Topbar.svelte
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="topbar">
|
||||||
|
<button on:click={() => {}} class="menuBtn">my account</button>
|
||||||
|
<button on:click={() => {}} class="menuBtn">files</button>
|
||||||
|
|
||||||
|
<!-- divider -->
|
||||||
|
<div style="height:calc( 100% - 10px ); top:5px; width:2px; background-color:#333333; margin:0 5px 0 5px;" />
|
||||||
|
|
||||||
|
<button on:click={() => {}} class="menuBtn">upload</button>
|
||||||
|
<button on:click={() => {}} class="menuBtn">clone</button>
|
||||||
|
|
||||||
|
<div />
|
||||||
|
</div>
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"include":["src/**/*"],
|
"include":["src/server/**/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue