Updated History WebUI to include information about each event.

This commit is contained in:
Abdulmhsen B. A. A
2024-05-11 20:10:36 +03:00
parent 465c80f5b4
commit c5573003a8
7 changed files with 508 additions and 76 deletions

View File

@@ -3,40 +3,336 @@
<div class="column is-12 is-clearfix">
<span class="title is-4">
<NuxtLink href="/history">History</NuxtLink>
: {{ id }}
: {{ data?.full_title ?? data?.title ?? id }}
</span>
<div class="is-pulled-right">
<div class="is-pulled-right" v-if="data?.via">
<div class="field is-grouped">
<p class="control">
<button class="button is-info" @click="loadContent(id)">
<button class="button is-info" @click="loadContent(id)" :class="{'is-loading':isLoading}">
<span class="icon"><i class="fas fa-sync"></i></span>
</button>
</p>
</div>
</div>
<div class="subtitle">
This page still not done, and will be updated at later stages.
<div class="subtitle" v-if="data?.via && getTitle !== data.title">
{{ getTitle }}
</div>
</div>
<div class="column is-12">
<pre><code>{{ data }}</code></pre>
<div class="column is-12" v-if="!data?.via && isLoading">
<Message>
<span class="icon"><i class="fas fa-spinner fa-pulse"></i></span>
<span>Loading data. Please wait...</span>
</Message>
</div>
<div class="column is-12" v-if="data?.via">
<div class="card" :class="{ 'is-success': parseInt(data.watched), 'is-danger': !data.watched }">
<header class="card-header">
<div class="card-header-title">Latest metadata info</div>
<div class="card-header-icon">
<button class="button is-small" @click="toggleWatched"
:class="{ 'is-success': !data.watched, 'is-danger': data.watched }">
<span class="icon" v-if="data.watched"><i class="fas fa-eye-slash"></i></span>
<span class="icon" v-else><i class="fas fa-eye"></i></span>
<span>Mark as <span v-if="data.watched">Unplayed</span><span v-else>played</span></span>
</button>
</div>
</header>
<div class="card-content">
<div class="columns is-multiline is-mobile">
<div class="column is-6 has-text-left">
<span class="icon-text">
<span class="icon"><i class="fas fa-server"></i></span>
<span>
<span class="is-hidden-mobile">Via:&nbsp;</span>
<NuxtLink :href="`/backend/${data.via}`" v-text="data.via"/>
</span>
</span>
</div>
<div class="column is-6 has-text-right">
<span class="icon-text">
<span class="icon"><i class="fas fa-passport"></i></span>
<span>
<span class="is-hidden-mobile">ID:</span>
{{ data.id }}
</span>
</span>
</div>
<div class="column is-6 has-text-left">
<span class="icon-text">
<span class="icon">
<i class="fas fa-eye-slash" v-if="!data.watched"></i>
<i class="fas fa-eye" v-else></i>
</span>
<span>
<span class="is-hidden-mobile">Status:</span>
{{ data.watched ? 'Played' : 'Unplayed' }}
</span>
</span>
</div>
<div class="column is-6 has-text-right">
<span class="icon-text">
<span class="icon"><i class="fas fa-envelope"></i></span>
<span>
<span class="is-hidden-mobile">Event:</span>
{{ ag(data.extra, `${data.via}.event`, 'Unknown') }}
</span>
</span>
</div>
<div class="column is-6 has-text-left">
<span class="icon-text">
<span class="icon"><i class="fas fa-calendar"></i></span>
<span>
<span class="is-hidden-mobile">Updated:</span>
{{ moment(data.updated).fromNow() }}
</span>
</span>
</div>
<div class="column is-6 has-text-right">
<span class="icon-text">
<span class="icon" v-if="'episode' === data.type"><i class="fas fa-tv"></i></span>
<span class="icon" v-else><i class="fas fa-film"></i></span>
<span>
<span class="is-hidden-mobile">Type:</span>
{{ ucFirst(data.type) }}
</span>
</span>
</div>
<div class="column is-6 has-text-left" v-if="'episode' === data.type">
<span class="icon-text">
<span class="icon"><i class="fas fa-tv"></i></span>
<span><span class="is-hidden-mobile">Season:</span> {{ data.season }}</span>
</span>
</div>
<div class="column is-6 has-text-right" v-if="'episode' === data.type">
<span class="icon-text">
<span class="icon"><i class="fas fa-tv"></i></span>
<span><span class="is-hidden-mobile">Episode:</span> {{ data.episode }}</span>
</span>
</div>
<div class="column is-12" v-if="data.guids && Object.keys(data.guids).length>0">
<span class="icon-text">
<span class="icon"><i class="fas fa-link"></i></span>
<span>
{{ ucFirst(data.type) }} GUIDs:
<span class="tag mr-1" v-for="(guid,source) in data.guids">
{{ source.split('guid_')[1] }} : {{ guid }}
</span>
</span>
</span>
</div>
<div class="column is-12" v-if="data.parent && Object.keys(data.parent).length>0">
<span class="icon-text">
<span class="icon"><i class="fas fa-link"></i></span>
<span>
Series GUIDs:
<span class="tag mr-1" v-for="(guid,source) in data.parent">
{{ source.split('guid_')[1] }} : {{ guid }}
</span>
</span>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="column is-12" v-if="data?.via && Object.keys(data.metadata).length>0">
<div class="card" v-for="(item, key) in data.metadata" :key="key"
:class="{ 'is-success': parseInt(item.watched), 'is-danger': !parseInt(item.watched) }">
<header class="card-header">
<div class="card-header-title">
Metadata from
</div>
<div class="card-header-icon">
<span class="icon-text">
<span class="icon"><i class="fas fa-server"></i></span>
<span>
<NuxtLink :href="`/backend/${key}`" v-text="key"/>
</span>
</span>
</div>
</header>
<div class="card-content">
<div class="columns is-multiline is-mobile">
<div class="column is-12 has-text-left">
<span class="icon-text">
<span class="icon"><i class="fas fa-passport"></i></span>
<span>
<span class="is-hidden-mobile">ID:</span>
{{ item.id }}
</span>
</span>
</div>
<div class="column is-6 has-text-left">
<span class="icon-text">
<span class="icon">
<i class="fas fa-eye-slash" v-if="!parseInt(item.watched)"></i>
<i class="fas fa-eye" v-else></i>
</span>
<span>
<span class="is-hidden-mobile">Status:</span>
{{ parseInt(item.watched) ? 'Played' : 'Unplayed' }}
</span>
</span>
</div>
<div class="column is-6 has-text-right">
<span class="icon-text">
<span class="icon"><i class="fas fa-envelope"></i></span>
<span>
<span class="is-hidden-mobile">Event:</span>
{{ ag(data.extra, `${key}.event`, 'Unknown') }}
</span>
</span>
</div>
<div class="column is-6 has-text-left">
<span class="icon-text">
<span class="icon"><i class="fas fa-calendar"></i></span>
<span>
<span class="is-hidden-mobile">Updated:</span>
{{ moment(ag(data.extra, `${key}.received_at`, data.updated)).fromNow() }}
</span>
</span>
</div>
<div class="column is-6 has-text-right">
<span class="icon-text">
<span class="icon" v-if="'episode' === item.type"><i class="fas fa-tv"></i></span>
<span class="icon" v-else><i class="fas fa-film"></i></span>
<span>
<span class="is-hidden-mobile">Type:</span>
{{ ucFirst(item.type) }}
</span>
</span>
</div>
<div class="column is-6 has-text-left" v-if="'episode' === item.type">
<span class="icon-text">
<span class="icon"><i class="fas fa-tv"></i></span>
<span><span class="is-hidden-mobile">Season:</span> {{ item.season }}</span>
</span>
</div>
<div class="column is-6 has-text-right" v-if="'episode' === item.type">
<span class="icon-text">
<span class="icon"><i class="fas fa-tv"></i></span>
<span><span class="is-hidden-mobile">Episode:</span> {{ item.episode }}</span>
</span>
</div>
<div class="column is-12" v-if="item.guids && Object.keys(item.guids).length>0">
<span class="icon-text">
<span class="icon"><i class="fas fa-link"></i></span>
<span>
{{ ucFirst(item.type) }} GUIDs:
<span class="tag mr-1" v-for="(guid,source) in item.guids">
{{ source.split('guid_')[1] }} : {{ guid }}
</span>
</span>
</span>
</div>
<div class="column is-12" v-if="item.parent && Object.keys(item.parent).length>0">
<span class="icon-text">
<span class="icon"><i class="fas fa-link"></i></span>
<span>
Series GUIDs:
<span class="tag mr-1" v-for="(guid,source) in item.parent">
{{ source.split('guid_')[1] }} : {{ guid }}
</span>
</span>
</span>
</div>
</div>
</div>
</div>
</div>
<div class=""></div>
</div>
</template>
<script setup>
import request from '~/utils/request.js'
import {ag, notification, ucFirst} from '~/utils/index.js'
import moment from 'moment'
const id = useRoute().params.id
useHead({title: `History : ${id}`})
const data = ref();
const isLoading = ref(false)
const data = ref({
id: id,
title: `${id}`,
via: null,
metadata: {},
guids: {},
parent: {},
});
const loadContent = async (id) => {
isLoading.value = true
const response = await request(`/history/${id}`)
data.value = await response.json();
const json = await response.json()
isLoading.value = false
if (200 !== response.status) {
notification('Error', 'Error loading data', `${json.error.code}: ${json.error.message}`);
return
}
data.value = json
}
const getTitle = computed(() => {
if (!data.value) {
return id
}
if (data.value?.via) {
return ag(data.value, `metadata.${data.value.via}.extra.title`, data.value.title)
}
return data.value.title
})
const toggleWatched = async () => {
if (!data.value) {
return
}
if (!confirm(`Mark '${data.value.full_title}' as ${data.value.watched ? 'unplayed' : 'played'}?`)) {
return
}
try {
const response = await request(`/history/${data.value.id}/watch`, {
method: data.value.watched ? 'DELETE' : 'POST'
})
const json = await response.json()
if (200 !== response.status) {
notification('error', 'Error', `${json.error.code}: ${json.error.message}`)
return
}
data.value = json
notification('success', '', `Marked '${data.value.full_title}' as ${data.value.watched ? 'played' : 'unplayed'}`)
} catch (e) {
notification('error', 'Error', `Failed to update watched status. ${e}`)
}
}
onMounted(async () => loadContent(id))

View File

@@ -117,10 +117,12 @@
<div class="column is-6-tablet" v-for="item in items" :key="item.id">
<div class="card">
<header class="card-header">
<p class="card-header-title is-text-overflow is-justify-center pr-1">
<NuxtLink :to="'/history/'+item.id">
{{ item.full_title ?? item.title }}
</NuxtLink>
<p class="card-header-title is-text-overflow pr-1">
<span class="icon" v-if="!item.progress">
<i class="fas fa-eye-slash" v-if="!item.watched"></i>
<i class="fas fa-eye" v-else></i>
</span>
<NuxtLink :to="'/history/'+item.id" v-text="item.full_title ?? item.title"/>
</p>
<span class="card-header-icon">
<span class="icon" v-if="'episode' === item.type"><i class="fas fa-tv"></i></span>
@@ -129,30 +131,36 @@
</header>
<div class="card-content">
<div class="columns is-multiline is-mobile has-text-centered">
<div class="column is-6-mobile">
{{ moment(item.updated).fromNow() }}
</div>
<div class="column is-6-mobile">
<NuxtLink :href="'/backend/'+item.via">
{{ item.via }}
</NuxtLink>
</div>
<div class="column is-6-mobile" v-if="item.event">
<span v-tooltip="'The event which triggered the update.'" class="has-tooltip">
{{ item.event }}
<div class="column is-4-tablet is-6-mobile has-text-left">
<span class="icon-text">
<span class="icon"><i class="fas fa-calendar"></i>&nbsp;</span>
{{ moment(item.updated).fromNow() }}
</span>
</div>
<div class="column is-6-mobile">
<span class="has-text-success" v-if="item.watched">Played</span>
<span class="has-text-danger" v-else>Unplayed</span>
<div class="column is-4-tablet is-6-mobile has-text-right-mobile">
<span class="icon-text">
<span class="icon"><i class="fas fa-server"></i></span>
<span>
<NuxtLink :href="'/backend/'+item.via" v-text="item.via"/>
</span>
</span>
</div>
<div class="column is-6-mobile" v-if="item.progress && !item.watched">
<span v-tooltip="'Play Progress'">
{{ item.progress }}
<div class="column is-4-tablet is-12-mobile has-text-left-mobile">
<span class="icon-text">
<span class="icon"><i class="fas fa-envelope"></i></span>
<span>{{ item.event }}</span>
</span>
</div>
</div>
</div>
<div class="card-footer" v-if="item.progress">
<div class="card-footer-item">
<span class="has-text-success" v-if="item.watched">Played</span>
<span class="has-text-danger" v-else>Unplayed</span>
</div>
<div class="card-footer-item">{{ item.progress }}</div>
</div>
</div>
</div>
</div>

View File

@@ -9,12 +9,14 @@
<div class="column is-12">
<div class="columns is-multiline" v-if="lastHistory.length>0">
<div class="column is-6-tablet" v-for="history in lastHistory" :key="history.id">
<div class="card">
<div class="card" :class="{ 'is-success': history.watched, 'is-danger': !history.watched }">
<header class="card-header">
<p class="card-header-title is-text-overflow is-justify-center pr-1">
<NuxtLink :href="`/history/${history.id}`">
{{ history.full_title ?? history.title }}
</NuxtLink>
<p class="card-header-title is-text-overflow pr-1">
<span class="icon" v-if="!history.progress">
<i class="fas fa-eye-slash" v-if="!history.watched"></i>
<i class="fas fa-eye" v-else></i>
</span>
<NuxtLink :href="`/history/${history.id}`" v-text="history.full_title ?? history.title"/>
</p>
<span class="card-header-icon">
<span class="icon" v-if="'episode' === history.type"><i class="fas fa-tv"></i></span>
@@ -22,31 +24,36 @@
</span>
</header>
<div class="card-content">
<div class="columns is-multiline is-mobile has-text-centered">
<div class="column is-6-mobile">
{{ moment(history.updated).fromNow() }}
</div>
<div class="column is-6-mobile">
<NuxtLink :href="'/backend/'+history.via">
{{ history.via }}
</NuxtLink>
</div>
<div class="column is-6-mobile" v-if="history.event">
<span v-tooltip="'The event which triggered the update.'" class="has-tooltip">
{{ history.event }}
<div class="columns is-multiline is-mobile">
<div class="column is-4-tablet is-6-mobile has-text-left">
<span class="icon-text">
<span class="icon"><i class="fas fa-calendar"></i>&nbsp;</span>
{{ moment(history.updated).fromNow() }}
</span>
</div>
<div class="column is-6-mobile">
<span class="has-text-success" v-if="history.watched">Played</span>
<span class="has-text-danger" v-else>Unplayed</span>
<div class="column is-4-tablet is-6-mobile has-text-right-mobile">
<span class="icon-text">
<span class="icon"><i class="fas fa-server"></i></span>
<span>
<NuxtLink :href="'/backend/'+history.via" v-text="history.via"/>
</span>
</span>
</div>
<div class="column is-6-mobile" v-if="history.progress && !history.watched">
<span v-tooltip="'Play Progress'">
{{ history.progress }}
<div class="column is-4-tablet is-12-mobile has-text-left-mobile">
<span class="icon-text">
<span class="icon"><i class="fas fa-envelope"></i></span>
<span>{{ history.event }}</span>
</span>
</div>
</div>
</div>
<div class="card-footer" v-if="history.progress">
<div class="card-footer-item">
<span class="has-text-success" v-if="history.watched">Played</span>
<span class="has-text-danger" v-else>Unplayed</span>
</div>
<div class="card-footer-item">{{ history.progress }}</div>
</div>
</div>
</div>
</div>

View File

@@ -2,23 +2,51 @@ import {useNotification} from '@kyvg/vue3-notification'
const {notify} = useNotification();
const ag = (obj, path, defaultValue = null, separator = '.') => {
const keys = path.split(separator)
const AG_SEPARATOR = '.'
/**
* Get value from object or function
*
* @param {Function|*} obj
* @returns {*}
*/
const getValue = (obj) => 'function' === typeof obj ? obj() : obj;
/**
* Get value from object or function and return default value if it's undefined or null
*
* @param {Object|Array} obj The object to get the value from.
* @param {string} path The path to the value.
* @param {*} defaultValue The default value to return if the path is not found.
*
* @returns {*} The value at the path or the default value.
*/
const ag = (obj, path, defaultValue = null) => {
const keys = path.split(AG_SEPARATOR)
let at = obj
for (let key of keys) {
if (typeof at === 'object' && at !== null && key in at) {
at = at[key]
} else {
return defaultValue
return getValue(defaultValue)
}
}
return at
return getValue(at)
}
const ag_set = (obj, path, value, separator = '.') => {
const keys = path.split(separator)
/**
* Set value in object by path
*
* @param {Object} obj The object to set the value in.
* @param {string} path The path to the value.
* @param {*} value The value to set.
*
* @returns {Object} The object with the value set.
*/
const ag_set = (obj, path, value) => {
const keys = path.split(AG_SEPARATOR)
let at = obj
while (keys.length > 0) {
@@ -39,33 +67,73 @@ const ag_set = (obj, path, value, separator = '.') => {
return obj
}
/**
* Convert bytes to human-readable file size
*
* @param {number} bytes The number of bytes.
* @param {boolean} showUnit Whether to show the unit.
* @param {number} decimals The number of decimals.
* @param {number} mod The mod.
*
* @returns {string} The human-readable file size.
*/
const humanFileSize = (bytes = 0, showUnit = true, decimals = 2, mod = 1000) => {
const sz = 'BKMGTP'
const factor = Math.floor((bytes.toString().length - 1) / 3)
return `${(bytes / (mod ** factor)).toFixed(decimals)}${showUnit ? sz[factor] : ''}`
}
/**
* Wait for an element to be loaded in the DOM
*
* @param {string} sel The selector of the element.
* @param {Function} callback The callback function.
*
* @returns {void}
*/
const awaitElement = (sel, callback) => {
let interval = undefined;
let interval = undefined
let $elm = document.querySelector(sel)
if ($elm) {
return callback(sel, $elm)
callback(sel, $elm)
return
}
interval = setInterval(() => {
let $elm = document.querySelector(sel);
let $elm = document.querySelector(sel)
if ($elm) {
clearInterval(interval);
callback(sel, $elm);
clearInterval(interval)
callback(sel, $elm)
}
}, 200)
}
/**
* Uppercase the first letter of a string
*
* @param {string} str The string to uppercase.
*
* @returns {string} The string with the first letter uppercased.
*/
const ucFirst = (str) => {
if (typeof str !== 'string') {
return str
}
return str.charAt(0).toUpperCase() + str.slice(1);
}
const ucFirst = (str) => str.charAt(0).toUpperCase() + str.slice(1);
/**
* Display a notification
*
* @param {string} type The type of the notification.
* @param {string} title The title of the notification.
* @param {string} text The text of the notification.
* @param {number} duration The duration of the notification.
*
* @returns {void}
*/
const notification = (type, title, text, duration = 3000) => {
let classes = ''

View File

@@ -750,7 +750,7 @@
which "^3.0.1"
ws "^8.16.0"
"@nuxt/kit@3.11.2", "@nuxt/kit@^3.10.2", "@nuxt/kit@^3.11.2":
"@nuxt/kit@3.11.2", "@nuxt/kit@^3.10.2", "@nuxt/kit@^3.11.2", "@nuxt/kit@^3.7.3":
version "3.11.2"
resolved "https://registry.yarnpkg.com/@nuxt/kit/-/kit-3.11.2.tgz#dfc43c05992691bcd6aa58c14f88cf43e3abb788"
integrity sha512-yiYKP0ZWMW7T3TCmsv4H8+jEsB/nFriRAR8bKoSqSV9bkVYWPE36sf7JDux30dQ91jSlQG6LQkB3vCHYTS2cIg==
@@ -859,6 +859,14 @@
vite-plugin-checker "^0.6.4"
vue-bundle-renderer "^2.0.0"
"@nuxtjs/device@^3.1.1":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@nuxtjs/device/-/device-3.1.1.tgz#51fa2308c365323b9f3cce923dbebd40cdffe7ac"
integrity sha512-wHTziEevt1hdgePQwPhEedWW3COalhP0YGVB+sGLqSrKujX8vdz7lcBFB01KIftpaP8kY5H8pssibNaJbxGcYw==
dependencies:
"@nuxt/kit" "^3.7.3"
defu "^6.1.2"
"@parcel/watcher-android-arm64@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz#c2c19a3c442313ff007d2d7a9c2c1dd3e1c9ca84"
@@ -2273,7 +2281,7 @@ define-lazy-prop@^3.0.0:
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f"
integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==
defu@^6.0.0, defu@^6.1.3, defu@^6.1.4:
defu@^6.0.0, defu@^6.1.2, defu@^6.1.3, defu@^6.1.4:
version "6.1.4"
resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479"
integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==

View File

@@ -6,12 +6,14 @@ namespace App\API\History;
use App\Commands\Database\ListCommand;
use App\Libs\Attributes\Route\Get;
use App\Libs\Attributes\Route\Route;
use App\Libs\Container;
use App\Libs\Database\DatabaseInterface as iDB;
use App\Libs\DataUtil;
use App\Libs\Entity\StateInterface as iState;
use App\Libs\Guid;
use App\Libs\HTTP_STATUS;
use App\Libs\Mappers\Import\DirectMapper;
use App\Libs\Uri;
use PDO;
use Psr\Http\Message\ResponseInterface as iResponse;
@@ -22,7 +24,7 @@ final class Index
public const string URL = '%{api.prefix}/history';
private PDO $pdo;
public function __construct(private readonly iDB $db)
public function __construct(private readonly iDB $db, private DirectMapper $mapper)
{
$this->pdo = $this->db->getPDO();
}
@@ -394,12 +396,55 @@ final class Index
return api_error('Not found', HTTP_STATUS::HTTP_NOT_FOUND);
}
$item = $item->getAll();
$response = $item->getAll();
$response['full_title'] = $item->getName();
$response[iState::COLUMN_WATCHED] = $item->isWatched();
$response[iState::COLUMN_UPDATED] = makeDate($item->updated);
$item[iState::COLUMN_WATCHED] = $entity->isWatched();
$item[iState::COLUMN_UPDATED] = makeDate($entity->updated);
return api_response(HTTP_STATUS::HTTP_OK, $item);
return api_response(HTTP_STATUS::HTTP_OK, $response);
}
#[Route(['GET', 'POST', 'DELETE'], self::URL . '/{id:\d+}/watch[/]', name: 'history.watch')]
public function historyPlayStatus(iRequest $request, array $args = []): iResponse
{
if (null === ($id = ag($args, 'id'))) {
return api_error('Invalid value for id path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$entity = Container::get(iState::class)::fromArray([iState::COLUMN_ID => $id]);
if (null === ($item = $this->db->get($entity))) {
return api_error('Not found', HTTP_STATUS::HTTP_NOT_FOUND);
}
if ('GET' === $request->getMethod()) {
return api_response(HTTP_STATUS::HTTP_OK, ['watched' => $item->isWatched()]);
}
if ('POST' === $request->getMethod() && true === $item->isWatched()) {
return api_error('Already watched', HTTP_STATUS::HTTP_CONFLICT);
}
if ('DELETE' === $request->getMethod() && false === $item->isWatched()) {
return api_error('Already unwatched', HTTP_STATUS::HTTP_CONFLICT);
}
$item->watched = 'POST' === $request->getMethod() ? 1 : 0;
$item->updated = time();
$item->extra = ag_set($item->getExtra(), $item->via, [
iState::COLUMN_EXTRA_EVENT => 'webui.mark' . ($item->isWatched() ? 'played' : 'unplayed'),
iState::COLUMN_EXTRA_DATE => (string)makeDate('now'),
]);
$this->mapper->add($item)->commit();
$response = $item->getAll();
$response['full_title'] = $item->getName();
$response[iState::COLUMN_WATCHED] = $item->isWatched();
$response[iState::COLUMN_UPDATED] = makeDate($item->updated);
queuePush($item);
return api_response(HTTP_STATUS::HTTP_OK, $response);
}
}

View File

@@ -488,7 +488,7 @@ final class ListCommand extends Command
$changeState = strtolower($changeState);
if (!$input->getOption('no-interaction')) {
$text = r(
'<question>Are you sure you want to mark [<notce>{total}</notce>] items as [<notice>{state}</notice>]</question> ? [<value>Y|N</value>] [<value>Default: No</value>]',
'<question>Are you sure you want to mark [<notice>{total}</notice>] items as [<notice>{state}</notice>]</question> ? [<value>Y|N</value>] [<value>Default: No</value>]',
[
'total' => count($rows),
'state' => 'played' === $changeState ? 'Played' : 'Unplayed',