Compare commits

...

1 Commits
master ... ui

Author SHA1 Message Date
  David Ludwig 0db343ec7e Some UI stuff... Not really working yet 4 years ago
14 changed files with 583 additions and 88 deletions
Split View
  1. +3
    -22
      src/app/App.vue
  2. BIN
      src/app/assets/logo.png
  3. +0
    -65
      src/app/components/HelloWorld.vue
  4. +3
    -0
      src/app/components/PaginationWidget.vue
  5. +89
    -0
      src/app/components/ProgressRing.vue
  6. +133
    -0
      src/app/components/TorrentList.vue
  7. +59
    -0
      src/app/components/TorrentListItem.vue
  8. +82
    -0
      src/app/components/TorrentListItemNew.vue
  9. +161
    -0
      src/app/components/TorrentListNew.vue
  10. +13
    -1
      src/app/index.ts
  11. +21
    -0
      src/app/routes/index.ts
  12. +1
    -0
      src/app/styles/index.css
  13. +15
    -0
      src/app/views/Home.vue
  14. +3
    -0
      src/app/views/Login.vue

+ 3
- 22
src/app/App.vue View File

@ -1,27 +1,8 @@
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Hello Vue 3 + TypeScript + Vite" />
<router-view></router-view>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
export default defineComponent({
name: 'App',
components: {
HelloWorld
}
})
// import { defineComponent } from 'vue'
// export default defineComponent({});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

BIN
src/app/assets/logo.png View File

Before After
Width: 200  |  Height: 200  |  Size: 6.7 KiB

+ 0
- 65
src/app/components/HelloWorld.vue View File

@ -1,65 +0,0 @@
<template>
<h1>{{ msg }}</h1>
<p>
Recommended IDE setup:
<a href="https://code.visualstudio.com/" target="_blank">VSCode</a>
+
<a
href="https://marketplace.visualstudio.com/items?itemName=octref.vetur"
target="_blank"
>Vetur</a>
or
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
(if using
<code>&lt;script setup&gt;</code>)
</p>
<p>See <code>README.md</code> for more information.</p>
<p>
<a href="https://vitejs.dev/guide/features.html" target="_blank">Vite Docs</a> |
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Docs</a>
</p>
<button @click="count++">count is: {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test hot module replacement.
</p>
</template>
<script lang="ts">
import { ref, defineComponent } from 'vue'
export default defineComponent({
name: 'HelloWorld',
props: {
msg: {
type: String,
required: true
}
},
setup: () => {
const count = ref(0)
return { count }
}
})
</script>
<style scoped>
a {
color: #42b983;
}
label {
margin: 0 0.5em;
font-weight: bold;
}
code {
background-color: #eee;
padding: 2px 4px;
border-radius: 4px;
color: #304455;
}
</style>

+ 3
- 0
src/app/components/PaginationWidget.vue View File

@ -0,0 +1,3 @@
<template>
<div></div>
</template>

+ 89
- 0
src/app/components/ProgressRing.vue View File

@ -0,0 +1,89 @@
<template>
<div class="box-border relative text-white radial-progress">
<svg class="transform -rotate-90" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style="height: 100%; width: 100%">
<circle
class="radial-progress-ring__circle"
:stroke-width="ringWidth"
:r="50 - ringWidth/2"
:cx="50"
:cy="50"
ref="ring"
/>
</svg>
<div class="antialiased flex absolute inset-0 items-center justify-center text-xs font-light rounded-full text-white">{{ progressFormatted }}%</div>
<div class="flex absolute inset-0 items-center justify-center text-sm font-thin rounded-full bg-modal" :class="{ 'show-state': isPaused }" @click="$emit('toggleState')">
<i class="fas fa-play" v-if="!isPaused"></i>
<i class="fas fa-pause" v-else></i>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export enum RadialProgressState {
Downloading,
Paused,
Halted,
Error
}
export default defineComponent({
computed: {
circumference(): number {
return 2*Math.PI*(50 - this.ringWidth/2);
},
progressFormatted(): number {
return Math.floor(100*(<number>this.progress));
}
},
methods: {
updateProgressBar() {
(<HTMLElement>this.$refs.ring).style.strokeDasharray = `${this.circumference} ${this.circumference}`;
(<HTMLElement>this.$refs.ring).style.strokeDashoffset = `${this.circumference - this.circumference*this.progress}`;
}
},
mounted() {
this.updateProgressBar();
},
props: {
progress: {
type: Number,
required: true
},
isPaused: {
type: Boolean,
default: false
},
ringWidth: {
type: Number,
default: 1
}
},
watch: {
progress() {
this.updateProgressBar();
}
}
});
</script>
<style lang="css">
.radial-progress-ring__circle {
fill: #1F2937;
stroke: #34D399;
transition: stroke-dashoffset 0.25s;
}
.bg-modal {
background: rgba(0, 0, 0, 0.35);
opacity: 0.0;
transition: opacity 0.25s;
}
.show-state {
opacity: 1.0;
}
.radial-progress:hover .bg-modal {
opacity: 1.0;
}
</style>

+ 133
- 0
src/app/components/TorrentList.vue View File

@ -0,0 +1,133 @@
<template>
<div class="bg-gray-800 rounded-md m-4 md:m-8 text-gray-300">
<div class="p-5">
<input type="text" placeholder="Search..." class="p-2 bg-gray-600 rounded w-full">
</div>
<div class="grid grid-cols-torrent-list">
<!-- Header -->
<div class="header contents col-span-5">
<div class="p-5 py-2 bg-gray-700"></div>
<div class="p-5 py-2 bg-gray-700 col-span-2 md:col-span-1">Name</div>
<div class="p-5 py-2 bg-gray-700 hidden md:block">Size</div>
<div class="p-5 py-2 bg-gray-700 hidden md:block">ETA</div>
<div class="p-5 py-2 bg-gray-700 hidden md:block">Peers</div>
<div class="p-5 py-2 bg-gray-700 hidden md:block">Actions</div>
</div>
<!-- Items -->
<torrent-list-item v-for="infoHash of infoHashes" :torrent="torrents[infoHash]" @delete="removeTorrent"></torrent-list-item>
</div>
<!-- <div v-for="i of order">{{ JSON.stringify(torrents[infoHashes[i]]) }}</div> -->
</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from "vue";
import TorrentListItem from "./TorrentListItem.vue";
import PaginationWidget from "./PaginationWidget.vue";
interface ITorrentMap {
[infoHash: string]: ITorrent
}
interface ITorrent {
name: string,
progress: number,
infoHash: string,
isPaused: boolean
}
export default defineComponent({
components: {
TorrentListItem,
PaginationWidget
},
data() {
return {
infoHashes: <string[]>[],
torrents: <ITorrentMap>{}
}
},
methods: {
updateTorrentsList(torrents: ITorrent[]) {
let toDelete = new Set(this.infoHashes);
torrents.forEach((torrent, index) => {
let infoHash = torrent.infoHash;
if (infoHash in this.torrents) {
toDelete.delete(infoHash);
this.torrents[infoHash].progress = torrent.progress;
this.torrents[infoHash].isPaused = torrent.isPaused;
} else {
this.infoHashes.splice(index, 0, infoHash);
this.torrents[infoHash] = reactive(torrent);
}
});
toDelete.forEach((infoHash) => {
delete this.torrents[infoHash];
});
},
removeTorrent(infoHash: string) {
delete this.torrents[infoHash];
this.infoHashes.splice(this.infoHashes.indexOf(infoHash), 1);
}
},
mounted() {
this.updateTorrentsList([{
name: "Big Buck Bunny",
progress: 0.5,
infoHash: "dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c",
isPaused: false
},
// {
// name: "Cosmos Laundromat",
// progress: 0.5,
// infoHash: "c9e15763f722f23e98a29decdfae341b98d53056",
// isPaused: false
// }, {
// name: "Sintel",
// progress: 0.5,
// infoHash: "08ada5a7a6183aae1e09d831df6748d566095a10",
// isPaused: false
// }, {
// name: "Tears of Steel",
// progress: 0.5,
// infoHash: "209c8226b299b308beaf2b9cd3fb49212dbd13ec",
// isPaused: false
// }, {
// name: "The WIRED CD - Rip. Sample. Mash. Share",
// progress: 0.5,
// infoHash: "a88fda5954e89178c372716a6a78b8180ed4dad3",
// isPaused: false
// }
]);
setInterval(() => {
for (let infoHash of this.infoHashes) {
this.torrents[infoHash].progress = Math.random();
}
}, 3000);
}
});
</script>
<style lang="css">
.grid-cols-torrent-list {
grid-template-columns: min-content 1fr min-content;
}
@media (min-width: 768px) {
.grid-cols-torrent-list {
grid-template-columns: min-content 1fr repeat(3, minmax(0, 0.20fr)) min-content;
}
}
.row > div {
align-items: center;
@apply border-b border-gray-700 cursor-pointer;
}
.row:last-of-type > div {
@apply border-b-0;
}
.row:hover > div {
@apply bg-gray-600;
}
</style>

+ 59
- 0
src/app/components/TorrentListItem.vue View File

@ -0,0 +1,59 @@
<template>
<div class="row contents">
<div class="pl-5 py-3 w-16 h-16 flex justify-center">
<progress-ring :ring-width="8" class="h-10" :is-paused="torrent.isPaused" @toggle-state="toggleState" :progress="torrent.progress"/>
</div>
<div class="pl-5 py-3 h-16 flex overflow-ellipsis overflow-hidden whitespace-nowrap">{{ torrent.name }}</div>
<div class="pl-5 py-3 h-16 hidden md:flex">1.21GB</div>
<div class="pl-5 py-3 h-16 hidden md:flex">5 Minutes</div>
<div class="pl-5 py-3 h-16 hidden md:flex">29</div>
<div class="p-5 py-3 h-16 flex">
<div class="dropdown" :class="{'active': showMenu}">
<button id="actions-menu" aria-expanded="true" aria-haspopup="true">...</button>
<div class="dropdown-menu origin-top-right absolute right: 0 mt-8 w-56" role="menu" aria-orientation="vertical" aria-labelledby="actions-menu">
<div class="py-1" role="none">
<a href="#" class="block px-4 py-2 text-sm text-gray-300">Thing</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import ProgressRing from "./ProgressRing.vue";
export default defineComponent({
components: {
ProgressRing
},
data() {
return {
showMenu: false
}
},
methods: {
toggleState() {
this.torrent.isPaused = !this.torrent.isPaused;
},
},
mounted() {
},
props: {
torrent: {
type: Object,
required: true
}
}
});
</script>
<style class="css">
.dropdown .dropdown-menu {
display: none;
}
.dropdown.active .dropdown-menu {
display: initial;
}
</style>

+ 82
- 0
src/app/components/TorrentListItemNew.vue View File

@ -0,0 +1,82 @@
<template>
<tr class="">
<td class="pl-5 py-3 w-1 h-16 handle cursor-move text-gray-500 hover:text-white">
<div class="text-center">
<i class="fas fa-bars transitions transition-colors"></i>
</div>
</td>
<td class="pl-5 py-3 w-16 h-16">
<progress-ring :ring-width="8" class="w-10 h-10" :is-paused="isPaused" @toggle-state="toggleState" :progress="progress"/>
</td>
<td class="p-5 py-3 h-16 overflow-ellipsis overflow-hidden whitespace-nowrap">
{{ name }}
</td>
<td class="p-5 py-3 h-16 w-32 hidden md:table-cell">1.21GB</td>
<td class="p-5 py-3 h-16 w-32 hidden md:table-cell">5 Minutes</td>
<td class="p-5 py-3 h-16 w-16 hidden md:table-cell">29</td>
<td class="p-5 py-3 h-16 w-1 hidden md:table-cell">
<div class="dropdown" :class="{'active': showMenu}">
<button class="w-9 h-9 rounded-full hover:bg-indigo-500 hover:text-white transitions transition-colors" id="actions-menu" aria-expanded="true" aria-haspopup="true"><i class="fas fa-ellipsis-v"></i></button>
<div class="dropdown-menu origin-top-right absolute right: 0 mt-8 w-56" role="menu" aria-orientation="vertical" aria-labelledby="actions-menu">
<div class="py-1" role="none">
<a href="#" class="block px-4 py-2 text-sm text-gray-300">Thing</a>
</div>
</div>
</div>
</td>
</tr>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import ProgressRing from "./ProgressRing.vue";
export default defineComponent({
components: {
ProgressRing
},
data() {
return {
showMenu: false
}
},
methods: {
toggleState() {
// this.isPaused = !this.isPaused;
},
},
mounted() {
},
props: {
id: {
type: Number,
required: true
},
name: {
type: String,
required: true
},
infoHash: {
type: String,
required: true,
},
progress: {
type: Number,
required: true
},
isPaused: {
type: Boolean,
required: true
}
}
});
</script>
<style class="css">
.dropdown .dropdown-menu {
display: none;
}
.dropdown.active .dropdown-menu {
display: initial;
}
</style>

+ 161
- 0
src/app/components/TorrentListNew.vue View File

@ -0,0 +1,161 @@
<template>
<div class="bg-gray-800 rounded-md m-4 md:m-8 text-gray-300">
<div class="p-5">
<input type="text" placeholder="Search..." class="p-2 bg-gray-600 rounded w-full">
</div>
<table class="w-full" cellspacing="0" cellpadding="0">
<thead class="w-full text-center">
<tr>
<th class="p-5 py-2 bg-gray-700" colspan="2"></th>
<th class="p-5 py-2 bg-gray-700 text-left">Name</th>
<th class="p-5 py-2 bg-gray-700 hidden md:table-cell">Size</th>
<th class="p-5 py-2 bg-gray-700 hidden md:table-cell">ETA</th>
<th class="p-5 py-2 bg-gray-700 hidden md:table-cell">Peers</th>
<th class="p-5 py-2 bg-gray-700 hidden md:table-cell">Actions</th>
</tr>
</thead>
<!-- Items -->
<draggable v-model="torrents" v-bind="dragOptions" item-key="id" tag="transition-group" :component-data="{tag: 'tbody'}" @start="isDragging=true" @end="isDragging=false" @sort="onSort">
<template v-slot:item="{element}">
<torrent-list-item-new v-bind="element"></torrent-list-item-new>
</template>
</draggable>
</table>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from "vue";
import Draggable from "vuedraggable";
import TorrentListItemNew from "./TorrentListItemNew.vue";
import PaginationWidget from "./PaginationWidget.vue";
interface ITorrentMap {
[infoHash: string]: ITorrent
}
interface ITorrent {
id: number,
name: string,
progress: number,
infoHash: string,
isPaused: boolean
}
export default defineComponent({
components: {
Draggable,
TorrentListItemNew,
PaginationWidget
},
data() {
return {
torrents: <ITorrent[]>[{
id: 0,
name: "Big Buck Bunny",
progress: 0.5,
infoHash: "dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c",
isPaused: false
},
{
id: 1,
name: "Cosmos Laundromat",
progress: 0.5,
infoHash: "c9e15763f722f23e98a29decdfae341b98d53056",
isPaused: false
}, {
id: 2,
name: "Sintel",
progress: 0.5,
infoHash: "08ada5a7a6183aae1e09d831df6748d566095a10",
isPaused: false
}, {
id: 3,
name: "Tears of Steel",
progress: 0.5,
infoHash: "209c8226b299b308beaf2b9cd3fb49212dbd13ec",
isPaused: false
}, {
id: 4,
name: "The WIRED CD - Rip. Sample. Mash. Share",
progress: 0.5,
infoHash: "a88fda5954e89178c372716a6a78b8180ed4dad3",
isPaused: false
}],
isDragging: false,
dragOptions: {
animation: 200,
ghostClass: "ghost",
handle: ".handle"
}
}
},
methods: {
updateTorrentsList(torrents: ITorrent[]) {
this.torrents = reactive(torrents);
},
onSort() {
console.log("Sorted");
}
},
mounted() {
// this.updateTorrentsList([{
// name: "Big Buck Bunny",
// progress: 0.5,
// infoHash: "dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c",
// isPaused: false
// },
// {
// name: "Cosmos Laundromat",
// progress: 0.5,
// infoHash: "c9e15763f722f23e98a29decdfae341b98d53056",
// isPaused: false
// }, {
// name: "Sintel",
// progress: 0.5,
// infoHash: "08ada5a7a6183aae1e09d831df6748d566095a10",
// isPaused: false
// }, {
// name: "Tears of Steel",
// progress: 0.5,
// infoHash: "209c8226b299b308beaf2b9cd3fb49212dbd13ec",
// isPaused: false
// }, {
// name: "The WIRED CD - Rip. Sample. Mash. Share",
// progress: 0.5,
// infoHash: "a88fda5954e89178c372716a6a78b8180ed4dad3",
// isPaused: false
// }
// ]);
setInterval(() => {
for (let torrent of this.torrents) {
torrent.progress = Math.random();
}
}, 3000);
}
});
</script>
<style lang="css">
.grid-cols-torrent-list {
grid-template-columns: min-content 1fr min-content;
}
@media (min-width: 768px) {
.grid-cols-torrent-list {
grid-template-columns: min-content 1fr repeat(3, minmax(0, 0.20fr)) min-content;
}
}
tr:last-of-type > div {
@apply border-b-0;
}
tr:hover {
@apply bg-gray-600;
}
.ghost {
opacity: 0.0;
}
</style>

+ 13
- 1
src/app/index.ts View File

@ -1,5 +1,17 @@
import { createApp } from 'vue'
import router from "./routes";
import App from './App.vue'
import "./styles/index.css";
createApp(App).mount('#app');
let app = createApp(App);
app.use(router);
app.mount("#app");
/**
* Dropdown menus
*/
document.addEventListener("click", (event) => {
let dropdownMenus = document.querySelectorAll(".dropdown.active").forEach((elem) => {
elem.classList.remove("active");
});
});

+ 21
- 0
src/app/routes/index.ts View File

@ -0,0 +1,21 @@
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
const routes: RouteRecordRaw[] = [
{
path: "/",
name: "Home",
component: () => import("../views/Home.vue")
},
{
path: "/login",
name: "Login",
component: () => import("../views/Login.vue")
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;

+ 1
- 0
src/app/styles/index.css View File

@ -1,3 +1,4 @@
@import '@fortawesome/fontawesome-free/css/all.css';
@tailwind base;
@tailwind components;
@tailwind utilities;

+ 15
- 0
src/app/views/Home.vue View File

@ -0,0 +1,15 @@
<template>
<torrent-list-new></torrent-list-new>
<!-- <sortable-list></sortable-list> -->
</template>
<script lang="ts">
import { defineComponent } from "vue";
import TorrentListNew from "../components/TorrentListNew.vue";
export default defineComponent({
components: {
TorrentListNew
}
});
</script>

+ 3
- 0
src/app/views/Login.vue View File

@ -0,0 +1,3 @@
<template>
<div>Login</div>
</template>

Loading…
Cancel
Save