refs #2500 Change settings database to sqlite3

This commit is contained in:
AkiraFukushima 2023-01-01 18:02:52 +09:00
parent 8feee0c4c7
commit 30cb45d967
No known key found for this signature in database
GPG Key ID: B6E51BAC4DE1A957
18 changed files with 296 additions and 418 deletions

View File

@ -1,22 +1,7 @@
import { Setting, Timeline, UnreadNotification, UseMarker } from '~/src/types/setting' import { Setting } from '~/src/types/setting'
const unreadNotification: UnreadNotification = {
direct: false,
local: true,
public: false
}
const useMarker: UseMarker = {
home: false,
notifications: true
}
const timeline: Timeline = {
unreadNotification: unreadNotification,
useMarker: useMarker
}
export const DefaultSetting: Setting = { export const DefaultSetting: Setting = {
accountID: '', accountId: 0,
timeline: timeline markerHome: false,
markerNotifications: true
} }

View File

@ -23,7 +23,7 @@ export const insertAccount = (
} }
let order = 1 let order = 1
if (row) { if (row) {
order = row.order order = row.sort + 1
} }
db.run( db.run(
'INSERT INTO accounts(username, account_id, avatar, client_id, client_secret, access_token, refresh_token, sort) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', 'INSERT INTO accounts(username, account_id, avatar, client_id, client_secret, access_token, refresh_token, sort) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
@ -161,3 +161,115 @@ FROM accounts INNER JOIN servers ON servers.account_id = accounts.id WHERE accou
) )
}) })
} }
export const removeAccount = (db: sqlite3.Database, id: number): Promise<null> => {
return new Promise((resolve, reject) => {
db.run('DELETE FROM accounts WHERE id = ?', id, err => {
if (err) {
reject(err)
}
resolve(null)
})
})
}
export const removeAllAccounts = (db: sqlite3.Database): Promise<null> => {
return new Promise((resolve, reject) => {
db.run('DELETE FROM accounts', err => {
if (err) {
reject(err)
}
resolve(null)
})
})
}
export const forwardAccount = (db: sqlite3.Database, account: LocalAccount): Promise<LocalAccount> => {
return new Promise((resolve, reject) => {
db.serialize(() => {
db.run('BEGIN TRANSACTION')
db.all('SELECT * FROM accounts ORDER BY sort', (err, rows) => {
if (err) {
reject(err)
}
const index = rows.findIndex(r => r.id === account.id)
if (index < 0 || index >= rows.length) {
db.run('ROLLBACK TRANSACTION')
return resolve(account)
}
const target = rows[index + 1]
const base = rows[index]
db.serialize(() => {
db.run('UPDATE accounts SET sort = ? WHERE id = ?', [-100, base.id], err => {
if (err) {
reject(err)
}
})
db.run('UPDATE accounts SET sort = ? WHERE id = ?', [base.sort, target.id], err => {
if (err) {
reject(err)
}
})
db.run('UPDATE accounts SET sort = ? WHERE id = ?', [target.sort, base.id], err => {
if (err) {
reject(err)
}
db.run('COMMIT')
resolve(
Object.assign(account, {
order: target.sort
})
)
})
})
})
})
})
}
export const backwardAccount = (db: sqlite3.Database, account: LocalAccount): Promise<LocalAccount> => {
return new Promise((resolve, reject) => {
db.serialize(() => {
db.run('BEGIN TRANSACTION')
db.all('SELECT * FROM accounts ORDER BY sort', (err, rows) => {
if (err) {
reject(err)
}
const index = rows.findIndex(r => r.id === account.id)
if (index < 1) {
return resolve(account)
}
const target = rows[index - 1]
const base = rows[index]
db.serialize(() => {
db.run('UPDATE accounts SET sort = ? WHERE id = ?', [-100, base.id], err => {
if (err) {
db.run('ROLLBACK TRANSACTION')
reject(err)
}
})
db.run('UPDATE accounts SET sort = ? WHERE id = ?', [base.sort, target.id], err => {
if (err) {
reject(err)
}
})
db.run('UPDATE accounts SET sort = ? WHERE id = ?', [target.sort, base.id], err => {
if (err) {
reject(err)
}
db.run('COMMIT')
resolve(
Object.assign(account, {
order: target.sort
})
)
})
})
})
})
})
}

View File

@ -1,48 +0,0 @@
import { isEmpty } from 'lodash'
import Datastore from 'nedb'
import fs from 'fs'
import { CachedAccount } from '~/src/types/cachedAccount'
export default class AccountCache {
private db: Datastore
constructor(path: string) {
this.db = new Datastore({
filename: path,
autoload: true,
onload: (err: Error) => {
if (err) {
fs.unlink(path, err => {
if (err) {
console.error(err)
}
})
}
}
})
}
listAccounts(ownerID: string): Promise<Array<CachedAccount>> {
return new Promise((resolve, reject) => {
this.db.find<CachedAccount>({ owner_id: ownerID }, (err, docs) => {
if (err) return reject(err)
resolve(docs)
})
})
}
insertAccount(ownerID: string, acct: string): Promise<CachedAccount> {
return new Promise((resolve, reject) => {
// At first confirm records for unique.
this.db.findOne<CachedAccount>({ owner_id: ownerID, acct: acct }, (err, doc) => {
if (err) return err
// Ignore error for unique constraints.
if (!isEmpty(doc)) return err
return this.db.insert<CachedAccount>({ owner_id: ownerID, acct: acct }, (err, doc) => {
if (err) return reject(err)
return resolve(doc)
})
})
})
}
}

View File

@ -1,45 +0,0 @@
import Datastore from 'nedb'
import fs from 'fs'
import { LocalTag } from '~/src/types/localTag'
export default class HashtagCache {
private db: Datastore
constructor(path: string) {
this.db = new Datastore({
filename: path,
autoload: true,
onload: (err: Error) => {
if (err) {
fs.unlink(path, err => {
if (err) {
console.error(err)
}
})
}
}
})
this.db.ensureIndex({ fieldName: 'tagName', unique: true, sparse: true }, err => {
if (err) console.error(err)
})
}
listTags(): Promise<Array<LocalTag>> {
return new Promise((resolve, reject) => {
this.db.find<LocalTag>({}, (err, docs) => {
if (err) return reject(err)
resolve(docs)
})
})
}
insertHashtag(tag: string): Promise<LocalTag> {
return new Promise((resolve, reject) => {
// Ignore error for unique constraints.
this.db.insert({ tagName: tag }, (err, doc) => {
if (err) return reject(err)
resolve(doc)
})
})
}
}

View File

@ -48,6 +48,19 @@ FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE)',
} }
} }
) )
db.run(
'CREATE TABLE IF NOT EXISTS settings(\
id INTEGER PRIMARY KEY, \
account_id INTEGER UNIQUE NOT NULL, \
marker_home BOOLEAN NOT NULL DEFAULT false, \
marker_notifications BOOLEAN NOT NULL DEFAULT true, \
FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE)',
err => {
if (err) {
console.error('failed to create settings: ', err)
}
}
)
}) })
return db return db

View File

@ -30,7 +30,7 @@ import sanitizeHtml from 'sanitize-html'
import AutoLaunch from 'auto-launch' import AutoLaunch from 'auto-launch'
import minimist from 'minimist' import minimist from 'minimist'
import { getAccount, insertAccount, listAccounts } from './account' import { backwardAccount, forwardAccount, getAccount, insertAccount, listAccounts, removeAccount, removeAllAccounts } from './account'
// import { StreamingURL, UserStreaming, DirectStreaming, LocalStreaming, PublicStreaming, ListStreaming, TagStreaming } from './websocket' // import { StreamingURL, UserStreaming, DirectStreaming, LocalStreaming, PublicStreaming, ListStreaming, TagStreaming } from './websocket'
import Preferences from './preferences' import Preferences from './preferences'
import Fonts from './fonts' import Fonts from './fonts'
@ -46,11 +46,11 @@ import ProxyConfiguration from './proxy'
import { Menu as MenuPreferences } from '~/src/types/preference' import { Menu as MenuPreferences } from '~/src/types/preference'
import { General as GeneralPreferences } from '~/src/types/preference' import { General as GeneralPreferences } from '~/src/types/preference'
import newDB from './database' import newDB from './database'
import Settings from './settings' import { Setting } from '~/src/types/setting'
import { BaseSettings, Setting } from '~/src/types/setting'
import { insertServer } from './server' import { insertServer } from './server'
import { LocalServer } from '~src/types/localServer' import { LocalServer } from '~src/types/localServer'
import { insertTag, listTags, removeTag } from './hashtags' import { insertTag, listTags, removeTag } from './hashtags'
import { createOrUpdateSetting, getSetting } from './settings'
/** /**
* Context menu * Context menu
@ -113,7 +113,6 @@ const splashURL =
? path.resolve(__dirname, '../../static/splash-screen.html') ? path.resolve(__dirname, '../../static/splash-screen.html')
: path.join(__dirname, '/static/splash-screen.html') : path.join(__dirname, '/static/splash-screen.html')
// https://github.com/louischatriot/nedb/issues/459
const userData = app.getPath('userData') const userData = app.getPath('userData')
const appPath = app.getPath('exe') const appPath = app.getPath('exe')
@ -122,8 +121,6 @@ const db = newDB(databasePath)
const preferencesDBPath = process.env.NODE_ENV === 'production' ? userData + './db/preferences.json' : 'preferences.json' const preferencesDBPath = process.env.NODE_ENV === 'production' ? userData + './db/preferences.json' : 'preferences.json'
const settingsDBPath = process.env.NODE_ENV === 'production' ? userData + './db/settings.json' : 'settings.json'
const soundBasePath = const soundBasePath =
process.env.NODE_ENV === 'development' ? path.join(__dirname, '../../build/sounds/') : path.join(process.resourcesPath!, 'build/sounds/') process.env.NODE_ENV === 'development' ? path.join(__dirname, '../../build/sounds/') : path.join(process.resourcesPath!, 'build/sounds/')
const iconBasePath = const iconBasePath =
@ -145,7 +142,7 @@ if (process.platform !== 'darwin') {
}) })
} }
async function changeAccount([account, _server]: [LocalAccount, LocalServer], index: number) { async function changeAccount(account: LocalAccount, index: number) {
// Sometimes application is closed to tray. // Sometimes application is closed to tray.
// In this time, mainWindow in not exist, so we have to create window. // In this time, mainWindow in not exist, so we have to create window.
if (mainWindow === null) { if (mainWindow === null) {
@ -229,7 +226,7 @@ async function createWindow() {
return { return {
label: s.domain, label: s.domain,
accelerator: `CmdOrCtrl+${index + 1}`, accelerator: `CmdOrCtrl+${index + 1}`,
click: () => changeAccount([a, s], index) click: () => changeAccount(a, index)
} }
}) })
@ -497,66 +494,52 @@ ipcMain.handle('get-local-account', async (_: IpcMainInvokeEvent, id: number) =>
return account return account
}) })
// ipcMain.handle('update-account', async (_: IpcMainInvokeEvent, acct: LocalAccount) => { ipcMain.handle('remove-account', async (_: IpcMainInvokeEvent, id: number) => {
// const proxy = await proxyConfiguration.forMastodon() await removeAccount(db, id)
// const ac: LocalAccount = await accountRepo.refresh(acct, proxy)
// return ac
// })
// ipcMain.handle('remove-account', async (_: IpcMainInvokeEvent, id: string) => { const accounts = await listAccounts(db)
// const accountId = await accountRepo.removeAccount(id) const accountsChange: Array<MenuItemConstructorOptions> = accounts.map(([account, server], index) => {
return {
label: server.domain,
accelerator: `CmdOrCtrl+${index + 1}`,
click: () => changeAccount(account, index)
}
})
// const accounts = await listAccounts() await updateApplicationMenu(accountsChange)
// const accountsChange: Array<MenuItemConstructorOptions> = accounts.map((a, index) => { await updateDockMenu(accountsChange)
// return { if (process.platform !== 'darwin' && tray !== null) {
// label: a.domain, tray.setContextMenu(TrayMenu(accountsChange, i18next))
// accelerator: `CmdOrCtrl+${index + 1}`, }
// click: () => changeAccount(a, index)
// }
// })
// await updateApplicationMenu(accountsChange) // TODO: stopUserStreaming(accountId)
// await updateDockMenu(accountsChange) })
// if (process.platform !== 'darwin' && tray !== null) {
// tray.setContextMenu(TrayMenu(accountsChange, i18next))
// }
// stopUserStreaming(accountId) ipcMain.handle('forward-account', async (_: IpcMainInvokeEvent, acct: LocalAccount) => {
// }) await forwardAccount(db, acct)
})
// ipcMain.handle('forward-account', async (_: IpcMainInvokeEvent, acct: LocalAccount) => { ipcMain.handle('backward-account', async (_: IpcMainInvokeEvent, acct: LocalAccount) => {
// await accountRepo.forwardAccount(acct) await backwardAccount(db, acct)
// }) })
// ipcMain.handle('backward-account', async (_: IpcMainInvokeEvent, acct: LocalAccount) => { ipcMain.handle('remove-all-accounts', async (_: IpcMainInvokeEvent) => {
// await accountRepo.backwardAccount(acct) await removeAllAccounts(db)
// }) const accounts = await listAccounts(db)
const accountsChange: Array<MenuItemConstructorOptions> = accounts.map(([account, server], index) => {
return {
label: server.domain,
accelerator: `CmdOrCtrl+${index + 1}`,
click: () => changeAccount(account, index)
}
})
// ipcMain.handle('refresh-accounts', async (_: IpcMainInvokeEvent) => { await updateApplicationMenu(accountsChange)
// const proxy = await proxyConfiguration.forMastodon() await updateDockMenu(accountsChange)
// const accounts = await accountRepo.refreshAccounts(proxy) if (process.platform !== 'darwin' && tray !== null) {
tray.setContextMenu(TrayMenu(accountsChange, i18next))
// return accounts }
// }) })
// ipcMain.handle('remove-all-accounts', async (_: IpcMainInvokeEvent) => {
// await accountRepo.removeAll()
// const accounts = await listAccounts()
// const accountsChange: Array<MenuItemConstructorOptions> = accounts.map((a, index) => {
// return {
// label: a.domain,
// accelerator: `CmdOrCtrl+${index + 1}`,
// click: () => changeAccount(a, index)
// }
// })
// await updateApplicationMenu(accountsChange)
// await updateDockMenu(accountsChange)
// if (process.platform !== 'darwin' && tray !== null) {
// tray.setContextMenu(TrayMenu(accountsChange, i18next))
// }
// })
ipcMain.handle('change-auto-launch', async (_: IpcMainInvokeEvent, enable: boolean) => { ipcMain.handle('change-auto-launch', async (_: IpcMainInvokeEvent, enable: boolean) => {
if (launcher) { if (launcher) {
@ -1072,7 +1055,7 @@ ipcMain.handle('change-language', async (_: IpcMainInvokeEvent, value: string) =
return { return {
label: s.domain, label: s.domain,
accelerator: `CmdOrCtrl+${index + 1}`, accelerator: `CmdOrCtrl+${index + 1}`,
click: () => changeAccount([a, s], index) click: () => changeAccount(a, index)
} }
}) })
@ -1139,18 +1122,17 @@ ipcMain.handle('list-fonts', async (_: IpcMainInvokeEvent) => {
// Settings // Settings
ipcMain.handle( ipcMain.handle(
'get-account-setting', 'get-account-setting',
async (_: IpcMainInvokeEvent, accountID: string): Promise<Setting> => { async (_: IpcMainInvokeEvent, accountId: number): Promise<Setting> => {
const settings = new Settings(settingsDBPath) const setting = await getSetting(db, accountId)
const setting = await settings.get(accountID)
return setting return setting
} }
) )
ipcMain.handle( ipcMain.handle(
'update-account-setting', 'update-account-setting',
async (_: IpcMainInvokeEvent, setting: Setting): Promise<BaseSettings> => { async (_: IpcMainInvokeEvent, setting: Setting): Promise<Setting> => {
const settings = new Settings(settingsDBPath) console.log(setting)
const res = await settings.update(setting) const res = await createOrUpdateSetting(db, setting)
return res return res
} }
) )

View File

@ -1,80 +1,55 @@
import storage from 'electron-json-storage' import sqlite3 from 'sqlite3'
import log from 'electron-log' import { Setting } from '~/src/types/setting'
import objectAssignDeep from 'object-assign-deep'
import { BaseSettings, Setting } from '~/src/types/setting'
import { DefaultSetting } from '~/src/constants/initializer/setting' import { DefaultSetting } from '~/src/constants/initializer/setting'
import { isEmpty } from 'lodash'
export default class Settings { export const getSetting = (db: sqlite3.Database, accountId: number): Promise<Setting> => {
private path: string return new Promise((resolve, reject) => {
db.get('SELECT * FROM settings WHERE account_id = ?', accountId, (err, row) => {
constructor(path: string) { if (err) {
this.path = path reject(err)
}
public async _load(): Promise<BaseSettings> {
try {
const settings = await this._get()
if (isEmpty(settings)) {
return []
} }
return settings if (row) {
} catch (err) { resolve({
log.error(err) accountId: row.account_id,
return [] markerHome: Boolean(row.marker_home),
} markerNotifications: Boolean(row.marker_notifications)
} })
}
public async get(accountID: string): Promise<Setting> { resolve(DefaultSetting)
const current = await this._load()
const find: Setting | undefined = current.find(d => {
return d.accountID === accountID
}) })
if (find) { })
return objectAssignDeep({}, DefaultSetting, find) }
}
return objectAssignDeep({}, DefaultSetting, { export const createOrUpdateSetting = (db: sqlite3.Database, setting: Setting): Promise<Setting> => {
accountID: accountID return new Promise((resolve, reject) => {
}) db.get('SELECT * FROM settings WHERE account_id = ?', setting.accountId, (err, row) => {
} if (err) {
reject(err)
private _get(): Promise<BaseSettings> { }
return new Promise((resolve, reject) => { if (row) {
storage.get(this.path, (err, data) => { db.run(
if (err) return reject(err) 'UPDATE settings SET marker_home = ?, marker_notifications = ? WHERE account_id = ?',
return resolve(data as BaseSettings) [setting.markerHome, setting.markerNotifications, setting.accountId],
}) err => {
}) if (err) {
} reject(err)
}
private _save(data: BaseSettings): Promise<BaseSettings> { resolve(setting)
return new Promise((resolve, reject) => { }
storage.set(this.path, data, err => { )
if (err) return reject(err) resolve(setting)
return resolve(data) } else {
}) db.run(
}) 'INSERT INTO settings(account_id, marker_home, marker_notifications) VALUES (?, ?, ?)',
} [setting.accountId, setting.markerHome, setting.markerNotifications],
function (err) {
public async update(obj: Setting): Promise<BaseSettings> { if (err) {
const current = await this._load() reject(err)
const find = current.find(d => { }
return d.accountID === obj.accountID resolve(setting)
}) }
if (find) { )
const data = current.map(d => { }
if (d.accountID !== obj.accountID) { })
return d })
}
const newData = objectAssignDeep({}, d, obj)
return newData
})
const result = await this._save(data)
return result
} else {
const data = current.concat([obj])
const result = await this._save(data)
return result
}
}
} }

View File

@ -64,7 +64,7 @@ export default defineComponent({
const activeRoute = computed(() => route.path) const activeRoute = computed(() => route.path)
onMounted(() => { onMounted(() => {
store.commit(`${space}/${MUTATION_TYPES.CHANGE_ACCOUNT_ID}`, id.value) store.commit(`${space}/${MUTATION_TYPES.CHANGE_ACCOUNT_ID}`, parseInt(id.value as string))
router.push(`/${id.value}/settings/general`) router.push(`/${id.value}/settings/general`)
}) })

View File

@ -1,23 +1,6 @@
<template> <template>
<div id="timeline"> <div id="timeline">
<h2>{{ $t('settings.timeline.title') }}</h2> <h2>{{ $t('settings.timeline.title') }}</h2>
<el-form class="unread-notification section" size="default" label-position="right" label-width="250px">
<h3>{{ $t('settings.timeline.unread_notification.title') }}</h3>
<p class="description">
{{ $t('settings.timeline.unread_notification.description') }}
</p>
<el-form-item for="direct" :label="$t('settings.timeline.unread_notification.direct')">
<el-switch v-model="directNotify" id="direct" />
</el-form-item>
<el-form-item for="local" :label="$t('settings.timeline.unread_notification.local')">
<el-switch v-model="localNotify" id="local" />
</el-form-item>
<el-form-item for="public" :label="$t('settings.timeline.unread_notification.public')">
<el-switch v-model="publicNotify" id="public" />
</el-form-item>
</el-form>
<el-form class="use-marker section" size="default" label-position="right" label-width="250px"> <el-form class="use-marker section" size="default" label-position="right" label-width="250px">
<h3>{{ $t('settings.timeline.use_marker.title') }}</h3> <h3>{{ $t('settings.timeline.use_marker.title') }}</h3>
<el-form-item for="marker_home" :label="$t('settings.timeline.use_marker.home')"> <el-form-item for="marker_home" :label="$t('settings.timeline.use_marker.home')">
@ -41,39 +24,18 @@ export default defineComponent({
const space = 'Settings/Timeline' const space = 'Settings/Timeline'
const store = useStore() const store = useStore()
const directNotify = computed({
get: () => store.state.Settings.Timeline.setting.unreadNotification.direct,
set: value =>
store.dispatch(`${space}/${ACTION_TYPES.CHANGE_UNREAD_NOTIFICATION}`, {
direct: value
})
})
const localNotify = computed({
get: () => store.state.Settings.Timeline.setting.unreadNotification.local,
set: value =>
store.dispatch(`${space}/${ACTION_TYPES.CHANGE_UNREAD_NOTIFICATION}`, {
local: value
})
})
const publicNotify = computed({
get: () => store.state.Settings.Timeline.setting.unreadNotification.public,
set: value =>
store.dispatch(`${space}/${ACTION_TYPES.CHANGE_UNREAD_NOTIFICATION}`, {
public: value
})
})
const marker_home = computed({ const marker_home = computed({
get: () => store.state.Settings.Timeline.setting.useMarker.home, get: () => store.state.Settings.Timeline.setting.markerHome,
set: value => set: value =>
store.dispatch(`${space}/${ACTION_TYPES.CHANGE_USER_MARKER}`, { store.dispatch(`${space}/${ACTION_TYPES.CHANGE_USER_MARKER}`, {
home: value markerHome: value
}) })
}) })
const marker_notifications = computed({ const marker_notifications = computed({
get: () => store.state.Settings.Timeline.setting.useMarker.notifications, get: () => store.state.Settings.Timeline.setting.markerNotifications,
set: value => set: value =>
store.dispatch(`${space}/${ACTION_TYPES.CHANGE_USER_MARKER}`, { store.dispatch(`${space}/${ACTION_TYPES.CHANGE_USER_MARKER}`, {
notifications: value markerNotifications: value
}) })
}) })
@ -82,9 +44,6 @@ export default defineComponent({
}) })
return { return {
directNotify,
localNotify,
publicNotify,
marker_home, marker_home,
marker_notifications marker_notifications
} }

View File

@ -299,7 +299,7 @@ export default defineComponent({
onMounted(() => { onMounted(() => {
store.dispatch(`${space}/${ACTION_TYPES.READ_COLLAPSE}`) store.dispatch(`${space}/${ACTION_TYPES.READ_COLLAPSE}`)
store.dispatch(`${space}/${ACTION_TYPES.LIST_TAGS}`) store.dispatch(`${space}/${ACTION_TYPES.LIST_TAGS}`, parseInt(id.value as string))
}) })
const handleProfile = (command: string) => { const handleProfile = (command: string) => {

View File

@ -149,7 +149,7 @@
<el-button v-if="quoteSupported" link class="quote-btn" @click="openQuote()"> <el-button v-if="quoteSupported" link class="quote-btn" @click="openQuote()">
<font-awesome-icon icon="quote-right" size="sm" /> <font-awesome-icon icon="quote-right" size="sm" />
</el-button> </el-button>
<template v-if="sns !== 'mastodon'"> <template v-if="server!.sns !== 'mastodon'">
<el-popover <el-popover
placement="bottom" placement="bottom"
width="281" width="281"
@ -300,7 +300,7 @@ export default defineComponent({
default: false default: false
} }
}, },
emits: ['selectToot', 'focusRight', 'focusLeft'], emits: ['selectToot', 'focusRight', 'focusLeft', 'update', 'delete', 'sizeChanged'],
setup(props, ctx) { setup(props, ctx) {
const space = 'organisms/Toot' const space = 'organisms/Toot'
const store = useStore() const store = useStore()
@ -320,7 +320,7 @@ export default defineComponent({
const displayNameStyle = computed(() => store.state.App.displayNameStyle) const displayNameStyle = computed(() => store.state.App.displayNameStyle)
const timeFormat = computed(() => store.state.App.timeFormat) const timeFormat = computed(() => store.state.App.timeFormat)
const language = computed(() => store.state.App.language) const language = computed(() => store.state.App.language)
const sns = computed(() => store.state.TimelineSpace.sns) const server = computed(() => store.state.TimelineSpace.server)
const account = computed(() => store.state.TimelineSpace.account) const account = computed(() => store.state.TimelineSpace.account)
const bookmarkSupported = computed(() => store.state.TimelineSpace.SideMenu.enabledTimelines.bookmark) const bookmarkSupported = computed(() => store.state.TimelineSpace.SideMenu.enabledTimelines.bookmark)
const shortcutEnabled = computed(() => focused.value && !overlaid.value) const shortcutEnabled = computed(() => focused.value && !overlaid.value)
@ -352,7 +352,7 @@ export default defineComponent({
return null return null
}) })
const isMyMessage = computed(() => { const isMyMessage = computed(() => {
return store.state.TimelineSpace.account.accountId === originalMessage.value.account.id return account.value!.accountId === originalMessage.value.account.id
}) })
const application = computed(() => { const application = computed(() => {
const msg = originalMessage.value const msg = originalMessage.value
@ -386,7 +386,7 @@ export default defineComponent({
return originalMessage.value.visibility === 'direct' return originalMessage.value.visibility === 'direct'
}) })
const quoteSupported = computed(() => { const quoteSupported = computed(() => {
return QuoteSupported(sns.value, account.value.domain) return QuoteSupported(server.value!.sns, server.value!.domain)
}) })
whenever(logicAnd(l, shortcutEnabled), () => { whenever(logicAnd(l, shortcutEnabled), () => {
@ -677,7 +677,7 @@ export default defineComponent({
displayNameStyle, displayNameStyle,
timeFormat, timeFormat,
language, language,
sns, server,
account, account,
bookmarkSupported, bookmarkSupported,
originalMessage, originalMessage,

View File

@ -5,20 +5,20 @@ import { Module, MutationTree } from 'vuex'
import { RootState } from '@/store' import { RootState } from '@/store'
export type SettingsState = { export type SettingsState = {
accountID: string | null accountId: number | null
} }
const state = (): SettingsState => ({ const state = (): SettingsState => ({
accountID: null accountId: null
}) })
export const MUTATION_TYPES = { export const MUTATION_TYPES = {
CHANGE_ACCOUNT_ID: 'changeAccountID' CHANGE_ACCOUNT_ID: 'changeAccountId'
} }
const mutations: MutationTree<SettingsState> = { const mutations: MutationTree<SettingsState> = {
[MUTATION_TYPES.CHANGE_ACCOUNT_ID]: (state, id: string) => { [MUTATION_TYPES.CHANGE_ACCOUNT_ID]: (state, id: number) => {
state.accountID = id state.accountId = id
} }
} }

View File

@ -1,18 +1,17 @@
import { Module, MutationTree, ActionTree } from 'vuex' import { Module, MutationTree, ActionTree } from 'vuex'
import { toRaw } from 'vue'
import { RootState } from '@/store' import { RootState } from '@/store'
import { MyWindow } from '~/src/types/global' import { MyWindow } from '~/src/types/global'
import { Setting, UnreadNotification, Timeline as TimelineSetting, UseMarker } from '~src/types/setting' import { Setting } from '~src/types/setting'
import { DefaultSetting } from '~/src/constants/initializer/setting' import { DefaultSetting } from '~/src/constants/initializer/setting'
const win = window as any as MyWindow const win = (window as any) as MyWindow
export type TimelineState = { export type TimelineState = {
setting: TimelineSetting setting: Setting
} }
const state = (): TimelineState => ({ const state = (): TimelineState => ({
setting: DefaultSetting.timeline setting: DefaultSetting
}) })
export const MUTATION_TYPES = { export const MUTATION_TYPES = {
@ -20,7 +19,7 @@ export const MUTATION_TYPES = {
} }
const mutations: MutationTree<TimelineState> = { const mutations: MutationTree<TimelineState> = {
[MUTATION_TYPES.UPDATE_TIMELINE_SETTING]: (state, setting: TimelineSetting) => { [MUTATION_TYPES.UPDATE_TIMELINE_SETTING]: (state, setting: Setting) => {
state.setting = setting state.setting = setting
} }
} }
@ -33,34 +32,16 @@ export const ACTION_TYPES = {
const actions: ActionTree<TimelineState, RootState> = { const actions: ActionTree<TimelineState, RootState> = {
[ACTION_TYPES.LOAD_TIMELINE_SETTING]: async ({ commit, rootState }): Promise<boolean> => { [ACTION_TYPES.LOAD_TIMELINE_SETTING]: async ({ commit, rootState }): Promise<boolean> => {
const setting: Setting = await win.ipcRenderer.invoke('get-account-setting', rootState.Settings.accountID) const setting: Setting = await win.ipcRenderer.invoke('get-account-setting', rootState.Settings.accountId)
commit(MUTATION_TYPES.UPDATE_TIMELINE_SETTING, setting.timeline) commit(MUTATION_TYPES.UPDATE_TIMELINE_SETTING, setting)
return true
},
[ACTION_TYPES.CHANGE_UNREAD_NOTIFICATION]: async ({ dispatch, state, rootState }, timeline: { key: boolean }): Promise<boolean> => {
const unread: UnreadNotification = Object.assign({}, state.setting.unreadNotification, timeline)
const tl: TimelineSetting = Object.assign({}, toRaw(state.setting), {
unreadNotification: unread
})
const setting: Setting = {
accountID: rootState.Settings.accountID!,
timeline: tl
}
await win.ipcRenderer.invoke('update-account-setting', setting)
dispatch('loadTimelineSetting')
return true return true
}, },
[ACTION_TYPES.CHANGE_USER_MARKER]: async ({ dispatch, state, rootState }, timeline: { key: boolean }) => { [ACTION_TYPES.CHANGE_USER_MARKER]: async ({ dispatch, state, rootState }, timeline: { key: boolean }) => {
const marker: UseMarker = Object.assign({}, state.setting.useMarker, timeline) const setting: Setting = Object.assign({}, state.setting, timeline)
const tl: TimelineSetting = Object.assign({}, toRaw(state.setting), { setting.accountId = rootState.Settings.accountId!
useMarker: marker console.log(setting)
})
const setting: Setting = {
accountID: rootState.Settings.accountID!,
timeline: tl
}
await win.ipcRenderer.invoke('update-account-setting', setting) await win.ipcRenderer.invoke('update-account-setting', setting)
dispatch('loadTimelineSetting') dispatch(ACTION_TYPES.LOAD_TIMELINE_SETTING)
return true return true
} }
} }

View File

@ -9,9 +9,9 @@ import { RootState } from '@/store'
import { AccountLoadError } from '@/errors/load' import { AccountLoadError } from '@/errors/load'
import { TimelineFetchError } from '@/errors/fetch' import { TimelineFetchError } from '@/errors/fetch'
import { MyWindow } from '~/src/types/global' import { MyWindow } from '~/src/types/global'
import { Timeline, Setting } from '~src/types/setting'
import { DefaultSetting } from '~/src/constants/initializer/setting'
import { LocalServer } from '~src/types/localServer' import { LocalServer } from '~src/types/localServer'
import { Setting } from '~/src/types/setting'
import { DefaultSetting } from '~/src/constants/initializer/setting'
const win = (window as any) as MyWindow const win = (window as any) as MyWindow
@ -21,8 +21,8 @@ export type TimelineSpaceState = {
loading: boolean loading: boolean
emojis: Array<Entity.Emoji> emojis: Array<Entity.Emoji>
tootMax: number tootMax: number
timelineSetting: Timeline
filters: Array<Entity.Filter> filters: Array<Entity.Filter>
setting: Setting
} }
const state = (): TimelineSpaceState => ({ const state = (): TimelineSpaceState => ({
@ -31,8 +31,8 @@ const state = (): TimelineSpaceState => ({
loading: false, loading: false,
emojis: [], emojis: [],
tootMax: 500, tootMax: 500,
timelineSetting: DefaultSetting.timeline, filters: [],
filters: [] setting: DefaultSetting
}) })
export const MUTATION_TYPES = { export const MUTATION_TYPES = {
@ -41,8 +41,8 @@ export const MUTATION_TYPES = {
CHANGE_LOADING: 'changeLoading', CHANGE_LOADING: 'changeLoading',
UPDATE_EMOJIS: 'updateEmojis', UPDATE_EMOJIS: 'updateEmojis',
UPDATE_TOOT_MAX: 'updateTootMax', UPDATE_TOOT_MAX: 'updateTootMax',
UPDATE_TIMELINE_SETTING: 'updateTimelineSetting', UPDATE_FILTERS: 'updateFilters',
UPDATE_FILTERS: 'updateFilters' UPDATE_SETTING: 'updateSetting'
} }
const mutations: MutationTree<TimelineSpaceState> = { const mutations: MutationTree<TimelineSpaceState> = {
@ -65,11 +65,11 @@ const mutations: MutationTree<TimelineSpaceState> = {
state.tootMax = 500 state.tootMax = 500
} }
}, },
[MUTATION_TYPES.UPDATE_TIMELINE_SETTING]: (state, setting: Timeline) => {
state.timelineSetting = setting
},
[MUTATION_TYPES.UPDATE_FILTERS]: (state, filters: Array<Entity.Filter>) => { [MUTATION_TYPES.UPDATE_FILTERS]: (state, filters: Array<Entity.Filter>) => {
state.filters = filters state.filters = filters
},
[MUTATION_TYPES.UPDATE_SETTING]: (state, setting: Setting) => {
state.setting = setting
} }
} }
@ -84,7 +84,6 @@ export const ACTION_TYPES = {
FETCH_EMOJIS: 'fetchEmojis', FETCH_EMOJIS: 'fetchEmojis',
FETCH_FILTERS: 'fetchFilters', FETCH_FILTERS: 'fetchFilters',
FETCH_INSTANCE: 'fetchInstance', FETCH_INSTANCE: 'fetchInstance',
LOAD_TIMELINE_SETTING: 'loadTimelineSetting',
FETCH_CONTENTS_TIMELINES: 'fetchContentsTimelines', FETCH_CONTENTS_TIMELINES: 'fetchContentsTimelines',
CLEAR_CONTENTS_TIMELINES: 'clearContentsTimelines', CLEAR_CONTENTS_TIMELINES: 'clearContentsTimelines',
BIND_STREAMINGS: 'bindStreamings', BIND_STREAMINGS: 'bindStreamings',
@ -92,7 +91,8 @@ export const ACTION_TYPES = {
BIND_LOCAL_STREAMING: 'bindLocalStreaming', BIND_LOCAL_STREAMING: 'bindLocalStreaming',
BIND_PUBLIC_STREAMING: 'bindPublicStreaming', BIND_PUBLIC_STREAMING: 'bindPublicStreaming',
BIND_DIRECT_MESSAGES_STREAMING: 'bindDirectMessagesStreaming', BIND_DIRECT_MESSAGES_STREAMING: 'bindDirectMessagesStreaming',
UPDATE_TOOT_FOR_ALL_TIMELINES: 'updateTootForAllTimelines' UPDATE_TOOT_FOR_ALL_TIMELINES: 'updateTootForAllTimelines',
LOAD_SETTING: 'loadSetting'
} }
const actions: ActionTree<TimelineSpaceState, RootState> = { const actions: ActionTree<TimelineSpaceState, RootState> = {
@ -107,7 +107,7 @@ const actions: ActionTree<TimelineSpaceState, RootState> = {
dispatch('TimelineSpace/SideMenu/fetchLists', null, { root: true }) dispatch('TimelineSpace/SideMenu/fetchLists', null, { root: true })
dispatch('TimelineSpace/SideMenu/fetchFollowRequests', null, { root: true }) dispatch('TimelineSpace/SideMenu/fetchFollowRequests', null, { root: true })
dispatch('TimelineSpace/SideMenu/confirmTimelines', null, { root: true }) dispatch('TimelineSpace/SideMenu/confirmTimelines', null, { root: true })
await dispatch(ACTION_TYPES.LOAD_TIMELINE_SETTING, accountId) await dispatch(ACTION_TYPES.LOAD_SETTING)
await dispatch(ACTION_TYPES.FETCH_FILTERS) await dispatch(ACTION_TYPES.FETCH_FILTERS)
commit(MUTATION_TYPES.CHANGE_LOADING, false) commit(MUTATION_TYPES.CHANGE_LOADING, false)
await dispatch(ACTION_TYPES.FETCH_CONTENTS_TIMELINES).catch(_ => { await dispatch(ACTION_TYPES.FETCH_CONTENTS_TIMELINES).catch(_ => {
@ -170,6 +170,10 @@ const actions: ActionTree<TimelineSpaceState, RootState> = {
commit(MUTATION_TYPES.UPDATE_EMOJIS, res.data) commit(MUTATION_TYPES.UPDATE_EMOJIS, res.data)
return res.data return res.data
}, },
[ACTION_TYPES.LOAD_SETTING]: async ({ commit, state }) => {
const setting: Setting = await win.ipcRenderer.invoke('get-account-setting', state.account!.id)
commit(MUTATION_TYPES.UPDATE_SETTING, setting)
},
/** /**
* fetchFilters * fetchFilters
*/ */
@ -197,11 +201,7 @@ const actions: ActionTree<TimelineSpaceState, RootState> = {
} }
return true return true
}, },
[ACTION_TYPES.LOAD_TIMELINE_SETTING]: async ({ commit }, accountID: string) => { [ACTION_TYPES.FETCH_CONTENTS_TIMELINES]: async ({ dispatch }) => {
const setting: Setting = await win.ipcRenderer.invoke('get-account-setting', accountID)
commit(MUTATION_TYPES.UPDATE_TIMELINE_SETTING, setting.timeline)
},
[ACTION_TYPES.FETCH_CONTENTS_TIMELINES]: async ({ dispatch, state }) => {
dispatch('TimelineSpace/Contents/changeLoading', true, { root: true }) dispatch('TimelineSpace/Contents/changeLoading', true, { root: true })
await dispatch('TimelineSpace/Contents/Home/fetchTimeline', {}, { root: true }).finally(() => { await dispatch('TimelineSpace/Contents/Home/fetchTimeline', {}, { root: true }).finally(() => {
dispatch('TimelineSpace/Contents/changeLoading', false, { root: true }) dispatch('TimelineSpace/Contents/changeLoading', false, { root: true })
@ -209,15 +209,9 @@ const actions: ActionTree<TimelineSpaceState, RootState> = {
await dispatch('TimelineSpace/Contents/Notifications/fetchNotifications', {}, { root: true }) await dispatch('TimelineSpace/Contents/Notifications/fetchNotifications', {}, { root: true })
await dispatch('TimelineSpace/Contents/Mentions/fetchMentions', {}, { root: true }) await dispatch('TimelineSpace/Contents/Mentions/fetchMentions', {}, { root: true })
if (state.timelineSetting.unreadNotification.direct) { await dispatch('TimelineSpace/Contents/DirectMessages/fetchTimeline', {}, { root: true })
await dispatch('TimelineSpace/Contents/DirectMessages/fetchTimeline', {}, { root: true }) await dispatch('TimelineSpace/Contents/Local/fetchLocalTimeline', {}, { root: true })
} await dispatch('TimelineSpace/Contents/Public/fetchPublicTimeline', {}, { root: true })
if (state.timelineSetting.unreadNotification.local) {
await dispatch('TimelineSpace/Contents/Local/fetchLocalTimeline', {}, { root: true })
}
if (state.timelineSetting.unreadNotification.public) {
await dispatch('TimelineSpace/Contents/Public/fetchPublicTimeline', {}, { root: true })
}
}, },
[ACTION_TYPES.CLEAR_CONTENTS_TIMELINES]: ({ commit }) => { [ACTION_TYPES.CLEAR_CONTENTS_TIMELINES]: ({ commit }) => {
commit('TimelineSpace/Contents/Home/clearTimeline', {}, { root: true }) commit('TimelineSpace/Contents/Home/clearTimeline', {}, { root: true })
@ -227,17 +221,11 @@ const actions: ActionTree<TimelineSpaceState, RootState> = {
commit('TimelineSpace/Contents/Public/clearTimeline', {}, { root: true }) commit('TimelineSpace/Contents/Public/clearTimeline', {}, { root: true })
commit('TimelineSpace/Contents/Mentions/clearMentions', {}, { root: true }) commit('TimelineSpace/Contents/Mentions/clearMentions', {}, { root: true })
}, },
[ACTION_TYPES.BIND_STREAMINGS]: ({ dispatch, state }) => { [ACTION_TYPES.BIND_STREAMINGS]: ({ dispatch }) => {
dispatch('bindUserStreaming') dispatch('bindUserStreaming')
if (state.timelineSetting.unreadNotification.direct) { dispatch('bindDirectMessagesStreaming')
dispatch('bindDirectMessagesStreaming') dispatch('bindLocalStreaming')
} dispatch('bindPublicStreaming')
if (state.timelineSetting.unreadNotification.local) {
dispatch('bindLocalStreaming')
}
if (state.timelineSetting.unreadNotification.public) {
dispatch('bindPublicStreaming')
}
}, },
// ------------------------------------------------ // ------------------------------------------------
// Each streaming methods // Each streaming methods
@ -312,19 +300,13 @@ const actions: ActionTree<TimelineSpaceState, RootState> = {
}) })
}, },
[ACTION_TYPES.UPDATE_TOOT_FOR_ALL_TIMELINES]: ({ commit, state }, status: Entity.Status): boolean => { [ACTION_TYPES.UPDATE_TOOT_FOR_ALL_TIMELINES]: ({ commit }, status: Entity.Status): boolean => {
commit('TimelineSpace/Contents/Home/updateToot', status, { root: true }) commit('TimelineSpace/Contents/Home/updateToot', status, { root: true })
commit('TimelineSpace/Contents/Notifications/updateToot', status, { root: true }) commit('TimelineSpace/Contents/Notifications/updateToot', status, { root: true })
commit('TimelineSpace/Contents/Mentions/updateToot', status, { root: true }) commit('TimelineSpace/Contents/Mentions/updateToot', status, { root: true })
if (state.timelineSetting.unreadNotification.direct) { commit('TimelineSpace/Contents/DirectMessages/updateToot', status, { root: true })
commit('TimelineSpace/Contents/DirectMessages/updateToot', status, { root: true }) commit('TimelineSpace/Contents/Local/updateToot', status, { root: true })
} commit('TimelineSpace/Contents/Public/updateToot', status, { root: true })
if (state.timelineSetting.unreadNotification.local) {
commit('TimelineSpace/Contents/Local/updateToot', status, { root: true })
}
if (state.timelineSetting.unreadNotification.public) {
commit('TimelineSpace/Contents/Public/updateToot', status, { root: true })
}
return true return true
} }
} }

View File

@ -144,7 +144,7 @@ const actions: ActionTree<HomeState, RootState> = {
console.error(err) console.error(err)
}) })
if (rootState.TimelineSpace.timelineSetting.useMarker.home && marker !== null && marker.home) { if (rootState.TimelineSpace.setting.markerHome && marker !== null && marker.home) {
const last = await client.getStatus(marker.home.last_read_id) const last = await client.getStatus(marker.home.last_read_id)
const lastReadStatus = last.data const lastReadStatus = last.data
@ -255,7 +255,7 @@ const actions: ActionTree<HomeState, RootState> = {
return res.data return res.data
}, },
[ACTION_TYPES.GET_MARKER]: async ({ rootState }): Promise<Entity.Marker | null> => { [ACTION_TYPES.GET_MARKER]: async ({ rootState }): Promise<Entity.Marker | null> => {
if (!rootState.TimelineSpace.timelineSetting.useMarker.home) { if (!rootState.TimelineSpace.setting.markerHome) {
return null return null
} }
const client = generator( const client = generator(

View File

@ -122,7 +122,7 @@ const actions: ActionTree<NotificationsState, RootState> = {
console.error(err) console.error(err)
}) })
if (rootState.TimelineSpace.timelineSetting.useMarker.notifications && marker !== null && marker.notifications) { if (rootState.TimelineSpace.setting.markerNotifications && marker !== null && marker.notifications) {
// The result does not contain max_id's notification, when we specify max_id parameter in get notifications. // The result does not contain max_id's notification, when we specify max_id parameter in get notifications.
// So we need to get max_id's notification. // So we need to get max_id's notification.
const nextResponse = await client.getNotifications({ limit: 1, min_id: marker.notifications.last_read_id }) const nextResponse = await client.getNotifications({ limit: 1, min_id: marker.notifications.last_read_id })
@ -224,7 +224,7 @@ const actions: ActionTree<NotificationsState, RootState> = {
win.ipcRenderer.send('reset-badge') win.ipcRenderer.send('reset-badge')
}, },
[ACTION_TYPES.GET_MARKER]: async ({ rootState }): Promise<Entity.Marker | null> => { [ACTION_TYPES.GET_MARKER]: async ({ rootState }): Promise<Entity.Marker | null> => {
if (!rootState.TimelineSpace.timelineSetting.useMarker.notifications) { if (!rootState.TimelineSpace.setting.markerNotifications) {
return null return null
} }
const client = generator( const client = generator(

View File

@ -223,10 +223,8 @@ const actions: ActionTree<SideMenuState, RootState> = {
commit(MUTATION_TYPES.CHANGE_COLLAPSE, value) commit(MUTATION_TYPES.CHANGE_COLLAPSE, value)
return value return value
}, },
[ACTION_TYPES.LIST_TAGS]: async ({ rootState, commit }) => { [ACTION_TYPES.LIST_TAGS]: async ({ commit }, accountId: number) => {
// TODO: Can not get account because too early. const tags: Array<LocalTag> = await win.ipcRenderer.invoke('list-hashtags', accountId)
// It should be executed after TimelineSpace obtain local account.
const tags: Array<LocalTag> = await win.ipcRenderer.invoke('list-hashtags', rootState.TimelineSpace.account!.id)
commit(MUTATION_TYPES.UPDATE_TAGS, tags) commit(MUTATION_TYPES.UPDATE_TAGS, tags)
return tags return tags
} }

View File

@ -1,21 +1,5 @@
export type UnreadNotification = {
direct: boolean
local: boolean
public: boolean
}
export type UseMarker = {
home: boolean
notifications: boolean
}
export type Timeline = {
unreadNotification: UnreadNotification
useMarker: UseMarker
}
export type Setting = { export type Setting = {
accountID: string accountId: number
timeline: Timeline markerHome: boolean
markerNotifications: boolean
} }
export type BaseSettings = Array<Setting>