Merge pull request #1468 from Materialious/update/1.16.2"
Update/1.16.2
This commit is contained in:
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "us.materialio.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 219
|
||||
versionName "1.16.1"
|
||||
versionCode 220
|
||||
versionName "1.16.2"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -84,7 +84,11 @@
|
||||
|
||||
|
||||
|
||||
<release version="1.16.1" date="2026-2-18">
|
||||
|
||||
<release version="1.16.2" date="2026-2-18">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.16.2</url>
|
||||
</release>
|
||||
<release version="1.16.1" date="2026-2-18">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.16.1</url>
|
||||
</release>
|
||||
<release version="1.16.0" date="2026-2-18">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.16.1",
|
||||
"version": "1.16.2",
|
||||
"description": "Modern material design for YouTube and Invidious.",
|
||||
"author": {
|
||||
"name": "Ward Pearce",
|
||||
@@ -45,4 +45,4 @@
|
||||
"capacitor",
|
||||
"electron"
|
||||
]
|
||||
}
|
||||
}
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "materialious",
|
||||
"version": "1.16.1",
|
||||
"version": "1.16.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "materialious",
|
||||
"version": "1.16.1",
|
||||
"version": "1.16.2",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@capacitor-community/electron": "^5.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "materialious",
|
||||
"version": "1.16.1",
|
||||
"version": "1.16.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run patch:github && vite dev",
|
||||
@@ -96,4 +96,4 @@
|
||||
"youtubei.js": "^16.0.1",
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,8 @@ export interface Ytjs {
|
||||
rawApiResponse: ApiResponse;
|
||||
}
|
||||
|
||||
export type FallbackPatches = 'youtubejs' | 'piped';
|
||||
|
||||
export interface VideoPlay extends Video {
|
||||
keywords: string[];
|
||||
likeCount: number;
|
||||
@@ -120,7 +122,7 @@ export interface VideoPlay extends Video {
|
||||
captions: Captions[];
|
||||
storyboards?: StoryBoard[];
|
||||
ytjs?: Ytjs;
|
||||
fallbackPatch?: 'youtubejs' | 'piped';
|
||||
fallbackPatch?: FallbackPatches;
|
||||
}
|
||||
|
||||
export interface StoryBoard {
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<script lang="ts" module>
|
||||
let trackVisible: boolean = $state(false);
|
||||
let captionsCues: VTTCue[] = $state([]);
|
||||
|
||||
const captionTracks: Record<string, string> = {};
|
||||
|
||||
export function setTextTrackVisibility(visible: boolean) {
|
||||
trackVisible = visible;
|
||||
}
|
||||
|
||||
// Fetch caption data for a selected language
|
||||
export async function selectTextTrack(language: string) {
|
||||
const resp = await fetch(captionTracks[language], { method: 'GET' });
|
||||
if (!resp.ok) {
|
||||
addToast({
|
||||
data: {
|
||||
text: 'Unable to fetch captions'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
captionsCues = (await parseText(await resp.text(), { strict: true, type: 'vtt' })).cues;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { VideoPlay } from '$lib/api/model';
|
||||
import { findElementForTime } from '$lib/misc';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { addToast } from '../Toast.svelte';
|
||||
import { parseText, renderVTTCueString, type VTTCue } from 'media-captions';
|
||||
import { getCaptionUrl } from '$lib/player/captions';
|
||||
|
||||
let {
|
||||
video,
|
||||
currentTime = $bindable(),
|
||||
showControls = $bindable()
|
||||
}: {
|
||||
video: VideoPlay;
|
||||
currentTime: number;
|
||||
showControls: boolean;
|
||||
} = $props();
|
||||
|
||||
let captionElement: HTMLElement | undefined = $state();
|
||||
let captionContainerHeight: number = $state(0);
|
||||
|
||||
let currentCaption: VTTCue | null = $state(null);
|
||||
|
||||
function updateCaptionHeight() {
|
||||
if (captionElement) {
|
||||
captionContainerHeight = captionElement.offsetHeight;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
updateCaptionHeight();
|
||||
|
||||
currentCaption = findElementForTime(
|
||||
captionsCues,
|
||||
currentTime,
|
||||
(cue: VTTCue) => cue.startTime,
|
||||
(cue: VTTCue) => cue.endTime
|
||||
);
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
window.addEventListener('resize', updateCaptionHeight);
|
||||
|
||||
if (video.captions) {
|
||||
for (const caption of video.captions) {
|
||||
const captionUrl = getCaptionUrl(caption, video.fallbackPatch);
|
||||
|
||||
if (!captionUrl) continue;
|
||||
|
||||
captionTracks[caption.language_code] = captionUrl;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
window.removeEventListener('resize', updateCaptionHeight);
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if trackVisible && captionsCues.length > 0}
|
||||
{#if currentCaption}
|
||||
<div
|
||||
class="caption-container"
|
||||
bind:this={captionElement}
|
||||
style:top={`calc(${showControls ? 'var(--video-player-height) * 0.85' : 'var(--video-player-height) * 0.98'} - ${captionContainerHeight}px)`}
|
||||
>
|
||||
{#if currentCaption}
|
||||
<p>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html renderVTTCueString(currentCaption, currentTime)}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.caption-container {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 5px;
|
||||
border-radius: 0.25rem;
|
||||
user-select: none;
|
||||
font-size: 1.5rem;
|
||||
color: #fff !important;
|
||||
background-color: rgb(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.caption-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -17,7 +17,6 @@
|
||||
import type { VideoPlay } from '$lib/api/model';
|
||||
import {
|
||||
invidiousAuthStore,
|
||||
invidiousInstanceStore,
|
||||
isAndroidTvStore,
|
||||
playerAlwaysLoopStore,
|
||||
playerAndroidLockOrientation,
|
||||
@@ -65,6 +64,7 @@
|
||||
import TouchControls from './TouchControls.svelte';
|
||||
import { Network, type ConnectionStatus } from '@capacitor/network';
|
||||
import { ScreenOrientation, type ScreenOrientationResult } from '@capacitor/screen-orientation';
|
||||
import ClosedCaptions from './ClosedCaptions.svelte';
|
||||
|
||||
interface Props {
|
||||
data: { video: VideoPlay; content: ParsedDescription; playlistId: string | null };
|
||||
@@ -286,28 +286,6 @@
|
||||
await player.load(dashUrl, await getLastPlayPos());
|
||||
}
|
||||
|
||||
if (data.video.captions) {
|
||||
for (const caption of data.video.captions) {
|
||||
let captionUrl: string;
|
||||
if (!getPublicEnv('DEFAULT_COMPANION_INSTANCE') && $invidiousInstanceStore) {
|
||||
captionUrl = caption.url.startsWith('http')
|
||||
? caption.url
|
||||
: `${new URL($invidiousInstanceStore).origin}${caption.url}`;
|
||||
} else {
|
||||
captionUrl = `${getPublicEnv('DEFAULT_COMPANION_INSTANCE')}${caption.url}`;
|
||||
}
|
||||
|
||||
await player.addTextTrackAsync(
|
||||
captionUrl,
|
||||
caption.language_code,
|
||||
'captions',
|
||||
undefined,
|
||||
undefined,
|
||||
caption.label
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.content.timestamps) {
|
||||
try {
|
||||
player.addChaptersTrack(
|
||||
@@ -479,31 +457,6 @@
|
||||
const error = (event as CustomEvent).detail as shaka.util.Error;
|
||||
console.error('Player error:', error);
|
||||
});
|
||||
player.getNetworkingEngine()?.registerResponseFilter((type, response) => {
|
||||
if (
|
||||
type !== shaka.net.NetworkingEngine.RequestType.SEGMENT ||
|
||||
!response.uri.includes('/api/timedtext')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(response.uri);
|
||||
|
||||
// Fix positioning for auto-generated subtitles
|
||||
// Credit to Freetube!
|
||||
if (
|
||||
url.hostname.endsWith('.youtube.com') &&
|
||||
url.pathname === '/api/timedtext' &&
|
||||
url.searchParams.get('caps') === 'asr' &&
|
||||
url.searchParams.get('kind') === 'asr' &&
|
||||
url.searchParams.get('fmt') === 'vtt'
|
||||
) {
|
||||
const stringBody = new TextDecoder().decode(response.data);
|
||||
// position:0% for LTR text and position:100% for RTL text
|
||||
const cleaned = stringBody.replaceAll(/ align:start position:(?:10)?0%$/gm, '');
|
||||
response.data = new TextEncoder().encode(cleaned).buffer;
|
||||
}
|
||||
});
|
||||
|
||||
playerElement?.addEventListener('volumechange', saveVolumePreference);
|
||||
|
||||
@@ -894,6 +847,8 @@
|
||||
}}
|
||||
bind:this={playerContainer}
|
||||
>
|
||||
<ClosedCaptions video={data.video} bind:currentTime bind:showControls />
|
||||
|
||||
<video
|
||||
controls={false}
|
||||
autoplay={$playerAutoPlayStore}
|
||||
@@ -905,6 +860,7 @@
|
||||
{data.video.title}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showControls}
|
||||
<div id="mobile-time" transition:fade>
|
||||
<p class="chip surface-container-highest s">
|
||||
@@ -974,7 +930,7 @@
|
||||
{/if}
|
||||
</p>
|
||||
{#if !$isAndroidTvStore}
|
||||
<CaptionSettings {player} video={data.video} />
|
||||
<CaptionSettings video={data.video} />
|
||||
{#if playerElement}
|
||||
<Settings {player} {playerElement} />
|
||||
<Airplay {playerElement} />
|
||||
|
||||
@@ -1,36 +1,25 @@
|
||||
<script lang="ts">
|
||||
import type shaka from 'shaka-player/dist/shaka-player.ui';
|
||||
import { onMount } from 'svelte';
|
||||
import { _ } from '$lib/i18n';
|
||||
import type { VideoPlay } from '$lib/api/model';
|
||||
import { selectTextTrack, setTextTrackVisibility } from '../ClosedCaptions.svelte';
|
||||
|
||||
let { player, video }: { player: shaka.Player; video: VideoPlay } = $props();
|
||||
|
||||
let playerTextTracks: shaka.extern.TextTrack[] | undefined = $state(undefined);
|
||||
|
||||
onMount(() => {
|
||||
playerTextTracks = player.getTextTracks();
|
||||
});
|
||||
let { video }: { video: VideoPlay } = $props();
|
||||
</script>
|
||||
|
||||
{#if playerTextTracks && playerTextTracks.length > 0 && !video.liveNow}
|
||||
{#if video.captions.length > 0 && !video.liveNow}
|
||||
<button class="surface-container-highest">
|
||||
<i>closed_caption</i>
|
||||
<menu class="no-wrap mobile player-settings" id="cc-menu" data-ui="#cc-menu">
|
||||
<li
|
||||
role="presentation"
|
||||
data-ui="#cc-menu"
|
||||
onclick={() => player.setTextTrackVisibility(false)}
|
||||
>
|
||||
<li role="presentation" data-ui="#cc-menu" onclick={() => setTextTrackVisibility(false)}>
|
||||
{$_('player.controls.off')}
|
||||
</li>
|
||||
{#each playerTextTracks as track (track)}
|
||||
{#each video.captions as track (track)}
|
||||
<li
|
||||
role="presentation"
|
||||
data-ui="#cc-menu"
|
||||
onclick={() => {
|
||||
player.selectTextTrack(track);
|
||||
player.setTextTrackVisibility(true);
|
||||
selectTextTrack(track.language_code);
|
||||
setTextTrackVisibility(true);
|
||||
}}
|
||||
>
|
||||
{track.label}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
let { player, playerElement }: { player: shaka.Player; playerElement: HTMLMediaElement } =
|
||||
$props();
|
||||
|
||||
let playerSettings: 'quality' | 'speed' | 'language' | 'root' = $state('root');
|
||||
let playerSettings: 'quality' | 'speed' | 'language' | 'caption' | 'root' = $state('root');
|
||||
let playerCurrentVideoTrack: shaka.extern.VideoTrack | undefined = $state(undefined);
|
||||
let playerCurrentAudioTrack: shaka.extern.AudioTrack | undefined = $state(undefined);
|
||||
let playerLoop = $state($playerAlwaysLoopStore);
|
||||
|
||||
@@ -508,7 +508,7 @@
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
.color-picker {
|
||||
--picker-width: 85vw;
|
||||
--picker-width: 95vw;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,34 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { videoLength } from '$lib/numbers';
|
||||
import Fuse from 'fuse.js';
|
||||
import { type VTTCue, parseText, type ParsedCaptionsResult } from 'media-captions';
|
||||
import {
|
||||
type VTTCue,
|
||||
parseText,
|
||||
type ParsedCaptionsResult,
|
||||
renderVTTCueString
|
||||
} from 'media-captions';
|
||||
import { _ } from '$lib/i18n';
|
||||
import type { VideoPlay } from '$lib/api/model';
|
||||
import { decodeHtmlCharCodes } from '$lib/misc';
|
||||
import { invidiousInstanceStore } from '$lib/store';
|
||||
import type { Captions, VideoPlay } from '$lib/api/model';
|
||||
import { getCaptionUrl } from '$lib/player/captions';
|
||||
|
||||
interface Props {
|
||||
video: VideoPlay;
|
||||
playerElement: HTMLMediaElement;
|
||||
currentTime: number;
|
||||
}
|
||||
|
||||
let { video, playerElement = $bindable() }: Props = $props();
|
||||
let { video, currentTime = $bindable() }: Props = $props();
|
||||
|
||||
let url: string | null = $state(null);
|
||||
let selectedCaption: Captions | undefined = $state();
|
||||
let autoScroll: boolean = $state(true);
|
||||
|
||||
let transcript: ParsedCaptionsResult | null = $state(null);
|
||||
let transcriptCues: VTTCue[] = $state([]);
|
||||
let isLoading = $state(false);
|
||||
let currentTime = $state(0);
|
||||
let search: string = $state('');
|
||||
|
||||
playerElement.addEventListener('timeupdate', () => {
|
||||
currentTime = playerElement.currentTime;
|
||||
$effect(() => {
|
||||
// currentTime must be referenced to update effect
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
currentTime;
|
||||
|
||||
if (autoScroll) {
|
||||
const currentTranscriptLine = document.querySelector(
|
||||
'.transcript-line.secondary-container'
|
||||
'.transcript-line.current-line'
|
||||
) as HTMLElement;
|
||||
const transcriptScrollable = document.getElementById('transcript');
|
||||
|
||||
@@ -40,28 +45,21 @@
|
||||
});
|
||||
|
||||
async function loadTranscript() {
|
||||
if (!url) {
|
||||
if (!selectedCaption) {
|
||||
transcript = null;
|
||||
return;
|
||||
}
|
||||
|
||||
let urlConstructed = '';
|
||||
|
||||
if (video.fallbackPatch === 'youtubejs') {
|
||||
urlConstructed = url;
|
||||
} else if ($invidiousInstanceStore) {
|
||||
urlConstructed = new URL($invidiousInstanceStore).origin + url;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
transcript = null;
|
||||
|
||||
const resp = await fetch(urlConstructed);
|
||||
if (!resp.ok) return;
|
||||
transcript = await parseText(await resp.text(), { strict: false });
|
||||
const captionUrl = getCaptionUrl(selectedCaption, video.fallbackPatch);
|
||||
|
||||
if (!captionUrl) return;
|
||||
|
||||
const resp = await fetch(captionUrl);
|
||||
if (!resp.ok) return;
|
||||
|
||||
transcript = await parseText(await resp.text(), { strict: false });
|
||||
transcriptCues = transcript.cues;
|
||||
|
||||
isLoading = false;
|
||||
@@ -87,10 +85,10 @@
|
||||
<article class="no-elevate padding" style="position: sticky; top: 0; z-index: 3;">
|
||||
<h6>{$_('transcript')}</h6>
|
||||
<div class="field label suffix surface-container-highest">
|
||||
<select bind:value={url} onchange={loadTranscript} name="captions">
|
||||
<select bind:value={selectedCaption} onchange={loadTranscript} name="captions">
|
||||
<option selected={true} value={null}>{$_('selectLang')}</option>
|
||||
{#each video.captions as caption (caption)}
|
||||
<option value={caption.url}>{caption.label}</option>
|
||||
<option value={caption}>{caption.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<label for="captions">{$_('language')}</label>
|
||||
@@ -124,15 +122,19 @@
|
||||
{#if transcript.cues.length > 0}
|
||||
{#if transcriptCues.length > 0}
|
||||
{#each transcriptCues as cue (cue)}
|
||||
{@const isCurrent = currentTime >= cue.startTime && currentTime <= cue.endTime}
|
||||
<div
|
||||
class="transcript-line"
|
||||
role="presentation"
|
||||
onclick={() => (playerElement.currentTime = cue.startTime)}
|
||||
class:surface-container-highest={currentTime >= cue.startTime &&
|
||||
currentTime <= cue.endTime}
|
||||
onclick={() => (currentTime = cue.startTime)}
|
||||
class:current-line={isCurrent}
|
||||
class:surface-container-highest={isCurrent}
|
||||
>
|
||||
<p class="chip no-margin">{videoLength(cue.startTime)}</p>
|
||||
<p class="transcript-text">{decodeHtmlCharCodes(cue.text.replace(/<[^>]+>/g, ''))}</p>
|
||||
<p class="transcript-text">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html renderVTTCueString(cue, currentTime)}
|
||||
</p>
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
|
||||
@@ -9,13 +9,15 @@ import { addOrUpdateKeyValue, getKeyValue } from '$lib/api/backend';
|
||||
import { rawMasterKeyStore } from '$lib/store';
|
||||
import { getPublicEnv } from '$lib/misc';
|
||||
|
||||
const allowNullOverwrite = ['authToken'];
|
||||
|
||||
export async function syncSettingsToBackend() {
|
||||
if (!isOwnBackend() || !get(rawMasterKeyStore)) return;
|
||||
|
||||
await Promise.all(
|
||||
persistedStores.map(async (store) => {
|
||||
getKeyValue(store.name).then((currentKeyValue) => {
|
||||
if (currentKeyValue !== null) {
|
||||
if (currentKeyValue !== null || allowNullOverwrite.includes(store.name)) {
|
||||
const currentKeyValueParsed = parseWithSchema(store.schema, currentKeyValue);
|
||||
if (currentKeyValueParsed !== null && currentKeyValueParsed !== undefined) {
|
||||
store.store.set(currentKeyValueParsed);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"login": "Login",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"captionStyle": "Caption styles",
|
||||
"invalidPassword": "Invalid password",
|
||||
"usernameTaken": "Username taken",
|
||||
"linkInvidious": "Link Invidious account",
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { Captions, FallbackPatches } from '$lib/api/model';
|
||||
import { getPublicEnv } from '$lib/misc';
|
||||
import { invidiousInstanceStore } from '$lib/store';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export function getCaptionUrl(
|
||||
caption: Captions,
|
||||
fallbackPath: FallbackPatches | undefined = undefined
|
||||
) {
|
||||
let captionUrl: string | undefined;
|
||||
|
||||
const invidiousInstance = get(invidiousInstanceStore);
|
||||
|
||||
if (getPublicEnv('DEFAULT_COMPANION_INSTANCE')) {
|
||||
captionUrl = `${getPublicEnv('DEFAULT_COMPANION_INSTANCE')}${caption.url}`;
|
||||
} else if (fallbackPath === 'youtubejs') {
|
||||
captionUrl = caption.url;
|
||||
} else if (invidiousInstance) {
|
||||
captionUrl = caption.url.startsWith('http')
|
||||
? caption.url
|
||||
: `${new URL(invidiousInstance).origin}${caption.url}`;
|
||||
}
|
||||
|
||||
return captionUrl;
|
||||
}
|
||||
@@ -148,7 +148,9 @@
|
||||
|
||||
onMount(async () => {
|
||||
if ($invidiousAuthStore && !isYTBackend()) {
|
||||
loadNotifications().catch(() => invidiousLogout());
|
||||
loadNotifications().catch(() => {
|
||||
invidiousLogout();
|
||||
});
|
||||
}
|
||||
|
||||
if ($rawMasterKeyStore) {
|
||||
|
||||
@@ -595,8 +595,8 @@
|
||||
</div>
|
||||
{#if !$playertheatreModeIsActive}
|
||||
<div class="s12 m12 l3 recommended">
|
||||
{#if showTranscript && playerElement}
|
||||
<Transcript video={data.video} bind:playerElement />
|
||||
{#if showTranscript}
|
||||
<Transcript video={data.video} bind:currentTime={playerCurrentTime} />
|
||||
{/if}
|
||||
{#if data.playlistId && data.playlistId in $playlistCacheStore}
|
||||
<Playlist video={data.video} playlist={$playlistCacheStore[data.playlistId]} />
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -3,7 +3,7 @@ import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
LATEST_VERSION = "1.16.1"
|
||||
LATEST_VERSION = "1.16.2"
|
||||
RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D
|
||||
|
||||
WORKING_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "materialious")
|
||||
|
||||
Reference in New Issue
Block a user