Improved soundtrack editing
This commit is contained in:
parent
6d0b2ccdf6
commit
37a9e6cbf4
@ -24,8 +24,9 @@ component InputTags(id string, value []string, label string)
|
||||
.tags(id=id)
|
||||
for index, tag := range value
|
||||
.tag
|
||||
span.tag-title= tag
|
||||
.tag-remove.action(data-action="arrayRemove", data-trigger="click", data-field=id, data-index=index) x
|
||||
span.tag-title.action(contenteditable="true", data-trigger="focusout", data-action="save", data-field=id + "[" + strconv.Itoa(index) + "]")= tag
|
||||
button.tag-remove.action(data-action="arrayRemove", data-trigger="click", data-field=id, data-index=index)
|
||||
RawIcon("trash")
|
||||
|
||||
button.tag-add.action(data-action="arrayAppend", data-trigger="click", data-field=id)
|
||||
RawIcon("plus")
|
||||
|
@ -7,13 +7,25 @@ component SoundTrackAllMedia(track *arn.SoundTrack)
|
||||
|
||||
component SoundTrackMedia(track *arn.SoundTrack, media *arn.ExternalMedia)
|
||||
.sound-track.mountable(id=track.ID)
|
||||
.sound-track-content
|
||||
SoundTrackContent(track, media)
|
||||
SoundTrackFooter(track)
|
||||
|
||||
component SoundTrackContent(track *arn.SoundTrack, media *arn.ExternalMedia)
|
||||
.sound-track-content
|
||||
if track.MainAnime() != nil
|
||||
a.sound-track-anime-link.ajax(href="/anime/" + track.MainAnime().ID)
|
||||
img.sound-track-anime-image.lazy(data-src=track.MainAnime().Image.Small, alt=track.MainAnime().Title.Canonical, title=track.MainAnime().Title.Canonical)
|
||||
|
||||
iframe.lazy(data-src=media.EmbedLink(), allowfullscreen="allowfullscreen")
|
||||
.sound-track-footer
|
||||
a.ajax(href=track.Link())
|
||||
Icon("music")
|
||||
span posted by
|
||||
a.ajax(href=track.CreatedByUser().Link())= track.CreatedByUser().Nick + " "
|
||||
ExternalMedia(media)
|
||||
|
||||
component SoundTrackFooter(track *arn.SoundTrack)
|
||||
.sound-track-footer
|
||||
if track.Title == ""
|
||||
a.ajax(href=track.Link() + "/edit") untitled
|
||||
else
|
||||
a.ajax(href=track.Link())= track.Title
|
||||
span posted by
|
||||
a.ajax(href=track.CreatedByUser().Link())= track.CreatedByUser().Nick + " "
|
||||
|
||||
component ExternalMedia(media *arn.ExternalMedia)
|
||||
iframe.lazy(data-src=media.EmbedLink(), allowfullscreen="allowfullscreen")
|
@ -1,7 +1,6 @@
|
||||
.widget-section
|
||||
button,
|
||||
.button
|
||||
margin-bottom 1rem
|
||||
.widget-section > button,
|
||||
.widget-section > .button
|
||||
margin-bottom 1rem
|
||||
|
||||
.avatar-preview
|
||||
margin 0 auto
|
@ -27,14 +27,17 @@ func Edit(ctx *aero.Context) string {
|
||||
ctx.Data = &arn.OpenGraph{
|
||||
Tags: map[string]string{
|
||||
"og:title": track.Title,
|
||||
"og:image": track.MainAnime().Image.Large,
|
||||
"og:url": "https://" + ctx.App.Config.Domain + track.Link(),
|
||||
"og:site_name": "notify.moe",
|
||||
"og:type": "music.song",
|
||||
},
|
||||
}
|
||||
|
||||
return ctx.HTML(EditForm(track, "Edit soundtrack"))
|
||||
if track.MainAnime() != nil {
|
||||
ctx.Data.(*arn.OpenGraph).Tags["og:image"] = track.MainAnime().Image.Large
|
||||
}
|
||||
|
||||
return ctx.HTML(components.SoundTrackTabs(track) + EditForm(track, "Edit soundtrack"))
|
||||
}
|
||||
|
||||
// EditForm ...
|
||||
@ -43,10 +46,11 @@ func EditForm(obj interface{}, title string) string {
|
||||
v := reflect.ValueOf(obj).Elem()
|
||||
id := reflect.Indirect(v.FieldByName("ID"))
|
||||
lowerCaseTypeName := strings.ToLower(t.Name())
|
||||
endpoint := `/api/` + lowerCaseTypeName + `/` + id.String()
|
||||
|
||||
var b bytes.Buffer
|
||||
b.WriteString(`<div class="widget-form">`)
|
||||
b.WriteString(`<div class="widget" data-api="/api/` + lowerCaseTypeName + `/` + id.String() + `">`)
|
||||
b.WriteString(`<div class="widget" data-api="` + endpoint + `">`)
|
||||
b.WriteString(`<h1>`)
|
||||
b.WriteString(title)
|
||||
b.WriteString(`</h1>`)
|
||||
@ -86,14 +90,22 @@ func RenderField(b *bytes.Buffer, v *reflect.Value, field reflect.StructField, i
|
||||
b.WriteString(components.InputTags(idPrefix+field.Name, fieldValue.Interface().([]string), field.Name))
|
||||
case "[]*arn.ExternalMedia":
|
||||
for sliceIndex := 0; sliceIndex < fieldValue.Len(); sliceIndex++ {
|
||||
b.WriteString(`<div class="widget-section">`)
|
||||
b.WriteString(`<div class="widget-title">` + strconv.Itoa(sliceIndex+1) + ". " + field.Name + `</div>`)
|
||||
|
||||
arrayObj := fieldValue.Index(sliceIndex).Interface()
|
||||
arrayIDPrefix := fmt.Sprintf("%s[%d].", field.Name, sliceIndex)
|
||||
RenderObject(b, arrayObj, arrayIDPrefix)
|
||||
|
||||
// Preview
|
||||
b.WriteString(components.ExternalMedia(fieldValue.Index(sliceIndex).Interface().(*arn.ExternalMedia)))
|
||||
|
||||
// Remove button
|
||||
b.WriteString(`<div class="buttons"><button class="action" data-action="arrayRemove" data-trigger="click" data-field="` + field.Name + `" data-index="`)
|
||||
b.WriteString(strconv.Itoa(sliceIndex))
|
||||
b.WriteString(`">` + utils.RawIcon("trash") + `</button></div>`)
|
||||
|
||||
b.WriteString(`</div>`)
|
||||
}
|
||||
|
||||
b.WriteString(`<div class="buttons"><button class="action" data-action="arrayAppend" data-trigger="click" data-field="` + field.Name + `">` + utils.Icon("plus") + `Add ` + field.Name + `</button></div>`)
|
||||
|
@ -1,8 +1,29 @@
|
||||
component Track(track *arn.SoundTrack)
|
||||
h1= track.Title
|
||||
SoundTrackTabs(track)
|
||||
|
||||
if track.Title == ""
|
||||
h1 untitled
|
||||
else
|
||||
h1= track.Title
|
||||
|
||||
.sound-tracks
|
||||
SoundTrackAllMedia(track)
|
||||
|
||||
p
|
||||
a.ajax(href=track.Link() + "/edit") Edit
|
||||
.footer.text-center
|
||||
if track.EditedBy != ""
|
||||
span Edited
|
||||
span.utc-date(data-date=track.Edited)
|
||||
span by
|
||||
span= track.EditedByUser().Nick
|
||||
else
|
||||
span Posted
|
||||
span.utc-date(data-date=track.Created)
|
||||
span by
|
||||
span= track.CreatedByUser().Nick
|
||||
|
||||
span .
|
||||
|
||||
component SoundTrackTabs(track *arn.SoundTrack)
|
||||
.tabs
|
||||
Tab("Soundtrack", "music", track.Link())
|
||||
Tab("Edit", "pencil", track.Link() + "/edit")
|
@ -25,8 +25,8 @@ export class AnimeNotifier {
|
||||
mainPageLoaded: boolean
|
||||
lastReloadContentPath: string
|
||||
|
||||
imageFound: MutationQueue
|
||||
imageNotFound: MutationQueue
|
||||
elementFound: MutationQueue
|
||||
elementNotFound: MutationQueue
|
||||
unmount: MutationQueue
|
||||
|
||||
constructor(app: Application) {
|
||||
@ -34,13 +34,13 @@ export class AnimeNotifier {
|
||||
this.user = null
|
||||
this.title = "Anime Notifier"
|
||||
|
||||
this.imageFound = new MutationQueue(elem => elem.classList.add("image-found"))
|
||||
this.imageNotFound = new MutationQueue(elem => elem.classList.add("image-not-found"))
|
||||
this.elementFound = new MutationQueue(elem => elem.classList.add("element-found"))
|
||||
this.elementNotFound = new MutationQueue(elem => elem.classList.add("element-not-found"))
|
||||
this.unmount = new MutationQueue(elem => elem.classList.remove("mounted"))
|
||||
|
||||
// These classes will never be removed on DOM diffs
|
||||
Diff.persistentClasses.add("mounted")
|
||||
Diff.persistentClasses.add("image-found")
|
||||
Diff.persistentClasses.add("element-found")
|
||||
|
||||
// Never remove src property on diffs
|
||||
Diff.persistentAttributes.add("src")
|
||||
@ -134,7 +134,7 @@ export class AnimeNotifier {
|
||||
|
||||
this.contentLoadedActions = Promise.all([
|
||||
Promise.resolve().then(() => this.mountMountables()),
|
||||
Promise.resolve().then(() => this.lazyLoadImages()),
|
||||
Promise.resolve().then(() => this.lazyLoad()),
|
||||
Promise.resolve().then(() => this.displayLocalDates()),
|
||||
Promise.resolve().then(() => this.setSelectBoxValue()),
|
||||
Promise.resolve().then(() => this.assignActions()),
|
||||
@ -500,37 +500,59 @@ export class AnimeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
lazyLoadImages() {
|
||||
lazyLoad() {
|
||||
for(let element of findAll("lazy")) {
|
||||
this.lazyLoadImage(element as HTMLImageElement)
|
||||
switch(element.tagName) {
|
||||
case "IMG":
|
||||
this.lazyLoadImage(element as HTMLImageElement)
|
||||
break
|
||||
|
||||
case "IFRAME":
|
||||
this.lazyLoadIFrame(element as HTMLIFrameElement)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazyLoadImage(img: HTMLImageElement) {
|
||||
lazyLoadImage(element: HTMLImageElement) {
|
||||
// Once the image becomes visible, load it
|
||||
img["became visible"] = () => {
|
||||
element["became visible"] = () => {
|
||||
// Replace URL with WebP if supported
|
||||
if(this.webpEnabled && img.dataset.webp) {
|
||||
let dot = img.dataset.src.lastIndexOf(".")
|
||||
img.src = img.dataset.src.substring(0, dot) + ".webp"
|
||||
if(this.webpEnabled && element.dataset.webp) {
|
||||
let dot = element.dataset.src.lastIndexOf(".")
|
||||
element.src = element.dataset.src.substring(0, dot) + ".webp"
|
||||
} else {
|
||||
img.src = img.dataset.src
|
||||
element.src = element.dataset.src
|
||||
}
|
||||
|
||||
if(img.naturalWidth === 0) {
|
||||
img.onload = () => {
|
||||
this.imageFound.queue(img)
|
||||
if(element.naturalWidth === 0) {
|
||||
element.onload = () => {
|
||||
this.elementFound.queue(element)
|
||||
}
|
||||
|
||||
img.onerror = () => {
|
||||
this.imageNotFound.queue(img)
|
||||
element.onerror = () => {
|
||||
this.elementNotFound.queue(element)
|
||||
}
|
||||
} else {
|
||||
this.imageFound.queue(img)
|
||||
this.elementFound.queue(element)
|
||||
}
|
||||
}
|
||||
|
||||
this.visibilityObserver.observe(img)
|
||||
this.visibilityObserver.observe(element)
|
||||
}
|
||||
|
||||
lazyLoadIFrame(element: HTMLIFrameElement) {
|
||||
// Once the iframe becomes visible, load it
|
||||
element["became visible"] = () => {
|
||||
// If the source is already set correctly, don't set it again to avoid iframe flickering.
|
||||
if(element.src !== element.dataset.src) {
|
||||
element.src = element.dataset.src
|
||||
}
|
||||
|
||||
this.elementFound.queue(element)
|
||||
}
|
||||
|
||||
this.visibilityObserver.observe(element)
|
||||
}
|
||||
|
||||
mountMountables() {
|
||||
@ -752,14 +774,18 @@ export class AnimeNotifier {
|
||||
return
|
||||
}
|
||||
|
||||
// Disallow Enter key in contenteditables
|
||||
if(activeElement.getAttribute("contenteditable") === "true" && e.keyCode == 13) {
|
||||
if("blur" in activeElement) {
|
||||
activeElement["blur"]()
|
||||
// Ignore hotkeys on contentEditable elements
|
||||
if(activeElement.getAttribute("contenteditable") === "true") {
|
||||
// Disallow Enter key in contenteditables and make it blur the element instead
|
||||
if(e.keyCode == 13) {
|
||||
if("blur" in activeElement) {
|
||||
activeElement["blur"]()
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,10 @@
|
||||
visibility hidden
|
||||
opacity 0
|
||||
|
||||
.image-found
|
||||
.element-found
|
||||
visibility visible
|
||||
opacity 1 !important
|
||||
|
||||
.image-not-found
|
||||
.element-not-found
|
||||
visibility hidden
|
||||
opacity 0 !important
|
@ -62,8 +62,8 @@ content-padding = 1.6rem
|
||||
content-padding-top = 1.6rem
|
||||
content-line-height = 1.7em
|
||||
hover-line-size = 3px
|
||||
nav-height = 3.11rem
|
||||
typography-margin = 0.4rem
|
||||
// nav-height = 3.11rem
|
||||
|
||||
// Timings
|
||||
fade-speed = 250ms
|
||||
|
@ -1,24 +1,24 @@
|
||||
mixin tag-dimensions
|
||||
padding 0.4rem 0.8rem
|
||||
margin 0.4rem
|
||||
height 40px
|
||||
|
||||
.tags
|
||||
horizontal-wrap
|
||||
|
||||
.tag
|
||||
ui-element
|
||||
padding 0.4rem 0.8rem
|
||||
margin 0.4rem
|
||||
|
||||
.tag-input
|
||||
horizontal
|
||||
|
||||
button
|
||||
margin-left 0.8rem
|
||||
tag-dimensions
|
||||
margin-right 0
|
||||
border-right none
|
||||
border-top-right-radius 0
|
||||
border-bottom-right-radius 0
|
||||
|
||||
.tag-remove
|
||||
display inline-block
|
||||
margin-left 0.4rem
|
||||
opacity 0.5
|
||||
|
||||
:hover
|
||||
cursor pointer
|
||||
tag-dimensions
|
||||
margin-left 0
|
||||
border-top-left-radius 0
|
||||
border-bottom-left-radius 0
|
||||
|
||||
.tag-add
|
||||
margin 0.4rem !important
|
||||
tag-dimensions
|
@ -1,6 +1,9 @@
|
||||
iframe
|
||||
min-height 200px
|
||||
|
||||
.video-container
|
||||
width 100%
|
||||
|
||||
.video
|
||||
width 100%
|
||||
height calc(100vh - nav-height)
|
||||
height 100vh
|
@ -1,6 +1,6 @@
|
||||
// pack:ignore
|
||||
|
||||
const CACHE = "v-2"
|
||||
const CACHE = "v-3"
|
||||
const RELOADS = new Map<string, Promise<Response>>()
|
||||
const ETAGS = new Map<string, string>()
|
||||
const CACHEREFRESH = new Map<string, Promise<void>>()
|
||||
|
Loading…
Reference in New Issue
Block a user