Set up some basic web components and basically format a track card #25

Merged
savanni merged 4 commits from track-formatting into main 2023-03-01 14:20:59 +00:00
13 changed files with 2793 additions and 4124 deletions

View File

@ -11,6 +11,7 @@
<div class="controls"><button class="play-pause">Pause</button></div> <div class="controls"><button class="play-pause">Pause</button></div>
<div class="track-list"> <div class="track-list">
<!--
<div class="track-list__grouping"> <div class="track-list__grouping">
<ul class="bulletless-list"> <ul class="bulletless-list">
<li> By Artist </li> <li> By Artist </li>
@ -19,60 +20,11 @@
<li> Dance Music </li> <li> Dance Music </li>
</ul> </ul>
</div> </div>
<div class="track-list__tracks">
<table>
<thead>
<tr>
<th> id </th>
<th> Track # </th>
<th> Title </th>
<th> Artist </th>
<th> Album </th>
<th> Length </th>
</tr>
</thead>
<tbody>
<!--
<tr class="track-list__track-row">
<td> 1 </td>
<td> Underground </td>
<td> Lindsey Stirling </td>
<td> Artemis </td>
<td> 4:24 </td>
</tr>
<tr class="track-list__track-row">
<td> 2 </td>
<td> Artemis </td>
<td> Lindsey Stirling </td>
<td> Artemis </td>
<td> 3:54 </td>
</tr>
<tr class="track-list__track-row">
<td> 3 </td>
<td> Til the Light Goes Out </td>
<td> Lindsey Stirling </td>
<td> Artemis </td>
<td> 4:46 </td>
</tr>
<tr class="track-list__track-row">
<td> 4 </td>
<td> Between Twilight </td>
<td> Lindsey Stirling </td>
<td> Artemis </td>
<td> 4:20 </td>
</tr>
<tr class="track-list__track-row">
<td> 5 </td>
<td> Foreverglow </td>
<td> Lindsey Stirling </td>
<td> Artemis </td>
<td> 3:58 </td>
</tr>
--> -->
</tbody>
</table> <div>
<ul class="track-list__tracks bulletless-list">
</ul>
</div> </div>
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@ -4,9 +4,9 @@
"description": "", "description": "",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"build": "browserify src/main.ts -p [ tsify ] > dist/bundle.js && cp index.html styles.css dist", "build": "webpack",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"watch": "exa index.html styles.css src/* | entr -s 'npm run build'" "watch": "webpack --watch"
}, },
"author": "Savanni D'Gerinel <savanni@luminescent-dreams.com>", "author": "Savanni D'Gerinel <savanni@luminescent-dreams.com>",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
@ -16,10 +16,12 @@
}, },
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.14.191", "@types/lodash": "^4.14.191",
"babelify": "^10.0.0", "copy-webpack-plugin": "^11.0.0",
"browserify": "^17.0.0", "css-loader": "^6.7.3",
"tsify": "^5.0.4", "style-loader": "^3.3.1",
"typescript": "^4.9.4", "ts-loader": "^9.4.2",
"watchify": "^4.0.0" "typescript": "^4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
} }
} }

View File

@ -0,0 +1,133 @@
import { TrackInfo } from "../client";
export class TrackName extends HTMLElement {
container: HTMLElement;
static get observedAttributes() {
return ["name"];
}
constructor() {
super();
this.container = document.createElement("div");
}
get name(): string | null {
return this.getAttribute("name");
}
set name(name: string | null) {
while (this.container.lastChild) {
this.container.removeChild(this.container.lastChild);
}
if (name) {
this.setAttribute("name", name);
this.container.appendChild(document.createTextNode(name));
} else {
this.removeAttribute("name");
}
}
connectedCallback() {
this.appendChild(this.container);
}
}
export class TrackCard extends HTMLElement {
static get observedAttributes() {
return ["id", "trackNumber", "name", "album", "artist"];
}
constructor() {
super();
}
attributeChangeCallback(
attrName: string,
oldValue: string,
newValue: string
): void {
if (newValue !== oldValue) {
this.updateContent();
}
}
get name(): string | null {
return this.getAttribute("name");
}
set name(name: string | null) {
if (name) {
this.setAttribute("name", name);
} else {
this.removeAttribute("open");
}
this.updateContent();
}
get artist(): string | null {
return this.getAttribute("artist");
}
set artist(artist: string | null) {
if (artist) {
this.setAttribute("artist", artist);
} else {
this.removeAttribute("open");
}
this.updateContent();
}
get album(): string | null {
return this.getAttribute("album");
}
set album(album: string | null) {
if (album) {
this.setAttribute("album", album);
} else {
this.removeAttribute("open");
}
this.updateContent();
}
get length(): string | null {
return this.getAttribute("length");
}
set length(length: string | null) {
if (length) {
this.setAttribute("length", length);
} else {
this.removeAttribute("open");
}
this.updateContent();
}
connectedCallback() {
this.updateContent();
}
updateContent() {
const container = document.createElement("div");
container.classList.add("track-card");
this.innerHTML = "";
this.appendChild(container);
while (container.lastChild) {
container.removeChild(container.lastChild);
}
if (this["name"]) {
const trackName = document.createElement("track-name");
trackName.name = this["name"];
container.appendChild(trackName);
}
this["length"] && container.appendChild(document.createTextNode("1:23"));
this["album"] &&
container.appendChild(document.createTextNode("Shatter Me"));
this["artist"] &&
container.appendChild(document.createTextNode("Lindsey Stirling"));
}
}

View File

@ -0,0 +1,10 @@
export interface TrackInfo {
id: string;
track_number?: number;
name?: string;
album?: string;
artist?: string;
}
export const getTracks = (): Promise<TrackInfo[]> =>
fetch("/api/v1/tracks").then((r) => r.json());

View File

@ -1,11 +1,15 @@
import * as _ from "lodash"; import * as _ from "lodash";
import { TrackInfo, getTracks } from "./client";
import { TrackName, TrackCard } from "./blocks/track";
interface TrackInfo { window.customElements.define("track-name", TrackName);
id: string; window.customElements.define("track-card", TrackCard);
track_number?: number;
name?: string; declare global {
album?: string; interface HTMLElementTagNameMap {
artist?: string; "track-name": TrackName;
"track-card": TrackCard;
}
} }
const replaceTitle = () => { const replaceTitle = () => {
@ -15,52 +19,22 @@ const replaceTitle = () => {
} }
}; };
const getTracks = () => fetch("/api/v1/tracks").then((r) => r.json());
const formatTrack = (track: TrackInfo) => {
let row = document.createElement("tr");
row.classList.add("track-list__row");
let track_id = document.createElement("td");
track_id.appendChild(document.createTextNode(track.id));
track_id.classList.add("track-list__cell");
let track_number = document.createElement("td");
track_number.appendChild(
document.createTextNode(track.track_number?.toString() || "")
);
track_number.classList.add("track-list__cell");
let name = document.createElement("td");
name.appendChild(document.createTextNode(track.name || ""));
name.classList.add("track-list__cell");
let album = document.createElement("td");
album.appendChild(document.createTextNode(track.album || ""));
album.classList.add("track-list__cell");
let artist = document.createElement("td");
artist.appendChild(document.createTextNode(track.artist || ""));
artist.classList.add("track-list__cell");
let length = document.createElement("td");
artist.appendChild(document.createTextNode(""));
length.classList.add("track-list__cell");
row.appendChild(track_id);
row.appendChild(track_number);
row.appendChild(name);
row.appendChild(artist);
row.appendChild(album);
row.appendChild(length);
return row;
};
const updateTrackList = (tracks: TrackInfo[]) => { const updateTrackList = (tracks: TrackInfo[]) => {
const track_list = document.querySelector(".track-list__tracks tbody"); const track_list = document.querySelector(".track-list__tracks");
if (track_list) { if (track_list) {
let track_formats = _.map(tracks, formatTrack); let track_formats = _.map(tracks, (info) => {
_.map(track_formats, (trackinfo) => track_list.appendChild(trackinfo)); let card: TrackCard = document.createElement("track-card");
card.name = info.name || null;
return card;
});
_.map(track_formats, (trackCard) => {
let listItem = document.createElement("li");
listItem.classList.add("track-list__row");
listItem.appendChild(trackCard);
track_list.appendChild(listItem);
});
} else {
console.log("track_list does not exist");
} }
}; };

View File

@ -26,9 +26,10 @@ body {
} }
.track-list__row { .track-list__row {
background-color: rgb(10, 10, 10); margin-top: 32px;
} }
/*
.track-list__row:nth-child(even) { .track-list__row:nth-child(even) {
background-color: rgb(255, 255, 255); background-color: rgb(255, 255, 255);
} }
@ -36,7 +37,25 @@ body {
.track-list__row:nth-child(odd) { .track-list__row:nth-child(odd) {
background-color: rgb(200, 200, 200); background-color: rgb(200, 200, 200);
} }
*/
.track-list__cell { .track-card {
border: 1px solid black;
border-radius: 5px;
padding: 8px; padding: 8px;
width: 300px;
height: 100px;
} }
.track-card__name {
}
.track-card__length {
}
.track-card__album {
}
.track-card__artist {
}

View File

@ -7,6 +7,8 @@
"outDir": "./dist", "outDir": "./dist",
"lib": ["es2016", "DOM"], "lib": ["es2016", "DOM"],
"sourceMap": true, "sourceMap": true,
"strict": true "strict": true,
} "noImplicitAny": true
},
"include": ["src"]
} }

View File

@ -0,0 +1,39 @@
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/main.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.html$/i,
type: 'asset/resource',
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
plugins: [
new CopyPlugin({
patterns: [
{ from: "index.html", to: "index.html" },
{ from: "styles.css", to: "styles.css" },
],
}),
],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};

View File

@ -583,6 +583,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"dbus", "dbus",
"flow", "flow",
"mime_guess",
"mpris", "mpris",
"rusqlite", "rusqlite",
"serde", "serde",

View File

@ -8,6 +8,7 @@ edition = "2021"
[dependencies] [dependencies]
dbus = { version = "0.9.7" } dbus = { version = "0.9.7" }
flow = { path = "../../flow" } flow = { path = "../../flow" }
mime_guess = "2.0.4"
mpris = { version = "2.0" } mpris = { version = "2.0" }
rusqlite = { version = "0.28" } rusqlite = { version = "0.28" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View File

@ -21,20 +21,21 @@ fn tracks(index: &Arc<impl MusicIndex>) -> Vec<TrackInfo> {
} }
} }
enum Bundle { struct Static(PathBuf);
Index,
App,
Styles,
}
impl Bundle { impl Static {
fn read(self, root: PathBuf) -> String { fn read(self, root: PathBuf) -> String {
/*
let mut path = root; let mut path = root;
match self { match self {
Bundle::Index => path.push(PathBuf::from("index.html")), Bundle::Index => path.push(PathBuf::from("index.html")),
Bundle::App => path.push(PathBuf::from("bundle.js")), Bundle::App => path.push(PathBuf::from("bundle.js")),
Bundle::Styles => path.push(PathBuf::from("styles.css")), Bundle::Styles => path.push(PathBuf::from("styles.css")),
}; };
std::fs::read_to_string(path).expect("to find the file")
*/
let mut path = root;
path.push(self.0);
println!("path: {:?}", path); println!("path: {:?}", path);
std::fs::read_to_string(path).expect("to find the file") std::fs::read_to_string(path).expect("to find the file")
} }
@ -68,23 +69,22 @@ pub async fn main() {
move || { move || {
warp::http::Response::builder() warp::http::Response::builder()
.header("content-type", "text/html") .header("content-type", "text/html")
.body(Bundle::Index.read(bundle_root.clone())) .body(Static(PathBuf::from("index.html")).read(bundle_root.clone()))
} }
}); });
let app = warp::path!("bundle.js").and(warp::get()).map({ let assets = warp::path!(String).and(warp::get()).map({
let bundle_root = bundle_root.clone(); let bundle_root = bundle_root.clone();
move || { move |filename: String| {
let mime_type = mime_guess::from_path(filename.clone())
.first()
.map(|m| m.essence_str().to_owned())
.unwrap_or("text/plain".to_owned());
println!("mime_type: {:?}", mime_type);
// let mut path = PathBuf::from("assets");
// path.push(filename);
warp::http::Response::builder() warp::http::Response::builder()
.header("content-type", "text/javascript") .header("content-type", mime_type)
.body(Bundle::App.read(bundle_root.clone())) .body(Static(PathBuf::from(filename)).read(bundle_root.clone()))
}
});
let styles = warp::path!("styles.css").and(warp::get()).map({
let bundle_root = bundle_root.clone();
move || {
warp::http::Response::builder()
.header("content-type", "text/css")
.body(Bundle::Styles.read(bundle_root.clone()))
} }
}); });
@ -128,12 +128,9 @@ pub async fn main() {
.or(queue) .or(queue)
.or(playing_status); .or(playing_status);
*/ */
let routes = root.or(app).or(styles).or(track_list); let routes = root.or(assets).or(track_list);
let server = warp::serve(routes); let server = warp::serve(routes);
server server
.run(SocketAddr::new( .run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8002))
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
8002,
))
.await; .await;
} }

View File

@ -46,7 +46,10 @@ impl FileIterator {
id: TrackId::from(path.to_str().unwrap().to_owned()), id: TrackId::from(path.to_str().unwrap().to_owned()),
album: None, album: None,
artist: None, artist: None,
name: None, name: path
.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_owned()),
track_number: None, track_number: None,
}) })
} }