Compare commits
	
		
			9 Commits
		
	
	
		
			372d151264
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| aee3052ad7 | |||
| a93598d8f6 | |||
| e165367133 | |||
| 96bbef8bbd | |||
| 95b0479600 | |||
| 6d1d53e43c | |||
| b7c2adae27 | |||
| 931e9b43e6 | |||
| dc6c8b642c | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -5,3 +5,4 @@ | ||||
| # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml | ||||
| hs_err_pid* | ||||
|  | ||||
| /res/songs/ | ||||
|   | ||||
							
								
								
									
										8
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| # Default ignored files | ||||
| /shelf/ | ||||
| /workspace.xml | ||||
| # Editor-based HTTP Client requests | ||||
| /httpRequests/ | ||||
| # Datasource local storage ignored files | ||||
| /dataSources/ | ||||
| /dataSources.local.xml | ||||
							
								
								
									
										7
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <component name="ProjectCodeStyleConfiguration"> | ||||
|   <code_scheme name="Project" version="173"> | ||||
|     <ScalaCodeStyleSettings> | ||||
|       <option name="MULTILINE_STRING_CLOSING_QUOTES_ON_NEW_LINE" value="true" /> | ||||
|     </ScalaCodeStyleSettings> | ||||
|   </code_scheme> | ||||
| </component> | ||||
							
								
								
									
										5
									
								
								.idea/codeStyles/codeStyleConfig.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.idea/codeStyles/codeStyleConfig.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| <component name="ProjectCodeStyleConfiguration"> | ||||
|   <state> | ||||
|     <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" /> | ||||
|   </state> | ||||
| </component> | ||||
							
								
								
									
										12
									
								
								.idea/libraries/io_methvin_directory_watcher.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.idea/libraries/io_methvin_directory_watcher.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <component name="libraryTable"> | ||||
|   <library name="io.methvin.directory.watcher" type="repository"> | ||||
|     <properties maven-id="io.methvin:directory-watcher:0.18.0" /> | ||||
|     <CLASSES> | ||||
|       <root url="jar://$MAVEN_REPOSITORY$/io/methvin/directory-watcher/0.18.0/directory-watcher-0.18.0.jar!/" /> | ||||
|       <root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.12.1/jna-5.12.1.jar!/" /> | ||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar!/" /> | ||||
|     </CLASSES> | ||||
|     <JAVADOC /> | ||||
|     <SOURCES /> | ||||
|   </library> | ||||
| </component> | ||||
							
								
								
									
										16
									
								
								.idea/libraries/lihaoyi_upickle_2_13.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								.idea/libraries/lihaoyi_upickle_2_13.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| <component name="libraryTable"> | ||||
|   <library name="lihaoyi.upickle_2.13" type="repository"> | ||||
|     <properties maven-id="com.lihaoyi:upickle_2.13:3.1.0" /> | ||||
|     <CLASSES> | ||||
|       <root url="jar://$MAVEN_REPOSITORY$/com/lihaoyi/upickle_2.13/3.1.0/upickle_2.13-3.1.0.jar!/" /> | ||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/scala-lang/scala-library/2.13.10/scala-library-2.13.10.jar!/" /> | ||||
|       <root url="jar://$MAVEN_REPOSITORY$/com/lihaoyi/ujson_2.13/3.1.0/ujson_2.13-3.1.0.jar!/" /> | ||||
|       <root url="jar://$MAVEN_REPOSITORY$/com/lihaoyi/upickle-core_2.13/3.1.0/upickle-core_2.13-3.1.0.jar!/" /> | ||||
|       <root url="jar://$MAVEN_REPOSITORY$/com/lihaoyi/geny_2.13/1.0.0/geny_2.13-1.0.0.jar!/" /> | ||||
|       <root url="jar://$MAVEN_REPOSITORY$/com/lihaoyi/upack_2.13/3.1.0/upack_2.13-3.1.0.jar!/" /> | ||||
|       <root url="jar://$MAVEN_REPOSITORY$/com/lihaoyi/upickle-implicits_2.13/3.1.0/upickle-implicits_2.13-3.1.0.jar!/" /> | ||||
|     </CLASSES> | ||||
|     <JAVADOC /> | ||||
|     <SOURCES /> | ||||
|   </library> | ||||
| </component> | ||||
							
								
								
									
										6
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK"> | ||||
|     <output url="file://$PROJECT_DIR$/out" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										8
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="ProjectModuleManager"> | ||||
|     <modules> | ||||
|       <module fileurl="file://$PROJECT_DIR$/Lab18.iml" filepath="$PROJECT_DIR$/Lab18.iml" /> | ||||
|     </modules> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										7
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="VcsDirectoryMappings"> | ||||
|     <mapping directory="" vcs="Git" /> | ||||
|     <mapping directory="$PROJECT_DIR$" vcs="Git" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										16
									
								
								Lab18.iml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Lab18.iml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <module type="JAVA_MODULE" version="4"> | ||||
|   <component name="NewModuleRootManager" inherit-compiler-output="true"> | ||||
|     <exclude-output /> | ||||
|     <content url="file://$MODULE_DIR$"> | ||||
|       <sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" /> | ||||
|       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> | ||||
|       <excludeFolder url="file://$MODULE_DIR$/res/songs" /> | ||||
|     </content> | ||||
|     <orderEntry type="inheritedJdk" /> | ||||
|     <orderEntry type="sourceFolder" forTests="false" /> | ||||
|     <orderEntry type="library" name="scala-sdk-2.13.12" level="application" /> | ||||
|     <orderEntry type="library" name="io.methvin.directory.watcher" level="project" /> | ||||
|     <orderEntry type="library" name="lihaoyi.upickle_2.13" level="project" /> | ||||
|   </component> | ||||
| </module> | ||||
							
								
								
									
										3
									
								
								res/template/album.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								res/template/album.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| td:first-child { | ||||
| 	text-align: right; | ||||
| } | ||||
							
								
								
									
										39
									
								
								res/template/album.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								res/template/album.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head> | ||||
| 	<meta charset="utf-8"> | ||||
| 	<meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| 	<title>Slopify V2 - Listen to the music you hate</title> | ||||
| 	<link rel="stylesheet" type="text/css" href="base.css"> | ||||
| 	<link rel="stylesheet" type="text/css" href="album.css"> | ||||
| </head> | ||||
| <body> | ||||
| 	<header id="navbar"> | ||||
| 		<a class="back" id="back-btn" href="{{artist.page}}"></a> | ||||
| 		<h1 class="title">Slopify</h1> | ||||
| 	</header> | ||||
| 	<div id="body"> | ||||
| 		<h2>Album</h2> | ||||
| 		<div id="crumbs"> | ||||
| 			<a href="{{artist.page}}">{{artist.name}}</a> | ||||
| 			<span class="sep"></span> | ||||
| 			<a href="./">{{album.name}}</a> | ||||
| 		</div> | ||||
| 		<table id="songs"> | ||||
| 			<thead> | ||||
| 				<tr> | ||||
| 					<th>No</th> | ||||
| 					<th>Name</th> | ||||
| 				</tr> | ||||
| 			</thead> | ||||
| 			<tbody> | ||||
| 				{{songs}} | ||||
| 			</tbody> | ||||
| 		</table> | ||||
| 	</div> | ||||
| 	<script type="text/javascript" src="base.js"></script> | ||||
| 	<script> | ||||
| 		initTable(document.getElementById("songs")) | ||||
| 	</script> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										0
									
								
								res/template/artist.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								res/template/artist.css
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										36
									
								
								res/template/artist.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								res/template/artist.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head> | ||||
| 	<meta charset="utf-8"> | ||||
| 	<meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| 	<title>Slopify V2 - Listen to the music you hate</title> | ||||
| 	<link rel="stylesheet" type="text/css" href="base.css"> | ||||
| 	<link rel="stylesheet" type="text/css" href="artist.css"> | ||||
| </head> | ||||
| <body> | ||||
| 	<header id="navbar"> | ||||
| 		<a class="back" id="back-btn" href="index.html"></a> | ||||
| 		<h1 class="title">Slopify</h1> | ||||
| 	</header> | ||||
| 	<div id="body"> | ||||
| 		<h2>Artist</h2> | ||||
| 		<div id="crumbs"> | ||||
| 			<a href="./">{{artist.name}}</a> | ||||
| 		</div> | ||||
| 		<table id="albums"> | ||||
| 			<thead> | ||||
| 				<tr> | ||||
| 					<th>Name</th> | ||||
| 				</tr> | ||||
| 			</thead> | ||||
| 			<tbody> | ||||
| 				{{albums}} | ||||
| 			</tbody> | ||||
| 		</table> | ||||
| 	</div> | ||||
| 	<script type="text/javascript" src="base.js"></script> | ||||
| 	<script> | ||||
| 		initTable(document.getElementById("albums")) | ||||
| 	</script> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										126
									
								
								res/template/base.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								res/template/base.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| * { | ||||
| 	margin: 0; | ||||
| 	box-sizing: border-box; | ||||
| } | ||||
|  | ||||
| html, body { | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
| } | ||||
|  | ||||
| body { | ||||
| 	background-color: #0d130d; | ||||
| 	font-family: Ubuntu; | ||||
| 	font-size: 16pt; | ||||
| 	color: white; | ||||
| } | ||||
|  | ||||
| #navbar { | ||||
| 	display: grid; | ||||
| 	grid-template-columns: auto 1fr auto; | ||||
| 	grid-template-areas: "back title space"; | ||||
| 	place-items: center; | ||||
| 	text-align: center; | ||||
| 	border-bottom: solid #3e5b3e 1px; | ||||
| 	padding: 0.4em 0.8em; | ||||
| 	gap: 0.8em; | ||||
| } | ||||
|  | ||||
| #navbar .back { | ||||
| 	grid-area: back; | ||||
| } | ||||
|  | ||||
| #back-btn { | ||||
| 	width: 2em; | ||||
| 	height: 2em; | ||||
| 	position: relative; | ||||
| } | ||||
|  | ||||
| #back-btn::before { | ||||
| 	content: ""; | ||||
| 	border: solid white 4px; | ||||
| 	border-style: solid none none solid; | ||||
| 	position: absolute; | ||||
| 	inset: 0; | ||||
| 	transform: rotate(-45deg); | ||||
| 	width: 0.5em; | ||||
| 	height: 0.5em; | ||||
| 	margin: auto; | ||||
| } | ||||
|  | ||||
| #navbar .title { | ||||
| 	grid-area: title; | ||||
| } | ||||
|  | ||||
| #body { | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| 	padding: 2em; | ||||
| 	gap: 1em; | ||||
| } | ||||
|  | ||||
| #crumbs { | ||||
| 	display: flex; | ||||
| 	gap: 1em; | ||||
| 	align-items: center; | ||||
| } | ||||
|  | ||||
| #crumbs .sep { | ||||
| 	position: relative; | ||||
| 	width: 0.5em; | ||||
| 	height: 0.5em; | ||||
| } | ||||
|  | ||||
| #crumbs .sep::before { | ||||
| 	content: ""; | ||||
| 	position: absolute; | ||||
| 	top: 50%; | ||||
| 	left: 50%; | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
| 	border: white 0.2em; | ||||
| 	border-style: solid solid none none; | ||||
| 	transform: translate(-50%, -50%) rotate(45deg); | ||||
| } | ||||
|  | ||||
| a { | ||||
| 	color: #00bf00; | ||||
| } | ||||
|  | ||||
| table { | ||||
| 	border-collapse: collapse; | ||||
| } | ||||
|  | ||||
| th, td { | ||||
| 	border: solid white 1px; | ||||
| 	padding: 0.5em 1em; | ||||
| } | ||||
|  | ||||
| tr:nth-child(even) { | ||||
| 	background-color: #aeaeae21; | ||||
| } | ||||
|  | ||||
| th { | ||||
| 	cursor: pointer; | ||||
| 	position: relative; | ||||
| } | ||||
|  | ||||
| th.sort-asc::before { | ||||
| 	content: ""; | ||||
| 	position: absolute; | ||||
| 	top: 50%; | ||||
| 	left: 0.5em; | ||||
| 	transform: translate(-50%, -25%); | ||||
| 	border: solid transparent 0.2em; | ||||
| 	border-top-color: white; | ||||
| } | ||||
|  | ||||
| th.sort-desc::before { | ||||
| 	content: ""; | ||||
| 	position: absolute; | ||||
| 	top: 50%; | ||||
| 	left: 0.5em; | ||||
| 	transform: translate(-50%, -55%); | ||||
| 	border: solid transparent 0.2em; | ||||
| 	border-bottom-color: white; | ||||
| } | ||||
							
								
								
									
										58
									
								
								res/template/base.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								res/template/base.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| function initTable(table) { | ||||
| 	table.tHead.querySelectorAll("th").forEach((header, i) => { | ||||
| 		header.addEventListener("click", () => { | ||||
| 			toggleSort(table, header, i) | ||||
| 		}) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| function toggleSort(table, header, colI) { | ||||
| 	if (header.classList.contains("sort-asc")) { | ||||
| 		header.classList.remove("sort-asc") | ||||
| 		header.classList.add("sort-desc") | ||||
| 		doSort(table, colI, true) | ||||
| 	} else if (header.classList.contains("sort-desc")) { | ||||
| 		header.classList.remove("sort-desc") | ||||
| 		header.classList.add("sort-asc") | ||||
| 		doSort(table, colI, false) | ||||
| 	} else { | ||||
| 		table.tHead.querySelectorAll("th.sort-asc,th.sort-desc").forEach(th => { | ||||
| 			th.classList.remove("sort-asc") | ||||
| 			th.classList.remove("sort-desc") | ||||
| 		}) | ||||
| 		header.classList.add("sort-asc") | ||||
| 		doSort(table, colI, false) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function doSort(table, colI, desc = false) { | ||||
| 	let swapped = true | ||||
| 	while (swapped) { | ||||
| 		let rows = Array.from(table.rows) | ||||
| 		swapped = false | ||||
| 		let rowA, rowB | ||||
| 		for (let i=1; i<rows.length-1; i++) { | ||||
| 			rowA = rows[i] | ||||
| 			rowB = rows[i+1] | ||||
| 			if (desc == compareRows(rowA, rowB, colI)) { | ||||
| 				swapped = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if (swapped) { | ||||
| 			rowA.parentNode.insertBefore(rowB, rowA) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function compareRows(rowA, rowB, colI) { | ||||
| 	let valA = rowA.querySelectorAll("td")[colI].innerText | ||||
| 	let valB = rowB.querySelectorAll("td")[colI].innerText | ||||
|  | ||||
| 	if (!Number.isNaN(+valA) && !Number.isNaN(+valB)) { | ||||
| 		valA = +valA | ||||
| 		valB = +valB | ||||
| 	} | ||||
|  | ||||
| 	return valB > valA | ||||
| } | ||||
							
								
								
									
										0
									
								
								res/template/index.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								res/template/index.css
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										33
									
								
								res/template/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								res/template/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head> | ||||
| 	<meta charset="utf-8"> | ||||
| 	<meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| 	<title>Slopify V2 - Listen to the music you hate</title> | ||||
| 	<link rel="stylesheet" type="text/css" href="base.css"> | ||||
| 	<link rel="stylesheet" type="text/css" href="index.css"> | ||||
| </head> | ||||
| <body> | ||||
| 	<header id="navbar"> | ||||
| 		<h1 class="title">Slopify</h1> | ||||
| 	</header> | ||||
| 	<div id="body"> | ||||
| 		<h2>Artists</h2> | ||||
| 		<div id="crumbs"></div> | ||||
| 		<table id="artists"> | ||||
| 			<thead> | ||||
| 				<tr> | ||||
| 					<th>Name</th> | ||||
| 				</tr> | ||||
| 			</thead> | ||||
| 			<tbody> | ||||
| 				{{artists}} | ||||
| 			</tbody> | ||||
| 		</table> | ||||
| 	</div> | ||||
| 	<script type="text/javascript" src="base.js"></script> | ||||
| 	<script> | ||||
| 		initTable(document.getElementById("artists")) | ||||
| 	</script> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										61
									
								
								res/template/song.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								res/template/song.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| @keyframes shadow-anim { | ||||
| 	0% { | ||||
| 		box-shadow: 0.0em 0.2em 0.6em #344c34a0; | ||||
| 	} | ||||
|  | ||||
| 	25% { | ||||
| 		box-shadow: 0.1em 0.3em 0.8em #344c34a0; | ||||
| 	} | ||||
|  | ||||
| 	50% { | ||||
| 		box-shadow: 0.2em 0.4em 1.2em #344c34a0; | ||||
| 	} | ||||
|  | ||||
| 	75% { | ||||
| 		box-shadow: 0.1em 0.3em 0.8em #344c34a0; | ||||
| 	} | ||||
|  | ||||
| 	100% { | ||||
| 		box-shadow: 0.0em 0.2em 0.6em #344c34a0; | ||||
| 	} | ||||
| 	 | ||||
| } | ||||
|  | ||||
| #song { | ||||
| 	display: grid; | ||||
| 	animation: shadow-anim 10s infinite alternate; | ||||
| 	border-radius: 0.8em; | ||||
| 	padding: 0.8em; | ||||
| 	grid-template-columns: auto 1fr auto; | ||||
| 	grid-template-rows: auto 1fr; | ||||
| 	grid-template-areas: | ||||
| 		"num title duration" | ||||
| 		"cover cover cover"; | ||||
| 	max-height: 20em; | ||||
| 	min-width: 50%; | ||||
| 	max-width: 30em; | ||||
| 	margin: auto; | ||||
| 	gap: 0.4em; | ||||
| 	align-items: center; | ||||
| } | ||||
|  | ||||
| #song .number { | ||||
| 	grid-area: num; | ||||
| } | ||||
|  | ||||
| #song .title { | ||||
| 	grid-area: title; | ||||
| } | ||||
|  | ||||
| #song .duration { | ||||
| 	grid-area: duration; | ||||
| } | ||||
|  | ||||
| #song .cover { | ||||
| 	grid-area: cover; | ||||
| 	object-fit: contain; | ||||
|  | ||||
| 	background-color: #393939; | ||||
| 	width: 8em; | ||||
| 	height: 8em; | ||||
| } | ||||
							
								
								
									
										33
									
								
								res/template/song.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								res/template/song.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head> | ||||
| 	<meta charset="utf-8"> | ||||
| 	<meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| 	<title>Slopify V2 - Listen to the music you hate</title> | ||||
| 	<link rel="stylesheet" type="text/css" href="base.css"> | ||||
| 	<link rel="stylesheet" type="text/css" href="song.css"> | ||||
| </head> | ||||
| <body> | ||||
| 	<header id="navbar"> | ||||
| 		<a class="back" id="back-btn" href="{{album.page}}"></a> | ||||
| 		<h1 class="title">Slopify</h1> | ||||
| 	</header> | ||||
| 	<div id="body"> | ||||
| 		<h2>Song</h2> | ||||
| 		<div id="crumbs"> | ||||
| 			<a href="{{artist.page}}">{{artist.name}}</a> | ||||
| 			<span class="sep"></span> | ||||
| 			<a href="{{album.page}}">{{album.name}}</a> | ||||
| 			<span class="sep"></span> | ||||
| 			<a href="./">{{song.title}}</a> | ||||
| 		</div> | ||||
| 		<div id="song"> | ||||
| 			<h4 class="number">{{song.number}}</h4> | ||||
| 			<h3 class="title">{{song.title}}</h3> | ||||
| 			<div class="cover"></div> | ||||
| 			<!-- <img src="{{album.image}}" alt="{{album.name}} cover image"> --> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<script type="text/javascript" src="base.js"></script> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										21
									
								
								src/ch/hevs/isc/slopify_v2/Album.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/ch/hevs/isc/slopify_v2/Album.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| package ch.hevs.isc.slopify_v2 | ||||
|  | ||||
| import scala.collection.mutable.ArrayBuffer | ||||
|  | ||||
| class Album(val name: String) extends Serializable { | ||||
|   private var _songs: ArrayBuffer[Song] = new ArrayBuffer() | ||||
|   def addSong(song: Song): Unit = _songs.addOne(song) | ||||
|   def containsSong(song: Song): Boolean = _songs.exists(s => s.number == song.number && s.title == song.title) | ||||
|   def getSongs(): Array[Song] = _songs.toArray | ||||
|  | ||||
|   def getSongByTitle(title: String): Option[Song] = { | ||||
|     for (song: Song <- _songs) { | ||||
|       if (song.title == title) { | ||||
|         return Some(song) | ||||
|       } | ||||
|     } | ||||
|     return None | ||||
|   } | ||||
|  | ||||
|   override def toString: String = s"<Album '$name': ${_songs.length} song(s)>" | ||||
| } | ||||
							
								
								
									
										22
									
								
								src/ch/hevs/isc/slopify_v2/Artist.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/ch/hevs/isc/slopify_v2/Artist.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| package ch.hevs.isc.slopify_v2 | ||||
|  | ||||
| import scala.collection.mutable.ArrayBuffer | ||||
|  | ||||
| class Artist(val name: String) extends Serializable { | ||||
|   private var _albums: ArrayBuffer[Album] = new ArrayBuffer() | ||||
|   def addAlbum(album: Album): Unit = _albums.addOne(album) | ||||
|  | ||||
|   def hasAlbum(album: Album): Boolean = _albums.exists(_.name == album.name) | ||||
|   def getAlbums(): Array[Album] = _albums.toArray | ||||
|  | ||||
|   def getAlbumByName(name: String): Option[Album] = { | ||||
|     for (album: Album <- _albums) { | ||||
|       if (album.name == name) { | ||||
|         return Some(album) | ||||
|       } | ||||
|     } | ||||
|     return None | ||||
|   } | ||||
|  | ||||
|   override def toString: String = s"<Artist '$name': ${_albums.length} album(s)>" | ||||
| } | ||||
							
								
								
									
										21
									
								
								src/ch/hevs/isc/slopify_v2/DataBase.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/ch/hevs/isc/slopify_v2/DataBase.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| package ch.hevs.isc.slopify_v2 | ||||
|  | ||||
| import scala.collection.mutable.ArrayBuffer | ||||
|  | ||||
| class DataBase extends Serializable { | ||||
|   private var _artists: ArrayBuffer[Artist] = new ArrayBuffer() | ||||
|   def addArtist(artist: Artist): Unit = _artists.addOne(artist) | ||||
|   def containsArtist(artist: Artist): Boolean = _artists.exists(_.name == artist.name) | ||||
|   def getArtists(): Array[Artist] = _artists.toArray | ||||
|  | ||||
|   def getArtistByName(name: String): Option[Artist] = { | ||||
|     for (artist: Artist <- _artists) { | ||||
|       if (artist.name == name) { | ||||
|         return Some(artist) | ||||
|       } | ||||
|     } | ||||
|     return None | ||||
|   } | ||||
|  | ||||
|   override def toString: String = s"<Database: ${_artists.length} artist(s)>" | ||||
| } | ||||
							
								
								
									
										166
									
								
								src/ch/hevs/isc/slopify_v2/DataBaseHelper.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								src/ch/hevs/isc/slopify_v2/DataBaseHelper.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,166 @@ | ||||
| package ch.hevs.isc.slopify_v2 | ||||
|  | ||||
| import java.io._ | ||||
| import scala.collection.immutable.HashMap | ||||
| import scala.io.{BufferedSource, Source} | ||||
|  | ||||
| object DataBaseHelper { | ||||
|   def create(directory:String) : DataBase = { | ||||
|     val db = new DataBase() | ||||
|     for (a <- new File(directory).listFiles() if a.isDirectory) { | ||||
|       val artistName = a.getName | ||||
|       //println(s"found new artist : $artistName") | ||||
|       val artist: Artist = new Artist(artistName) | ||||
|  | ||||
|       for (b <- a.listFiles() if b.isDirectory ) { | ||||
|         val albumName = b.getName | ||||
|         //println(s"found new album $albumName for artist : $artistName") | ||||
|         val album: Album = new Album(albumName) | ||||
|  | ||||
|         for (c <- b.listFiles() if c.isFile if c.getName.toLowerCase().endsWith(".mp3") ) { | ||||
|           val fileName = c.getName.substring(0, c.getName.length - 4) | ||||
|           val format1 = """(\d*) (.*)""".r | ||||
|           val format2 = """(\d*)-(\d*) (.*)""".r | ||||
|           val format3 = """(\d*)-(.*)""".r | ||||
|           var songName: String = "" | ||||
|           var songNumber: Int = 0 | ||||
|  | ||||
|           fileName match { | ||||
|             case format1(nr, name) => { | ||||
|               //println(s"found song nr #$nr name:'$name' in album '$albumName' for artist : '${a.getName}'") | ||||
|               songName = name | ||||
|               songNumber = Integer.parseInt(nr) | ||||
|             } | ||||
|             case format2(cd, nr, name) => { | ||||
|               //println(s"found song nr #$nr on cd#$cd name:'$name' in album '$albumName' for artist : '${a.getName}'") | ||||
|               songName = name | ||||
|               songNumber = Integer.parseInt(nr) | ||||
|             } | ||||
|             case format3(nr, name) => { | ||||
|               //println(s"found song nr #$nr name:'$name' in album '$albumName' for artist : '$artistName'") | ||||
|               songName = name | ||||
|               songNumber = Integer.parseInt(nr) | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           val song: Song = new Song(songName, songNumber) | ||||
|           album.addSong(song) | ||||
|         } | ||||
|         artist.addAlbum(album) | ||||
|       } | ||||
|       db.addArtist(artist) | ||||
|     } | ||||
|  | ||||
|     return db | ||||
|   } | ||||
|  | ||||
|   def save(fileName: String, db: DataBase) : Unit = { | ||||
|     val fos: FileOutputStream = new FileOutputStream(fileName) | ||||
|     val oos: ObjectOutputStream = new ObjectOutputStream(fos) | ||||
|     oos.writeObject(db) | ||||
|     oos.close() | ||||
|   } | ||||
|   def load(fileName:String) : DataBase = { | ||||
|     val fis: FileInputStream = new FileInputStream(fileName) | ||||
|     val ois: ObjectInputStream = new ObjectInputStream(fis) | ||||
|     val db: DataBase = ois.readObject().asInstanceOf[DataBase] | ||||
|     ois.close() | ||||
|     return db | ||||
|   } | ||||
|  | ||||
|   def exportHTML(directory: String, dataBase: DataBase): Unit = { | ||||
|     var artists: Array[Artist] = dataBase.getArtists().sortBy(_.name) | ||||
|  | ||||
|     var artistRows: String = "" | ||||
|     for (artist: Artist <- artists) { | ||||
|       val artistFileName: String = formatName(artist.name) | ||||
|       val artistUrl: String = s"./$artistFileName.html" | ||||
|       artistRows += s"<tr><td><a href='$artistUrl'>${artist.name}</a></td></tr>\n" | ||||
|  | ||||
|       val albums: Array[Album] = artist.getAlbums().sortBy(_.name) | ||||
|       var albumRows: String = "" | ||||
|       for (album: Album <- albums) { | ||||
|         val albumFileName: String = artistFileName + "_" + formatName(album.name) | ||||
|         val albumUrl: String = s"./$albumFileName.html" | ||||
|         albumRows += s"<tr><td><a href='$albumUrl'>${album.name}</a></td></tr>\n" | ||||
|  | ||||
|         val songs: Array[Song] = album.getSongs().sortBy(_.number) | ||||
|         var songRows: String = "" | ||||
|         for (song: Song <- songs) { | ||||
|           val songFileName: String = albumFileName + "_" + formatName(song.number + "-" + song.title) | ||||
|           val songUrl: String = s"./$songFileName.html" | ||||
|           songRows += s"<tr><td>${song.number}</td><td><a href='$songUrl'>${song.title}</a></td></tr>\n" | ||||
|  | ||||
|           formatTemplate("res/template/song.html", HashMap( | ||||
|             "artist.name" -> artist.name, | ||||
|             "artist.page" -> artistUrl, | ||||
|             "album.name" -> album.name, | ||||
|             "album.page" -> albumUrl, | ||||
|             "song.title" -> song.title, | ||||
|             "song.number" -> song.number.toString | ||||
|           ), directory + "/" + songFileName + ".html") | ||||
|         } | ||||
|  | ||||
|         formatTemplate("res/template/album.html", HashMap( | ||||
|           "songs" -> songRows, | ||||
|           "artist.name" -> artist.name, | ||||
|           "artist.page" -> artistUrl, | ||||
|           "album.name" -> album.name | ||||
|         ), directory + "/" + albumFileName + ".html") | ||||
|       } | ||||
|  | ||||
|       formatTemplate("res/template/artist.html", HashMap( | ||||
|         "albums" -> albumRows, | ||||
|         "artist.name" -> artist.name | ||||
|       ), directory + "/" + artistFileName + ".html") | ||||
|     } | ||||
|     formatTemplate("res/template/index.html", HashMap( | ||||
|       "artists" -> artistRows | ||||
|     ), directory + "/" + "index.html") | ||||
|  | ||||
|     copyFile("res/template/base.js", directory + "/base.js") | ||||
|     copyFile("res/template/base.css", directory + "/base.css") | ||||
|     copyFile("res/template/index.css", directory + "/index.css") | ||||
|     copyFile("res/template/artist.css", directory + "/artist.css") | ||||
|     copyFile("res/template/album.css", directory + "/album.css") | ||||
|     copyFile("res/template/song.css", directory + "/song.css") | ||||
|   } | ||||
|  | ||||
|   def copyFile(from: String, to: String): Unit = { | ||||
|     val fis: FileInputStream = new FileInputStream(from) | ||||
|     val fos: FileOutputStream = new FileOutputStream(to) | ||||
|  | ||||
|     fos.write(fis.readAllBytes()) | ||||
|  | ||||
|     fis.close() | ||||
|     fos.close() | ||||
|   } | ||||
|  | ||||
|   def formatName(name: String): String = { | ||||
|     var formatted: String = name.toLowerCase | ||||
|     formatted = formatted.replace(" ", "_") | ||||
|     formatted = formatted.replace("'", "-") | ||||
|     formatted = formatted.replaceAll("[^a-zA-Z_0-9\\-().]", "") | ||||
|     return formatted | ||||
|   } | ||||
|  | ||||
|   def formatTemplate(templateName: String, variables: Map[String, String], outPath: String): Unit = { | ||||
|     val source: BufferedSource = Source.fromFile(templateName) | ||||
|     var template: String = source.getLines().mkString("\n") | ||||
|     source.close() | ||||
|  | ||||
|     for ((key: String, value: String) <- variables) { | ||||
|       template = template.replace(s"{{$key}}", value) | ||||
|     } | ||||
|  | ||||
|     val fos: FileOutputStream = new FileOutputStream(outPath) | ||||
|     fos.write(template.getBytes) | ||||
|     fos.close() | ||||
|   } | ||||
|  | ||||
|   def main(args: Array[String]): Unit = { | ||||
|     val db: DataBase = create("res/songs") | ||||
|  | ||||
|     exportHTML("/tmp/html", db) | ||||
|   } | ||||
| } | ||||
							
								
								
									
										236
									
								
								src/ch/hevs/isc/slopify_v2/GUI.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								src/ch/hevs/isc/slopify_v2/GUI.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,236 @@ | ||||
| package ch.hevs.isc.slopify_v2 | ||||
|  | ||||
| import io.methvin.watcher.{DirectoryChangeEvent, DirectoryChangeListener, DirectoryWatcher} | ||||
|  | ||||
| import java.awt.event.{KeyEvent, KeyListener} | ||||
| import java.awt.{GridBagConstraints, GridBagLayout, LayoutManager} | ||||
| import java.nio.file.Paths | ||||
| import java.util | ||||
| import javax.swing._ | ||||
| import javax.swing.table.{DefaultTableModel, TableModel, TableRowSorter} | ||||
|  | ||||
| class GUI extends JFrame { | ||||
|   val DIRECTORY: String = "res/songs/" | ||||
|   val DB_PATH: String = "res/songs_db.bin" | ||||
|  | ||||
|   setSize(800, 600) | ||||
|   setTitle("Slopify V2") | ||||
|  | ||||
|   // Artists | ||||
|   val artistsListModel: DefaultListModel[String] = new DefaultListModel[String]() | ||||
|   val artistsList: JList[String] = new JList(artistsListModel) | ||||
|   artistsList.setLayoutOrientation(JList.VERTICAL) | ||||
|   val col1: JPanel = new JPanel() | ||||
|   val col1Layout: GridBagLayout = new GridBagLayout() | ||||
|   col1.setLayout(col1Layout) | ||||
|  | ||||
|   val artistsPane: JScrollPane = new JScrollPane(artistsList) | ||||
|   artistsList.addListSelectionListener(_ => { | ||||
|     val artistName: String = artistsList.getSelectedValue | ||||
|     val artist: Option[Artist] = db.getArtistByName(artistName) | ||||
|     if (artist.isDefined) { | ||||
|       selectArtist(artist.get) | ||||
|     } else { | ||||
|       clearAlbums() | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|   val artistsSearchBar: JTextField = new JTextField() | ||||
|   artistsSearchBar.addKeyListener(new KeyListener { | ||||
|     override def keyTyped(keyEvent: KeyEvent): Unit = {} | ||||
|     override def keyPressed(keyEvent: KeyEvent): Unit = {} | ||||
|     override def keyReleased(keyEvent: KeyEvent): Unit = updateArtistsSearch() | ||||
|   }) | ||||
|  | ||||
|   val col1Constraints: GridBagConstraints = new GridBagConstraints() | ||||
|   col1Constraints.fill = GridBagConstraints.BOTH | ||||
|   col1Constraints.gridx = 0 | ||||
|   col1Constraints.gridy = 0 | ||||
|   col1Constraints.weightx = 1 | ||||
|   col1Constraints.weighty = 1 | ||||
|   col1.add(artistsPane, col1Constraints) | ||||
|  | ||||
|   col1Constraints.fill = GridBagConstraints.HORIZONTAL | ||||
|   col1Constraints.gridx = 0 | ||||
|   col1Constraints.gridy = 1 | ||||
|   col1Constraints.weighty = 0 | ||||
|   col1.add(artistsSearchBar, col1Constraints) | ||||
|  | ||||
|   // Albums | ||||
|   val albumsListModel: DefaultListModel[String] = new DefaultListModel[String]() | ||||
|   val albumsList: JList[String] = new JList(albumsListModel) | ||||
|   val col2: JScrollPane = new JScrollPane(albumsList) | ||||
|   albumsList.addListSelectionListener(_ => { | ||||
|     if (curArtist.isDefined) { | ||||
|       val albumName: String = albumsList.getSelectedValue | ||||
|       val album: Option[Album] = curArtist.get.getAlbumByName(albumName) | ||||
|       if (album.isDefined) { | ||||
|         selectAlbum(album.get) | ||||
|       } else { | ||||
|         clearSongs() | ||||
|       } | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|   // Songs | ||||
|   val songsTableHeaders: Array[Object] = Array("Track no", "Title") | ||||
|   val songsTable: JTable = new JTable(Array.empty[Array[Object]], songsTableHeaders) | ||||
|   val songsTableModel: DefaultTableModel = new DefaultTableModel() | ||||
|   songsTableModel.addColumn("Track no") | ||||
|   songsTableModel.addColumn("Title") | ||||
|   songsTable.setModel(songsTableModel) | ||||
|   songsTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN) | ||||
|  | ||||
|   val tableSorter: TableRowSorter[TableModel] = new TableRowSorter(songsTable.getModel) | ||||
|   val songsSortKeys: util.List[RowSorter.SortKey] = new util.ArrayList[RowSorter.SortKey]() | ||||
|   songsSortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING)) | ||||
|   tableSorter.setSortKeys(songsSortKeys) | ||||
|   songsTable.setRowSorter(tableSorter) | ||||
|  | ||||
|   val col3: JScrollPane = new JScrollPane(songsTable) | ||||
|   val songsSelectionModel: ListSelectionModel = songsTable.getSelectionModel | ||||
|   songsSelectionModel.addListSelectionListener(e => { | ||||
|     val row: Int = e.getFirstIndex | ||||
|     try { | ||||
|       val songName: String = songsTableModel.getValueAt(row, 0).asInstanceOf[String] | ||||
|       val song: Option[Song] = curAlbum.get.getSongByTitle(songName) | ||||
|       if (song.isDefined) { | ||||
|         selectSong(song.get) | ||||
|       } | ||||
|     } catch { | ||||
|       case e: ArrayIndexOutOfBoundsException => {} | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|   val split1: JSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, col1, col2) | ||||
|   val split2: JSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, split1, col3) | ||||
|   split1.setResizeWeight(0.5) | ||||
|   split2.setResizeWeight(0.66) | ||||
|  | ||||
|   val myMenuBar: JMenuBar = new JMenuBar() | ||||
|   val fileMenu: JMenu = new JMenu("File") | ||||
|   val refreshDbBtn: JMenuItem = new JMenuItem("Refresh Database") | ||||
|   refreshDbBtn.addActionListener(_ => refreshDatabase()) | ||||
|  | ||||
|   fileMenu.add(refreshDbBtn) | ||||
|   myMenuBar.add(fileMenu) | ||||
|   setJMenuBar(myMenuBar) | ||||
|   getContentPane.add(split2) | ||||
|   setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE) | ||||
|  | ||||
|   var db: DataBase = _ | ||||
|   var curArtist: Option[Artist] = None | ||||
|   var curAlbum: Option[Album] = None | ||||
|   var curSong: Option[Song] = None | ||||
|  | ||||
|   DirectoryWatcher.builder() | ||||
|     .path(Paths.get(DIRECTORY)) | ||||
|     .listener(e => onDirectoryChange(e)) | ||||
|     .build().watchAsync() | ||||
|  | ||||
|   refreshDatabase() | ||||
|   setVisible(true) | ||||
|  | ||||
|  | ||||
|   def clearArtists(): Unit = { | ||||
|     artistsListModel.clear() | ||||
|     curArtist = None | ||||
|   } | ||||
|   def clearAlbums(): Unit = { | ||||
|     albumsListModel.clear() | ||||
|     curAlbum = None | ||||
|   } | ||||
|   def clearSongs(): Unit = { | ||||
|     songsTableModel.setRowCount(0) | ||||
|     curSong = None | ||||
|   } | ||||
|   def addArtist(artist: Artist): Unit = { | ||||
|     //println(s"Adding $artist") | ||||
|     artistsListModel.addElement(artist.name) | ||||
|   } | ||||
|   def addAlbum(album: Album): Unit = { | ||||
|     //println(s"Adding $album") | ||||
|     albumsListModel.addElement(album.name) | ||||
|   } | ||||
|   def addSong(song: Song): Unit = { | ||||
|     //println(s"Adding $song") | ||||
|     val row: Array[String] = Array( | ||||
|       song.number.toString.reverse.padTo(2, '0').reverse, | ||||
|       song.title | ||||
|     ) | ||||
|     songsTableModel.addRow(row.asInstanceOf[Array[AnyRef]]) | ||||
|   } | ||||
|   def refreshDatabase(): Unit = { | ||||
|     db = DataBaseHelper.create(DIRECTORY) | ||||
|     val oldArtist: Option[Artist] = curArtist | ||||
|     val oldAlbum: Option[Album] = curAlbum | ||||
|     val oldSong: Option[Song] = curSong | ||||
|  | ||||
|     clearArtists() | ||||
|     clearAlbums() | ||||
|     clearSongs() | ||||
|     val artists: Array[Artist] = db.getArtists().sortBy(_.name) | ||||
|     for (artist: Artist <- artists) { | ||||
|       addArtist(artist) | ||||
|     } | ||||
|  | ||||
|     if (oldArtist.isDefined && db.containsArtist(oldArtist.get)) { | ||||
|       artistsList.setSelectedIndex(artistsListModel.indexOf(oldArtist.get.name)) | ||||
|  | ||||
|       if (oldAlbum.isDefined && curArtist.get.hasAlbum(oldAlbum.get)) { | ||||
|         albumsList.setSelectedIndex(albumsListModel.indexOf(oldAlbum.get.name)) | ||||
|  | ||||
|         if (oldSong.isDefined && curAlbum.get.containsSong(oldSong.get)) { | ||||
|           selectSong(oldSong.get) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     DataBaseHelper.save(DB_PATH, db) | ||||
|   } | ||||
|   def selectArtist(artist: Artist): Unit = { | ||||
|     curArtist = Some(artist) | ||||
|     clearAlbums() | ||||
|     val albums: Array[Album] = artist.getAlbums().sortBy(_.name) | ||||
|     for (album: Album <- albums) { | ||||
|       addAlbum(album) | ||||
|     } | ||||
|   } | ||||
|   def selectAlbum(album: Album): Unit = { | ||||
|     curAlbum = Some(album) | ||||
|     clearSongs() | ||||
|     for (song: Song <- album.getSongs()) { | ||||
|       addSong(song) | ||||
|     } | ||||
|   } | ||||
|   def selectSong(song: Song): Unit = { | ||||
|     curSong = Some(song) | ||||
|   } | ||||
|  | ||||
|   def updateArtistsSearch(): Unit = { | ||||
|     val search: String = artistsSearchBar.getText.toLowerCase | ||||
|  | ||||
|     clearArtists() | ||||
|     clearAlbums() | ||||
|     clearSongs() | ||||
|     var artists: Array[Artist] = db.getArtists().sortBy(_.name) | ||||
|     if (search.nonEmpty) { | ||||
|       artists = artists.filter(_.name.toLowerCase.contains(search)) | ||||
|     } | ||||
|     for (artist: Artist <- artists) { | ||||
|       addArtist(artist) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   def onDirectoryChange(event: DirectoryChangeEvent): Unit = { | ||||
|     println("Changed " + event) | ||||
|     SwingUtilities.invokeLater(() => { | ||||
|       refreshDatabase() | ||||
|     }) | ||||
|   } | ||||
| } | ||||
|  | ||||
| object GUI { | ||||
|   def main(args: Array[String]): Unit = { | ||||
|     val gui: GUI = new GUI() | ||||
|   } | ||||
| } | ||||
							
								
								
									
										5
									
								
								src/ch/hevs/isc/slopify_v2/Song.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/ch/hevs/isc/slopify_v2/Song.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| package ch.hevs.isc.slopify_v2 | ||||
|  | ||||
| class Song(val title: String, val number: Int) extends Serializable { | ||||
|   override def toString: String = s"<Song n° $number '$title'>" | ||||
| } | ||||
		Reference in New Issue
	
	Block a user