add image upload feature in dev mode
This commit is contained in:
parent
47c2b74ecb
commit
0ce7f50285
11 changed files with 369 additions and 15 deletions
|
@ -130,7 +130,8 @@ body,
|
|||
pre,
|
||||
code,
|
||||
kbd,
|
||||
button {
|
||||
button,
|
||||
select {
|
||||
font-family: "RecVar", sans-serif;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
@ -142,7 +143,8 @@ body {
|
|||
pre,
|
||||
code,
|
||||
kbd,
|
||||
button {
|
||||
button,
|
||||
select {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
|
|
53
static/css/page/treehouse/dev/tools.css
Normal file
53
static/css/page/treehouse/dev/tools.css
Normal file
|
@ -0,0 +1,53 @@
|
|||
th-picture-upload {
|
||||
display: block;
|
||||
|
||||
border: 1px solid var(--border-1);
|
||||
padding: 8px 12px;
|
||||
margin-right: 8px;
|
||||
border-radius: 8px;
|
||||
|
||||
cursor: default;
|
||||
|
||||
&:focus {
|
||||
border-color: var(--liquidex-brand-blue);
|
||||
}
|
||||
|
||||
& > .nothing-pasted {
|
||||
text-align: center;
|
||||
opacity: 50%;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
& > .have-picture {
|
||||
& > p {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
& > img {
|
||||
max-height: 480px;
|
||||
}
|
||||
}
|
||||
|
||||
& > .copied-to-clipboard {
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* State display */
|
||||
|
||||
& > * {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&[data-state="init"] > .nothing-pasted {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&[data-state="have-picture"] > .have-picture {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&[data-state="copied-to-clipboard"] > .copied-to-clipboard {
|
||||
display: block;
|
||||
}
|
||||
}
|
149
static/js/dev/picture-upload.js
Normal file
149
static/js/dev/picture-upload.js
Normal file
|
@ -0,0 +1,149 @@
|
|||
class PictureUpload extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.tabIndex = 0;
|
||||
this.gotoInit();
|
||||
|
||||
this.preview = this.querySelector("img[name='preview']");
|
||||
|
||||
this.addEventListener("click", (event) => {
|
||||
if (event.target == this || event.target.parentElement == this) {
|
||||
event.preventDefault();
|
||||
this.focus();
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("paste", async (event) => {
|
||||
if (event.clipboardData.items.length != 1) {
|
||||
console.error("only one item is supported");
|
||||
return;
|
||||
}
|
||||
|
||||
let item = event.clipboardData.items[0];
|
||||
await this.paste(item);
|
||||
});
|
||||
}
|
||||
|
||||
gotoInit() {
|
||||
this.setState("init");
|
||||
this.innerHTML = `
|
||||
<div class="nothing-pasted">
|
||||
paste or drop an image here to make a picture out of it
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async gotoHavePicture(imageType, imageFile) {
|
||||
this.setState("have-picture");
|
||||
this.innerHTML = `
|
||||
<div class="have-picture">
|
||||
<img name="preview" class="pic" alt="preview">
|
||||
<p>
|
||||
<span name="preview-width"></span> × <span name="preview-height"></span> px (<span name="file-size"></span>)
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="label">label</label>
|
||||
<input name="label" type="text" placeholder="untitled"></input>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="compression">compression</label>
|
||||
<select name="compression">
|
||||
<option value="Lossless">lossless</option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<button name="upload">upload</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let preview = this.querySelector("img[name='preview']");
|
||||
let previewWidth = this.querySelector("[name='preview-width']");
|
||||
let previewHeight = this.querySelector("[name='preview-height']");
|
||||
let fileSize = this.querySelector("[name='file-size']");
|
||||
let label = this.querySelector("[name='label']");
|
||||
let compression = this.querySelector("[name='compression']");
|
||||
let upload = this.querySelector("button[name='upload']");
|
||||
|
||||
fileSize.textContent = formatSizeSI(imageFile.size);
|
||||
|
||||
let url = URL.createObjectURL(imageFile);
|
||||
preview.src = url;
|
||||
|
||||
createImageBitmap(imageFile).then((bitmap) => {
|
||||
console.log(bitmap);
|
||||
previewWidth.textContent = bitmap.width.toString();
|
||||
previewHeight.textContent = bitmap.height.toString();
|
||||
});
|
||||
|
||||
upload.addEventListener("click", async () => {
|
||||
let params = new URLSearchParams({
|
||||
label: label.value,
|
||||
format: imageType,
|
||||
compression: compression.value,
|
||||
});
|
||||
let response = await fetch(`/dev/picture-upload?${params}`, {
|
||||
method: "POST",
|
||||
body: imageFile,
|
||||
});
|
||||
let json = await response.json();
|
||||
if (json.error != null) {
|
||||
console.error(json.error);
|
||||
} else {
|
||||
await navigator.clipboard.writeText(json.ulid);
|
||||
await this.gotoCopiedToClipboard();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async gotoCopiedToClipboard() {
|
||||
this.setState("copied-to-clipboard");
|
||||
this.innerHTML = `
|
||||
<div class="copied-to-clipboard">ulid copied to clipboard; the window will now refresh</div>
|
||||
`;
|
||||
}
|
||||
|
||||
setState(name) {
|
||||
this.setAttribute("data-state", name);
|
||||
}
|
||||
|
||||
async paste(item) {
|
||||
console.log(item);
|
||||
if (!isSupportedImageType(item.type)) {
|
||||
console.error("unsupported mime type", item.type);
|
||||
return;
|
||||
}
|
||||
|
||||
let file = item.getAsFile();
|
||||
if (file == null) {
|
||||
console.error("data transfer does not contain a file");
|
||||
return;
|
||||
}
|
||||
|
||||
await this.gotoHavePicture(item.type, file);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("th-picture-upload", PictureUpload);
|
||||
|
||||
function isSupportedImageType(mime) {
|
||||
return (
|
||||
mime == "image/png" ||
|
||||
mime == "image/jpeg" ||
|
||||
mime == "image/svg+xml" ||
|
||||
mime == "image/webp"
|
||||
);
|
||||
}
|
||||
|
||||
function formatSizeSI(bytes) {
|
||||
return new Intl.NumberFormat(undefined, {
|
||||
style: "unit",
|
||||
unit: "byte",
|
||||
notation: "compact",
|
||||
unitDisplay: "narrow",
|
||||
}).format(bytes);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue