Load feeds quicker with stores
This commit is contained in:
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "us.materialio.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 111
|
||||
versionName "1.8.7"
|
||||
versionCode 112
|
||||
versionName "1.8.8"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -56,7 +56,11 @@
|
||||
|
||||
|
||||
|
||||
<release version="1.8.7" date="2025-5-30">
|
||||
|
||||
<release version="1.8.8" date="2025-5-31">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.8.8</url>
|
||||
</release>
|
||||
<release version="1.8.7" date="2025-5-30">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.8.7</url>
|
||||
</release>
|
||||
<release version="1.8.6" date="2025-5-29">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.8.7",
|
||||
"version": "1.8.8",
|
||||
"description": "Modern material design for Invidious.",
|
||||
"author": {
|
||||
"name": "Ward Pearce",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "materialious",
|
||||
"version": "1.8.7",
|
||||
"version": "1.8.8",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -71,4 +71,4 @@
|
||||
"terser": "^5.34.1",
|
||||
"youtubei.js": "^13.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,29 @@
|
||||
<script lang="ts">
|
||||
import Thumbnail from '$lib/components/Thumbnail.svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import { removePlaylistVideo } from '../api';
|
||||
import type { Notification, PlaylistPageVideo, Video, VideoBase } from '../api/model';
|
||||
import { authStore } from '../store';
|
||||
import type { PlaylistPageVideo, Video, VideoBase } from '../api/model';
|
||||
import { authStore, feedLastItemId } from '../store';
|
||||
import ContentColumn from './ContentColumn.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
videos?: VideoBase[] | Video[] | Notification[] | PlaylistPageVideo[];
|
||||
videos?: (VideoBase | Video | PlaylistPageVideo)[];
|
||||
playlistId?: string;
|
||||
playlistAuthor?: string;
|
||||
}
|
||||
|
||||
let { videos = [], playlistId = '', playlistAuthor = '' }: Props = $props();
|
||||
|
||||
let auth = get(authStore);
|
||||
|
||||
async function removePlaylistItem(indexId: string) {
|
||||
if (!playlistId) return;
|
||||
await removePlaylistVideo(playlistId, indexId);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if ($feedLastItemId)
|
||||
document.getElementById($feedLastItemId)?.scrollIntoView({ behavior: 'instant' });
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="page right active">
|
||||
@@ -28,9 +31,14 @@
|
||||
<div class="grid">
|
||||
{#each videos as video}
|
||||
<ContentColumn>
|
||||
<article class="no-padding" style="height: 100%;">
|
||||
<article
|
||||
class="no-padding"
|
||||
style="height: 100%;"
|
||||
id={video.videoId}
|
||||
onclick={() => feedLastItemId.set(video.videoId)}
|
||||
>
|
||||
<Thumbnail {video} {playlistId} />
|
||||
{#if auth && decodeURIComponent(auth.username) === playlistAuthor && 'indexId' in video}
|
||||
{#if $authStore && decodeURIComponent($authStore.username) === playlistAuthor && 'indexId' in video}
|
||||
<div class="right-align" style="margin: 1em .5em;">
|
||||
<button
|
||||
onclick={async () => removePlaylistItem(video.indexId)}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { DataConnection } from 'peerjs';
|
||||
import { persisted } from 'svelte-persisted-store';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import type { TitleCase } from './letterCasing';
|
||||
import type { PlaylistPageVideo, Video, VideoBase } from './api/model';
|
||||
|
||||
function platformDependentDefault(givenValue: any, defaultValue: any): any {
|
||||
if (typeof givenValue !== 'undefined' && typeof givenValue !== null) {
|
||||
@@ -14,11 +15,14 @@ function platformDependentDefault(givenValue: any, defaultValue: any): any {
|
||||
}
|
||||
|
||||
export const instanceStore: Writable<string> = persisted(
|
||||
"invidiousInstance",
|
||||
platformDependentDefault(import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE, 'https://invidious.materialio.us')
|
||||
'invidiousInstance',
|
||||
platformDependentDefault(
|
||||
import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE,
|
||||
'https://invidious.materialio.us'
|
||||
)
|
||||
);
|
||||
|
||||
export const authStore: Writable<null | { username: string; token: string; }> = persisted(
|
||||
export const authStore: Writable<null | { username: string; token: string }> = persisted(
|
||||
'authToken',
|
||||
null
|
||||
);
|
||||
@@ -28,8 +32,6 @@ export const themeColorStore: Writable<null | string> = persisted('themeColor',
|
||||
|
||||
export const showWarningStore = persisted('showWarning', true);
|
||||
|
||||
export const activePageStore: Writable<string | null> = writable('home');
|
||||
|
||||
export const playerAutoPlayStore = persisted('autoPlay', true);
|
||||
export const playerAlwaysLoopStore = persisted('alwaysLoop', false);
|
||||
export const playerProxyVideosStore = persisted('proxyVideos', true);
|
||||
@@ -44,13 +46,20 @@ export const playerDefaultLanguage = persisted('defaultLanguage', '');
|
||||
export const returnYtDislikesStore = persisted('returnYtDislikes', false);
|
||||
export const returnYTDislikesInstanceStore: Writable<string | null | undefined> = persisted(
|
||||
'returnYTDislikesInstance',
|
||||
platformDependentDefault(import.meta.env.VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE, 'https://ryd-proxy.materialio.us')
|
||||
platformDependentDefault(
|
||||
import.meta.env.VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE,
|
||||
'https://ryd-proxy.materialio.us'
|
||||
)
|
||||
);
|
||||
|
||||
export const synciousStore = persisted('syncious', true);
|
||||
export const synciousInstanceStore: Writable<string | null | undefined> = persisted(
|
||||
'synciousInstance',
|
||||
platformDependentDefault(import.meta.env.VITE_DEFAULT_SYNCIOUS_INSTANCE || import.meta.env.VITE_DEFAULT_API_EXTENDED_INSTANCE, 'https://extended-api.materialio.us')
|
||||
platformDependentDefault(
|
||||
import.meta.env.VITE_DEFAULT_SYNCIOUS_INSTANCE ||
|
||||
import.meta.env.VITE_DEFAULT_API_EXTENDED_INSTANCE,
|
||||
'https://extended-api.materialio.us'
|
||||
)
|
||||
);
|
||||
|
||||
export const interfaceRegionStore: Writable<string> = persisted('interfaceRegion', 'US');
|
||||
@@ -96,10 +105,14 @@ export const deArrowThumbnailInstanceStore = persisted(
|
||||
export const syncPartyPeerStore: Writable<Peer | null> = writable(null);
|
||||
export const syncPartyConnectionsStore: Writable<DataConnection[] | null> = writable();
|
||||
|
||||
export const playlistSettingsStore: Writable<Record<string, { shuffle: boolean; loop: boolean; }>> =
|
||||
export const playlistSettingsStore: Writable<Record<string, { shuffle: boolean; loop: boolean }>> =
|
||||
writable({});
|
||||
|
||||
|
||||
export const poTokenCacheStore: Writable<string | undefined> = writable();
|
||||
|
||||
export const searchHistoryStore: Writable<string[]> = persisted('searchHistory', []);
|
||||
|
||||
export const feedCacheStore: Writable<{
|
||||
[key: string]: (VideoBase | Video | PlaylistPageVideo)[];
|
||||
}> = writable({});
|
||||
export const feedLastItemId: Writable<string | undefined> = writable(undefined);
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { activePageStore } from '$lib/store';
|
||||
|
||||
activePageStore.set(null);
|
||||
</script>
|
||||
|
||||
<div class="space"></div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { afterNavigate, beforeNavigate, goto } from '$app/navigation';
|
||||
|
||||
import { navigating } from '$app/stores';
|
||||
import { navigating, page } from '$app/stores';
|
||||
import '$lib/android/http/androidRequests';
|
||||
import colorTheme, { convertToHexColorCode } from '$lib/android/plugins/colorTheme';
|
||||
import { getFeed } from '$lib/api/index';
|
||||
@@ -16,7 +16,6 @@
|
||||
import { bookmarkletLoadFromUrl, loadSettingsFromEnv } from '$lib/externalSettings';
|
||||
import { getPages } from '$lib/navPages';
|
||||
import {
|
||||
activePageStore,
|
||||
authStore,
|
||||
darkModeStore,
|
||||
instanceStore,
|
||||
@@ -33,7 +32,7 @@
|
||||
import 'beercss';
|
||||
import ui from 'beercss';
|
||||
import 'material-dynamic-colors';
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import { pwaInfo } from 'virtual:pwa-info';
|
||||
@@ -42,9 +41,6 @@
|
||||
|
||||
let mobileSearchShow = $state(false);
|
||||
|
||||
let currentPage: string | null = $state('');
|
||||
activePageStore.subscribe((page) => (currentPage = page));
|
||||
|
||||
let isLoggedIn = $state(false);
|
||||
authStore.subscribe((value) => {
|
||||
isLoggedIn = value !== null;
|
||||
@@ -120,41 +116,11 @@
|
||||
notifications = feed.notifications;
|
||||
}
|
||||
|
||||
let scrollPositions = new Map();
|
||||
let scrollableRoot: HTMLElement | null = null;
|
||||
|
||||
beforeNavigate(({ from }) => {
|
||||
if (from?.url && scrollableRoot) {
|
||||
// Save the scroll position of the root element
|
||||
scrollPositions.set(from.url.pathname, scrollableRoot.scrollTop);
|
||||
}
|
||||
});
|
||||
|
||||
// Restore the scroll position after navigating to a new page
|
||||
afterNavigate(async ({ to, type }) => {
|
||||
if (!scrollableRoot) return;
|
||||
|
||||
if (to?.url && scrollPositions.has(to.url.pathname)) {
|
||||
// Check user went back
|
||||
if (type === 'popstate') {
|
||||
await tick();
|
||||
scrollableRoot.scrollTo(0, scrollPositions.get(to.url.pathname) || 0);
|
||||
} else {
|
||||
scrollPositions.delete(to.url.pathname);
|
||||
}
|
||||
} else {
|
||||
// Default behavior: scroll to top
|
||||
scrollableRoot.scrollTo(0, 0);
|
||||
}
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
ui();
|
||||
|
||||
document.addEventListener('click', linkClickOverwrite);
|
||||
|
||||
scrollableRoot = document.querySelector('.root');
|
||||
|
||||
loadSettingsFromEnv();
|
||||
// Should always be loaded after env settings
|
||||
// So user preferences overwrite instance preferences.
|
||||
@@ -205,11 +171,11 @@
|
||||
|
||||
<nav class="left m l small">
|
||||
<header></header>
|
||||
{#each getPages() as page}
|
||||
{#if !page.requiresAuth || isLoggedIn}
|
||||
<a href={page.href} class:active={currentPage === page.name.toLowerCase()}
|
||||
><i>{page.icon}</i>
|
||||
<div>{page.name}</div>
|
||||
{#each getPages() as navPage}
|
||||
{#if !navPage.requiresAuth || isLoggedIn}
|
||||
<a href={navPage.href} class:active={$page.url.href.endsWith(navPage.href)}
|
||||
><i>{navPage.icon}</i>
|
||||
<div>{navPage.name}</div>
|
||||
</a>
|
||||
{/if}
|
||||
{/each}
|
||||
@@ -288,12 +254,12 @@
|
||||
</nav>
|
||||
|
||||
<nav class="bottom s">
|
||||
{#each getPages() as page}
|
||||
{#if !page.requiresAuth || isLoggedIn}
|
||||
<a class="round" href={page.href} class:active={currentPage === page.name.toLowerCase()}
|
||||
><i>{page.icon}</i>
|
||||
{#if currentPage === page.name.toLowerCase()}
|
||||
<span style="font-size: .8em;">{page.name}</span>
|
||||
{#each getPages() as navPage}
|
||||
{#if !navPage.requiresAuth || isLoggedIn}
|
||||
<a class="round" href={navPage.href} class:active={$page.url.href.endsWith(navPage.href)}
|
||||
><i>{navPage.icon}</i>
|
||||
{#if $page.url.href.endsWith(navPage.href)}
|
||||
<span style="font-size: .8em;">{navPage.name}</span>
|
||||
{/if}
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<script lang="ts">
|
||||
import VideoList from '$lib/components/VideoList.svelte';
|
||||
import { activePageStore } from '$lib/store';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
activePageStore.set('home');
|
||||
</script>
|
||||
|
||||
{#if data.popularDisabled}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import VideoList from '$lib/components/VideoList.svelte';
|
||||
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
|
||||
import { cleanNumber } from '$lib/numbers';
|
||||
import { activePageStore, authStore, interfaceLowBandwidthMode } from '$lib/store';
|
||||
import { authStore, interfaceLowBandwidthMode } from '$lib/store';
|
||||
import { Clipboard } from '@capacitor/clipboard';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
activePageStore.set(null);
|
||||
|
||||
let isSubscribed = $state(false);
|
||||
|
||||
let tab: 'videos' | 'playlists' | 'streams' | 'shorts' = $state('videos');
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { getHashtag } from '$lib/api';
|
||||
import type { Video } from '$lib/api/model.js';
|
||||
import VideoList from '$lib/components/VideoList.svelte';
|
||||
import { activePageStore } from '$lib/store';
|
||||
import { onMount } from 'svelte';
|
||||
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
|
||||
|
||||
@@ -15,8 +14,6 @@
|
||||
videos = [...data.hashTagVideos.results];
|
||||
});
|
||||
|
||||
activePageStore.set('hashtag');
|
||||
|
||||
async function loadMore(event: InfiniteEvent) {
|
||||
currentPage++;
|
||||
const hashtagVideos = (await getHashtag(data.hashtag, currentPage)).results;
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
import type { VideoPlay } from '$lib/api/model';
|
||||
import PageLoading from '$lib/components/PageLoading.svelte';
|
||||
import VideoList from '$lib/components/VideoList.svelte';
|
||||
import { activePageStore, synciousStore } from '$lib/store';
|
||||
import { synciousStore } from '$lib/store';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { onMount } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
activePageStore.set('history');
|
||||
|
||||
let history: VideoPlay[] = $state([]);
|
||||
let loaded = $state(false);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import VideoList from '$lib/components/VideoList.svelte';
|
||||
import { unsafeRandomItem } from '$lib/misc';
|
||||
import { cleanNumber } from '$lib/numbers';
|
||||
import { activePageStore, playlistSettingsStore } from '$lib/store';
|
||||
import { playlistSettingsStore } from '$lib/store';
|
||||
import { Clipboard } from '@capacitor/clipboard';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -12,8 +12,6 @@
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
activePageStore.set(null);
|
||||
|
||||
let videos: PlaylistPageVideo[] | undefined = $state();
|
||||
if (data.playlist.videos.length > 0) {
|
||||
videos = data.playlist.videos.sort((a: PlaylistPageVideo, b: PlaylistPageVideo) => {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import { deletePersonalPlaylist, getPersonalPlaylists, postPersonalPlaylist } from '$lib/api';
|
||||
import ContentColumn from '$lib/components/ContentColumn.svelte';
|
||||
import PlaylistThumbnail from '$lib/components/PlaylistThumbnail.svelte';
|
||||
import { activePageStore } from '$lib/store';
|
||||
import { ui } from 'beercss';
|
||||
import { _ } from 'svelte-i18n';
|
||||
|
||||
@@ -12,8 +11,6 @@
|
||||
|
||||
let playlists = $state(data);
|
||||
|
||||
activePageStore.set('playlists');
|
||||
|
||||
let playlistPrivacy: 'public' | 'private' | 'unlisted' = 'public';
|
||||
let playlistTitle: string = $state('');
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import PageLoading from '$lib/components/PageLoading.svelte';
|
||||
import PlaylistThumbnail from '$lib/components/PlaylistThumbnail.svelte';
|
||||
import Thumbnail from '$lib/components/Thumbnail.svelte';
|
||||
import { activePageStore } from '$lib/store';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
|
||||
|
||||
@@ -16,8 +15,6 @@
|
||||
|
||||
let currentPage = 1;
|
||||
|
||||
activePageStore.set(null);
|
||||
|
||||
async function changeType(type: 'playlist' | 'all' | 'video' | 'channel') {
|
||||
searchResults.searchType = type;
|
||||
currentPage = 1;
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { getFeed } from '$lib/api/index';
|
||||
import type { Notification, Video } from '$lib/api/model.js';
|
||||
import type { PlaylistPageVideo, Video, VideoBase } from '$lib/api/model.js';
|
||||
import VideoList from '$lib/components/VideoList.svelte';
|
||||
import { activePageStore } from '$lib/store';
|
||||
import { onMount } from 'svelte';
|
||||
import { feedCacheStore } from '$lib/store';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
let currentPage = 1;
|
||||
let videos: (Notification | Video)[] = $state([]);
|
||||
|
||||
onMount(() => {
|
||||
videos = [...data.feed.notifications, ...data.feed.videos];
|
||||
});
|
||||
|
||||
activePageStore.set('subscriptions');
|
||||
let videos: (VideoBase | Video | PlaylistPageVideo)[] = $state(data.videos);
|
||||
|
||||
async function loadMore(event: InfiniteEvent) {
|
||||
currentPage++;
|
||||
@@ -25,6 +18,7 @@
|
||||
event.detail.complete();
|
||||
} else {
|
||||
videos = [...videos, ...feed.videos, ...feed.notifications];
|
||||
feedCacheStore.set({ subscription: videos });
|
||||
event.detail.loaded();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
import { getFeed } from '$lib/api/index';
|
||||
import { feedCacheStore } from '$lib/store';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export async function load({ params }) {
|
||||
let feed;
|
||||
export async function load() {
|
||||
let videos = get(feedCacheStore).subscription;
|
||||
|
||||
try {
|
||||
feed = await getFeed(100, 1);
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
if (!videos) {
|
||||
let feeds;
|
||||
try {
|
||||
feeds = await getFeed(100, 1);
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
|
||||
videos = [...feeds.notifications, ...feeds.videos];
|
||||
|
||||
feedCacheStore.set({ subscription: videos });
|
||||
} else {
|
||||
await getFeed(100, 1).then((feeds) => {
|
||||
const newVideos = [...feeds.notifications, ...feeds.videos];
|
||||
feedCacheStore.set({ subscription: { ...Array.from(new Set(newVideos)), ...videos } });
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
feed: feed
|
||||
videos: videos
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { deleteUnsubscribe } from '$lib/api';
|
||||
import { activePageStore } from '$lib/store';
|
||||
import Fuse from 'fuse.js';
|
||||
import { _ } from 'svelte-i18n';
|
||||
|
||||
activePageStore.set(null);
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
let subscriptions = $state(structuredClone(data.subscriptions));
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<script lang="ts">
|
||||
import VideoList from '$lib/components/VideoList.svelte';
|
||||
import { activePageStore } from '$lib/store';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
activePageStore.set('trending');
|
||||
</script>
|
||||
|
||||
<VideoList videos={data.trending} />
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
import { cleanNumber, humanizeSeconds, numberWithCommas } from '$lib/numbers';
|
||||
import type { PlayerEvents } from '$lib/player.js';
|
||||
import {
|
||||
activePageStore,
|
||||
authStore,
|
||||
interfaceAutoExpandComments,
|
||||
interfaceAutoExpandDesc,
|
||||
@@ -56,8 +55,6 @@
|
||||
let personalPlaylists: PlaylistPage[] | null = $state(null);
|
||||
data.streamed.personalPlaylists?.then((streamPlaylists) => (personalPlaylists = streamPlaylists));
|
||||
|
||||
activePageStore.set(null);
|
||||
|
||||
let playlistVideos: PlaylistPageVideo[] = $state([]);
|
||||
let playlist: PlaylistPage | null = $state(null);
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
LATEST_VERSION = "1.8.7"
|
||||
LATEST_VERSION = "1.8.8"
|
||||
RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D
|
||||
|
||||
WORKING_DIR = os.path.join(
|
||||
|
||||
Reference in New Issue
Block a user