2022-12-29 02:19:48 +01:00
|
|
|
import fs from 'fs/promises'
|
2022-06-21 03:43:45 +02:00
|
|
|
import path from 'path'
|
2022-02-02 04:11:38 +01:00
|
|
|
import i18n from '../../i18n/index'
|
Store Revamp / Full database synchronization across windows (#1833)
* History: Refactor history module
* Profiles: Refactor profiles module
* IPC: Move channel ids to their own file and make them constants
* IPC: Replace single sync channel for one channel per sync type
* Everywhere: Replace default profile id magic strings with constant ref
* Profiles: Refactor `activeProfile` property from store
This commit makes it so that `activeProfile`'s getter returns
the entire profile, while the related update function only needs
the profile id (instead of the previously used array index)
to change the currently active profile.
This change was made due to inconsistency regarding the active profile
when creating new profiles.
If a new profile coincidentally landed in the current active profile's
array index after sorting, the app would mistakenly change to it
without any action from the user apart from the profile's creation.
Turning the profile id into the selector instead solves this issue.
* Revert "Store: Implement history synchronization between windows"
This reverts commit 99b61e617873412eb393d8f4dfccd8f8c172021f.
This is necessary for an upcoming improved implementation of the
history synchronization.
* History: Remove unused mutation
* Everywhere: Create abstract database handlers
The project now utilizes abstract handlers to fetch, modify
or otherwise manipulate data from the database.
This facilitates 3 aspects of the app, in addition of
making them future proof:
- Switching database libraries is now trivial
Since most of the app utilizes the abstract handlers, it's incredibly
easily to change to a different DB library.
Hypothetically, all that would need to be done is to simply replace the
the file containing the base handlers, while the rest of the app
would go unchanged.
- Syncing logic between Electron and web is now properly separated
There are now two distinct DB handling APIs: the Electron one and
the web one.
The app doesn't need to manually choose the API, because it's detected
which platform is being utilized on import.
- All Electron windows now share the same database instance
This provides a single source of truth, improving consistency
regarding data manipulation and windows synchronization.
As a sidenote, syncing implementation has been left as is
(web unimplemented; Electron only syncs settings, remaining
datastore syncing will be implemented in the upcoming commits).
* Electron/History: Implement history synchronization
* Profiles: Implement suplementary profile creation logic
* ft-profile-edit: Small fix on profile name missing display
* Electron/Profiles: Implement profile synchronization
* Electron/Playlists: Implement playlist synchronization
2021-12-15 19:42:24 +01:00
|
|
|
|
|
|
|
import { IpcChannels } from '../../../constants'
|
2022-12-29 02:19:48 +01:00
|
|
|
import { pathExists } from '../../helpers/filesystem'
|
2022-11-05 16:13:25 +01:00
|
|
|
import {
|
2023-03-01 01:39:33 +01:00
|
|
|
CHANNEL_HANDLE_REGEX,
|
2022-11-05 16:13:25 +01:00
|
|
|
createWebURL,
|
2023-01-01 03:52:49 +01:00
|
|
|
getVideoParamsFromUrl,
|
2022-11-05 16:13:25 +01:00
|
|
|
openExternalLink,
|
2022-12-02 08:29:01 +01:00
|
|
|
replaceFilenameForbiddenChars,
|
2022-11-05 16:13:25 +01:00
|
|
|
searchFiltersMatch,
|
2023-01-01 03:52:49 +01:00
|
|
|
showExternalPlayerUnsupportedActionToast,
|
2022-11-05 16:13:25 +01:00
|
|
|
showSaveDialog,
|
|
|
|
showToast
|
|
|
|
} from '../../helpers/utils'
|
Store Revamp / Full database synchronization across windows (#1833)
* History: Refactor history module
* Profiles: Refactor profiles module
* IPC: Move channel ids to their own file and make them constants
* IPC: Replace single sync channel for one channel per sync type
* Everywhere: Replace default profile id magic strings with constant ref
* Profiles: Refactor `activeProfile` property from store
This commit makes it so that `activeProfile`'s getter returns
the entire profile, while the related update function only needs
the profile id (instead of the previously used array index)
to change the currently active profile.
This change was made due to inconsistency regarding the active profile
when creating new profiles.
If a new profile coincidentally landed in the current active profile's
array index after sorting, the app would mistakenly change to it
without any action from the user apart from the profile's creation.
Turning the profile id into the selector instead solves this issue.
* Revert "Store: Implement history synchronization between windows"
This reverts commit 99b61e617873412eb393d8f4dfccd8f8c172021f.
This is necessary for an upcoming improved implementation of the
history synchronization.
* History: Remove unused mutation
* Everywhere: Create abstract database handlers
The project now utilizes abstract handlers to fetch, modify
or otherwise manipulate data from the database.
This facilitates 3 aspects of the app, in addition of
making them future proof:
- Switching database libraries is now trivial
Since most of the app utilizes the abstract handlers, it's incredibly
easily to change to a different DB library.
Hypothetically, all that would need to be done is to simply replace the
the file containing the base handlers, while the rest of the app
would go unchanged.
- Syncing logic between Electron and web is now properly separated
There are now two distinct DB handling APIs: the Electron one and
the web one.
The app doesn't need to manually choose the API, because it's detected
which platform is being utilized on import.
- All Electron windows now share the same database instance
This provides a single source of truth, improving consistency
regarding data manipulation and windows synchronization.
As a sidenote, syncing implementation has been left as is
(web unimplemented; Electron only syncs settings, remaining
datastore syncing will be implemented in the upcoming commits).
* Electron/History: Implement history synchronization
* Profiles: Implement suplementary profile creation logic
* ft-profile-edit: Small fix on profile name missing display
* Electron/Profiles: Implement profile synchronization
* Electron/Playlists: Implement playlist synchronization
2021-12-15 19:42:24 +01:00
|
|
|
|
2020-02-16 19:30:00 +01:00
|
|
|
const state = {
|
|
|
|
isSideNavOpen: false,
|
|
|
|
sessionSearchHistory: [],
|
2020-08-18 17:51:56 +02:00
|
|
|
popularCache: null,
|
2021-08-21 23:08:38 +02:00
|
|
|
trendingCache: {
|
|
|
|
default: null,
|
|
|
|
music: null,
|
|
|
|
gaming: null,
|
|
|
|
movies: null
|
|
|
|
},
|
2022-12-19 11:43:28 +01:00
|
|
|
cachedPlaylist: null,
|
2020-08-31 23:35:22 +02:00
|
|
|
showProgressBar: false,
|
|
|
|
progressBarPercentage: 0,
|
2020-10-22 20:56:49 +02:00
|
|
|
regionNames: [],
|
|
|
|
regionValues: [],
|
2020-09-20 20:22:39 +02:00
|
|
|
recentBlogPosts: [],
|
2020-02-16 19:30:00 +01:00
|
|
|
searchSettings: {
|
|
|
|
sortBy: 'relevance',
|
|
|
|
time: '',
|
|
|
|
type: 'all',
|
|
|
|
duration: ''
|
2020-05-23 23:29:42 +02:00
|
|
|
},
|
2021-06-13 17:31:43 +02:00
|
|
|
externalPlayerNames: [],
|
2022-08-10 23:52:31 +02:00
|
|
|
externalPlayerNameTranslationKeys: [],
|
2021-06-13 17:31:43 +02:00
|
|
|
externalPlayerValues: [],
|
|
|
|
externalPlayerCmdArguments: {}
|
2020-02-16 19:30:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const getters = {
|
2020-03-01 04:37:02 +01:00
|
|
|
getIsSideNavOpen () {
|
|
|
|
return state.isSideNavOpen
|
|
|
|
},
|
|
|
|
|
|
|
|
getCurrentVolume () {
|
|
|
|
return state.currentVolume
|
|
|
|
},
|
|
|
|
|
2020-02-16 19:30:00 +01:00
|
|
|
getSessionSearchHistory () {
|
|
|
|
return state.sessionSearchHistory
|
|
|
|
},
|
|
|
|
|
2020-08-13 16:26:20 +02:00
|
|
|
getPopularCache () {
|
|
|
|
return state.popularCache
|
|
|
|
},
|
|
|
|
|
2020-08-22 22:37:09 +02:00
|
|
|
getTrendingCache () {
|
|
|
|
return state.trendingCache
|
|
|
|
},
|
|
|
|
|
2022-12-19 11:43:28 +01:00
|
|
|
getCachedPlaylist() {
|
|
|
|
return state.cachedPlaylist
|
|
|
|
},
|
|
|
|
|
2020-02-16 19:30:00 +01:00
|
|
|
getSearchSettings () {
|
|
|
|
return state.searchSettings
|
2020-08-24 04:56:33 +02:00
|
|
|
},
|
|
|
|
|
2020-08-31 23:35:22 +02:00
|
|
|
getShowProgressBar () {
|
|
|
|
return state.showProgressBar
|
|
|
|
},
|
|
|
|
|
|
|
|
getProgressBarPercentage () {
|
|
|
|
return state.progressBarPercentage
|
2020-09-20 20:22:39 +02:00
|
|
|
},
|
|
|
|
|
2020-10-22 20:56:49 +02:00
|
|
|
getRegionNames () {
|
|
|
|
return state.regionNames
|
|
|
|
},
|
|
|
|
|
|
|
|
getRegionValues () {
|
|
|
|
return state.regionValues
|
|
|
|
},
|
|
|
|
|
2020-09-20 20:22:39 +02:00
|
|
|
getRecentBlogPosts () {
|
|
|
|
return state.recentBlogPosts
|
2021-06-13 17:31:43 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
getExternalPlayerNames () {
|
|
|
|
return state.externalPlayerNames
|
|
|
|
},
|
|
|
|
|
2022-08-10 23:52:31 +02:00
|
|
|
getExternalPlayerNameTranslationKeys () {
|
|
|
|
return state.externalPlayerNameTranslationKeys
|
|
|
|
},
|
|
|
|
|
2021-06-13 17:31:43 +02:00
|
|
|
getExternalPlayerValues () {
|
|
|
|
return state.externalPlayerValues
|
|
|
|
},
|
|
|
|
|
|
|
|
getExternalPlayerCmdArguments () {
|
|
|
|
return state.externalPlayerCmdArguments
|
2020-02-16 19:30:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-23 23:29:42 +02:00
|
|
|
const actions = {
|
2022-12-02 08:29:01 +01:00
|
|
|
async downloadMedia({ rootState }, { url, title, extension, fallingBackPath }) {
|
2022-10-18 10:15:28 +02:00
|
|
|
if (!process.env.IS_ELECTRON) {
|
|
|
|
openExternalLink(url)
|
|
|
|
return
|
2022-09-21 09:00:21 +02:00
|
|
|
}
|
|
|
|
|
2022-12-02 08:29:01 +01:00
|
|
|
const fileName = `${replaceFilenameForbiddenChars(title)}.${extension}`
|
2022-10-13 13:51:15 +02:00
|
|
|
const errorMessage = i18n.t('Downloading failed', { videoTitle: title })
|
2022-02-02 04:11:38 +01:00
|
|
|
let folderPath = rootState.settings.downloadFolderPath
|
|
|
|
|
|
|
|
if (folderPath === '') {
|
|
|
|
const options = {
|
|
|
|
defaultPath: fileName,
|
|
|
|
filters: [
|
|
|
|
{
|
2022-05-30 15:24:34 +02:00
|
|
|
name: extension.toUpperCase(),
|
2022-02-02 04:11:38 +01:00
|
|
|
extensions: [extension]
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
2022-10-25 16:44:18 +02:00
|
|
|
const response = await showSaveDialog(options)
|
2022-02-02 04:11:38 +01:00
|
|
|
|
|
|
|
if (response.canceled || response.filePath === '') {
|
|
|
|
// User canceled the save dialog
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
folderPath = response.filePath
|
2022-06-21 03:43:45 +02:00
|
|
|
} else {
|
2022-12-29 02:19:48 +01:00
|
|
|
if (!(await pathExists(folderPath))) {
|
2022-06-21 03:43:45 +02:00
|
|
|
try {
|
2022-12-29 02:19:48 +01:00
|
|
|
await fs.mkdir(folderPath, { recursive: true })
|
2022-06-21 03:43:45 +02:00
|
|
|
} catch (err) {
|
|
|
|
console.error(err)
|
2022-10-14 07:59:49 +02:00
|
|
|
showToast(err)
|
2022-06-21 03:43:45 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
folderPath = path.join(folderPath, fileName)
|
2022-01-30 18:49:16 +01:00
|
|
|
}
|
|
|
|
|
2022-10-14 07:59:49 +02:00
|
|
|
showToast(i18n.t('Starting download', { videoTitle: title }))
|
2022-01-30 18:49:16 +01:00
|
|
|
|
2022-02-02 04:11:38 +01:00
|
|
|
const response = await fetch(url).catch((error) => {
|
2022-09-23 03:04:10 +02:00
|
|
|
console.error(error)
|
2022-10-14 07:59:49 +02:00
|
|
|
showToast(errorMessage)
|
2022-02-02 04:11:38 +01:00
|
|
|
})
|
2022-01-30 18:49:16 +01:00
|
|
|
|
2022-02-02 04:11:38 +01:00
|
|
|
const reader = response.body.getReader()
|
2022-01-30 18:49:16 +01:00
|
|
|
const chunks = []
|
|
|
|
|
2022-02-02 04:11:38 +01:00
|
|
|
const handleError = (err) => {
|
2022-09-23 03:04:10 +02:00
|
|
|
console.error(err)
|
2022-10-14 07:59:49 +02:00
|
|
|
showToast(errorMessage)
|
2022-02-02 04:11:38 +01:00
|
|
|
}
|
2022-01-30 18:49:16 +01:00
|
|
|
|
2022-02-02 04:11:38 +01:00
|
|
|
const processText = async ({ done, value }) => {
|
2022-01-30 18:49:16 +01:00
|
|
|
if (done) {
|
2022-02-02 04:11:38 +01:00
|
|
|
return
|
2022-01-30 18:49:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
chunks.push(value)
|
2022-02-02 04:11:38 +01:00
|
|
|
// Can be used in the future to determine download percentage
|
2022-02-19 23:17:58 +01:00
|
|
|
// const contentLength = response.headers.get('Content-Length')
|
|
|
|
// const receivedLength = value.length
|
|
|
|
// const percentage = receivedLength / contentLength
|
2022-02-02 04:11:38 +01:00
|
|
|
await reader.read().then(processText).catch(handleError)
|
2022-01-30 18:49:16 +01:00
|
|
|
}
|
|
|
|
|
2022-02-02 04:11:38 +01:00
|
|
|
await reader.read().then(processText).catch(handleError)
|
2022-01-30 18:49:16 +01:00
|
|
|
|
|
|
|
const blobFile = new Blob(chunks)
|
|
|
|
const buffer = await blobFile.arrayBuffer()
|
|
|
|
|
2022-12-29 02:19:48 +01:00
|
|
|
try {
|
|
|
|
await fs.writeFile(folderPath, new DataView(buffer))
|
|
|
|
|
|
|
|
showToast(i18n.t('Downloading has completed', { videoTitle: title }))
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err)
|
|
|
|
showToast(errorMessage)
|
|
|
|
}
|
2022-01-30 18:49:16 +01:00
|
|
|
},
|
|
|
|
|
2022-05-30 15:24:34 +02:00
|
|
|
parseScreenshotCustomFileName: function({ rootState }, payload) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const { pattern = rootState.settings.screenshotFilenamePattern, date, playerTime, videoId } = payload
|
|
|
|
const keywords = [
|
|
|
|
['%Y', date.getFullYear()], // year 4 digits
|
|
|
|
['%M', (date.getMonth() + 1).toString().padStart(2, '0')], // month 2 digits
|
|
|
|
['%D', date.getDate().toString().padStart(2, '0')], // day 2 digits
|
|
|
|
['%H', date.getHours().toString().padStart(2, '0')], // hour 2 digits
|
|
|
|
['%N', date.getMinutes().toString().padStart(2, '0')], // minute 2 digits
|
|
|
|
['%S', date.getSeconds().toString().padStart(2, '0')], // second 2 digits
|
|
|
|
['%T', date.getMilliseconds().toString().padStart(3, '0')], // millisecond 3 digits
|
|
|
|
['%s', parseInt(playerTime)], // video position second n digits
|
|
|
|
['%t', (playerTime % 1).toString().slice(2, 5) || '000'], // video position millisecond 3 digits
|
|
|
|
['%i', videoId] // video id
|
|
|
|
]
|
|
|
|
|
|
|
|
let parsedString = pattern
|
|
|
|
for (const [key, value] of keywords) {
|
|
|
|
parsedString = parsedString.replaceAll(key, value)
|
|
|
|
}
|
|
|
|
|
2022-12-02 08:29:01 +01:00
|
|
|
if (parsedString !== replaceFilenameForbiddenChars(parsedString)) {
|
|
|
|
reject(new Error('Forbidden Characters')) // use message as translation key
|
2022-05-30 15:24:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
let filename
|
2022-12-02 08:29:01 +01:00
|
|
|
if (parsedString.indexOf(path.sep) !== -1) {
|
|
|
|
const lastIndex = parsedString.lastIndexOf(path.sep)
|
2022-05-30 15:24:34 +02:00
|
|
|
filename = parsedString.substring(lastIndex + 1)
|
|
|
|
} else {
|
|
|
|
filename = parsedString
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!filename) {
|
|
|
|
reject(new Error('Empty File Name'))
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve(parsedString)
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2020-08-31 23:35:22 +02:00
|
|
|
updateShowProgressBar ({ commit }, value) {
|
|
|
|
commit('setShowProgressBar', value)
|
|
|
|
},
|
|
|
|
|
2022-10-22 10:31:34 +02:00
|
|
|
async getRegionData ({ commit }, { locale }) {
|
|
|
|
let localePathExists
|
|
|
|
// Exclude __dirname from path if not in electron
|
|
|
|
const fileLocation = `${process.env.IS_ELECTRON ? process.env.NODE_ENV === 'development' ? '.' : __dirname : ''}/static/geolocations/`
|
|
|
|
if (process.env.IS_ELECTRON) {
|
2022-12-29 02:19:48 +01:00
|
|
|
localePathExists = await pathExists(`${fileLocation}${locale}`)
|
2020-10-22 20:56:49 +02:00
|
|
|
} else {
|
2022-10-22 10:31:34 +02:00
|
|
|
localePathExists = process.env.GEOLOCATION_NAMES.includes(locale)
|
2020-10-22 20:56:49 +02:00
|
|
|
}
|
2022-10-22 10:31:34 +02:00
|
|
|
const pathName = `${fileLocation}${localePathExists ? locale : 'en-US'}/countries.json`
|
2022-12-29 02:19:48 +01:00
|
|
|
const fileData = process.env.IS_ELECTRON ? JSON.parse(await fs.readFile(pathName)) : await (await fetch(createWebURL(pathName))).json()
|
2022-10-22 10:31:34 +02:00
|
|
|
|
|
|
|
const countries = fileData.map((entry) => { return { id: entry.id, name: entry.name, code: entry.alpha2 } })
|
2020-10-22 20:56:49 +02:00
|
|
|
countries.sort((a, b) => { return a.id - b.id })
|
|
|
|
|
|
|
|
const regionNames = countries.map((entry) => { return entry.name })
|
|
|
|
const regionValues = countries.map((entry) => { return entry.code })
|
|
|
|
|
|
|
|
commit('setRegionNames', regionNames)
|
|
|
|
commit('setRegionValues', regionValues)
|
|
|
|
},
|
|
|
|
|
2023-03-01 01:39:33 +01:00
|
|
|
async getYoutubeUrlInfo({ rootState, state }, urlStr) {
|
2021-04-28 19:21:16 +02:00
|
|
|
// Returns
|
|
|
|
// - urlType [String] `video`, `playlist`
|
|
|
|
//
|
|
|
|
// If `urlType` is "video"
|
|
|
|
// - videoId [String]
|
|
|
|
// - timestamp [String]
|
|
|
|
//
|
|
|
|
// If `urlType` is "playlist"
|
|
|
|
// - playlistId [String]
|
|
|
|
// - query [Object]
|
|
|
|
//
|
|
|
|
// If `urlType` is "search"
|
|
|
|
// - searchQuery [String]
|
|
|
|
// - query [Object]
|
|
|
|
//
|
|
|
|
// If `urlType` is "hashtag"
|
|
|
|
// Nothing else
|
|
|
|
//
|
|
|
|
// If `urlType` is "channel"
|
|
|
|
// - channelId [String]
|
|
|
|
//
|
|
|
|
// If `urlType` is "unknown"
|
|
|
|
// Nothing else
|
|
|
|
//
|
|
|
|
// If `urlType` is "invalid_url"
|
|
|
|
// Nothing else
|
2023-03-01 01:39:33 +01:00
|
|
|
|
|
|
|
if (CHANNEL_HANDLE_REGEX.test(urlStr)) {
|
|
|
|
urlStr = `https://www.youtube.com/${urlStr}`
|
|
|
|
}
|
|
|
|
|
2023-01-01 03:52:49 +01:00
|
|
|
const { videoId, timestamp, playlistId } = getVideoParamsFromUrl(urlStr)
|
2021-04-28 19:21:16 +02:00
|
|
|
if (videoId) {
|
|
|
|
return {
|
|
|
|
urlType: 'video',
|
|
|
|
videoId,
|
2021-05-31 13:23:35 +02:00
|
|
|
playlistId,
|
2021-04-28 19:21:16 +02:00
|
|
|
timestamp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let url
|
2021-01-15 17:34:44 +01:00
|
|
|
try {
|
2021-04-28 19:21:16 +02:00
|
|
|
url = new URL(urlStr)
|
|
|
|
} catch {
|
|
|
|
return {
|
|
|
|
urlType: 'invalid_url'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let urlType = 'unknown'
|
|
|
|
|
|
|
|
const channelPattern =
|
2023-03-03 21:38:27 +01:00
|
|
|
/^\/(?:(?:channel|user|c)\/)?(?<channelId>[^/]+)(?:\/(?<tab>join|featured|videos|playlists|about|community|channels))?\/?$/
|
2021-04-28 19:21:16 +02:00
|
|
|
|
|
|
|
const typePatterns = new Map([
|
2022-12-22 15:22:12 +01:00
|
|
|
['playlist', /^(\/playlist\/?|\/embed(\/?videoseries)?)$/],
|
2021-04-28 19:21:16 +02:00
|
|
|
['search', /^\/results\/?$/],
|
2023-01-03 19:19:41 +01:00
|
|
|
['hashtag', /^\/hashtag\/([^#&/?]+)$/],
|
2021-04-28 19:21:16 +02:00
|
|
|
['channel', channelPattern]
|
|
|
|
])
|
|
|
|
|
|
|
|
for (const [type, pattern] of typePatterns) {
|
|
|
|
const matchFound = pattern.test(url.pathname)
|
|
|
|
if (matchFound) {
|
|
|
|
urlType = type
|
|
|
|
break
|
|
|
|
}
|
2021-01-15 17:34:44 +01:00
|
|
|
}
|
|
|
|
|
2021-04-28 19:21:16 +02:00
|
|
|
switch (urlType) {
|
|
|
|
case 'playlist': {
|
|
|
|
if (!url.searchParams.has('list')) {
|
|
|
|
throw new Error('Playlist: "list" field not found')
|
|
|
|
}
|
|
|
|
|
|
|
|
const playlistId = url.searchParams.get('list')
|
|
|
|
url.searchParams.delete('list')
|
|
|
|
|
|
|
|
const query = {}
|
|
|
|
for (const [param, value] of url.searchParams) {
|
|
|
|
query[param] = value
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
urlType: 'playlist',
|
|
|
|
playlistId,
|
|
|
|
query
|
2021-01-15 17:34:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-28 19:21:16 +02:00
|
|
|
case 'search': {
|
|
|
|
if (!url.searchParams.has('search_query')) {
|
|
|
|
throw new Error('Search: "search_query" field not found')
|
|
|
|
}
|
|
|
|
|
|
|
|
const searchQuery = url.searchParams.get('search_query')
|
|
|
|
url.searchParams.delete('search_query')
|
|
|
|
|
2021-05-11 17:28:26 +02:00
|
|
|
const searchSettings = state.searchSettings
|
2021-04-28 19:21:16 +02:00
|
|
|
const query = {
|
2021-05-11 17:28:26 +02:00
|
|
|
sortBy: searchSettings.sortBy,
|
|
|
|
time: searchSettings.time,
|
|
|
|
type: searchSettings.type,
|
|
|
|
duration: searchSettings.duration
|
2021-04-28 19:21:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for (const [param, value] of url.searchParams) {
|
|
|
|
query[param] = value
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
urlType: 'search',
|
|
|
|
searchQuery,
|
|
|
|
query
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
case 'hashtag': {
|
|
|
|
return {
|
|
|
|
urlType: 'hashtag'
|
|
|
|
}
|
|
|
|
}
|
2021-11-02 12:45:50 +01:00
|
|
|
/*
|
|
|
|
Using RegExp named capture groups from ES2018
|
|
|
|
To avoid access to specific captured value broken
|
2021-04-28 19:21:16 +02:00
|
|
|
|
2021-11-02 12:45:50 +01:00
|
|
|
Channel URL (ID-based)
|
|
|
|
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw
|
|
|
|
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw/about
|
|
|
|
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw/channels
|
|
|
|
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw/community
|
|
|
|
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw/featured
|
|
|
|
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw/join
|
|
|
|
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw/playlists
|
|
|
|
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw/videos
|
|
|
|
|
|
|
|
Custom URL
|
|
|
|
|
|
|
|
https://www.youtube.com/c/YouTubeCreators
|
|
|
|
https://www.youtube.com/c/YouTubeCreators/about
|
|
|
|
etc.
|
|
|
|
|
|
|
|
Legacy Username URL
|
|
|
|
|
|
|
|
https://www.youtube.com/user/ufoludek
|
|
|
|
https://www.youtube.com/user/ufoludek/about
|
|
|
|
etc.
|
|
|
|
|
|
|
|
*/
|
2021-04-28 19:21:16 +02:00
|
|
|
case 'channel': {
|
2022-08-08 11:26:04 +02:00
|
|
|
const match = url.pathname.match(channelPattern)
|
|
|
|
const channelId = match.groups.channelId
|
2021-04-28 19:21:16 +02:00
|
|
|
if (!channelId) {
|
|
|
|
throw new Error('Channel: could not extract id')
|
|
|
|
}
|
|
|
|
|
2021-11-02 12:45:50 +01:00
|
|
|
let subPath = null
|
2023-03-03 21:38:27 +01:00
|
|
|
switch (match.groups.tab) {
|
2021-11-02 12:45:50 +01:00
|
|
|
case 'playlists':
|
|
|
|
subPath = 'playlists'
|
|
|
|
break
|
|
|
|
case 'channels':
|
|
|
|
case 'about':
|
|
|
|
subPath = 'about'
|
|
|
|
break
|
|
|
|
case 'community':
|
Channel community page (#1568)
* Comunity page strings, Communtiy tab, Community initial API call
Added:
1) Community page strings - the first few strings are now available
2) Community tab - A clickable tab is now displayed on channel pages
3) Community initial API call - on loading the page, the initial access
* Comunity page strings, Communtiy tab, Community initial API call
Added:
1) Community page strings - the first few strings are now available
2) Community tab - A clickable tab is now displayed on channel pages
3) Community initial API call - on loading the page, the initial access
* Data returning added
* Comunity page strings, Communtiy tab, Community initial API call
Added:
1) Community page strings - the first few strings are now available
2) Community tab - A clickable tab is now displayed on channel pages
3) Community initial API call - on loading the page, the initial access
* Data returning added
* Images are now displayed in the community tab
* Comunity page strings, Communtiy tab, Community initial API call
Added:
1) Community page strings - the first few strings are now available
2) Community tab - A clickable tab is now displayed on channel pages
3) Community initial API call - on loading the page, the initial access
* Data returning added
* Images are now displayed in the community tab
* Added primitive video display
* Current changes
* Added preston's change with the ftcard and started on some layout basics
* Created Community Post Component and added fetch more button + functionality
* Fixed problem with videothumbnails not loading and adjusted their height to 100% in the ft-list sass file
* Added poll and ft-list-video to the community page
* Added author name placeholder (missing in module), the published date, the likes and dislikes as well as comment counts to posts. Additionally scaling of images was added
* Added basis for community page playlists
* Finalized a setup for playlists when wide enough
* Fix for missing key in custom list
* Added publish date translation
* Add empty alt tags
Co-authored-by: Jason <84899178+jasonhenriquez@users.noreply.github.com>
* fix accessibility issue
Co-authored-by: Jason <84899178+jasonhenriquez@users.noreply.github.com>
* change: ununique ids to classes
* add missing alt tag
* Redirect channel/id/community to the channel's community tab
* update yt-channel-info
* update to 3.0.1
* Update yarn.lock
* add basic multiImage support
* use tiny-slider for multiImage community posts
* update getChannelCommunityPostsMore
* Update yarn.lock
* fix yarn lock
* swap community and about tab
* Update yarn.lock
* Fix missing comma
* Removed trailing spaces
* Clearing all community post data when changing to another channel
* Restructuring of how the post cards are added, Empty page text,
ft-element-list props customization
1) Now the community page uses the same setup of ft-element-list as the
other pages on the channel.
2) If no posts are available, now it displays a message saying so
3) The ft-element-list component's display style can now be forced into
a certain display mode (list/grid) with the new prop. It will overwrite
the corresponding default value for list display
* Fixed display text path
* Fix lint"
* Adjusted css to fit to new layout
* Final touches community page to tidy up the console
* fix icons, fix linter
* fix hiding showmore button for community page
* fix showToast calls
* change all this.showToast to showToaast
* reinstall tinyslider
* use helpers
* small fixes
* fix: getting continuation of community posts
* remove unused code
* improve slider style import
* fix hiding 'ShowMore' button
* fix weird typo in css
* add invidous community tab support
* remove console testing code
* Apply suggestions from code review
Co-authored-by: absidue <48293849+absidue@users.noreply.github.com>
* implement suggestions, improve thumbnail replacement
* use flip horizontal
* readd invidious fallback code, remove author name workaround
* replace another google domain when using invidious
* suppport invidious multiImage posts
* Use youtube.js for community posts
* add invidious polls, remove support for fetching more
* reorder icons alpabetically
* re-allow loading more when using localapi
* fix styling of multiImage, hide NA text
* fix loading playlist
* fix spacing of items
* fix issue with direct url to community tab
* make review recommendations
Co-Authored-By: absidue <48293849+absidue@users.noreply.github.com>
* fix displaying selected tab, get best quality image
---------
Co-authored-by: Preston <freetubeapp@protonmail.com>
Co-authored-by: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com>
Co-authored-by: Jason <84899178+jasonhenriquez@users.noreply.github.com>
Co-authored-by: absidue <48293849+absidue@users.noreply.github.com>
2023-03-04 09:56:04 +01:00
|
|
|
subPath = 'community'
|
|
|
|
break
|
2021-11-02 12:45:50 +01:00
|
|
|
default:
|
|
|
|
subPath = 'videos'
|
|
|
|
break
|
|
|
|
}
|
2021-04-28 19:21:16 +02:00
|
|
|
return {
|
|
|
|
urlType: 'channel',
|
2021-11-02 12:45:50 +01:00
|
|
|
channelId,
|
2023-03-01 01:39:33 +01:00
|
|
|
subPath,
|
|
|
|
url: url.toString()
|
2021-04-28 19:21:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
default: {
|
|
|
|
// Unknown URL type
|
|
|
|
return {
|
|
|
|
urlType: 'unknown'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-15 17:34:44 +01:00
|
|
|
},
|
|
|
|
|
2020-08-22 22:51:04 +02:00
|
|
|
clearSessionSearchHistory ({ commit }) {
|
|
|
|
commit('setSessionSearchHistory', [])
|
|
|
|
},
|
|
|
|
|
2022-12-29 02:19:48 +01:00
|
|
|
async getExternalPlayerCmdArgumentsData ({ commit }, payload) {
|
2021-06-13 17:31:43 +02:00
|
|
|
const fileName = 'external-player-map.json'
|
|
|
|
let fileData
|
2023-01-12 07:55:21 +01:00
|
|
|
/* eslint-disable-next-line n/no-path-concat */
|
2022-09-26 22:15:13 +02:00
|
|
|
const fileLocation = process.env.NODE_ENV === 'development' ? './static/' : `${__dirname}/static/`
|
2021-06-13 17:31:43 +02:00
|
|
|
|
2022-12-29 02:19:48 +01:00
|
|
|
if (await pathExists(`${fileLocation}${fileName}`)) {
|
|
|
|
fileData = await fs.readFile(`${fileLocation}${fileName}`)
|
2021-06-13 17:31:43 +02:00
|
|
|
} else {
|
|
|
|
fileData = '[{"name":"None","value":"","cmdArguments":null}]'
|
|
|
|
}
|
|
|
|
|
|
|
|
const externalPlayerMap = JSON.parse(fileData).map((entry) => {
|
2022-08-10 23:52:31 +02:00
|
|
|
return { name: entry.name, nameTranslationKey: entry.nameTranslationKey, value: entry.value, cmdArguments: entry.cmdArguments }
|
2021-06-13 17:31:43 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
const externalPlayerNames = externalPlayerMap.map((entry) => { return entry.name })
|
2022-08-10 23:52:31 +02:00
|
|
|
const externalPlayerNameTranslationKeys = externalPlayerMap.map((entry) => { return entry.nameTranslationKey })
|
2021-06-13 17:31:43 +02:00
|
|
|
const externalPlayerValues = externalPlayerMap.map((entry) => { return entry.value })
|
|
|
|
const externalPlayerCmdArguments = externalPlayerMap.reduce((result, item) => {
|
|
|
|
result[item.value] = item.cmdArguments
|
|
|
|
return result
|
|
|
|
}, {})
|
|
|
|
|
|
|
|
commit('setExternalPlayerNames', externalPlayerNames)
|
2022-08-10 23:52:31 +02:00
|
|
|
commit('setExternalPlayerNameTranslationKeys', externalPlayerNameTranslationKeys)
|
2021-06-13 17:31:43 +02:00
|
|
|
commit('setExternalPlayerValues', externalPlayerValues)
|
|
|
|
commit('setExternalPlayerCmdArguments', externalPlayerCmdArguments)
|
|
|
|
},
|
|
|
|
|
2023-01-01 03:52:49 +01:00
|
|
|
openInExternalPlayer ({ state, rootState }, payload) {
|
2021-06-13 17:31:43 +02:00
|
|
|
const args = []
|
|
|
|
const externalPlayer = rootState.settings.externalPlayer
|
|
|
|
const cmdArgs = state.externalPlayerCmdArguments[externalPlayer]
|
|
|
|
const executable = rootState.settings.externalPlayerExecutable !== ''
|
|
|
|
? rootState.settings.externalPlayerExecutable
|
|
|
|
: cmdArgs.defaultExecutable
|
|
|
|
const ignoreWarnings = rootState.settings.externalPlayerIgnoreWarnings
|
|
|
|
const customArgs = rootState.settings.externalPlayerCustomArgs
|
|
|
|
|
2021-10-07 08:29:43 +02:00
|
|
|
// Append custom user-defined arguments,
|
|
|
|
// or use the default ones specified for the external player.
|
|
|
|
if (typeof customArgs === 'string' && customArgs !== '') {
|
|
|
|
const custom = customArgs.split(';')
|
|
|
|
args.push(...custom)
|
|
|
|
} else if (typeof cmdArgs.defaultCustomArguments === 'string' && cmdArgs.defaultCustomArguments !== '') {
|
|
|
|
const defaultCustomArguments = cmdArgs.defaultCustomArguments.split(';')
|
|
|
|
args.push(...defaultCustomArguments)
|
|
|
|
}
|
|
|
|
|
2022-06-19 20:02:57 +02:00
|
|
|
if (payload.watchProgress > 0 && payload.watchProgress < payload.videoLength - 10) {
|
2021-06-13 17:31:43 +02:00
|
|
|
if (typeof cmdArgs.startOffset === 'string') {
|
|
|
|
args.push(`${cmdArgs.startOffset}${payload.watchProgress}`)
|
2022-10-13 13:51:15 +02:00
|
|
|
} else if (!ignoreWarnings) {
|
2023-01-01 03:52:49 +01:00
|
|
|
showExternalPlayerUnsupportedActionToast(externalPlayer, 'starting video at offset')
|
2021-06-13 17:31:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-09 19:24:27 +01:00
|
|
|
if (payload.playbackRate != null) {
|
2021-06-13 17:31:43 +02:00
|
|
|
if (typeof cmdArgs.playbackRate === 'string') {
|
|
|
|
args.push(`${cmdArgs.playbackRate}${payload.playbackRate}`)
|
2022-10-13 13:51:15 +02:00
|
|
|
} else if (!ignoreWarnings) {
|
2023-01-01 03:52:49 +01:00
|
|
|
showExternalPlayerUnsupportedActionToast(externalPlayer, 'setting a playback rate')
|
2021-06-13 17:31:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether the video is in a playlist
|
2023-02-09 19:24:27 +01:00
|
|
|
if (typeof cmdArgs.playlistUrl === 'string' && payload.playlistId != null && payload.playlistId !== '') {
|
|
|
|
if (payload.playlistIndex != null) {
|
2021-06-13 17:31:43 +02:00
|
|
|
if (typeof cmdArgs.playlistIndex === 'string') {
|
|
|
|
args.push(`${cmdArgs.playlistIndex}${payload.playlistIndex}`)
|
2022-10-13 13:51:15 +02:00
|
|
|
} else if (!ignoreWarnings) {
|
2023-01-01 03:52:49 +01:00
|
|
|
showExternalPlayerUnsupportedActionToast(externalPlayer, 'opening specific video in a playlist (falling back to opening the video)')
|
2021-06-13 17:31:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (payload.playlistReverse) {
|
|
|
|
if (typeof cmdArgs.playlistReverse === 'string') {
|
|
|
|
args.push(cmdArgs.playlistReverse)
|
2022-10-13 13:51:15 +02:00
|
|
|
} else if (!ignoreWarnings) {
|
2023-01-01 03:52:49 +01:00
|
|
|
showExternalPlayerUnsupportedActionToast(externalPlayer, 'reversing playlists')
|
2021-06-13 17:31:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (payload.playlistShuffle) {
|
|
|
|
if (typeof cmdArgs.playlistShuffle === 'string') {
|
|
|
|
args.push(cmdArgs.playlistShuffle)
|
2022-10-13 13:51:15 +02:00
|
|
|
} else if (!ignoreWarnings) {
|
2023-01-01 03:52:49 +01:00
|
|
|
showExternalPlayerUnsupportedActionToast(externalPlayer, 'shuffling playlists')
|
2021-06-13 17:31:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (payload.playlistLoop) {
|
|
|
|
if (typeof cmdArgs.playlistLoop === 'string') {
|
|
|
|
args.push(cmdArgs.playlistLoop)
|
2022-10-13 13:51:15 +02:00
|
|
|
} else if (!ignoreWarnings) {
|
2023-01-01 03:52:49 +01:00
|
|
|
showExternalPlayerUnsupportedActionToast(externalPlayer, 'looping playlists')
|
2021-06-13 17:31:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (cmdArgs.supportsYtdlProtocol) {
|
|
|
|
args.push(`${cmdArgs.playlistUrl}ytdl://${payload.playlistId}`)
|
|
|
|
} else {
|
|
|
|
args.push(`${cmdArgs.playlistUrl}https://youtube.com/playlist?list=${payload.playlistId}`)
|
|
|
|
}
|
|
|
|
} else {
|
2023-02-09 19:24:27 +01:00
|
|
|
if (payload.playlistId != null && payload.playlistId !== '' && !ignoreWarnings) {
|
2023-01-01 03:52:49 +01:00
|
|
|
showExternalPlayerUnsupportedActionToast(externalPlayer, 'opening playlists')
|
2021-06-13 17:31:43 +02:00
|
|
|
}
|
2023-02-09 19:24:27 +01:00
|
|
|
if (payload.videoId != null) {
|
2021-06-13 17:31:43 +02:00
|
|
|
if (cmdArgs.supportsYtdlProtocol) {
|
|
|
|
args.push(`${cmdArgs.videoUrl}ytdl://${payload.videoId}`)
|
|
|
|
} else {
|
|
|
|
args.push(`${cmdArgs.videoUrl}https://www.youtube.com/watch?v=${payload.videoId}`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-09 19:24:27 +01:00
|
|
|
const videoOrPlaylist = payload.playlistId != null && payload.playlistId !== ''
|
|
|
|
? i18n.t('Video.External Player.playlist')
|
|
|
|
: i18n.t('Video.External Player.video')
|
2022-10-13 13:51:15 +02:00
|
|
|
|
2022-10-14 07:59:49 +02:00
|
|
|
showToast(i18n.t('Video.External Player.OpeningTemplate', { videoOrPlaylist, externalPlayer }))
|
2021-06-13 17:31:43 +02:00
|
|
|
|
|
|
|
const { ipcRenderer } = require('electron')
|
Store Revamp / Full database synchronization across windows (#1833)
* History: Refactor history module
* Profiles: Refactor profiles module
* IPC: Move channel ids to their own file and make them constants
* IPC: Replace single sync channel for one channel per sync type
* Everywhere: Replace default profile id magic strings with constant ref
* Profiles: Refactor `activeProfile` property from store
This commit makes it so that `activeProfile`'s getter returns
the entire profile, while the related update function only needs
the profile id (instead of the previously used array index)
to change the currently active profile.
This change was made due to inconsistency regarding the active profile
when creating new profiles.
If a new profile coincidentally landed in the current active profile's
array index after sorting, the app would mistakenly change to it
without any action from the user apart from the profile's creation.
Turning the profile id into the selector instead solves this issue.
* Revert "Store: Implement history synchronization between windows"
This reverts commit 99b61e617873412eb393d8f4dfccd8f8c172021f.
This is necessary for an upcoming improved implementation of the
history synchronization.
* History: Remove unused mutation
* Everywhere: Create abstract database handlers
The project now utilizes abstract handlers to fetch, modify
or otherwise manipulate data from the database.
This facilitates 3 aspects of the app, in addition of
making them future proof:
- Switching database libraries is now trivial
Since most of the app utilizes the abstract handlers, it's incredibly
easily to change to a different DB library.
Hypothetically, all that would need to be done is to simply replace the
the file containing the base handlers, while the rest of the app
would go unchanged.
- Syncing logic between Electron and web is now properly separated
There are now two distinct DB handling APIs: the Electron one and
the web one.
The app doesn't need to manually choose the API, because it's detected
which platform is being utilized on import.
- All Electron windows now share the same database instance
This provides a single source of truth, improving consistency
regarding data manipulation and windows synchronization.
As a sidenote, syncing implementation has been left as is
(web unimplemented; Electron only syncs settings, remaining
datastore syncing will be implemented in the upcoming commits).
* Electron/History: Implement history synchronization
* Profiles: Implement suplementary profile creation logic
* ft-profile-edit: Small fix on profile name missing display
* Electron/Profiles: Implement profile synchronization
* Electron/Playlists: Implement playlist synchronization
2021-12-15 19:42:24 +01:00
|
|
|
ipcRenderer.send(IpcChannels.OPEN_IN_EXTERNAL_PLAYER, { executable, args })
|
2020-05-23 23:29:42 +02:00
|
|
|
}
|
|
|
|
}
|
2020-02-16 19:30:00 +01:00
|
|
|
|
|
|
|
const mutations = {
|
|
|
|
toggleSideNav (state) {
|
|
|
|
state.isSideNavOpen = !state.isSideNavOpen
|
|
|
|
},
|
|
|
|
|
2020-08-31 23:35:22 +02:00
|
|
|
setShowProgressBar (state, value) {
|
|
|
|
state.showProgressBar = value
|
|
|
|
},
|
|
|
|
|
|
|
|
setProgressBarPercentage (state, value) {
|
|
|
|
state.progressBarPercentage = value
|
|
|
|
},
|
|
|
|
|
2020-02-16 19:30:00 +01:00
|
|
|
setSessionSearchHistory (state, history) {
|
|
|
|
state.sessionSearchHistory = history
|
|
|
|
},
|
|
|
|
|
|
|
|
addToSessionSearchHistory (state, payload) {
|
|
|
|
const sameSearch = state.sessionSearchHistory.findIndex((search) => {
|
2022-11-05 16:13:25 +01:00
|
|
|
return search.query === payload.query && searchFiltersMatch(payload.searchSettings, search.searchSettings)
|
2020-02-16 19:30:00 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
if (sameSearch !== -1) {
|
2022-03-26 02:20:22 +01:00
|
|
|
state.sessionSearchHistory[sameSearch].data = payload.data
|
2020-02-16 19:30:00 +01:00
|
|
|
state.sessionSearchHistory[sameSearch].nextPageRef = payload.nextPageRef
|
|
|
|
} else {
|
|
|
|
state.sessionSearchHistory.push(payload)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-08-13 16:26:20 +02:00
|
|
|
setPopularCache (state, value) {
|
|
|
|
state.popularCache = value
|
|
|
|
},
|
|
|
|
|
2022-04-09 21:34:55 +02:00
|
|
|
setTrendingCache (state, { value, page }) {
|
2021-08-21 23:08:38 +02:00
|
|
|
state.trendingCache[page] = value
|
2020-08-22 22:37:09 +02:00
|
|
|
},
|
|
|
|
|
2022-12-19 11:43:28 +01:00
|
|
|
setCachedPlaylist(state, value) {
|
|
|
|
state.cachedPlaylist = value
|
|
|
|
},
|
|
|
|
|
2020-02-16 19:30:00 +01:00
|
|
|
setSearchSortBy (state, value) {
|
|
|
|
state.searchSettings.sortBy = value
|
|
|
|
},
|
|
|
|
|
|
|
|
setSearchTime (state, value) {
|
|
|
|
state.searchSettings.time = value
|
|
|
|
},
|
|
|
|
|
|
|
|
setSearchType (state, value) {
|
|
|
|
state.searchSettings.type = value
|
|
|
|
},
|
|
|
|
|
|
|
|
setSearchDuration (state, value) {
|
|
|
|
state.searchSettings.duration = value
|
2020-09-20 20:22:39 +02:00
|
|
|
},
|
|
|
|
|
2020-10-22 20:56:49 +02:00
|
|
|
setRegionNames (state, value) {
|
|
|
|
state.regionNames = value
|
|
|
|
},
|
|
|
|
|
|
|
|
setRegionValues (state, value) {
|
|
|
|
state.regionValues = value
|
|
|
|
},
|
|
|
|
|
2020-09-20 20:22:39 +02:00
|
|
|
setRecentBlogPosts (state, value) {
|
|
|
|
state.recentBlogPosts = value
|
2021-06-13 17:31:43 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
setExternalPlayerNames (state, value) {
|
|
|
|
state.externalPlayerNames = value
|
|
|
|
},
|
|
|
|
|
2022-08-10 23:52:31 +02:00
|
|
|
setExternalPlayerNameTranslationKeys (state, value) {
|
|
|
|
state.externalPlayerNameTranslationKeys = value
|
|
|
|
},
|
|
|
|
|
2021-06-13 17:31:43 +02:00
|
|
|
setExternalPlayerValues (state, value) {
|
|
|
|
state.externalPlayerValues = value
|
|
|
|
},
|
|
|
|
|
|
|
|
setExternalPlayerCmdArguments (state, value) {
|
|
|
|
state.externalPlayerCmdArguments = value
|
2020-02-16 19:30:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default {
|
|
|
|
state,
|
|
|
|
getters,
|
|
|
|
actions,
|
|
|
|
mutations
|
|
|
|
}
|