Compare commits
9 Commits
372d151264
...
aee3052ad7
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
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
hs_err_pid*
|
hs_err_pid*
|
||||||
|
|
||||||
|
/res/songs/
|
||||||
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
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
Normal file
7
.idea/codeStyles/Project.xml
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
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
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
Normal file
12
.idea/libraries/io_methvin_directory_watcher.xml
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
Normal file
16
.idea/libraries/lihaoyi_upickle_2_13.xml
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
Normal file
6
.idea/misc.xml
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
Normal file
8
.idea/modules.xml
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
Normal file
7
.idea/vcs.xml
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'>"
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user