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