Add tag cloud
This commit is contained in:
		
							parent
							
								
									15ff7e5b47
								
							
						
					
					
						commit
						44e1b73b1f
					
				
					 7 changed files with 131 additions and 39 deletions
				
			
		
							
								
								
									
										7
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -19,6 +19,7 @@
 | 
				
			||||||
        "h3": "^1.15.1",
 | 
					        "h3": "^1.15.1",
 | 
				
			||||||
        "heatsync": "^2.8.1",
 | 
					        "heatsync": "^2.8.1",
 | 
				
			||||||
        "htmx.org": "^2.0.4",
 | 
					        "htmx.org": "^2.0.4",
 | 
				
			||||||
 | 
					        "wordcloud": "^1.2.3",
 | 
				
			||||||
        "zod": "^3.24.2"
 | 
					        "zod": "^3.24.2"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					@ -983,6 +984,12 @@
 | 
				
			||||||
        "node": ">= 10.0.0"
 | 
					        "node": ">= 10.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/wordcloud": {
 | 
				
			||||||
 | 
					      "version": "1.2.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/wordcloud/-/wordcloud-1.2.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-9by77b7Sd9e1K75kSmVeAD+JnGpiLR1Z4EX1mYQL91jKrU1/4bHw4h4DExQ+dzfT+PvihDcH7OS7V4Y5UkbF2w==",
 | 
				
			||||||
 | 
					      "license": "MIT"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/wrappy": {
 | 
					    "node_modules/wrappy": {
 | 
				
			||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,7 @@
 | 
				
			||||||
    "h3": "^1.15.1",
 | 
					    "h3": "^1.15.1",
 | 
				
			||||||
    "heatsync": "^2.8.1",
 | 
					    "heatsync": "^2.8.1",
 | 
				
			||||||
    "htmx.org": "^2.0.4",
 | 
					    "htmx.org": "^2.0.4",
 | 
				
			||||||
 | 
					    "wordcloud": "^1.2.3",
 | 
				
			||||||
    "zod": "^3.24.2"
 | 
					    "zod": "^3.24.2"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,12 +9,12 @@ block view
 | 
				
			||||||
            img(loading="lazy" src=item.item_art_url width=210 height=210)
 | 
					            img(loading="lazy" src=item.item_art_url width=210 height=210)
 | 
				
			||||||
            p.fs-body3.mb8= item.item_title
 | 
					            p.fs-body3.mb8= item.item_title
 | 
				
			||||||
          .d-flex.fw-wrap.g4
 | 
					          .d-flex.fw-wrap.g4
 | 
				
			||||||
            span.s-tag.s-tag__xs
 | 
					 | 
				
			||||||
              span.s-tag--sponsor!= icons.get("compact-disc", 16)
 | 
					 | 
				
			||||||
              = item.total_duration
 | 
					 | 
				
			||||||
            a.s-tag.s-tag__xs(href=and({arrange: "track", filter_field: "item_id", filter: item.item_id}))
 | 
					            a.s-tag.s-tag__xs(href=and({arrange: "track", filter_field: "item_id", filter: item.item_id}))
 | 
				
			||||||
              span.s-tag--sponsor!= icons.get("music-note", 16)
 | 
					              span.s-tag--sponsor!= icons.get("music-note", 16)
 | 
				
			||||||
              = item.track_count
 | 
					              = item.track_count
 | 
				
			||||||
 | 
					            span.s-tag.s-tag__xs
 | 
				
			||||||
 | 
					              span.s-tag--sponsor!= icons.get("compact-disc", 16)
 | 
				
			||||||
 | 
					              = item.total_duration
 | 
				
			||||||
            a.s-tag.s-tag__xs(href=and({filter_field: "band_name", filter: item.band_name}))
 | 
					            a.s-tag.s-tag__xs(href=and({filter_field: "band_name", filter: item.band_name}))
 | 
				
			||||||
              span.s-tag--sponsor!= icons.get("people-tag", 16)
 | 
					              span.s-tag--sponsor!= icons.get("people-tag", 16)
 | 
				
			||||||
              = item.band_name
 | 
					              = item.band_name
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@ html
 | 
				
			||||||
    title BC Explorer
 | 
					    title BC Explorer
 | 
				
			||||||
    link(rel="stylesheet" type="text/css" href="/static/stacks.min.css")
 | 
					    link(rel="stylesheet" type="text/css" href="/static/stacks.min.css")
 | 
				
			||||||
    script(src="/static/htmx.js")
 | 
					    script(src="/static/htmx.js")
 | 
				
			||||||
 | 
					    script(src="/static/wordcloud.js")
 | 
				
			||||||
    meta(name="htmx-config" content='{"requestClass":"is-loading"}')
 | 
					    meta(name="htmx-config" content='{"requestClass":"is-loading"}')
 | 
				
			||||||
    style.
 | 
					    style.
 | 
				
			||||||
      .themed {
 | 
					      .themed {
 | 
				
			||||||
| 
						 | 
					@ -40,11 +41,17 @@ html
 | 
				
			||||||
      .s-sidebarwidget th {
 | 
					      .s-sidebarwidget th {
 | 
				
			||||||
        font-weight: normal;
 | 
					        font-weight: normal;
 | 
				
			||||||
        color: var(--black-400);
 | 
					        color: var(--black-400);
 | 
				
			||||||
 | 
					        text-align: right;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      .album-grid-link {
 | 
					      .album-grid-link {
 | 
				
			||||||
        --_li-fc: var(--black);
 | 
					        --_li-fc: var(--black);
 | 
				
			||||||
        --_li-fc-visited: var(--black-400);
 | 
					        --_li-fc-visited: var(--black-400);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      .wc-hl {
 | 
				
			||||||
 | 
					        cursor: pointer;
 | 
				
			||||||
 | 
					        color: blue;
 | 
				
			||||||
 | 
					        outline: 1px solid black;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
  body.themed.theme-system.overflow-y-scroll(hx-boost="true")
 | 
					  body.themed.theme-system.overflow-y-scroll(hx-boost="true")
 | 
				
			||||||
    header.s-topbar
 | 
					    header.s-topbar
 | 
				
			||||||
      .s-topbar--container.wmx9
 | 
					      .s-topbar--container.wmx9
 | 
				
			||||||
| 
						 | 
					@ -81,15 +88,7 @@ html
 | 
				
			||||||
          .s-sidebarwidget.wmn3
 | 
					          .s-sidebarwidget.wmn3
 | 
				
			||||||
            .s-sidebarwidget--header Collection
 | 
					            .s-sidebarwidget--header Collection
 | 
				
			||||||
            table.s-sidebarwidget--content.s-sidebarwidget__items
 | 
					            table.s-sidebarwidget--content.s-sidebarwidget__items
 | 
				
			||||||
              tr.s-sidebarwidget--item
 | 
					              each stat in count
 | 
				
			||||||
                th albums
 | 
					                tr.s-sidebarwidget--item
 | 
				
			||||||
                td= albumCount
 | 
					                  th= stat[0]
 | 
				
			||||||
              tr.s-sidebarwidget--item
 | 
					                  td= stat[1]
 | 
				
			||||||
                th singles
 | 
					 | 
				
			||||||
                td= singleCount
 | 
					 | 
				
			||||||
              tr.s-sidebarwidget--item
 | 
					 | 
				
			||||||
                th tracks
 | 
					 | 
				
			||||||
                td= trackCount
 | 
					 | 
				
			||||||
              tr.s-sidebarwidget--item
 | 
					 | 
				
			||||||
                th value
 | 
					 | 
				
			||||||
                td #{displayCurrencySymbol}#{purchaseValue} #{displayCurrency}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										40
									
								
								pug/tag_grid.pug
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								pug/tag_grid.pug
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					extends includes/layout.pug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					block view
 | 
				
			||||||
 | 
					  .mx-auto.w100.wmx11.fs-body1#content(style="cursor: default")
 | 
				
			||||||
 | 
					  script
 | 
				
			||||||
 | 
					    | var items =
 | 
				
			||||||
 | 
					    != JSON.stringify(items)
 | 
				
			||||||
 | 
					  script.
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					      const content = document.getElementById("content")
 | 
				
			||||||
 | 
					      content.style.height = `${Math.round(Math.min(content.clientWidth, window.innerHeight)*0.8)}px`
 | 
				
			||||||
 | 
					      WordCloud(content, {
 | 
				
			||||||
 | 
					        list: items,
 | 
				
			||||||
 | 
					        fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI Adjusted", "Segoe UI", "Liberation Sans", sans-serif',
 | 
				
			||||||
 | 
					        fontWeight: "bold",
 | 
				
			||||||
 | 
					        color: "random-dark",
 | 
				
			||||||
 | 
					        minSize: 4,
 | 
				
			||||||
 | 
					        gridSize: Math.round(content.clientWidth / 200),
 | 
				
			||||||
 | 
					        weightFactor: size => size * content.clientWidth / 180,
 | 
				
			||||||
 | 
					        rotateRatio: 0,
 | 
				
			||||||
 | 
					        click: item => {
 | 
				
			||||||
 | 
					          const highlightedItem = document.querySelector(".wc-hl")?.textContent || item[0]
 | 
				
			||||||
 | 
					          const newURL = new URL(location)
 | 
				
			||||||
 | 
					          newURL.searchParams.set("filter", highlightedItem)
 | 
				
			||||||
 | 
					          newURL.searchParams.set("filter_field", "tag")
 | 
				
			||||||
 | 
					          newURL.searchParams.set("arrange", "label")
 | 
				
			||||||
 | 
					          location = newURL
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      content.addEventListener("wordcloudstop", () => {
 | 
				
			||||||
 | 
					        for (const child of content.children) {
 | 
				
			||||||
 | 
					          child.addEventListener("mouseenter", () => {
 | 
				
			||||||
 | 
					            child.classList.add("wc-hl")
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					          child.addEventListener("mouseleave", () => {
 | 
				
			||||||
 | 
					            child.classList.remove("wc-hl")
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
| 
						 | 
					@ -18,17 +18,29 @@ const currencyExchange = new Map([
 | 
				
			||||||
])
 | 
					])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sqls = {
 | 
					const sqls = {
 | 
				
			||||||
	album_grid:  "SELECT item.*, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {WHERE} GROUP BY item_id {ORDER}",
 | 
						album_grid:  "SELECT item.*, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {JOIN TAG} {WHERE} GROUP BY item_id {ORDER}",
 | 
				
			||||||
	album_list:  "SELECT item.*, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {WHERE} GROUP BY item_id ORDER BY band_url, band_name COLLATE NOCASE, item_title COLLATE NOCASE",
 | 
						album_list:  "SELECT item.*, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {JOIN TAG} {WHERE} GROUP BY item_id ORDER BY band_url, band_name COLLATE NOCASE, item_title COLLATE NOCASE",
 | 
				
			||||||
	artist_grid: "SELECT band_name, count(DISTINCT item_id) AS album_count, group_concat(DISTINCT band_url) AS labels, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {WHERE} GROUP BY band_name ORDER BY band_name COLLATE NOCASE",
 | 
						artist_grid: "SELECT band_name, count(DISTINCT item_id) AS album_count, group_concat(DISTINCT band_url) AS labels, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {JOIN TAG} {WHERE} GROUP BY band_name ORDER BY band_name COLLATE NOCASE",
 | 
				
			||||||
	artist_list: "SELECT band_name, count(DISTINCT item_id) AS album_count, band_url, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {WHERE} GROUP BY band_name ORDER BY band_name COLLATE NOCASE",
 | 
						artist_list: "SELECT band_name, count(DISTINCT item_id) AS album_count, band_url, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {JOIN TAG} {WHERE} GROUP BY band_name ORDER BY band_name COLLATE NOCASE",
 | 
				
			||||||
	label_grid:  "SELECT iif(count(DISTINCT band_name) = 1, band_name, band_url) AS display_name, band_url, count(DISTINCT item_id) AS album_count, count(DISTINCT band_name) AS artist_count, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {WHERE} GROUP BY band_url ORDER BY display_name COLLATE NOCASE",
 | 
						label_grid:  "SELECT iif(count(DISTINCT band_name) = 1, band_name, band_url) AS display_name, band_url, count(DISTINCT item_id) AS album_count, count(DISTINCT band_name) AS artist_count, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {JOIN TAG} {WHERE} GROUP BY band_url ORDER BY display_name COLLATE NOCASE",
 | 
				
			||||||
	label_list:  "SELECT iif(count(DISTINCT band_name) = 1, band_name, band_url) AS display_name, band_url, count(DISTINCT item_id) AS album_count, count(DISTINCT band_name) AS artist_count, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {WHERE} GROUP BY band_url ORDER BY display_name COLLATE NOCASE",
 | 
						label_list:  "SELECT iif(count(DISTINCT band_name) = 1, band_name, band_url) AS display_name, band_url, count(DISTINCT item_id) AS album_count, count(DISTINCT band_name) AS artist_count, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {JOIN TAG} {WHERE} GROUP BY band_url ORDER BY display_name COLLATE NOCASE",
 | 
				
			||||||
	track_list:  "SELECT * FROM track INNER JOIN item USING (item_id) {WHERE} ORDER BY band_url, item_title COLLATE NOCASE, track_number"
 | 
						tag_grid:    "SELECT tag, count(*) AS count FROM (SELECT tag, band_url, band_name, item_id, count(*) AS count FROM item_tag INNER JOIN item USING (item_id) GROUP BY tag, band_url) {WHERE} GROUP BY tag ORDER BY count DESC",
 | 
				
			||||||
 | 
						track_list:  "SELECT * FROM track INNER JOIN item USING (item_id) {JOIN TAG} {WHERE} ORDER BY band_url, item_title COLLATE NOCASE, track_number"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function loadPreviews(locals, field, number) {
 | 
					function loadPreviews(locals, field, number, whereClause, filter_field, filter) {
 | 
				
			||||||
	const previews = db.prepare(`SELECT ${field}, item_url, item_art_url FROM (SELECT ${field}, item_url, item_art_url, row_number() OVER (PARTITION BY ${field} ORDER BY purchased DESC) AS row_number FROM item) WHERE row_number <= ?`).all(number)
 | 
						const params = [number]
 | 
				
			||||||
 | 
						let sql = `SELECT ${field}, item_url, item_art_url FROM (SELECT ${field}, item_url, item_art_url, row_number() OVER (PARTITION BY ${field} ORDER BY purchased DESC) AS row_number FROM item {JOIN TAG} {WHERE}) WHERE row_number <= ?`
 | 
				
			||||||
 | 
						sql = sql.replace("{WHERE}", whereClause)
 | 
				
			||||||
 | 
						if (whereClause) {
 | 
				
			||||||
 | 
							params.unshift(filter)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (filter_field === "tag" && filter) {
 | 
				
			||||||
 | 
							sql = sql.replace("{JOIN TAG}", "INNER JOIN item_tag USING (item_id)")
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							sql = sql.replace("{JOIN TAG}", "")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						const previews = db.prepare(sql).all(params)
 | 
				
			||||||
	// TODO: performance?
 | 
						// TODO: performance?
 | 
				
			||||||
	for (const item of locals.items) {
 | 
						for (const item of locals.items) {
 | 
				
			||||||
		item.previews = []
 | 
							item.previews = []
 | 
				
			||||||
| 
						 | 
					@ -43,7 +55,7 @@ function loadPreviews(locals, field, number) {
 | 
				
			||||||
const schema = z.object({
 | 
					const schema = z.object({
 | 
				
			||||||
	arrange: z.enum(["album", "artist", "label", "tag", "track"]),
 | 
						arrange: z.enum(["album", "artist", "label", "tag", "track"]),
 | 
				
			||||||
	shape: z.enum(["grid", "list"]),
 | 
						shape: z.enum(["grid", "list"]),
 | 
				
			||||||
	filter_field: z.enum(["band_name", "band_url", "item_id"]).optional(),
 | 
						filter_field: z.enum(["band_name", "band_url", "item_id", "tag"]).optional(),
 | 
				
			||||||
	filter: z.string().optional()
 | 
						filter: z.string().optional()
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,31 +75,54 @@ router.get("/", defineEventHandler({
 | 
				
			||||||
		const mode = `${arrange}_${shape}`
 | 
							const mode = `${arrange}_${shape}`
 | 
				
			||||||
		const params = []
 | 
							const params = []
 | 
				
			||||||
		let sql = sqls[mode]
 | 
							let sql = sqls[mode]
 | 
				
			||||||
 | 
							let whereClause = ""
 | 
				
			||||||
		if (filter_field && filter) {
 | 
							if (filter_field && filter) {
 | 
				
			||||||
			sql = sql.replace("{WHERE}", `WHERE ${filter_field} LIKE ?`)
 | 
								let operator = "="
 | 
				
			||||||
 | 
								if (filter_field === "band_url") {
 | 
				
			||||||
 | 
									operator = "LIKE"
 | 
				
			||||||
 | 
									params.push(`%${filter}%`)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									params.push(filter)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								whereClause = `WHERE ${filter_field} ${operator} ?`
 | 
				
			||||||
			sql = sql.replace("{ORDER}", "ORDER BY item_title COLLATE NOCASE")
 | 
								sql = sql.replace("{ORDER}", "ORDER BY item_title COLLATE NOCASE")
 | 
				
			||||||
			params.push(`%${filter}%`)
 | 
					 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			sql = sql.replace("{WHERE}", "")
 | 
					 | 
				
			||||||
			sql = sql.replace("{ORDER}", "ORDER BY purchased DESC")
 | 
								sql = sql.replace("{ORDER}", "ORDER BY purchased DESC")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							sql = sql.replace("{WHERE}", whereClause)
 | 
				
			||||||
 | 
							if (filter_field === "tag" && filter) {
 | 
				
			||||||
 | 
								sql = sql.replace("{JOIN TAG}", "INNER JOIN item_tag USING (item_id)")
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								sql = sql.replace("{JOIN TAG}", "")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		const prepared = db.prepare(sql)
 | 
							const prepared = db.prepare(sql)
 | 
				
			||||||
 | 
							if (arrange === "tag") {
 | 
				
			||||||
 | 
								prepared.raw()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		const locals = {
 | 
							const locals = {
 | 
				
			||||||
			items: prepared.all(params),
 | 
								items: prepared.all(params),
 | 
				
			||||||
			albumCount: db.prepare("SELECT count(*) FROM item WHERE item_type = 'album'").pluck().get(),
 | 
								query,
 | 
				
			||||||
			singleCount: db.prepare("SELECT count(*) FROM item WHERE item_type = 'track'").pluck().get(),
 | 
								count: [
 | 
				
			||||||
			trackCount: db.prepare("SELECT count(*) FROM track").pluck().get(),
 | 
									["total", db.prepare("SELECT count(*) FROM item").pluck().get()],
 | 
				
			||||||
			purchaseValue: Math.round(select("item", ["currency", "price"]).all().map(c => {
 | 
									["runtime", db.prepare("SELECT iif(sum(duration) > 86400, cast(total(duration)/86400 AS INTEGER) || 'd ' || cast(total(duration)/3600%24 AS INTEGER) || 'h', cast(total(duration)/3600 AS INTEGER) || 'h') FROM track").pluck().get()],
 | 
				
			||||||
				return (currencyExchange.get(c.currency) || 0.5) * c.price / (currencyExchange.get(displayCurrency) || 1) / 10
 | 
									["albums", db.prepare("SELECT count(*) FROM item WHERE item_type = 'album'").pluck().get()],
 | 
				
			||||||
			}).reduce((a, c) => a + c, 0)) * 10,
 | 
									["singles", db.prepare("SELECT count(*) FROM item WHERE item_type = 'track'").pluck().get()],
 | 
				
			||||||
			displayCurrencySymbol,
 | 
									["free", db.prepare("SELECT count(*) FROM item WHERE price = 0").pluck().get()],
 | 
				
			||||||
			displayCurrency,
 | 
									["paid", db.prepare("SELECT count(*) FROM item WHERE price > 0").pluck().get()],
 | 
				
			||||||
			query
 | 
									["tracks", db.prepare("SELECT count(*) FROM track").pluck().get()],
 | 
				
			||||||
 | 
									["avg tracks", Math.round(db.prepare("SELECT avg(count) FROM (SELECT count(*) AS count FROM track INNER JOIN item USING (item_id) WHERE item_type = 'album' GROUP BY item_id)").pluck().get()*10)/10],
 | 
				
			||||||
 | 
									["tags", db.prepare("SELECT count(*) FROM item_tag").pluck().get()],
 | 
				
			||||||
 | 
									["avg tags", Math.round(db.prepare("SELECT avg(count) FROM (SELECT count(*) AS count FROM item_tag GROUP BY item_id)").pluck().get()*10)/10],
 | 
				
			||||||
 | 
									["lonely tags", db.prepare("SELECT count(*) FROM (SELECT tag FROM item_tag GROUP BY tag HAVING count(*) = 1)").pluck().get()],
 | 
				
			||||||
 | 
									["value", displayCurrencySymbol + Math.round(select("item", ["currency", "price"]).all().map(c => {
 | 
				
			||||||
 | 
										return (currencyExchange.get(c.currency) || 0.6) * c.price / (currencyExchange.get(displayCurrency) || 1) / 10
 | 
				
			||||||
 | 
									}).reduce((a, c) => a + c, 0)) * 10 + " " + displayCurrency]
 | 
				
			||||||
 | 
								]
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (mode === "artist_grid") {
 | 
							if (mode === "artist_grid") {
 | 
				
			||||||
			loadPreviews(locals, "band_name", 4)
 | 
								loadPreviews(locals, "band_name", 4, whereClause, filter_field, filter)
 | 
				
			||||||
		} else if (mode === "label_grid") {
 | 
							} else if (mode === "label_grid") {
 | 
				
			||||||
			loadPreviews(locals, "band_url", 6)
 | 
								loadPreviews(locals, "band_url", 6, whereClause, filter_field, filter)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return pugSync.render(event, `${arrange}_${shape}.pug`, locals)
 | 
							return pugSync.render(event, `${arrange}_${shape}.pug`, locals)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										10
									
								
								server.js
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								server.js
									
										
									
									
									
								
							| 
						 | 
					@ -11,6 +11,7 @@ const {defineEventHandler, defaultContentType, getRequestHeader, setResponseHead
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const passthrough = require("./passthrough")
 | 
					const passthrough = require("./passthrough")
 | 
				
			||||||
const sync = new HeatSync()
 | 
					const sync = new HeatSync()
 | 
				
			||||||
 | 
					sync.events.on("error", console.error)
 | 
				
			||||||
passthrough.db = new sqlite("bc-explorer.db")
 | 
					passthrough.db = new sqlite("bc-explorer.db")
 | 
				
			||||||
const app = createApp()
 | 
					const app = createApp()
 | 
				
			||||||
const router = createRouter()
 | 
					const router = createRouter()
 | 
				
			||||||
| 
						 | 
					@ -56,4 +57,13 @@ router.get("/static/htmx.js", defineEventHandler({
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}))
 | 
					}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router.get("/static/wordcloud.js", defineEventHandler({
 | 
				
			||||||
 | 
						onBeforeResponse: pugSync.compressResponse,
 | 
				
			||||||
 | 
						handler: async event => {
 | 
				
			||||||
 | 
							handleCacheHeaders(event, {maxAge: 86400})
 | 
				
			||||||
 | 
							defaultContentType(event, "text/javascript")
 | 
				
			||||||
 | 
							return fs.promises.readFile(require.resolve("wordcloud/src/wordcloud2.js"), "utf-8")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
createServer(toNodeListener(app)).listen(2239)
 | 
					createServer(toNodeListener(app)).listen(2239)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue