Page MenuHomePhorge

No OneTemporary

Size
26 KB
Referenced Files
None
Subscribers
None
diff --git a/src/components/settings_modal/helpers/boolean_setting.vue b/src/components/settings_modal/helpers/boolean_setting.vue
new file mode 100644
index 0000000000..11b354edba
--- /dev/null
+++ b/src/components/settings_modal/helpers/boolean_setting.vue
@@ -0,0 +1,57 @@
+<template>
+ <label
+ class="BooleanSetting"
+ >
+ <Checkbox
+ :checked="state"
+ @change="update"
+ :disabled="disabled"
+ >
+ <span
+ v-if="!!$slots.default"
+ class="label"
+ >
+ <slot />
+ </span>
+ </Checkbox>
+ <span v-if="isChanged">
+ <strong>CHANGED</strong>
+ </span>
+ </label>
+</template>
+
+<script>
+import { get, set } from 'lodash'
+import Checkbox from 'src/components/checkbox/checkbox.vue'
+export default {
+ props: [
+ 'path',
+ 'disabled'
+ ],
+ components: {
+ Checkbox
+ },
+ computed: {
+ pathDefault () {
+ const [firstSegment, ...rest] = this.path.split('.')
+ return [firstSegment + 'DefaultValue', ...rest].join('.')
+ },
+ state () {
+ return get(this.$parent, this.path)
+ },
+ isChanged () {
+ return get(this.$parent, this.path) !== get(this.$parent, this.pathDefault)
+ }
+ },
+ methods: {
+ update (e) {
+ set(this.$parent, this.path, e)
+ }
+ }
+}
+</script>
+
+<style lang="scss">
+.BooleanSetting {
+}
+</style>
diff --git a/src/components/settings_modal/helpers/shared_computed_object.js b/src/components/settings_modal/helpers/shared_computed_object.js
index 86703697d2..3cbcd28847 100644
--- a/src/components/settings_modal/helpers/shared_computed_object.js
+++ b/src/components/settings_modal/helpers/shared_computed_object.js
@@ -1,58 +1,45 @@
-import {
- instanceDefaultProperties,
- multiChoiceProperties,
- defaultState as configDefaultState
-} from 'src/modules/config.js'
+import { defaultState as configDefaultState } from 'src/modules/config.js'
const SharedComputedObject = () => ({
user () {
return this.$store.state.users.currentUser
},
- // Getting localized values for instance-default properties
- ...instanceDefaultProperties
- .filter(key => multiChoiceProperties.includes(key))
+ // Getting values for default properties
+ ...Object.keys(configDefaultState)
.map(key => [
key + 'DefaultValue',
function () {
- return this.$store.getters.instanceDefaultConfig[key]
- }
- ])
- .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
- ...instanceDefaultProperties
- .filter(key => !multiChoiceProperties.includes(key))
- .map(key => [
- key + 'LocalizedValue',
- function () {
- return this.$t('settings.values.' + this.$store.getters.instanceDefaultConfig[key])
+ console.log(this.$store.getters.defaultConfig)
+ return this.$store.getters.defaultConfig[key]
}
])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
// Generating computed values for vuex properties
...Object.keys(configDefaultState)
.map(key => [key, {
get () { return this.$store.getters.mergedConfig[key] },
set (value) {
this.$store.dispatch('setOption', { name: key, value })
}
}])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
// Special cases (need to transform values or perform actions first)
useStreamingApi: {
get () { return this.$store.getters.mergedConfig.useStreamingApi },
set (value) {
const promise = value
? this.$store.dispatch('enableMastoSockets')
: this.$store.dispatch('disableMastoSockets')
promise.then(() => {
this.$store.dispatch('setOption', { name: 'useStreamingApi', value })
}).catch((e) => {
console.error('Failed starting MastoAPI Streaming socket', e)
this.$store.dispatch('disableMastoSockets')
this.$store.dispatch('setOption', { name: 'useStreamingApi', value: false })
})
}
}
})
export default SharedComputedObject
diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js
index 3b2df55604..042732111d 100644
--- a/src/components/settings_modal/tabs/filtering_tab.js
+++ b/src/components/settings_modal/tabs/filtering_tab.js
@@ -1,47 +1,47 @@
import { filter, trim } from 'lodash'
-import Checkbox from 'src/components/checkbox/checkbox.vue'
+import BooleanSetting from '../helpers/boolean_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const FilteringTab = {
data () {
return {
muteWordsStringLocal: this.$store.getters.mergedConfig.muteWords.join('\n')
}
},
components: {
- Checkbox
+ BooleanSetting
},
computed: {
...SharedComputedObject(),
muteWordsString: {
get () {
return this.muteWordsStringLocal
},
set (value) {
this.muteWordsStringLocal = value
this.$store.dispatch('setOption', {
name: 'muteWords',
value: filter(value.split('\n'), (word) => trim(word).length > 0)
})
}
}
},
// Updating nested properties
watch: {
notificationVisibility: {
handler (value) {
this.$store.dispatch('setOption', {
name: 'notificationVisibility',
value: this.$store.getters.mergedConfig.notificationVisibility
})
},
deep: true
},
replyVisibility () {
this.$store.dispatch('queueFlushAll')
}
}
}
export default FilteringTab
diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue
index eea41514dd..6c42718b14 100644
--- a/src/components/settings_modal/tabs/filtering_tab.vue
+++ b/src/components/settings_modal/tabs/filtering_tab.vue
@@ -1,86 +1,86 @@
<template>
<div :label="$t('settings.filtering')">
<div class="setting-item">
<div class="select-multiple">
<span class="label">{{ $t('settings.notification_visibility') }}</span>
<ul class="option-list">
<li>
- <Checkbox v-model="notificationVisibility.likes">
+ <BooleanSetting path="notificationVisibility.likes">
{{ $t('settings.notification_visibility_likes') }}
- </Checkbox>
+ </BooleanSetting>
</li>
<li>
- <Checkbox v-model="notificationVisibility.repeats">
+ <BooleanSetting path="notificationVisibility.repeats">
{{ $t('settings.notification_visibility_repeats') }}
- </Checkbox>
+ </BooleanSetting>
</li>
<li>
- <Checkbox v-model="notificationVisibility.follows">
+ <BooleanSetting path="notificationVisibility.follows">
{{ $t('settings.notification_visibility_follows') }}
- </Checkbox>
+ </BooleanSetting>
</li>
<li>
- <Checkbox v-model="notificationVisibility.mentions">
+ <BooleanSetting path="notificationVisibility.mentions">
{{ $t('settings.notification_visibility_mentions') }}
- </Checkbox>
+ </BooleanSetting>
</li>
<li>
- <Checkbox v-model="notificationVisibility.moves">
+ <BooleanSetting path="notificationVisibility.moves">
{{ $t('settings.notification_visibility_moves') }}
- </Checkbox>
+ </BooleanSetting>
</li>
<li>
- <Checkbox v-model="notificationVisibility.emojiReactions">
+ <BooleanSetting path="notificationVisibility.emojiReactions">
{{ $t('settings.notification_visibility_emoji_reactions') }}
- </Checkbox>
+ </BooleanSetting>
</li>
</ul>
</div>
<div>
{{ $t('settings.replies_in_timeline') }}
<label
for="replyVisibility"
class="select"
>
<select
id="replyVisibility"
v-model="replyVisibility"
>
<option
value="all"
selected
>{{ $t('settings.reply_visibility_all') }}</option>
<option value="following">{{ $t('settings.reply_visibility_following') }}</option>
<option value="self">{{ $t('settings.reply_visibility_self') }}</option>
</select>
<i class="icon-down-open" />
</label>
</div>
<div>
- <Checkbox v-model="hidePostStats">
- {{ $t('settings.hide_post_stats') }} {{ $t('settings.instance_default', { value: hidePostStatsLocalizedValue }) }}
- </Checkbox>
+ <BooleanSetting path="hidePostStats">
+ {{ $t('settings.hide_post_stats') }}
+ </BooleanSetting>
</div>
<div>
- <Checkbox v-model="hideUserStats">
- {{ $t('settings.hide_user_stats') }} {{ $t('settings.instance_default', { value: hideUserStatsLocalizedValue }) }}
- </Checkbox>
+ <BooleanSetting path="hideUserStats">
+ {{ $t('settings.hide_user_stats') }}
+ </BooleanSetting>
</div>
</div>
<div class="setting-item">
<div>
<p>{{ $t('settings.filtering_explanation') }}</p>
<textarea
id="muteWords"
v-model="muteWordsString"
/>
</div>
<div>
- <Checkbox v-model="hideFilteredStatuses">
- {{ $t('settings.hide_filtered_statuses') }} {{ $t('settings.instance_default', { value: hideFilteredStatusesLocalizedValue }) }}
- </Checkbox>
+ <BooleanSetting path="hideFilteredStatuses">
+ {{ $t('settings.hide_filtered_statuses') }}
+ </BooleanSetting>
</div>
</div>
</div>
</template>
<script src="./filtering_tab.js"></script>
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index 0eb37e44eb..679ef684f2 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -1,31 +1,31 @@
-import Checkbox from 'src/components/checkbox/checkbox.vue'
+import BooleanSetting from '../helpers/boolean_setting.vue'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const GeneralTab = {
data () {
return {
loopSilentAvailable:
// Firefox
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
// Chrome-likes
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') ||
// Future spec, still not supported in Nightly 63 as of 08/2018
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks')
}
},
components: {
- Checkbox,
+ BooleanSetting,
InterfaceLanguageSwitcher
},
computed: {
postFormats () {
return this.$store.state.instance.postFormats || []
},
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
...SharedComputedObject()
}
}
export default GeneralTab
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index 7f06d0bd3a..e03e731b23 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -1,262 +1,262 @@
<template>
<div :label="$t('settings.general')">
<div class="setting-item">
<h2>{{ $t('settings.interface') }}</h2>
<ul class="setting-list">
<li>
<interface-language-switcher />
</li>
<li v-if="instanceSpecificPanelPresent">
- <Checkbox v-model="hideISP">
+ <BooleanSetting path="hideISP">
{{ $t('settings.hide_isp') }}
- </Checkbox>
+ </BooleanSetting>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('nav.timeline') }}</h2>
<ul class="setting-list">
<li>
- <Checkbox v-model="hideMutedPosts">
- {{ $t('settings.hide_muted_posts') }} {{ $t('settings.instance_default', { value: hideMutedPostsLocalizedValue }) }}
- </Checkbox>
+ <BooleanSetting path="hideMutedPosts">
+ {{ $t('settings.hide_muted_posts') }}
+ </BooleanSetting>
</li>
<li>
- <Checkbox v-model="collapseMessageWithSubject">
- {{ $t('settings.collapse_subject') }} {{ $t('settings.instance_default', { value: collapseMessageWithSubjectLocalizedValue }) }}
- </Checkbox>
+ <BooleanSetting path="collapseMessageWithSubject">
+ {{ $t('settings.collapse_subject') }}
+ </BooleanSetting>
</li>
<li>
- <Checkbox v-model="streaming">
+ <BooleanSetting path="streaming">
{{ $t('settings.streaming') }}
- </Checkbox>
+ </BooleanSetting>
<ul
class="setting-list suboptions"
:class="[{disabled: !streaming}]"
>
<li>
- <Checkbox
- v-model="pauseOnUnfocused"
+ <BooleanSetting
+ path="pauseOnUnfocused"
:disabled="!streaming"
>
{{ $t('settings.pause_on_unfocused') }}
- </Checkbox>
+ </BooleanSetting>
</li>
</ul>
</li>
<li>
- <Checkbox v-model="useStreamingApi">
+ <BooleanSetting path="useStreamingApi">
{{ $t('settings.useStreamingApi') }}
<br>
<small>
{{ $t('settings.useStreamingApiWarning') }}
</small>
- </Checkbox>
+ </BooleanSetting>
</li>
<li>
- <Checkbox v-model="emojiReactionsOnTimeline">
+ <BooleanSetting path="emojiReactionsOnTimeline">
{{ $t('settings.emoji_reactions_on_timeline') }}
- </Checkbox>
+ </BooleanSetting>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('settings.composing') }}</h2>
<ul class="setting-list">
<li>
- <Checkbox v-model="scopeCopy">
- {{ $t('settings.scope_copy') }} {{ $t('settings.instance_default', { value: scopeCopyLocalizedValue }) }}
- </Checkbox>
+ <BooleanSetting path="scopeCopy">
+ {{ $t('settings.scope_copy') }}
+ </BooleanSetting>
</li>
<li>
- <Checkbox v-model="alwaysShowSubjectInput">
- {{ $t('settings.subject_input_always_show') }} {{ $t('settings.instance_default', { value: alwaysShowSubjectInputLocalizedValue }) }}
- </Checkbox>
+ <BooleanSetting path="alwaysShowSubjectInput">
+ {{ $t('settings.subject_input_always_show') }}
+ </BooleanSetting>
</li>
<li>
<div>
{{ $t('settings.subject_line_behavior') }}
<label
for="subjectLineBehavior"
class="select"
>
<select
id="subjectLineBehavior"
v-model="subjectLineBehavior"
>
<option value="email">
{{ $t('settings.subject_line_email') }}
{{ subjectLineBehaviorDefaultValue == 'email' ? $t('settings.instance_default_simple') : '' }}
</option>
<option value="masto">
{{ $t('settings.subject_line_mastodon') }}
{{ subjectLineBehaviorDefaultValue == 'mastodon' ? $t('settings.instance_default_simple') : '' }}
</option>
<option value="noop">
{{ $t('settings.subject_line_noop') }}
{{ subjectLineBehaviorDefaultValue == 'noop' ? $t('settings.instance_default_simple') : '' }}
</option>
</select>
<i class="icon-down-open" />
</label>
</div>
</li>
<li v-if="postFormats.length > 0">
<div>
{{ $t('settings.post_status_content_type') }}
<label
for="postContentType"
class="select"
>
<select
id="postContentType"
v-model="postContentType"
>
<option
v-for="postFormat in postFormats"
:key="postFormat"
:value="postFormat"
>
{{ $t(`post_status.content_type["${postFormat}"]`) }}
{{ postContentTypeDefaultValue === postFormat ? $t('settings.instance_default_simple') : '' }}
</option>
</select>
<i class="icon-down-open" />
</label>
</div>
</li>
<li>
- <Checkbox v-model="minimalScopesMode">
- {{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.instance_default', { value: minimalScopesModeLocalizedValue }) }}
- </Checkbox>
+ <BooleanSetting path="minimalScopesMode">
+ {{ $t('settings.minimal_scopes_mode') }} {{ minimalScopesModeDefaultValue }}
+ </BooleanSetting>
</li>
<li>
- <Checkbox v-model="autohideFloatingPostButton">
+ <BooleanSetting path="autohideFloatingPostButton">
{{ $t('settings.autohide_floating_post_button') }}
- </Checkbox>
+ </BooleanSetting>
</li>
<li>
- <Checkbox v-model="padEmoji">
+ <BooleanSetting path="padEmoji">
{{ $t('settings.pad_emoji') }}
- </Checkbox>
+ </BooleanSetting>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('settings.attachments') }}</h2>
<ul class="setting-list">
<li>
- <Checkbox v-model="hideAttachments">
+ <BooleanSetting path="hideAttachments">
{{ $t('settings.hide_attachments_in_tl') }}
- </Checkbox>
+ </BooleanSetting>
</li>
<li>
- <Checkbox v-model="hideAttachmentsInConv">
+ <BooleanSetting path="hideAttachmentsInConv">
{{ $t('settings.hide_attachments_in_convo') }}
- </Checkbox>
+ </BooleanSetting>
</li>
<li>
<label for="maxThumbnails">
{{ $t('settings.max_thumbnails') }}
</label>
<input
id="maxThumbnails"
- v-model.number="maxThumbnails"
+ path.number="maxThumbnails"
class="number-input"
type="number"
min="0"
step="1"
>
</li>
<li>
- <Checkbox v-model="hideNsfw">
+ <BooleanSetting path="hideNsfw">
{{ $t('settings.nsfw_clickthrough') }}
- </Checkbox>
+ </BooleanSetting>
</li>
<ul class="setting-list suboptions">
<li>
- <Checkbox
- v-model="preloadImage"
+ <BooleanSetting
+ path="preloadImage"
:disabled="!hideNsfw"
>
{{ $t('settings.preload_images') }}
- </Checkbox>
+ </BooleanSetting>
</li>
<li>
- <Checkbox
- v-model="useOneClickNsfw"
+ <BooleanSetting
+ path="useOneClickNsfw"
:disabled="!hideNsfw"
>
{{ $t('settings.use_one_click_nsfw') }}
- </Checkbox>
+ </BooleanSetting>
</li>
</ul>
<li>
- <Checkbox v-model="stopGifs">
+ <BooleanSetting path="stopGifs">
{{ $t('settings.stop_gifs') }}
- </Checkbox>
+ </BooleanSetting>
</li>
<li>
- <Checkbox v-model="loopVideo">
+ <BooleanSetting path="loopVideo">
{{ $t('settings.loop_video') }}
- </Checkbox>
+ </BooleanSetting>
<ul
class="setting-list suboptions"
:class="[{disabled: !streaming}]"
>
<li>
- <Checkbox
- v-model="loopVideoSilentOnly"
+ <BooleanSetting
+ path="loopVideoSilentOnly"
:disabled="!loopVideo || !loopSilentAvailable"
>
{{ $t('settings.loop_video_silent_only') }}
- </Checkbox>
+ </BooleanSetting>
<div
v-if="!loopSilentAvailable"
class="unavailable"
>
<i class="icon-globe" />! {{ $t('settings.limited_availability') }}
</div>
</li>
</ul>
</li>
<li>
- <Checkbox v-model="playVideosInModal">
+ <BooleanSetting path="playVideosInModal">
{{ $t('settings.play_videos_in_modal') }}
- </Checkbox>
+ </BooleanSetting>
</li>
<li>
- <Checkbox v-model="useContainFit">
+ <BooleanSetting path="useContainFit">
{{ $t('settings.use_contain_fit') }}
- </Checkbox>
+ </BooleanSetting>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('settings.notifications') }}</h2>
<ul class="setting-list">
<li>
- <Checkbox v-model="webPushNotifications">
+ <BooleanSetting path="webPushNotifications">
{{ $t('settings.enable_web_push_notifications') }}
- </Checkbox>
+ </BooleanSetting>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('settings.fun') }}</h2>
<ul class="setting-list">
<li>
- <Checkbox v-model="greentext">
- {{ $t('settings.greentext') }} {{ $t('settings.instance_default', { value: greentextLocalizedValue }) }}
- </Checkbox>
+ <BooleanSetting path="greentext">
+ {{ $t('settings.greentext') }}
+ </BooleanSetting>
</li>
</ul>
</div>
</div>
</template>
<script src="./general_tab.js"></script>
diff --git a/src/modules/config.js b/src/modules/config.js
index 409d77a451..2c1e881f5f 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -1,127 +1,133 @@
import { set, delete as del } from 'vue'
import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
import messages from '../i18n/messages'
const browserLocale = (window.navigator.language || 'en').split('-')[0]
/* TODO this is a bit messy.
* We need to declare settings with their types and also deal with
* instance-default settings in some way, hopefully try to avoid copy-pasta
* in general.
*/
export const multiChoiceProperties = [
'postContentType',
'subjectLineBehavior'
]
export const defaultState = {
colors: {},
theme: undefined,
customTheme: undefined,
customThemeSource: undefined,
hideISP: false,
// bad name: actually hides posts of muted USERS
hideMutedPosts: undefined, // instance default
collapseMessageWithSubject: undefined, // instance default
padEmoji: true,
hideAttachments: false,
hideAttachmentsInConv: false,
maxThumbnails: 16,
hideNsfw: true,
preloadImage: true,
loopVideo: true,
loopVideoSilentOnly: true,
streaming: false,
emojiReactionsOnTimeline: true,
autohideFloatingPostButton: false,
pauseOnUnfocused: true,
stopGifs: false,
replyVisibility: 'all',
notificationVisibility: {
follows: true,
mentions: true,
likes: true,
repeats: true,
moves: true,
emojiReactions: false,
followRequest: true,
chatMention: true
},
webPushNotifications: false,
muteWords: [],
highlight: {},
interfaceLanguage: browserLocale,
hideScopeNotice: false,
useStreamingApi: false,
scopeCopy: undefined, // instance default
subjectLineBehavior: undefined, // instance default
alwaysShowSubjectInput: undefined, // instance default
postContentType: undefined, // instance default
minimalScopesMode: undefined, // instance default
// This hides statuses filtered via a word filter
hideFilteredStatuses: undefined, // instance default
playVideosInModal: false,
useOneClickNsfw: false,
useContainFit: false,
greentext: undefined, // instance default
hidePostStats: undefined, // instance default
hideUserStats: undefined // instance default
}
// caching the instance default properties
export const instanceDefaultProperties = Object.entries(defaultState)
.filter(([key, value]) => value === undefined)
.map(([key, value]) => key)
const config = {
- state: defaultState,
+ state: { ...defaultState },
getters: {
- mergedConfig (state, getters, rootState, rootGetters) {
+ defaultConfig (state, getters, rootState, rootGetters) {
const { instance } = rootState
+ console.log('DC', instance.minimalScopesMode)
+ return {
+ ...defaultState,
+ ...Object.fromEntries(
+ instanceDefaultProperties.map(key => [key, instance[key]])
+ )
+ }
+ },
+ mergedConfig (state, getters, rootState, rootGetters) {
+ const { defaultConfig } = rootGetters
+ console.log('DC2', defaultConfig.hideISP)
return {
- ...state,
- ...instanceDefaultProperties
- .map(key => [key, state[key] === undefined
- ? instance[key]
- : state[key]
- ])
- .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
+ ...defaultConfig,
+ ...state
}
}
},
mutations: {
setOption (state, { name, value }) {
set(state, name, value)
},
setHighlight (state, { user, color, type }) {
const data = this.state.config.highlight[user]
if (color || type) {
set(state.highlight, user, { color: color || data.color, type: type || data.type })
} else {
del(state.highlight, user)
}
}
},
actions: {
setHighlight ({ commit, dispatch }, { user, color, type }) {
commit('setHighlight', { user, color, type })
},
setOption ({ commit, dispatch }, { name, value }) {
commit('setOption', { name, value })
switch (name) {
case 'theme':
setPreset(value)
break
case 'customTheme':
case 'customThemeSource':
applyTheme(value)
break
case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value)
break
}
}
}
}
export default config

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 10, 2:02 PM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1785263
Default Alt Text
(26 KB)

Event Timeline