Skip to content

Commit

Permalink
Merge pull request #389 from MrBrax/develop-ts
Browse files Browse the repository at this point in the history
Develop ts
  • Loading branch information
MrBrax authored Dec 4, 2022
2 parents 3ca8148 + c2958c7 commit 31a6e3f
Show file tree
Hide file tree
Showing 44 changed files with 590 additions and 450 deletions.
9 changes: 7 additions & 2 deletions .github/workflows/check-client.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@ jobs:
matrix:
node-version: [18.x]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: "true"
- name: Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install -g yarn
- name: yarn install, lint and build
run: |
cd ./client-vue
cd ./twitch-vod-chat
yarn install --immutable --immutable-cache
yarn buildlib
cd ../client-vue
yarn install --immutable --immutable-cache
yarn lint
yarn test
Expand Down
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ COPY --chown=node:node --chmod=775 ./twitch-vod-chat /usr/local/share/twitchauto
RUN cd /usr/local/share/twitchautomator/twitch-vod-chat \
&& yarn install --immutable --immutable-cache \
&& yarn build --base=/vodplayer \
&& yarn buildlib \
&& rm -rf node_modules \
&& rm -rf .yarn/cache \
&& yarn cache clean --all
Expand Down Expand Up @@ -111,6 +112,7 @@ COPY ./docker/fonts /home/node/.fonts
# twitchautomator docker specific configs
ENV TCD_BIN_DIR=/usr/local/bin
ENV TCD_FFMPEG_PATH=/usr/bin/ffmpeg
ENV TCD_BIN_PATH_PYTHON=/usr/bin/python
ENV TCD_MEDIAINFO_PATH=/usr/bin/mediainfo
ENV TCD_NODE_PATH=/usr/local/bin/node
ENV TCD_DOCKER=1
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ If you want the public webapp to have a custom base folder, you must provide `BA
2. Place the downloaded files in a folder with good permissions.
3. Enter the root folder and run `pip install -r requirements.txt`
4. Build the packages (yarn pnp is now used, so `yarn install` might not be required)
- Enter the `/twitch-vod-chat` folder and run `yarn install` and `yarn run buildlib`.
- Enter the `/client-vue` folder and run `yarn install` and `yarn run build`.
- Enter the `/server` folder and run `yarn install` and `yarn run build`.
- Enter the `/twitch-chat-dumper` folder and run `yarn install` and `yarn run build`.
- Enter the `/twitch-vod-chat` folder and run `yarn install` and `yarn run build --base=/vodplayer`.
5. In the `/server` folder, run `yarn run start` to start the server in production mode.
6. Go to the settings page and set up basic stuff, get api key from twitch dev site.
7. Check the About page for subscription status.
Expand Down
2 changes: 1 addition & 1 deletion client-vue/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "livestreamdvr-client",
"version": "2.2.3",
"version": "2.2.4",
"private": true,
"homepage": "https://github.com/MrBrax/LiveStreamDVR",
"scripts": {
Expand Down
19 changes: 11 additions & 8 deletions client-vue/src/components/VodItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ import type { ApiResponse, ApiSettingsResponse } from "@common/Api/Api";
import { formatString } from "@common/Format";
import { format } from "date-fns";
import axios from "axios";
import { useRoute } from "vue-router";
import { useRoute, useRouter } from "vue-router";
import { isTwitchVOD } from "@/mixins/newhelpers";
import VodItemControls from "./VodItemControls.vue";
import type { VODTypes } from "@/twitchautomator";
Expand Down Expand Up @@ -346,6 +346,7 @@ const emit = defineEmits(["forceFetchData", "refresh", "toggleMinimize"]);
const store = useStore();
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
Expand Down Expand Up @@ -410,6 +411,7 @@ const renameVodTemplatePreview = computed(() => {
season: props.vod.stream_season || "",
absolute_season: props.vod.stream_absolute_season ? props.vod.stream_absolute_season.toString().padStart(2, "0") : "",
episode: props.vod.stream_number ? props.vod.stream_number.toString().padStart(2, "0") : "",
absolute_episode: props.vod.stream_absolute_number ? props.vod.stream_absolute_number.toString().padStart(2, "0") : "",
};
const replaced_string = formatString(renameVodSettings.value.template, replacements);
return replaced_string;
Expand Down Expand Up @@ -533,22 +535,23 @@ function doFixIssues(): void {
function openPlayer(): void {
if (!props.vod) return;
let url = `${store.cfg<string>("basepath", "")}/vodplayer/index.html#&`;
url += "source=file_http";
// let url = `${store.cfg<string>("basepath", "")}/vodplayer#&`;
let hash = "#&source=file_http";
if (playerSettings.value.vodSource == "captured"){
url += `&video_path=${props.vod.webpath}/${props.vod.basename}.mp4`;
hash += `&video_path=${props.vod.webpath}/${props.vod.basename}.mp4`;
} else {
url += `&video_path=${props.vod.webpath}/${props.vod.basename}_vod.mp4`;
hash += `&video_path=${props.vod.webpath}/${props.vod.basename}_vod.mp4`;
}
if (playerSettings.value.chatSource == "captured"){
url += `&chatfile=${props.vod.webpath}/${props.vod.basename}.chatdump`;
hash += `&chatfile=${props.vod.webpath}/${props.vod.basename}.chatdump`;
} else {
url += `&chatfile=${props.vod.webpath}/${props.vod.basename}_chat.json`;
hash += `&chatfile=${props.vod.webpath}/${props.vod.basename}_chat.json`;
}
// url.searchParams.set("offset", this.playerSettings.offset.toString());
window.open(url.toString(), "_blank");
// window.open(url.toString(), "_blank");
router.push({ name: "VODPlayer", hash });
}
Expand Down
2 changes: 1 addition & 1 deletion client-vue/src/components/VodItemChapters.vue
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ function playerLink(offset = 0, chatdownload = false): string {
if (!props.vod || !store.config) return "#";
const video_path = `${props.vod.webpath}/${props.vod.basename}.mp4`;
const chat_path = `${props.vod.webpath}/${props.vod.basename}.${chatdownload ? "chat" : "chatdump"}`;
return `${store.cfg<string>("basepath", "")}/vodplayer/index.html#source=file_http&video_path=${video_path}&chatfile=${chat_path}&offset=${offset}`;
return `${store.cfg<string>("basepath", "")}/vodplayer#source=file_http&video_path=${video_path}&chatfile=${chat_path}&offset=${offset}`;
}
function twitchVideoLink(video_id: string): string {
Expand Down
108 changes: 59 additions & 49 deletions client-vue/src/components/forms/SettingsForm.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<form
v-if="!loading && settingsFields && formData"
v-if="!loading && fetchedSettingsFields && formData"
method="POST"
enctype="multipart/form-data"
action="#"
Expand All @@ -23,8 +23,8 @@
</h3>
<ul>
<li
v-for="setting in newAndInterestingSettings"
:key="setting.key"
v-for="(setting, key) of newAndInterestingSettings"
:key="key"
>
Under <strong>{{ setting.group }}</strong>: {{ setting.text }} ({{ setting.help }})
</li>
Expand All @@ -38,16 +38,16 @@
>
<summary>{{ te('configgroup.' + groupData.name) ? t('configgroup.' + groupData.name) : groupData.name }}</summary>
<div
v-for="(data, index) in groupData.fields"
:key="index"
v-for="(data, key) of groupData.fields"
:key="key"
class="field"
>
<label
v-if="data.type != 'boolean'"
class="label"
:for="`input_${data.key}`"
:for="`input_${key}`"
>
{{ te('config.' + data.key) ? t('config.' + data.key) : data.text }} <span
{{ te('config.' + key) ? t('config.' + key) : data.text }} <span
v-if="data.required"
class="required"
>*</span>
Expand All @@ -64,10 +64,10 @@
>
<label class="checkbox">
<input
:id="`input_${data.key}`"
v-model="(formData.config[data.key] as boolean)"
:id="`input_${key}`"
v-model="(formData.config[key] as boolean)"
type="checkbox"
:name="data.key"
:name="key"
>
{{ data.text }}
</label>
Expand All @@ -80,20 +80,20 @@
>
<input
v-if="!data.multiline"
:id="`input_${data.key}`"
v-model="formData.config[data.key]"
:id="`input_${key}`"
v-model="formData.config[key]"
class="input"
type="text"
:name="data.key"
:name="key.toString()"
:title="data.help"
:pattern="data.pattern"
>
<textarea
v-if="data.multiline"
:id="`input_${data.key}`"
v-model="(formData.config[data.key] as string)"
:id="`input_${key}`"
v-model="(formData.config[key] as string)"
class="input textarea"
:name="data.key"
:name="key.toString()"
:title="data.help"
:pattern="data.pattern"
/>
Expand All @@ -105,11 +105,11 @@
class="control"
>
<input
:id="`input_${data.key}`"
v-model.number="formData.config[data.key]"
:id="`input_${key}`"
v-model.number="formData.config[key]"
class="input"
type="number"
:name="data.key"
:name="key"
>
</div>

Expand All @@ -122,19 +122,19 @@
<div class="select">
<select
v-if="data.choices"
:id="`input_${data.key}`"
v-model="formData.config[data.key]"
:id="`input_${key}`"
v-model="formData.config[key]"
class="input"
:name="data.key"
:name="key"
:data-is-array="data.choices && Array.isArray(data.choices)"
>
<template v-if="data.choices && Array.isArray(data.choices)">
<option
v-for="(item, ix) in data.choices"
:key="ix"
:selected="
(formData.config[data.key] !== undefined && formData.config[data.key] === item) ||
(formData.config[data.key] === undefined && item === data.default)
(formData.config[key] !== undefined && formData.config[key] === item) ||
(formData.config[key] === undefined && item === data.default)
"
>
{{ item }}
Expand All @@ -146,8 +146,8 @@
:key="ix"
:value="ix"
:selected="
(formData.config[data.key] !== undefined && formData.config[data.key] === item) ||
(formData.config[data.key] === undefined && ix === data.default)
(formData.config[key] !== undefined && formData.config[key] === item) ||
(formData.config[key] === undefined && ix === data.default)
"
>
{{ item }}
Expand All @@ -165,19 +165,19 @@
>
<textarea
v-if="data.multiline"
:id="`input_${data.key}`"
v-model="(formData.config[data.key] as string)"
:id="`input_${key}`"
v-model="(formData.config[key] as string)"
class="input"
type="text"
:name="data.key"
:name="key"
/>
<input
v-else
:id="`input_${data.key}`"
v-model="formData.config[data.key]"
:id="`input_${key}`"
v-model="formData.config[key]"
class="input"
type="text"
:name="data.key"
:name="key"
>
<ul class="template-replacements">
<li
Expand All @@ -189,21 +189,21 @@
type="button"
class="deprecated"
title="Deprecated"
@click="insertReplacement(data.key, ix)"
@click="insertReplacement(key, ix)"
>
<span class="strikethrough">&lbrace;{{ ix }}&rbrace;</span>
</button>
<button
v-else
type="button"
@click="insertReplacement(data.key, ix)"
@click="insertReplacement(key, ix)"
>
&lbrace;{{ ix }}&rbrace;
</button>
</li>
</ul>
<p class="template-preview">
{{ templatePreview(data, formData.config[data.key] as string) }}
{{ templatePreview(data, formData.config[key] as string) }}
</p>
</div>

Expand Down Expand Up @@ -270,6 +270,7 @@ import { formatString } from "@common/Format";
import YoutubeAuth from "@/components/YoutubeAuth.vue";
import TwitchAuth from "@/components/TwitchAuth.vue";
import FormSubmit from "@/components/reusables/FormSubmit.vue";
import type { settingsFields } from "@common/ServerConfig";
import { library } from "@fortawesome/fontawesome-svg-core";
import { faGlobe, faSave } from "@fortawesome/free-solid-svg-icons";
Expand All @@ -279,7 +280,7 @@ library.add(faGlobe, faSave);
interface SettingsGroup {
name: string;
fields: SettingField<string | number | boolean>[];
fields: Record<string, SettingField<string | number | boolean>>;
}
// emit
Expand All @@ -293,27 +294,29 @@ const { t, te } = useI18n();
const formStatusText = ref<string>("Ready");
const formStatus = ref<FormStatus>("IDLE");
const formData = ref<{ config: Record<string, string | number | boolean> }>({ config: {} });
const settingsFields = ref<SettingField<string | number | boolean>[]>([]);
const fetchedSettingsFields = ref<typeof settingsFields>({});
const loading = ref<boolean>(false);
const searchText = ref<string>("");
// computed
const settingsGroups = computed((): SettingsGroup[] => {
if (!settingsFields.value) return [];
if (!fetchedSettingsFields.value) return [];
const groups: Record<string, SettingsGroup> = {};
for (const field of settingsFields.value) {
for (const key in fetchedSettingsFields.value) {
const field = fetchedSettingsFields.value[key];
if (!field.group) continue;
if (searchText.value) {
if (
!field.key.toLowerCase().includes(searchText.value.toLowerCase()) &&
!key.toLowerCase().includes(searchText.value.toLowerCase()) &&
!field.help?.toLowerCase().includes(searchText.value.toLowerCase()) &&
!field.text?.toLowerCase().includes(searchText.value.toLowerCase())
) continue;
}
if (!groups[field.group]) groups[field.group] = { name: field.group, fields: [] };
groups[field.group].fields.push(field);
if (!groups[field.group]) groups[field.group] = { name: field.group, fields: {} };
groups[field.group].fields[key] = field;
}
return Object.values(groups).filter((group) => group.fields.length > 0);
// return Object.values(groups).filter((group) => group.fields.length > 0);
return Object.values(groups);
/*
return Object.values(groups).filter((group) => {
Expand All @@ -325,8 +328,14 @@ const settingsGroups = computed((): SettingsGroup[] => {
*/
});
const newAndInterestingSettings = computed((): SettingField<string | number | boolean>[] => {
return settingsFields.value.filter((field) => field.new);
const newAndInterestingSettings = computed((): typeof settingsFields => {
const newSettings: typeof settingsFields = {};
for (const key in fetchedSettingsFields.value) {
const field = fetchedSettingsFields.value[key];
if (field.new) newSettings[key] = field;
}
return newSettings;
// return fetchedSettingsFields.value.filter((field) => field.new);
});
/*
Expand Down Expand Up @@ -367,12 +376,13 @@ function fetchData(): void {
axios.get<ApiSettingsResponse>("/api/v0/settings").then((response) => {
const data = response.data;
formData.value.config = data.data.config;
settingsFields.value = data.data.fields;
fetchedSettingsFields.value = data.data.fields;
// set defaults
for (const field of settingsFields.value) {
if (field.default !== undefined && formData.value.config[field.key] === undefined) {
formData.value.config[field.key] = field.default;
for (const key in fetchedSettingsFields.value) {
const field = fetchedSettingsFields.value[key];
if (field.default !== undefined && formData.value.config[key] === undefined) {
formData.value.config[key] = field.default;
}
}
Expand Down
Loading

0 comments on commit 31a6e3f

Please sign in to comment.