Skip to content

Commit 109831d

Browse files
committed
Added more advanced image attaching.
1 parent a72840a commit 109831d

File tree

9 files changed

+141
-45
lines changed

9 files changed

+141
-45
lines changed

mystbin/backend/models/responses.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ class File(BaseModel):
2727
content: str
2828
loc: int
2929
charcount: int
30+
tab_id: int | None
31+
image: str | None
32+
3033

3134
class Config:
3235
schema_extra = {
@@ -35,6 +38,8 @@ class Config:
3538
"content": "import datetime\\nprint(datetime.datetime.utcnow())",
3639
"loc": 2,
3740
"charcount": 49,
41+
"tab_id": 0,
42+
"url": "..."
3843
}
3944
}
4045

mystbin/backend/routers/pastes.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -188,28 +188,29 @@ async def put_pastes(
188188
include_in_schema=False,
189189
)
190190
@limit("postpastes")
191-
async def get_image_upload_link(request: MystbinRequest, paste_id: str, images: List[UploadFile] = File(...)):
191+
async def get_image_upload_link(request: MystbinRequest, paste_id: str, password: Optional[str] = None, images: List[UploadFile] = File(...)):
192+
""" user = request.state.user
193+
if not user:
194+
return UJSONResponse({"error": "Unauthorized", "notice": "You must be signed in to use this route"}, status_code=401)"""
195+
196+
paste = await request.app.state.db.get_paste(paste_id, password)
197+
if paste is None:
198+
return UJSONResponse({"error": "Not Found"}, status_code=404)
199+
192200
headers = {
193201
"Content-Type": "application/octet-stream",
194202
"AccessKey": f"{__config['bunny_cdn']['token']}"
195203
}
196204

197205
for image in images:
198-
await asyncio.sleep(0.1)
206+
i = image.filename[0]
207+
await request.app.state.db.update_paste_with_files(paste_id=paste_id, tab_id=i, url=f'https://mystbin.b-cdn.net/images/{image.filename}')
199208

200209
URL = f'https://storage.bunnycdn.com/{__config["bunny_cdn"]["hostname"]}/images/{image.filename}'
201210
data = await image.read()
202211

203212
async with request.app.state.client.put(URL, headers=headers, data=data) as resp:
204-
print(resp.status)
205-
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)
209-
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)
213+
pass
213214

214215
return Response(status_code=201)
215216

mystbin/backend/utils/db.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,15 @@ async def get_paste(self, paste_id: str, password: Optional[str] = None) -> Opti
183183
resp = dict(resp[0])
184184
resp["files"] = [{a: str(b) for a, b in x.items()} for x in contents]
185185

186+
images = await self.get_images(paste_id=paste_id)
187+
188+
for index, file in enumerate(resp['files']):
189+
try:
190+
file['image'] = images[index]['url']
191+
file['tab_id'] = images[index]['tab_id']
192+
except IndexError:
193+
pass
194+
186195
return resp
187196
else:
188197
return None
@@ -292,6 +301,18 @@ async def put_paste(
292301

293302
return resp
294303

304+
async def update_paste_with_files(self, *, paste_id: str, tab_id: str, url: str) -> None:
305+
query = """INSERT INTO images VALUES($1, $2, $3)"""
306+
307+
async with self.pool.acquire() as conn:
308+
await self._do_query(query, paste_id, int(tab_id), url)
309+
310+
async def get_images(self, *, paste_id: str):
311+
312+
query = """SELECT * FROM images WHERE parent_id = $1"""
313+
async with self.pool.acquire() as conn:
314+
return [dict(x) for x in await self._do_query(query, paste_id)]
315+
295316
@wrapped_hook_callback
296317
async def edit_paste(
297318
self,

mystbin/database/schema.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ CREATE TABLE IF NOT EXISTS files (
3838
CREATE TABLE IF NOT EXISTS images (
3939
parent_id TEXT REFERENCES pastes(id) ON DELETE CASCADE,
4040
tab_id BIGINT,
41+
url TEXT,
4142
index SERIAL NOT NULL,
4243
PRIMARY KEY (parent_id, index)
4344

mystbin/frontend/components/EditorTabs.tsx

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,7 @@ export default function EditorTabs({
6767
hasPassword = false,
6868
pid = null,
6969
}: TabInfo) {
70-
const [value, setValue] = useState<Record<string, string>[]>([
71-
{ title: "file.txt", content: "", image: null },
72-
]);
70+
const [value, setValue] = useState([{ title: "file.txt", content: "", image: "" },]);
7371
const [currTab, setCurrTab] = useState(0);
7472
const [charCountToast, setCharCountToast] = useState(false);
7573
const [passwordModal, setPasswordModal] = useState(!!hasPassword);
@@ -86,9 +84,10 @@ export default function EditorTabs({
8684
const tabRef = useRef();
8785
const imageRef = useRef();
8886

87+
8988
function handleSetImage(e) {
9089
let file = e.currentTarget.files[0]
91-
let allowed = ['image/gif', 'image/jpeg', 'image/png', 'image/webp'];
90+
let allowed = ['image/gif', 'image/jpeg', 'image/png'];
9291

9392
if (!!file && !allowed.includes(file['type'])) {
9493
alert('Only images are currently supported.')
@@ -111,10 +110,12 @@ export default function EditorTabs({
111110
pasteDispatcher.dispatch({ paste: value });
112111
const maxCharCount = config["paste"]["character_limit"];
113112

114-
if (initialData !== null && !initialState) {
115-
setValue(initialData);
116-
setInitialState(true);
117-
}
113+
useEffect( () => {
114+
if (initialData !== null && !initialState) {
115+
setValue(initialData);
116+
setInitialState(true);
117+
}
118+
}, [])
118119

119120
useEffect(() => {
120121
if (sessionStorage.getItem("pasteCopy") !== null) {
@@ -185,12 +186,13 @@ export default function EditorTabs({
185186

186187
return (
187188
<>
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"}/>
189+
<div>
190+
{value.map((v, i) => (
191+
<div className={styles.attachmentImageBackdrop} onClick={(e) => setShowImage(-1)} style={{ display: showImage === i ? "block" : "none",}}>
192+
<img className={styles.attachmentImage} src={value[i]['image']} style={{ display: showImage === i ? "block" : "none",}}/>
191193
</div>
192-
: null}
193-
<input ref={imageRef} type="file" style={{display: 'none'}} onChange={e => handleSetImage(e)}/>
194+
))}
195+
</div>
194196

195197
<PasswordModal
196198
show={passwordModal}
@@ -220,17 +222,19 @@ export default function EditorTabs({
220222
setValue(newValue);
221223
}}
222224
>
223-
{!pid && value[i]['image'] === null ?
224-
<div className={styles.addImageButtonContainer} onClick={((e) => {
225+
<input ref={imageRef} type="file" style={{display: 'none'}} onChange={e => (handleSetImage(e))} />
226+
{!pid && value[i]['image'] === null || value[i]['image'] === "" ?
227+
<div className={styles.addImageButtonContainer} onClick={(e) => {
225228
e.preventDefault();
226-
imageRef.current.click();
229+
// @ts-ignore
230+
imageRef.current.click();
227231

228-
})} >
232+
}} >
229233
<AddBoxIcon className={styles.addImagesButtonNS} ></AddBoxIcon>
230234
</div> : null}
231235

232236
{ value[i]['image'] ?
233-
<div className={styles.addImageButtonContainer} onClick={(e) => setShowImage(true)}>
237+
<div className={styles.addImageButtonContainer} onClick={(e) => setShowImage(i)}>
234238
<LibraryAddCheckIcon style={{color: "#E3E3E3"}}/>
235239
</div>
236240
: null }
@@ -277,7 +281,7 @@ export default function EditorTabs({
277281
<NewTabButton
278282
onClick={() => {
279283
let newValue = [...value];
280-
newValue.push({ title: "default_name.ext", content: "" });
284+
newValue.push({ title: "file.txt", content: "", image: "" });
281285
setValue(newValue);
282286
setCurrTab(value.length);
283287
}}

mystbin/frontend/components/OptsBar.tsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Slide } from "@material-ui/core";
2323
import config from "../config.json";
2424
import { useMediaQuery } from "react-responsive";
2525
import SetPasswordModal from "./SetPasswordModal";
26+
import axios from "axios";
2627

2728
export default function OptsBar() {
2829
const [currentModal, setCurrentModal] = useState(null);
@@ -36,6 +37,8 @@ export default function OptsBar() {
3637
!useMediaQuery({ query: `(max-width: 768px)` })
3738
);
3839

40+
const [uploaded, setUploaded] = useState(false)
41+
3942
function handleFileUploads(id) {
4043
let paste = pasteStore.getPaste();
4144
let FD = new FormData();
@@ -44,16 +47,25 @@ export default function OptsBar() {
4447
if (element['image'] === null || element['image'] == undefined) {
4548
continue
4649
}
50+
else {
51+
let name = `${index}-${id}-${element['image'].name}`
52+
FD.append('images', element['image'], name)
53+
}
4754

48-
let name = `${index}-${id}-${element['image'].name}`
49-
FD.append('images', element['image'], name)
55+
if (FD.entries().next().done === true) {
56+
setUploaded(true)
57+
return
58+
}
5059
}
5160

52-
fetch(`${config['site']['backend_site']}/images/upload/${id}`, {
53-
method: "PUT",
54-
body: FD
55-
}).then((r) => console.log(r.status))
56-
}
61+
axios({
62+
url: `${config['site']['backend_site']}/images/upload/${id}`,
63+
method: 'PUT',
64+
data: FD,
65+
}).then((response) => {
66+
setUploaded(true);
67+
})
68+
}
5769

5870
const personal = [
5971
{
@@ -186,7 +198,7 @@ export default function OptsBar() {
186198
setSaveSuccessToast(d.id);
187199
setTimeout(() => {
188200
router.push(path);
189-
}, 3000);
201+
}, 6000);
190202
});
191203
}
192204
});

mystbin/frontend/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
"@material-ui/x-grid": "^4.0.0-alpha.37",
1616
"@monaco-editor/react": "^3.7.0",
1717
"@types/crypto-js": "^4.0.1",
18+
"@types/node": "*",
19+
"@types/react": "18.0.1",
20+
"axios": "^0.27.2",
1821
"boostrap": "^2.0.0",
1922
"bootstrap": "^5.2.0",
2023
"cookie-cutter": "^0.2.0",
@@ -33,9 +36,7 @@
3336
"react-loader-spinner": "^5.1.7-beta.1",
3437
"react-popout": "^1.0.3",
3538
"react-responsive": "^9.0.0-beta.10",
36-
"react-tabs": "^3.1.1",
37-
"@types/node": "*",
38-
"@types/react": "18.0.1"
39+
"react-tabs": "^3.1.1"
3940
},
4041
"devDependencies": {
4142
"@types/node": "^14.14.7",

mystbin/frontend/pages/[pid].tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import dynamic from "next/dynamic";
22
import Base from "../components/Base";
33
import styles from "../styles/Home.module.css";
4-
import { PropsWithoutRef } from "react";
5-
import { GetServerSideProps } from "next";
4+
import {PropsWithoutRef} from "react";
5+
import {GetServerSideProps} from "next";
66
import Head from "next/head";
77

88
import config from "../config.json";
@@ -22,7 +22,7 @@ export default function Pastey(props: PropsWithoutRef<{ paste }>) {
2222
});
2323
} else {
2424
for (let file of paste["files"]) {
25-
initialData.push({ title: file["filename"], content: file["content"] });
25+
initialData.push({ title: file["filename"], content: file["content"], image: file['image'] });
2626
}
2727
}
2828

@@ -69,7 +69,7 @@ export default function Pastey(props: PropsWithoutRef<{ paste }>) {
6969
export const getServerSideProps: GetServerSideProps = async ({ query }) => {
7070
let { pid } = query;
7171
pid = pid.toString().split(".", 1)[0];
72-
const response = await fetch(
72+
let response = await fetch(
7373
`${config["site"]["backend_site"]}/paste/${pid}`
7474
);
7575

0 commit comments

Comments
 (0)