Initial commit moving front end out of main repo
This commit is contained in:
126
src/components/Channel_Add.vue
Normal file
126
src/components/Channel_Add.vue
Normal file
@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="grid-x">
|
||||
<div class="medium-11 cell"></div>
|
||||
<div class="medium-1 cell">
|
||||
<button type="button" class="button input-group" v-on:click='Expanded = !Expanded'>
|
||||
<i class="fa fa-plus" aria-hidden="true"></i> Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<transition name="fade">
|
||||
<form v-cloak v-if="Expanded">
|
||||
<h2>Add new Channel</h2>
|
||||
<div class="grid-x">
|
||||
<div class="input-group medium-6 cell">
|
||||
<span class="input-group-label">Title</span>
|
||||
<input class="input-group-field" type="text" placeholder="Title" v-model="Title">
|
||||
</div>
|
||||
<div class="input-group medium-1 cell"></div>
|
||||
<div class="input-group medium-5 cell">
|
||||
<span class="input-group-label">Channel</span>
|
||||
<input class="input-group-field" v-bind:class="{ error: !Valid }" type="text"
|
||||
placeholder="URL" v-model="Channel_Identification_Box">
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<span class="input-group-label">Description</span>
|
||||
<input class="input-group-field" type="text" placeholder="Description" v-model="Description">
|
||||
</div>
|
||||
<div class="grid-x">
|
||||
<img class="small-4 cell" :src="Thumbnail" />
|
||||
<div class="small-7"></div>
|
||||
<button type="button" style="max-height:50px" class="button input-group small-1 cell" @click="Submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import * as Dyt from "../dumbyt";
|
||||
import Axios from "axios";
|
||||
import SA2 from "sweetalert2";
|
||||
|
||||
// Vue class for keeping state of the videos.
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
Title: "",
|
||||
Description: "",
|
||||
ID: "",
|
||||
Thumbnail: "",
|
||||
Valid: false,
|
||||
Expanded: false,
|
||||
Channel_Identification_Box : ""
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
Channel_Identification_Box(newcontents: string) {
|
||||
this.GetChannelFromYT(newcontents);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
Submit() : void {
|
||||
Dyt.channel_modify(this.ID, Dyt.Modification.Add).then((resp) => {
|
||||
if (resp == null)
|
||||
SA2(
|
||||
"Channel Add Success!",
|
||||
"The channel has been added succesfully, video contents should be updated shortly.",
|
||||
"success"
|
||||
);
|
||||
else
|
||||
SA2(
|
||||
"Channel Add Fail!",
|
||||
"The channel has not been added due to the following: \n" + resp,
|
||||
"error"
|
||||
);
|
||||
});
|
||||
|
||||
// Hide the bars and erase internal state.
|
||||
this.Expanded = false;
|
||||
this.Title = "";
|
||||
this.Description = "";
|
||||
this.ID = "";
|
||||
this.Thumbnail = "";
|
||||
this.Valid = false;
|
||||
this.Channel_Identification_Box = "";
|
||||
},
|
||||
|
||||
GetChannelFromYT(Channel: string) : void {
|
||||
// Say it failed first so if we exit early then correctly marked fail.
|
||||
this.Valid = false;
|
||||
|
||||
// Copy over to internal ID box.
|
||||
this.ID = Channel;
|
||||
|
||||
// Remove any potential youtube URL from the field.
|
||||
const ytchurl = "https://www.youtube.com/channel/";
|
||||
if (this.ID.startsWith(ytchurl))
|
||||
this.ID = this.ID.replace(ytchurl, "");
|
||||
|
||||
// Check if what remains looks like a youtube channel ID.
|
||||
if (this.ID.length != "UCyS4xQE6DK4_p3qXQwJQAyA".length)
|
||||
return;
|
||||
|
||||
// Get channel metadata.
|
||||
const API = 'https://www.googleapis.com/youtube/v3/channels?';
|
||||
const API_Parts = 'part=snippet%2CcontentDetails%2Cstatistics';
|
||||
const API_Key = '&key=AIzaSyCuIYkMc5SktlnXRXNaDf2ObX-fQvtWCnQ'
|
||||
const API_Search_ID = '&id=' + this.ID;
|
||||
Axios.get(API + API_Parts + API_Search_ID + API_Key).then((resp) => {
|
||||
this.Description = resp.data.items[0].snippet.description;
|
||||
this.Title = resp.data.items[0].snippet.title;
|
||||
this.Thumbnail = resp.data.items[0].snippet.thumbnails.medium.url;
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
40
src/components/Channel_Table.vue
Normal file
40
src/components/Channel_Table.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Title</th>
|
||||
<th>Description</th>
|
||||
<th>User Tags</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-cloak v-for="channel in Channels" :key=channel.ID class="Grid_Small_Text">
|
||||
<td>{{channel.ID}}</td>
|
||||
<td>{{channel.Title}}</td>
|
||||
<td>{{channel.Description}}</td>
|
||||
<td>{{channel.User_Tags}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue, {PropOptions} from "vue";
|
||||
import * as Dyt from "../dumbyt";
|
||||
|
||||
// Vue class for keeping state of the videos.
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
Channels: {
|
||||
type: Array,
|
||||
} as PropOptions<Dyt.Channel[]>,
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.Grid_Small_Text {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
</style>
|
45
src/components/Search.vue
Normal file
45
src/components/Search.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div>
|
||||
<input id="searchbox0" class="searchbox" type="text" v-model="searchbox_str"
|
||||
placeholder="Search String goes here, for example: >5m,c++"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue, {PropOptions} from "vue";
|
||||
import * as _ from "lodash";
|
||||
import * as Dyt from "../dumbyt";
|
||||
|
||||
// Vue class for keeping state of the videos.
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
searchbox_str: "",
|
||||
}
|
||||
},
|
||||
|
||||
// Manually attaching functions to watchers of data.
|
||||
watch: {
|
||||
// Searchbox updater with debouncing.
|
||||
searchbox_str (s: string) {
|
||||
var v = _.debounce((s) => {
|
||||
Dyt.search_videos(s).then(videos => {
|
||||
this.$emit('search_complete', videos)
|
||||
});
|
||||
}, 400)
|
||||
v(s);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.searchbox {
|
||||
font-size: 1.2em;
|
||||
max-width: 40em;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 2em;
|
||||
}
|
||||
</style>
|
52
src/components/Video_Grid.vue
Normal file
52
src/components/Video_Grid.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="grid-x large-up-6">
|
||||
<div v-for="video in Videos" :key="video.ID" class="cell">
|
||||
<div class="card ytcard">
|
||||
<a :href="video.URL"><img :src="video.Thumbnail"></a>
|
||||
<div class="card-section description">
|
||||
<div class="descriptiontitle">{{ video.Title }}</div>
|
||||
<div class="descriptionchannel">{{ video.Channel }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue, {PropOptions} from "vue";
|
||||
import * as Dyt from "../dumbyt";
|
||||
|
||||
// Vue class for keeping state of the videos.
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
Videos: {
|
||||
type: Array,
|
||||
} as PropOptions<Dyt.Video[]>,
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ytcard {
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
background-color:#ddd9d4;
|
||||
margin: 6px 2px 6px;
|
||||
}
|
||||
|
||||
.description {
|
||||
padding: 7px 5px 7px;
|
||||
}
|
||||
|
||||
.descriptiontitle {
|
||||
font-size: 14px;
|
||||
padding-bottom:10px;
|
||||
}
|
||||
|
||||
.descriptionchannel {
|
||||
font-size: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
43
src/components/Video_Table.vue
Normal file
43
src/components/Video_Table.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Title</th>
|
||||
<th>Channel</th>
|
||||
<th>Seconds</th>
|
||||
<th>Tags</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-cloak v-for="video in Videos" :key=video.ID class="Grid_Small_Text">
|
||||
<td>{{video.ID}}</td>
|
||||
<td>{{video.Title}}</td>
|
||||
<td>{{video.Channel}}</td>
|
||||
<td>{{video.Seconds}}</td>
|
||||
<td>{{video.Tags}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue, {PropOptions} from "vue";
|
||||
import * as Dyt from "../dumbyt";
|
||||
|
||||
// Vue class for keeping state of the videos.
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
Videos: {
|
||||
type: Array,
|
||||
} as PropOptions<Dyt.Video[]>,
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.Grid_Small_Text {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user