Add loading indicator

This commit is contained in:
simon 2019-06-22 21:36:01 -04:00
parent f2760d8f3e
commit 3ab1e90638
7 changed files with 87 additions and 61 deletions

View File

@ -1,6 +1,5 @@
import * as d3 from 'd3' import * as d3 from 'd3'
import icons from './icons' import icons from './icons'
import {MusicGraphApi} from './MusicGraphApi'
import {fitCaptionIntoCircle} from './graphGeometry' import {fitCaptionIntoCircle} from './graphGeometry'
// TODO: export somewhere else // TODO: export somewhere else
@ -22,13 +21,13 @@ export function MusicGraph(data) {
const width = window.innerWidth - 7 const width = window.innerWidth - 7
const height = window.innerHeight - 7 const height = window.innerHeight - 7
this._data = data this._data = data
this.api = this._data.api
this.nodeById = new Map() this.nodeById = new Map()
this.expandedNodes = new Set() this.expandedNodes = new Set()
this.nodes = [] this.nodes = []
this.links = [] this.links = []
this._originSet = false this._originSet = false
this.api = new MusicGraphApi()
this.simulation = d3.forceSimulation() this.simulation = d3.forceSimulation()
.force('charge', d3.forceManyBody()) .force('charge', d3.forceManyBody())
@ -43,7 +42,9 @@ export function MusicGraph(data) {
} }
this.dismiss = () => { this.dismiss = () => {
this.menu.remove() if (this.menu) {
this.menu.remove()
}
const menuNode = this.nodes.find(d => d.menu) const menuNode = this.nodes.find(d => d.menu)
if (menuNode !== undefined) { if (menuNode !== undefined) {
menuNode.menu = null menuNode.menu = null

View File

@ -52,22 +52,33 @@ const nodeUtils = {
} }
} }
export function MusicGraphApi() { export function MusicGraphApi(data) {
this.url = window.location.protocol + '//' + window.location.hostname + '/api' this.url = window.location.protocol + '//' + window.location.hostname + '/api'
this._data = data
let loadWrapper = (fn) => {
return (...args) => {
this._data.loading = true
return fn(...args).then(x => {
this._data.loading = false
return x
})
}
}
this.resolveCoverUrl = function (mbid) { this.resolveCoverUrl = function (mbid) {
return this.url + '/cover/' + mbid return this.url + '/cover/' + mbid
} }
this.getArtistDetails = function (mbid) { this.getArtistDetails = loadWrapper((mbid) => {
return d3.json(this.url + '/artist/details/' + mbid) return d3.json(this.url + '/artist/details/' + mbid)
} })
/** /**
* Works in both directions * Works in both directions
* @returns {Promise<{newNodes: *, relations: *} | never>} * @returns {Promise<{newNodes: *, relations: *} | never>}
*/ */
this.getGroupMembers = function (mbid, originId) { this.getGroupMembers = loadWrapper((mbid, originId) => {
return d3.json(this.url + '/artist/members/' + mbid) return d3.json(this.url + '/artist/members/' + mbid)
.then((r) => { .then((r) => {
return { return {
@ -81,29 +92,9 @@ export function MusicGraphApi() {
}) })
} }
}) })
} })
this.getArtistReleases = function (mbid, originId) { this.getArtistLabels = loadWrapper((mbid, originId) => {
return d3.json(this.url + '/artist/details/' + mbid)
.then((r) => {
const newNodes = r.releases
.map(nodeUtils.fromRawDict)
.filter(release => release.type === 'Album')
return {
newNodes: newNodes,
relations: newNodes.map(t => {
return {
source: originId,
target: t.id,
weight: 0.8
}
})
}
})
}
this.getArtistLabels = function (mbid, originId) {
return d3.json(this.url + '/artist/details/' + mbid) return d3.json(this.url + '/artist/details/' + mbid)
.then((r) => { .then((r) => {
const newNodes = r.labels const newNodes = r.labels
@ -124,25 +115,25 @@ export function MusicGraphApi() {
}) })
} }
}) })
} })
this._filterTags = function (tags) { this._filterTags = loadWrapper((tags) => {
if (ONLY_GENRE_TAGS) { if (ONLY_GENRE_TAGS) {
return tags.filter(tag => genres.has(tag.name)) return tags.filter(tag => genres.has(tag.name))
} else if (IGNORE_DATES_TAG) { } else if (IGNORE_DATES_TAG) {
return tags.filter(tag => isNaN(tag.name) && isNaN(tag.name.slice(0, -1))) return tags.filter(tag => isNaN(tag.name) && isNaN(tag.name.slice(0, -1)))
} }
return tags return tags
} })
this._addTagLabel = function (objects) { this._addTagLabel = loadWrapper((objects) => {
return objects.map(tag => { return objects.map(tag => {
tag.labels = ['Tag'] tag.labels = ['Tag']
return tag return tag
}) })
} })
this.getArtistTags = function (mbid, originId) { this.getArtistTags = loadWrapper((mbid, originId) => {
return d3.json(this.url + '/artist/details/' + mbid) return d3.json(this.url + '/artist/details/' + mbid)
.then((r) => { .then((r) => {
const tags = this._filterTags(r.tags) const tags = this._filterTags(r.tags)
@ -158,9 +149,9 @@ export function MusicGraphApi() {
}) })
} }
}) })
} })
this.getRelatedTags = function (tagId) { this.getRelatedTags = loadWrapper((tagId) => {
return d3.json(this.url + '/tag/tag/' + tagId) return d3.json(this.url + '/tag/tag/' + tagId)
.then((r) => { .then((r) => {
const tags = this._filterTags(r.tags) const tags = this._filterTags(r.tags)
@ -182,9 +173,9 @@ export function MusicGraphApi() {
relations: directedRelations relations: directedRelations
} }
}) })
} })
this.getRelatedByMbid = function (mbid) { this.getRelatedByMbid = loadWrapper((mbid) => {
return d3.json(this.url + '/artist/related/' + mbid) return d3.json(this.url + '/artist/related/' + mbid)
.then((r) => { .then((r) => {
let node = nodeUtils.fromRawDict(r.artists.find(a => a.mbid === mbid)) let node = nodeUtils.fromRawDict(r.artists.find(a => a.mbid === mbid))
@ -207,9 +198,9 @@ export function MusicGraphApi() {
relations: directedRelations relations: directedRelations
} }
}) })
} })
this.getRelatedByTag = function (tagid) { this.getRelatedByTag = loadWrapper((tagid) => {
return d3.json(this.url + '/tag/related/' + tagid) return d3.json(this.url + '/tag/related/' + tagid)
.then((r) => { .then((r) => {
return { return {
@ -229,9 +220,9 @@ export function MusicGraphApi() {
}) })
} }
}) })
} })
this.getReleaseDetails = function (mbid, originId) { this.getReleaseDetails = loadWrapper((mbid, originId) => {
return d3.json(this.url + '/release/details/' + mbid) return d3.json(this.url + '/release/details/' + mbid)
.then((r) => { .then((r) => {
const tags = this._filterTags(r.tags) const tags = this._filterTags(r.tags)
@ -247,12 +238,13 @@ export function MusicGraphApi() {
}) })
} }
}) })
} })
this.autoComplete = function (prefix) { this.autoComplete = loadWrapper((prefix) => {
prefix = prefix.replace(/[^\w.\-!?& ]/g, '_').toUpperCase() prefix = prefix
prefix = prefix.replace(/ /g, '+') .replace(/[^\w.\-!?& ]/g, '_').toUpperCase()
.replace(/ /g, '+')
return d3.json(this.url + '/autocomplete/' + prefix) return d3.json(this.url + '/autocomplete/' + prefix)
} })
} }

View File

@ -26,14 +26,12 @@
</template> </template>
<script> <script>
import {MusicGraphApi} from '../MusicGraphApi'
export default { export default {
name: 'AlbumCarousel', name: 'AlbumCarousel',
props: ['releases', 'interval', 'alone'], props: ['releases', 'interval', 'alone', 'api'],
data() { data() {
return { return {
api: new MusicGraphApi(),
current: '', current: '',
index: 0 index: 0
} }

View File

@ -7,6 +7,7 @@
<AlbumCarousel <AlbumCarousel
style="float: right" style="float: right"
:releases="artistInfo.releases" :releases="artistInfo.releases"
:api="api"
interval="1250"/> interval="1250"/>
<div> <div>
<p v-if="artistInfo.comment!==null" <p v-if="artistInfo.comment!==null"
@ -31,13 +32,12 @@
<script> <script>
import AlbumCarousel from './AlbumCarousel' import AlbumCarousel from './AlbumCarousel'
import {MusicGraphApi} from '../MusicGraphApi'
import {genres} from '../genres' import {genres} from '../genres'
export default { export default {
name: 'ArtistInfo', name: 'ArtistInfo',
components: {AlbumCarousel}, components: {AlbumCarousel},
props: ['artist'], props: ['artist', 'api'],
watch: { watch: {
artist: function (a) { artist: function (a) {
if (a !== undefined) { if (a !== undefined) {
@ -49,8 +49,7 @@ export default {
return { return {
artistInfo: { artistInfo: {
releases: [] releases: []
}, }
api: new MusicGraphApi()
} }
}, },
methods: { methods: {

View File

@ -1,13 +1,19 @@
<template> <template>
<div> <div>
<div id="mm"></div> <div id="mm"></div>
<InputBar v-on:addArtist="onAddArtist($event)" v-on:addTag="onAddTag($event)"></InputBar> <InputBar
v-on:addArtist="onAddArtist($event)"
v-on:addTag="onAddTag($event)"
:api="api"
></InputBar>
<ArtistInfo <ArtistInfo
v-bind:artist="hoverArtist" v-bind:artist="hoverArtist"
v-on:addTag="onAddTag($event)" v-on:addTag="onAddTag($event)"
:api="api"
/> />
<canvas id="textMeasurementCanvas"></canvas> <canvas id="textMeasurementCanvas"></canvas>
<Watermark text="music-graph v1.0"/> <Watermark text="music-graph v1.0"/>
<LoadingIndicator :loading="loading"/>
</div> </div>
</template> </template>
@ -16,14 +22,18 @@ import ArtistInfo from './ArtistInfo'
import Watermark from './Watermark' import Watermark from './Watermark'
import {MusicGraph} from '../MusicGraph' import {MusicGraph} from '../MusicGraph'
import InputBar from './InputBar' import InputBar from './InputBar'
import LoadingIndicator from './LoadingIndicator'
import {MusicGraphApi} from '../MusicGraphApi'
let data = { let data = {
hoverArtist: undefined, hoverArtist: undefined,
mm: undefined mm: undefined,
api: undefined,
loading: undefined
} }
export default { export default {
components: {InputBar, ArtistInfo, Watermark}, components: {LoadingIndicator, InputBar, ArtistInfo, Watermark},
data() { data() {
return data return data
}, },
@ -36,6 +46,7 @@ export default {
} }
}, },
mounted() { mounted() {
this.api = new MusicGraphApi(data)
this.mm = new MusicGraph(data) this.mm = new MusicGraph(data)
this.$notify({ this.$notify({

View File

@ -20,16 +20,15 @@
</template> </template>
<script> <script>
import {MusicGraphApi} from '../MusicGraphApi'
export default { export default {
name: 'InputBar', name: 'InputBar',
data: () => { data: () => {
return { return {
query: '', query: ''
api: new MusicGraphApi()
} }
}, },
props: ['api'],
methods: { methods: {
onSubmit: function (line) { onSubmit: function (line) {
if (line.type === 'artist') { if (line.type === 'artist') {

View File

@ -0,0 +1,26 @@
<template>
<el-icon class="el-icon-loading" :class="{hidden: !loading}" id="loading"></el-icon>
</template>
<script>
export default {
name: 'LoadingIndicator',
props: ['loading']
}
</script>
<style scoped>
.hidden {
display: none;
}
#loading {
position: fixed;
top: calc(100% - 90px);
left: 75px;
pointer-events: none;
color: rgba(0, 0, 0, 0.67);
font-size: 50px;
}
</style>