diff --git a/images/covers/.gitignore b/images/covers/.gitignore new file mode 100644 index 00000000..c96a04f0 --- /dev/null +++ b/images/covers/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/mixins/Input.pixy b/mixins/Input.pixy index c93ae69f..15b9a588 100644 --- a/mixins/Input.pixy +++ b/mixins/Input.pixy @@ -30,10 +30,10 @@ component InputSelection(id string, value string, label string, placeholder stri each option in options option(value=option.Value)= option.Label -component InputImage(id string, label string) +component InputImage(id string, label string, endpoint string) .widget-section label(for=id)= label + ":" - button.action(data-action="selectFile", data-trigger="click", data-preview-image-id=id + "-preview") + button.action(data-action="selectFile", data-trigger="click", data-preview-image-id=id + "-preview", data-endpoint=endpoint) Icon("upload") span Select file diff --git a/pages/index.go b/pages/index.go index 61617b35..f84c8965 100644 --- a/pages/index.go +++ b/pages/index.go @@ -190,6 +190,7 @@ func Configure(app *aero.Application) { // Upload app.Post("/api/upload/avatar", upload.Avatar) + app.Post("/api/upload/cover", upload.Cover) // Admin l.Page("/admin", admin.Get) diff --git a/pages/profile/profile.pixy b/pages/profile/profile.pixy index beef9e81..27f5ca61 100644 --- a/pages/profile/profile.pixy +++ b/pages/profile/profile.pixy @@ -39,7 +39,7 @@ component ProfileHeader(viewUser *arn.User, user *arn.User, uri string) component ProfileHead(viewUser *arn.User, user *arn.User, uri string) .profile - img.profile-cover.lazy(data-src=viewUser.CoverImageURL(), data-webp="true", alt="Cover image") + img.profile-cover.lazy(data-src=viewUser.CoverLink("large"), data-webp="true", alt="Cover image") .profile-image-container.mountable.never-unmount ProfileImage(viewUser) diff --git a/pages/settings/settings.pixy b/pages/settings/settings.pixy index e526d706..ef9bfb89 100644 --- a/pages/settings/settings.pixy +++ b/pages/settings/settings.pixy @@ -27,7 +27,7 @@ component SettingsPersonal(user *arn.User) .widget.mountable(data-api="/api/settings/" + user.ID) h3.widget-title - Icon("picture-o") + Icon("camera") span Avatar //- .widget-section @@ -55,16 +55,26 @@ component SettingsPersonal(user *arn.User) //- //- File upload //- if user.Settings().Avatar.Source == "FileSystem" - InputImage("avatar-input", "File") + InputImage("avatar-input", "File", "/api/upload/avatar") .profile-image-container.avatar-preview if user.HasAvatar() - img#avatar-input-preview.profile-image.mountable(src=user.AvatarLink("large"), alt="Profile image", title="Recommended: 560 x 560 | PNG or JPG") + img#avatar-input-preview.profile-image.lazy(data-src=user.AvatarLink("large"), data-webp="true", alt="Profile image", title="Recommended: 560 x 560 | PNG or JPG") else img#avatar-input-preview.profile-image.hidden(src=user.AvatarLink("large"), alt="Profile image", title="Recommended: 560 x 560 | PNG or JPG") #avatar-input-preview-svg SVGProfileImage(user) + + .widget.mountable(data-api="/api/settings/" + user.ID) + h3.widget-title + Icon("picture-o") + span Cover + + InputImage("cover-input", "File", "/api/upload/cover") + + .cover-preview + img#cover-input-preview.profile-cover.lazy(data-src=user.CoverLink("small"), data-webp="true", alt="Cover image") component SettingsNotifications(user *arn.User) SettingsTabs @@ -237,13 +247,13 @@ component SettingsAccounts(user *arn.User) ImportLists(user) - .widget.mountable - h3.widget-title - Icon("upload") - span Export + //- .widget.mountable + //- h3.widget-title + //- Icon("upload") + //- span Export - .widget-section - label JSON: - a.button(href="/api/animelist/" + user.ID) - Icon("upload") - span Export anime list as JSON \ No newline at end of file + //- .widget-section + //- label JSON: + //- a.button(href="/api/animelist/" + user.ID) + //- Icon("upload") + //- span Export anime list as JSON \ No newline at end of file diff --git a/pages/settings/settings.scarlet b/pages/settings/settings.scarlet index dca971ca..9af30b10 100644 --- a/pages/settings/settings.scarlet +++ b/pages/settings/settings.scarlet @@ -16,6 +16,16 @@ .avatar-preview margin 0 auto +.cover-preview + width 100% + height 0 + padding-top 25% + position relative + margin 0 auto + +#cover-input-preview + border-radius 3px + .settings-info-text text-align center font-size 0.9rem diff --git a/pages/upload/avatar.go b/pages/upload/avatar.go index 86d54b0e..cd4d1bb9 100644 --- a/pages/upload/avatar.go +++ b/pages/upload/avatar.go @@ -7,7 +7,7 @@ import ( "github.com/animenotifier/notify.moe/utils" ) -// Avatar ... +// Avatar handles the avatar upload. func Avatar(ctx *aero.Context) string { user := utils.GetUser(ctx) diff --git a/pages/upload/cover.go b/pages/upload/cover.go new file mode 100644 index 00000000..fdb59e5a --- /dev/null +++ b/pages/upload/cover.go @@ -0,0 +1,36 @@ +package upload + +import ( + "net/http" + + "github.com/aerogo/aero" + "github.com/animenotifier/notify.moe/utils" +) + +// Cover handles the cover image upload. +func Cover(ctx *aero.Context) string { + user := utils.GetUser(ctx) + + if user == nil { + return ctx.Error(http.StatusUnauthorized, "Not logged in", nil) + } + + // Retrieve file from post body + data, err := ctx.Request().Body().Bytes() + + if err != nil { + return ctx.Error(http.StatusInternalServerError, "Reading request body failed", err) + } + + // Set cover image file + err = user.SetCoverBytes(data) + + if err != nil { + return ctx.Error(http.StatusInternalServerError, "Invalid image format", err) + } + + // Save cover image information + user.Save() + + return "ok" +} diff --git a/scripts/Actions/Upload.ts b/scripts/Actions/Upload.ts index 08937e96..65562d86 100644 --- a/scripts/Actions/Upload.ts +++ b/scripts/Actions/Upload.ts @@ -3,8 +3,11 @@ import { StatusMessage } from "../StatusMessage" // Select file export function selectFile(arn: AnimeNotifier, button: HTMLButtonElement) { - let input = document.createElement("input") let preview = document.getElementById(button.dataset.previewImageId) as HTMLImageElement + let endpoint = button.dataset.endpoint + + // Click on virtual file input element + let input = document.createElement("input") input.setAttribute("type", "file") input.onchange = () => { @@ -19,22 +22,24 @@ export function selectFile(arn: AnimeNotifier, button: HTMLButtonElement) { return } - previewImage(file, preview) - uploadFile(file, "/api/upload/avatar", arn) + previewImage(file, endpoint, preview) + uploadFile(file, endpoint, arn) } input.click() } // Preview image -function previewImage(file: File, preview: HTMLImageElement) { +function previewImage(file: File, endpoint: string, preview: HTMLImageElement) { let reader = new FileReader() reader.onloadend = () => { - let svgPreview = document.getElementById("avatar-input-preview-svg") as HTMLImageElement + if(endpoint === "/api/upload/avatar") { + let svgPreview = document.getElementById("avatar-input-preview-svg") as HTMLImageElement - if(svgPreview) { - svgPreview.classList.add("hidden") + if(svgPreview) { + svgPreview.classList.add("hidden") + } } preview.classList.remove("hidden") @@ -49,7 +54,7 @@ function uploadFile(file: File, endpoint: string, arn: AnimeNotifier) { let reader = new FileReader() reader.onloadend = async () => { - arn.statusMessage.showInfo("Uploading avatar...", 60000) + arn.statusMessage.showInfo("Uploading image...", 60000) let response = await fetch(endpoint, { method: "POST", @@ -60,13 +65,15 @@ function uploadFile(file: File, endpoint: string, arn: AnimeNotifier) { body: reader.result }) - let newURL = await response.text() - updateSideBarAvatar(newURL) + if(endpoint === "/api/upload/avatar") { + let newURL = await response.text() + updateSideBarAvatar(newURL) + } if(response.ok) { - arn.statusMessage.showInfo("Successfully uploaded your new avatar.") + arn.statusMessage.showInfo("Successfully uploaded your new image.") } else { - arn.statusMessage.showError("Failed uploading your new avatar.") + arn.statusMessage.showError("Failed uploading your new image.") } }