Skip to content

Commit a72840a

Browse files
committed
Added the basis of tab attachments.
1 parent cb30c12 commit a72840a

File tree

8 files changed

+871
-3785
lines changed

8 files changed

+871
-3785
lines changed

mystbin/backend/routers/pastes.py

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"""
1919
from __future__ import annotations
2020

21+
import asyncio
2122
import datetime
2223
import json
2324
import pathlib
@@ -26,7 +27,7 @@
2627
from typing import Dict, List, Optional, Union
2728

2829
from asyncpg import Record
29-
from fastapi import APIRouter, Response
30+
from fastapi import APIRouter, Response, UploadFile, File
3031
from fastapi.responses import UJSONResponse
3132
from models import errors, payloads, responses
3233

@@ -53,9 +54,6 @@
5354
del __p, __f # micro-opt, don't keep unneeded variables in-ram
5455

5556

56-
CF_DLURL = f"https://api.cloudflare.com/client/v4/accounts/{__config['cf_images_account']}/images/v2/direct_upload?requireSignedURLs=true"
57-
58-
5957
def generate_paste_id():
6058
"""Generate three random words."""
6159
word_samples = sample(word_list, 3)
@@ -179,26 +177,41 @@ async def put_pastes(
179177
paste = recursive_hook(paste.dict())
180178
return UJSONResponse(paste, status_code=201)
181179

182-
183-
@router.get(
184-
"/images/upload",
180+
@router.put(
181+
"/images/upload/{paste_id}",
185182
tags=["pastes"],
186-
status_code=201,
183+
responses={
184+
201: {"model": responses.PastePostResponse},
185+
401: {"model": errors.Unauthorized},
186+
404: {"model": errors.NotFound}
187+
},
187188
include_in_schema=False,
188189
)
189-
async def get_image_upload_link(request: MystbinRequest, auth: Optional[str] = ''):
190-
if auth != __config['cf_images_frontend_secret']:
191-
return UJSONResponse({'error': 'Unauthorized.'}, status_code=401)
192-
190+
@limit("postpastes")
191+
async def get_image_upload_link(request: MystbinRequest, paste_id: str, images: List[UploadFile] = File(...)):
193192
headers = {
194-
'Authorization': f'Bearer {__config["cf_images_token"]}'}
193+
"Content-Type": "application/octet-stream",
194+
"AccessKey": f"{__config['bunny_cdn']['token']}"
195+
}
196+
197+
for image in images:
198+
await asyncio.sleep(0.1)
199+
200+
URL = f'https://storage.bunnycdn.com/{__config["bunny_cdn"]["hostname"]}/images/{image.filename}'
201+
data = await image.read()
202+
203+
async with request.app.state.client.put(URL, headers=headers, data=data) as resp:
204+
print(resp.status)
195205

196-
async with request.app.state.client.post(CF_DLURL, headers=headers) as resp:
197-
data = await resp.json()
206+
user = request.state.user
207+
if not user:
208+
return UJSONResponse({"error": "Unauthorized", "notice": "You must be signed in to use this route"}, status_code=401)
198209

199-
result = data['result']['uploadURL']
200-
return UJSONResponse({'url': result}, status_code=201)
210+
paste = await request.app.state.db.get_paste(paste_id, password)
211+
if paste is None:
212+
return UJSONResponse({"error": "Not Found"}, status_code=404)
201213

214+
return Response(status_code=201)
202215

203216
desc = f"""Get a paste by ID.
204217

mystbin/frontend/components/EditorTabs.tsx

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import NewTabButton from "./NewTabButton";
88
import pasteDispatcher from "../dispatchers/PasteDispatcher";
99
import getLanguage from "../stores/languageStore";
1010
import config from "../config.json";
11-
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
1211
import { Button } from "@material-ui/core";
1312
import DropdownItem from "react-bootstrap/DropdownItem";
1413
import React from "react";
15-
import InsertPhotoIcon from "@material-ui/icons/InsertPhoto";
1614
import SettingsIcon from "@material-ui/icons/Settings";
15+
import AddBoxIcon from '@material-ui/icons/AddBox';
16+
import LibraryAddCheckIcon from '@material-ui/icons/LibraryAddCheck';
1717

1818
const languages = {
1919
py: "python",
@@ -68,7 +68,7 @@ export default function EditorTabs({
6868
pid = null,
6969
}: TabInfo) {
7070
const [value, setValue] = useState<Record<string, string>[]>([
71-
{ title: "file.txt", content: "" },
71+
{ title: "file.txt", content: "", image: null },
7272
]);
7373
const [currTab, setCurrTab] = useState(0);
7474
const [charCountToast, setCharCountToast] = useState(false);
@@ -80,8 +80,33 @@ export default function EditorTabs({
8080
const [initialState, setInitialState] = useState(false);
8181
const [langDropDown, setLangDropDown] = useState(false);
8282
const [dropLang, setDropLang] = useState(null);
83+
const [image, setImage] = useState(null)
84+
const [showImage, setShowImage] = useState(null)
8385

8486
const tabRef = useRef();
87+
const imageRef = useRef();
88+
89+
function handleSetImage(e) {
90+
let file = e.currentTarget.files[0]
91+
let allowed = ['image/gif', 'image/jpeg', 'image/png', 'image/webp'];
92+
93+
if (!!file && !allowed.includes(file['type'])) {
94+
alert('Only images are currently supported.')
95+
}
96+
else if (file.size / 1024 / 1024 > 4) {
97+
alert('You can only upload files 4Mb in size or less.')
98+
}
99+
else {
100+
setImage(file)
101+
}
102+
}
103+
104+
useEffect( () => {
105+
let newValue = [...value];
106+
newValue[currTab]['image'] = image
107+
108+
setValue(newValue)
109+
}, [image])
85110

86111
pasteDispatcher.dispatch({ paste: value });
87112
const maxCharCount = config["paste"]["character_limit"];
@@ -160,6 +185,13 @@ export default function EditorTabs({
160185

161186
return (
162187
<>
188+
{showImage ?
189+
<div className={styles.attachmentImageBackdrop} onClick={(e) => setShowImage(false)}>
190+
<img className={styles.attachmentImage} src={"https://mystbin.b-cdn.net/images/0-OffSpecificationCharm-9a0f0b7719a4f55a5e104922e4d42204.png"}/>
191+
</div>
192+
: null}
193+
<input ref={imageRef} type="file" style={{display: 'none'}} onChange={e => handleSetImage(e)}/>
194+
163195
<PasswordModal
164196
show={passwordModal}
165197
shake={shake}
@@ -188,6 +220,21 @@ export default function EditorTabs({
188220
setValue(newValue);
189221
}}
190222
>
223+
{!pid && value[i]['image'] === null ?
224+
<div className={styles.addImageButtonContainer} onClick={((e) => {
225+
e.preventDefault();
226+
imageRef.current.click();
227+
228+
})} >
229+
<AddBoxIcon className={styles.addImagesButtonNS} ></AddBoxIcon>
230+
</div> : null}
231+
232+
{ value[i]['image'] ?
233+
<div className={styles.addImageButtonContainer} onClick={(e) => setShowImage(true)}>
234+
<LibraryAddCheckIcon style={{color: "#E3E3E3"}}/>
235+
</div>
236+
: null }
237+
191238
{!!pid ? (
192239
<div className={styles.dropdownContainer} ref={tabRef}>
193240
<Button

mystbin/frontend/components/OptsBar.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,25 @@ export default function OptsBar() {
3636
!useMediaQuery({ query: `(max-width: 768px)` })
3737
);
3838

39+
function handleFileUploads(id) {
40+
let paste = pasteStore.getPaste();
41+
let FD = new FormData();
42+
43+
for (const [index, element] of paste.entries()) {
44+
if (element['image'] === null || element['image'] == undefined) {
45+
continue
46+
}
47+
48+
let name = `${index}-${id}-${element['image'].name}`
49+
FD.append('images', element['image'], name)
50+
}
51+
52+
fetch(`${config['site']['backend_site']}/images/upload/${id}`, {
53+
method: "PUT",
54+
body: FD
55+
}).then((r) => console.log(r.status))
56+
}
57+
3958
const personal = [
4059
{
4160
title: "Dashboard",
@@ -158,6 +177,8 @@ export default function OptsBar() {
158177
})
159178
.then((d) => {
160179
if (d && d.id) {
180+
handleFileUploads(d.id)
181+
161182
let path = `/${d.id}`;
162183
let full = window.location.origin + path;
163184
navigator.clipboard.writeText(full).then(() => {

mystbin/frontend/next-env.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/types/global" />
3+
/// <reference types="next/image-types/global" />
4+
5+
// NOTE: This file should not be edited
6+
// see https://nextjs.org/docs/basic-features/typescript for more information.

mystbin/frontend/pages/_app.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Script from "next/script";
55

66
function MyApp({ Component, pageProps }) {
77
return(<>
8-
<script async src={"https://media.ethicalads.io/media/client/ethicalads.min.js"}/>
8+
<Script async src={"https://media.ethicalads.io/media/client/ethicalads.min.js"}/>
99
<Component {...pageProps} />;
1010
</>)
1111
}

mystbin/frontend/styles/EditorTabs.module.css

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -79,30 +79,38 @@
7979
position: relative;
8080
}
8181

82-
.addAttachmentIcon {
82+
.addImageButtonContainer {
83+
display: flex;
84+
align-items: center;
85+
justify-content: center;
86+
padding: 0.5rem
87+
}
88+
89+
.addImagesButtonNS {
8390
color: #e3e3e3;
84-
height: 1.75rem!important;
85-
width: 1.75rem!important;
8691
}
8792

88-
.addAttachmentIconContainer {
89-
background-color: #3C94DF;
90-
display: flex;
91-
flex-direction: row;
92-
border-radius: 0 0 5px 5px;
93-
width: fit-content;
93+
.addImagesButtonNS:hover {
94+
filter: brightness(80%);
9495
}
9596

96-
.addAttachmentsText {
97-
font-size: 0.8em;
98-
color: #e3e3e3;
99-
padding: 0.5rem;
100-
margin-right: 1rem;
101-
margin-left: -0.5rem;
102-
align-self: center;
97+
.addImagesButton {
98+
color: #3C94DF;
10399
}
104100

105-
.addAttachmentIconContainer:hover {
106-
cursor: pointer;
107-
filter: brightness(120%);
101+
.attachmentImageBackdrop {
102+
position: fixed;
103+
width: 100vw;
104+
height: 100vh;
105+
background-color: rgba(0, 0, 0, 0.5);
106+
z-index: 9;
107+
}
108+
109+
.attachmentImage {
110+
position: fixed;
111+
left: 50%;
112+
top: 50%;
113+
transform: translate(-50%, -50%);
114+
z-index: 8;
115+
max-width: 100%;
108116
}

mystbin/frontend/styles/Tab.module.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,25 @@
44
border: none;
55
border-bottom: 2px solid #3C94DF;
66
outline: none !important;
7-
font-size: 1em;
87
min-width: 4rem;
98
display: inline-flex;
109
min-height: 2.5rem;
1110
text-align: center;
1211
vertical-align: bottom;
12+
font-size: smaller;
1313
}
1414

1515
.tabs {
1616
background: #2b2b2b;
1717
color: rgba(255, 255, 255, 0.4);
1818
border-bottom: 2px solid transparent;
1919
outline: none !important;
20-
font-size: 1em;
2120
min-width: 4rem;
2221
display: inline-flex;
2322
text-align: center;
2423
vertical-align: bottom;
2524
filter: brightness(70%);
25+
font-size: smaller;
2626
}
2727

2828
.tabs:hover {

0 commit comments

Comments
 (0)