more work on profile page
This commit is contained in:
parent
5e7438cb52
commit
88d59678f2
|
@ -1,20 +1,26 @@
|
||||||
<div class="account-profile {{headerIsMissing ? 'header-is-missing' : ''}}" style="background-image: url({{profile.header}});">
|
<div class="account-profile {{headerIsMissing ? 'header-is-missing' : ''}}" style="background-image: url({{profile.header}});">
|
||||||
<div class="account-profile-grid">
|
<div class="account-profile-grid">
|
||||||
<div class="account-profile-avatar">
|
<div class="account-profile-avatar">
|
||||||
<img src="{{profile.avatar}}">
|
<img src="{{profile.avatar}}" aria-hidden="true">
|
||||||
</div>
|
</div>
|
||||||
<div class="account-profile-name">
|
<div class="account-profile-name">
|
||||||
{{profile.display_name}}
|
{{profile.display_name}}
|
||||||
</div>
|
</div>
|
||||||
<div class="account-profile-following">
|
<div class="account-profile-followed-by">
|
||||||
|
{{#if relationship && relationship.followed_by}}
|
||||||
<span>
|
<span>
|
||||||
Follows you
|
Follows you
|
||||||
</span>
|
</span>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="account-profile-follow">
|
<div class="account-profile-follow">
|
||||||
<svg>
|
{{#if verifyCredentials && relationship && verifyCredentials.id !== relationship.id}}
|
||||||
<use xlink:href="#fa-user-plus" />
|
<IconButton
|
||||||
</svg>
|
label="{{relationship && relationship.following ? 'Unfollow' : 'Follow'}}"
|
||||||
|
href="{{relationship && relationship.following ? '#fa-user-times' : '#fa-user-plus'}}"
|
||||||
|
big="true"
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="account-profile-note">
|
<div class="account-profile-note">
|
||||||
{{{profile.note}}}
|
{{{profile.note}}}
|
||||||
|
@ -32,6 +38,7 @@
|
||||||
|
|
||||||
.account-profile.header-is-missing {
|
.account-profile.header-is-missing {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
|
background-color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-profile-background {
|
.account-profile-background {
|
||||||
|
@ -45,7 +52,7 @@
|
||||||
|
|
||||||
.account-profile-grid {
|
.account-profile-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas: "avatar name following follow"
|
grid-template-areas: "avatar name followed-by follow"
|
||||||
"avatar note note note";
|
"avatar note note note";
|
||||||
grid-template-columns: min-content auto 1fr min-content;
|
grid-template-columns: min-content auto 1fr min-content;
|
||||||
grid-column-gap: 10px;
|
grid-column-gap: 10px;
|
||||||
|
@ -68,19 +75,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-profile-following, .account-profile-avatar, .account-profile-follow,
|
.account-profile-followed-by, .account-profile-avatar, .account-profile-follow,
|
||||||
.account-profile-name, .account-profile-username, .account-profile-note {
|
.account-profile-name, .account-profile-username, .account-profile-note {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-profile-following {
|
.account-profile-followed-by {
|
||||||
grid-area: following;
|
grid-area: followed-by;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--deemphasized-text-color);
|
color: var(--deemphasized-text-color);
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
.account-profile-following span {
|
.account-profile-followed-by span {
|
||||||
background: rgba(30, 30, 30, 0.2);
|
background: rgba(30, 30, 30, 0.2);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 3px 5px;
|
padding: 3px 5px;
|
||||||
|
@ -99,11 +106,6 @@
|
||||||
grid-area: follow;
|
grid-area: follow;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
.account-profile-follow svg {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
fill: var(--svg-fill);
|
|
||||||
}
|
|
||||||
.account-profile-name {
|
.account-profile-name {
|
||||||
grid-area: name;
|
grid-area: name;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
|
@ -121,9 +123,14 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
|
import IconButton from './IconButton.html'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
headerIsMissing: (profile) => profile.header.endsWith('missing.png')
|
headerIsMissing: (profile) => profile.header.endsWith('missing.png')
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
IconButton
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
56
routes/_components/IconButton.html
Normal file
56
routes/_components/IconButton.html
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
{{#if pressable}}
|
||||||
|
<button type="button"
|
||||||
|
aria-label="{{label}}"
|
||||||
|
aria-pressed="{{!!pressed}}"
|
||||||
|
class="icon-button {{pressed ? 'pressed' : ''}} {{big ? 'big-icon' : ''}}">
|
||||||
|
<svg>
|
||||||
|
<use xlink:href="{{href}}" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{{else}}
|
||||||
|
<button type="button"
|
||||||
|
aria-label="{{label}}"
|
||||||
|
class="icon-button {{big ? 'big-icon' : ''}}">
|
||||||
|
<svg>
|
||||||
|
<use xlink:href="{{href}}" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
<style>
|
||||||
|
button.icon-button {
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.icon-button svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
fill: var(--action-button-fill-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.icon-button.big-icon svg {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.icon-button:hover svg {
|
||||||
|
fill: var(--action-button-fill-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.icon-button:active svg {
|
||||||
|
fill: var(--action-button-fill-color-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.icon-button.pressed svg {
|
||||||
|
fill: var(--action-button-fill-color-pressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
button.icon-button.pressed:hover svg {
|
||||||
|
fill: var(--action-button-fill-color-pressed-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.icon-button.pressed:active svg {
|
||||||
|
fill: var(--action-button-fill-color-pressed-active);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,30 +1,24 @@
|
||||||
<div class="status-toolbar">
|
<div class="status-toolbar">
|
||||||
<button aria-label="Reply" type="button">
|
<IconButton
|
||||||
<svg>
|
label="Reply"
|
||||||
<use xlink:href="#fa-reply" />
|
href="#fa-reply"
|
||||||
</svg>
|
/>
|
||||||
</button>
|
<IconButton
|
||||||
<button aria-label="Boost" aria-pressed="{{status.reblogged}}" class="{{status.reblogged ? 'selected' : ''}}" type="button">
|
label="Boost"
|
||||||
<svg>
|
pressable="true"
|
||||||
{{#if status.visibility === 'private'}}
|
pressed="{{status.reblogged}}"
|
||||||
<use xlink:href="#fa-lock" />
|
href="{{status.visibility === 'private' ? '#fa-lock' : status.visibility === 'direct' ? '#fa-envelope' : '#fa-retweet'}}"
|
||||||
{{elseif status.visibility === 'direct'}}
|
/>
|
||||||
<use xlink:href="#fa-envelope" />
|
<IconButton
|
||||||
{{else}}
|
label="Favorite"
|
||||||
<use xlink:href="#fa-retweet" />
|
pressable="true"
|
||||||
{{/if}}
|
pressed="{{status.favourited}}"
|
||||||
</svg>
|
href="#fa-star"
|
||||||
</button>
|
/>
|
||||||
<button aria-label="Favorite" aria-pressed="{{status.favourited}}" class="{{status.favourited ? 'selected' : ''}}" type="button">
|
<IconButton
|
||||||
<svg>
|
label="Show more actions"
|
||||||
<use xlink:href="#fa-star" />
|
href="#fa-ellipsis-h"
|
||||||
</svg>
|
/>
|
||||||
</button>
|
|
||||||
<button aria-label="Show more actions" type="button">
|
|
||||||
<svg>
|
|
||||||
<use xlink:href="#fa-ellipsis-h" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<style>
|
<style>
|
||||||
.status-toolbar {
|
.status-toolbar {
|
||||||
|
@ -32,41 +26,14 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-toolbar button {
|
|
||||||
padding: 6px 10px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-toolbar button svg {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
fill: var(--action-button-fill-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-toolbar button:hover svg {
|
|
||||||
fill: var(--action-button-fill-color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-toolbar button:active svg {
|
|
||||||
fill: var(--action-button-fill-color-active);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-toolbar button.selected svg {
|
|
||||||
fill: var(--action-button-fill-color-pressed)
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-toolbar button.selected:hover svg {
|
|
||||||
fill: var(--action-button-fill-color-pressed-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-toolbar button.selected:active svg {
|
|
||||||
fill: var(--action-button-fill-color-pressed-active);
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import IconButton from '../IconButton.html'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
IconButton
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -1,10 +1,10 @@
|
||||||
<div class="lazy-timeline">
|
<div class="lazy-timeline">
|
||||||
{{#if !$initialized}}
|
{{#if !$initialized}}
|
||||||
<div transition:fade>
|
<!-- <div transition:fade> -->
|
||||||
<div class="loading-page">
|
<div class="loading-page">
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- </div> -->
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#await promise}}
|
{{#await promise}}
|
||||||
{{then constructor}}
|
{{then constructor}}
|
||||||
|
@ -33,7 +33,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { importTimeline } from '../../_utils/asyncModules'
|
import { importTimeline } from '../../_utils/asyncModules'
|
||||||
import LoadingSpinner from '../LoadingSpinner.html'
|
import LoadingSpinner from '../LoadingSpinner.html'
|
||||||
import { fade } from 'svelte-transitions'
|
// TODO: the transition seems to occasionally cause an error in Svelte, transition_run is undefined
|
||||||
|
//import { fade } from 'svelte-transitions'
|
||||||
import { store } from '../../_utils/store'
|
import { store } from '../../_utils/store'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -49,9 +50,9 @@
|
||||||
}),
|
}),
|
||||||
components: {
|
components: {
|
||||||
LoadingSpinner
|
LoadingSpinner
|
||||||
},
|
}/*,
|
||||||
transitions: {
|
transitions: {
|
||||||
fade
|
fade
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
61
routes/_utils/database/cache.js
Normal file
61
routes/_utils/database/cache.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import QuickLRU from 'quick-lru'
|
||||||
|
|
||||||
|
export const statusesCache = {
|
||||||
|
maxSize: 100,
|
||||||
|
caches: {}
|
||||||
|
}
|
||||||
|
export const accountsCache = {
|
||||||
|
maxSize: 50,
|
||||||
|
caches: {}
|
||||||
|
}
|
||||||
|
export const relationshipsCache = {
|
||||||
|
maxSize: 20,
|
||||||
|
caches: {}
|
||||||
|
}
|
||||||
|
export const metaCache = {
|
||||||
|
maxSize: 20,
|
||||||
|
caches: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.browser && process.env.NODE_ENV !== 'production') {
|
||||||
|
window.cacheStats = {
|
||||||
|
statuses: statusesCache,
|
||||||
|
accounts: accountsCache,
|
||||||
|
relationships: relationshipsCache,
|
||||||
|
meta: metaCache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOrCreateInstanceCache(cache, instanceName) {
|
||||||
|
let cached = cache.caches[instanceName]
|
||||||
|
if (!cached) {
|
||||||
|
cached = cache.caches[instanceName] = new QuickLRU({maxSize: cache.maxSize})
|
||||||
|
}
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearCache(cache, instanceName) {
|
||||||
|
delete cache.caches[instanceName]
|
||||||
|
}
|
||||||
|
export function setInCache(cache, instanceName, key, value) {
|
||||||
|
let instanceCache = getOrCreateInstanceCache(cache, instanceName)
|
||||||
|
return instanceCache.set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInCache(cache, instanceName, key) {
|
||||||
|
let instanceCache = getOrCreateInstanceCache(cache, instanceName)
|
||||||
|
return instanceCache.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasInCache(cache, instanceName, key) {
|
||||||
|
let instanceCache = getOrCreateInstanceCache(cache, instanceName)
|
||||||
|
let res = instanceCache.has(key)
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
if (res) {
|
||||||
|
cache.hits = (cache.hits || 0) + 1
|
||||||
|
} else {
|
||||||
|
cache.misses = (cache.misses || 0) + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
export const STATUSES_STORE = 'statuses'
|
export const STATUSES_STORE = 'statuses'
|
||||||
export const TIMELINE_STORE = 'timelines'
|
export const TIMELINE_STORE = 'timelines'
|
||||||
export const META_STORE = 'meta'
|
export const META_STORE = 'meta'
|
||||||
export const ACCOUNTS_STORE= 'accounts'
|
export const ACCOUNTS_STORE = 'accounts'
|
||||||
|
export const RELATIONSHIPS_STORE = 'relationships'
|
|
@ -10,69 +10,44 @@ import {
|
||||||
import {
|
import {
|
||||||
META_STORE,
|
META_STORE,
|
||||||
TIMELINE_STORE,
|
TIMELINE_STORE,
|
||||||
STATUSES_STORE, ACCOUNTS_STORE
|
STATUSES_STORE,
|
||||||
|
ACCOUNTS_STORE,
|
||||||
|
RELATIONSHIPS_STORE
|
||||||
} from './constants'
|
} from './constants'
|
||||||
|
|
||||||
import QuickLRU from 'quick-lru'
|
import {
|
||||||
|
statusesCache,
|
||||||
|
relationshipsCache,
|
||||||
|
accountsCache,
|
||||||
|
metaCache,
|
||||||
|
clearCache,
|
||||||
|
getInCache,
|
||||||
|
hasInCache,
|
||||||
|
setInCache
|
||||||
|
} from './cache'
|
||||||
|
|
||||||
const statusesCache = {
|
//
|
||||||
maxSize: 100,
|
// helpers
|
||||||
caches: {}
|
//
|
||||||
}
|
|
||||||
const accountsCache = {
|
|
||||||
maxSize: 50,
|
|
||||||
caches: {}
|
|
||||||
}
|
|
||||||
const metaCache = {
|
|
||||||
maxSize: 20,
|
|
||||||
caches: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.browser && process.env.NODE_ENV !== 'production') {
|
async function getGenericEntityWithId(store, cache, instanceName, id) {
|
||||||
window.cacheStats = {
|
if (hasInCache(cache, instanceName, id)) {
|
||||||
statuses: {
|
return getInCache(cache, instanceName, id)
|
||||||
cache: statusesCache,
|
|
||||||
hits: 0,
|
|
||||||
misses: 0
|
|
||||||
},
|
|
||||||
accounts: {
|
|
||||||
cache: accountsCache,
|
|
||||||
hits: 0,
|
|
||||||
misses: 0
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
cache: accountsCache,
|
|
||||||
hits: 0,
|
|
||||||
misses: 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const db = await getDatabase(instanceName)
|
||||||
|
let result = await dbPromise(db, store, 'readonly', (store, callback) => {
|
||||||
|
store.get(id).onsuccess = (e) => callback(e.target.result)
|
||||||
|
})
|
||||||
|
setInCache(cache, instanceName, id, result)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearCache(cache, instanceName) {
|
async function setGenericEntityWithId(store, cache, instanceName, entity) {
|
||||||
delete cache.caches[instanceName]
|
setInCache(cache, instanceName, entity.id, entity)
|
||||||
}
|
const db = await getDatabase(instanceName)
|
||||||
|
return await dbPromise(db, store, 'readwrite', (store) => {
|
||||||
function getOrCreateInstanceCache(cache, instanceName) {
|
store.put(entity)
|
||||||
let cached = cache.caches[instanceName]
|
})
|
||||||
if (!cached) {
|
|
||||||
cached = cache.caches[instanceName] = new QuickLRU({maxSize: cache.maxSize})
|
|
||||||
}
|
|
||||||
return cached
|
|
||||||
}
|
|
||||||
|
|
||||||
function setInCache(cache, instanceName, key, value) {
|
|
||||||
let instanceCache = getOrCreateInstanceCache(cache, instanceName)
|
|
||||||
return instanceCache.set(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInCache(cache, instanceName, key) {
|
|
||||||
let instanceCache = getOrCreateInstanceCache(cache, instanceName)
|
|
||||||
return instanceCache.get(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasInCache(cache, instanceName, key) {
|
|
||||||
let instanceCache = getOrCreateInstanceCache(cache, instanceName)
|
|
||||||
return instanceCache.has(key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -129,23 +104,7 @@ export async function insertStatuses(instanceName, timeline, statuses) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStatus(instanceName, statusId) {
|
export async function getStatus(instanceName, statusId) {
|
||||||
if (hasInCache(statusesCache, instanceName, statusId)) {
|
return await getGenericEntityWithId(STATUSES_STORE, statusesCache, instanceName, statusId)
|
||||||
if (process.browser && process.env.NODE_ENV !== 'production') {
|
|
||||||
window.cacheStats.statuses.hits++
|
|
||||||
}
|
|
||||||
return getInCache(statusesCache, instanceName, statusId)
|
|
||||||
}
|
|
||||||
const db = await getDatabase(instanceName)
|
|
||||||
let result = await dbPromise(db, STATUSES_STORE, 'readonly', (store, callback) => {
|
|
||||||
store.get(statusId).onsuccess = (e) => {
|
|
||||||
callback(e.target.result && e.target.result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setInCache(statusesCache, instanceName, statusId, result)
|
|
||||||
if (process.browser && process.env.NODE_ENV !== 'production') {
|
|
||||||
window.cacheStats.statuses.misses++
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -154,9 +113,6 @@ export async function getStatus(instanceName, statusId) {
|
||||||
|
|
||||||
async function getMetaProperty(instanceName, key) {
|
async function getMetaProperty(instanceName, key) {
|
||||||
if (hasInCache(metaCache, instanceName, key)) {
|
if (hasInCache(metaCache, instanceName, key)) {
|
||||||
if (process.browser && process.env.NODE_ENV !== 'production') {
|
|
||||||
window.cacheStats.meta.hits++
|
|
||||||
}
|
|
||||||
return getInCache(metaCache, instanceName, key)
|
return getInCache(metaCache, instanceName, key)
|
||||||
}
|
}
|
||||||
const db = await getDatabase(instanceName)
|
const db = await getDatabase(instanceName)
|
||||||
|
@ -166,9 +122,6 @@ async function getMetaProperty(instanceName, key) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
setInCache(metaCache, instanceName, key, result)
|
setInCache(metaCache, instanceName, key, result)
|
||||||
if (process.browser && process.env.NODE_ENV !== 'production') {
|
|
||||||
window.cacheStats.meta.misses++
|
|
||||||
}
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,34 +153,23 @@ export async function setInstanceInfo(instanceName, value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// accounts
|
// accounts/relationships
|
||||||
//
|
//
|
||||||
|
|
||||||
export async function getAccount(instanceName, accountId) {
|
export async function getAccount(instanceName, accountId) {
|
||||||
if (hasInCache(accountsCache, instanceName, accountId)) {
|
return await getGenericEntityWithId(ACCOUNTS_STORE, accountsCache, instanceName, accountId)
|
||||||
if (process.browser && process.env.NODE_ENV !== 'production') {
|
|
||||||
window.cacheStats.accounts.hits++
|
|
||||||
}
|
|
||||||
return getInCache(accountsCache, instanceName, accountId)
|
|
||||||
}
|
|
||||||
const db = await getDatabase(instanceName)
|
|
||||||
let result = await dbPromise(db, ACCOUNTS_STORE, 'readonly', (store, callback) => {
|
|
||||||
store.get(accountId).onsuccess = (e) => {
|
|
||||||
callback(e.target.result && e.target.result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (process.browser && process.env.NODE_ENV !== 'production') {
|
|
||||||
window.cacheStats.accounts.misses++
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setAccount(instanceName, account) {
|
export async function setAccount(instanceName, account) {
|
||||||
setInCache(accountsCache, instanceName, account.id, account)
|
return await setGenericEntityWithId(ACCOUNTS_STORE, accountsCache, instanceName, account)
|
||||||
const db = await getDatabase(instanceName)
|
}
|
||||||
return await dbPromise(db, ACCOUNTS_STORE, 'readwrite', (store) => {
|
|
||||||
store.put(account)
|
export async function getRelationship(instanceName, accountId) {
|
||||||
})
|
return await getGenericEntityWithId(RELATIONSHIPS_STORE, relationshipsCache, instanceName, accountId)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setRelationship(instanceName, relationship) {
|
||||||
|
return await setGenericEntityWithId(RELATIONSHIPS_STORE, relationshipsCache, instanceName, relationship)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
const openReqs = {}
|
const openReqs = {}
|
||||||
const databaseCache = {}
|
const databaseCache = {}
|
||||||
|
|
||||||
|
const DB_VERSION = 2
|
||||||
|
|
||||||
import {
|
import {
|
||||||
META_STORE,
|
META_STORE,
|
||||||
TIMELINE_STORE,
|
TIMELINE_STORE,
|
||||||
STATUSES_STORE,
|
STATUSES_STORE,
|
||||||
ACCOUNTS_STORE
|
ACCOUNTS_STORE,
|
||||||
|
RELATIONSHIPS_STORE
|
||||||
} from './constants'
|
} from './constants'
|
||||||
|
|
||||||
export function getDatabase(instanceName) {
|
export function getDatabase(instanceName) {
|
||||||
|
@ -17,19 +20,24 @@ export function getDatabase(instanceName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
databaseCache[instanceName] = new Promise((resolve, reject) => {
|
databaseCache[instanceName] = new Promise((resolve, reject) => {
|
||||||
let req = indexedDB.open(instanceName, 1)
|
let req = indexedDB.open(instanceName, DB_VERSION)
|
||||||
openReqs[instanceName] = req
|
openReqs[instanceName] = req
|
||||||
req.onerror = reject
|
req.onerror = reject
|
||||||
req.onblocked = () => {
|
req.onblocked = () => {
|
||||||
console.log('idb blocked')
|
console.log('idb blocked')
|
||||||
}
|
}
|
||||||
req.onupgradeneeded = () => {
|
req.onupgradeneeded = (e) => {
|
||||||
let db = req.result;
|
let db = req.result;
|
||||||
db.createObjectStore(META_STORE, {keyPath: 'key'})
|
if (e.oldVersion < 1) {
|
||||||
db.createObjectStore(STATUSES_STORE, {keyPath: 'id'})
|
db.createObjectStore(META_STORE, {keyPath: 'key'})
|
||||||
db.createObjectStore(ACCOUNTS_STORE, {keyPath: 'id'})
|
db.createObjectStore(STATUSES_STORE, {keyPath: 'id'})
|
||||||
let timelineStore = db.createObjectStore(TIMELINE_STORE, {keyPath: 'id'})
|
db.createObjectStore(ACCOUNTS_STORE, {keyPath: 'id'})
|
||||||
timelineStore.createIndex('statusId', 'statusId')
|
let timelineStore = db.createObjectStore(TIMELINE_STORE, {keyPath: 'id'})
|
||||||
|
timelineStore.createIndex('statusId', 'statusId')
|
||||||
|
}
|
||||||
|
if (e.oldVersion < 2) {
|
||||||
|
db.createObjectStore(RELATIONSHIPS_STORE, {keyPath: 'id'})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
req.onsuccess = () => resolve(req.result)
|
req.onsuccess = () => resolve(req.result)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { get } from '../ajax'
|
import { get, paramsString } from '../ajax'
|
||||||
import { basename } from './utils'
|
import { basename } from './utils'
|
||||||
|
|
||||||
export function getVerifyCredentials(instanceName, accessToken) {
|
export function getVerifyCredentials(instanceName, accessToken) {
|
||||||
|
@ -13,4 +13,13 @@ export function getAccount(instanceName, accessToken, accountId) {
|
||||||
return get(url, {
|
return get(url, {
|
||||||
'Authorization': `Bearer ${accessToken}`
|
'Authorization': `Bearer ${accessToken}`
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getRelationship(instanceName, accessToken, accountId) {
|
||||||
|
let url = `${basename(instanceName)}/api/v1/accounts/relationships`
|
||||||
|
url += '?' + paramsString({id: accountId})
|
||||||
|
let res = await get(url, {
|
||||||
|
'Authorization': `Bearer ${accessToken}`
|
||||||
|
})
|
||||||
|
return res[0]
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { Store } from 'svelte/store.js'
|
import { Store } from 'svelte/store.js'
|
||||||
|
import { storeObservers } from './storeObservers'
|
||||||
|
|
||||||
const LOCAL_STORAGE_KEYS = new Set([
|
const LOCAL_STORAGE_KEYS = new Set([
|
||||||
"currentInstance",
|
"currentInstance",
|
||||||
|
@ -112,6 +113,12 @@ store.compute(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
store.compute(
|
||||||
|
'currentVerifyCredentials',
|
||||||
|
['currentInstance', 'verifyCredentials'],
|
||||||
|
(currentInstance, verifyCredentials) => verifyCredentials && verifyCredentials[currentInstance]
|
||||||
|
)
|
||||||
|
|
||||||
store.compute('currentTimelineData', ['currentInstance', 'currentTimeline', 'timelines'],
|
store.compute('currentTimelineData', ['currentInstance', 'currentTimeline', 'timelines'],
|
||||||
(currentInstance, currentTimeline, timelines) => {
|
(currentInstance, currentTimeline, timelines) => {
|
||||||
return ((timelines && timelines[currentInstance]) || {})[currentTimeline] || {}
|
return ((timelines && timelines[currentInstance]) || {})[currentTimeline] || {}
|
||||||
|
@ -122,6 +129,8 @@ store.compute('runningUpdate', ['currentTimelineData'], (currentTimelineData) =>
|
||||||
store.compute('initialized', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.initialized)
|
store.compute('initialized', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.initialized)
|
||||||
store.compute('lastStatusId', ['statusIds'], (statusIds) => statusIds.length && statusIds[statusIds.length - 1])
|
store.compute('lastStatusId', ['statusIds'], (statusIds) => statusIds.length && statusIds[statusIds.length - 1])
|
||||||
|
|
||||||
|
storeObservers(store)
|
||||||
|
|
||||||
if (process.browser && process.env.NODE_ENV !== 'production') {
|
if (process.browser && process.env.NODE_ENV !== 'production') {
|
||||||
window.store = store // for debugging
|
window.store = store // for debugging
|
||||||
}
|
}
|
||||||
|
|
7
routes/_utils/storeObservers.js
Normal file
7
routes/_utils/storeObservers.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { updateVerifyCredentialsForInstance } from '../settings/instances/_actions/[instanceName]'
|
||||||
|
|
||||||
|
export function storeObservers(store) {
|
||||||
|
store.observe('currentInstance', (currentInstance) => {
|
||||||
|
updateVerifyCredentialsForInstance(currentInstance)
|
||||||
|
})
|
||||||
|
}
|
|
@ -12,7 +12,10 @@
|
||||||
{{#if $isUserLoggedIn}}
|
{{#if $isUserLoggedIn}}
|
||||||
<DynamicPageBanner title="{{profileName}}" />
|
<DynamicPageBanner title="{{profileName}}" />
|
||||||
{{#if $currentAccountProfile}}
|
{{#if $currentAccountProfile}}
|
||||||
<AccountProfile profile="{{$currentAccountProfile}}" />
|
<AccountProfile profile="{{$currentAccountProfile}}"
|
||||||
|
relationship="{{$currentAccountRelationship}}"
|
||||||
|
verifyCredentials="{{$currentVerifyCredentials}}"
|
||||||
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<LazyTimeline timeline='account/{{params.accountId}}' />
|
<LazyTimeline timeline='account/{{params.accountId}}' />
|
||||||
{{else}}
|
{{else}}
|
||||||
|
@ -32,13 +35,16 @@
|
||||||
import { store } from '../_utils/store.js'
|
import { store } from '../_utils/store.js'
|
||||||
import HiddenFromSSR from '../_components/HiddenFromSSR'
|
import HiddenFromSSR from '../_components/HiddenFromSSR'
|
||||||
import DynamicPageBanner from '../_components/DynamicPageBanner.html'
|
import DynamicPageBanner from '../_components/DynamicPageBanner.html'
|
||||||
import { showAccountProfile } from './_actions/[accountId]'
|
import { updateProfileAndRelationship } from './_actions/[accountId]'
|
||||||
import AccountProfile from '../_components/AccountProfile.html'
|
import AccountProfile from '../_components/AccountProfile.html'
|
||||||
|
import { updateVerifyCredentialsForInstance } from '../settings/instances/_actions/[instanceName]'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate() {
|
oncreate() {
|
||||||
let accountId = this.get('params').accountId
|
let accountId = this.get('params').accountId
|
||||||
showAccountProfile(accountId)
|
let instanceName = this.store.get('currentInstance')
|
||||||
|
updateProfileAndRelationship(accountId)
|
||||||
|
updateVerifyCredentialsForInstance(instanceName)
|
||||||
},
|
},
|
||||||
store: () => store,
|
store: () => store,
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
@ -1,24 +1,54 @@
|
||||||
import { getAccount } from '../../_utils/mastodon/user'
|
import { getAccount, getRelationship } from '../../_utils/mastodon/user'
|
||||||
import { database } from '../../_utils/database/database'
|
import { database } from '../../_utils/database/database'
|
||||||
import { store } from '../../_utils/store'
|
import { store } from '../../_utils/store'
|
||||||
|
|
||||||
export async function showAccountProfile(accountId) {
|
async function updateAccount(accountId, instanceName, accessToken) {
|
||||||
store.set({currentAccountProfile: null})
|
|
||||||
let instanceName = store.get('currentInstance')
|
|
||||||
let accessToken = store.get('accessToken')
|
|
||||||
|
|
||||||
let localPromise = database.getAccount(instanceName, accountId)
|
let localPromise = database.getAccount(instanceName, accountId)
|
||||||
let remotePromise = getAccount(instanceName, accessToken, accountId).then(account => {
|
let remotePromise = getAccount(instanceName, accessToken, accountId).then(account => {
|
||||||
database.setAccount(instanceName, account)
|
database.setAccount(instanceName, account)
|
||||||
return account
|
return account
|
||||||
})
|
})
|
||||||
|
|
||||||
let localAccount = await localPromise
|
|
||||||
store.set({currentAccountProfile: localAccount})
|
|
||||||
try {
|
try {
|
||||||
let remoteAccount = await remotePromise
|
store.set({currentAccountProfile: (await localPromise)})
|
||||||
store.set({currentAccountProfile: remoteAccount})
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("couldn't fetch profile", e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
store.set({currentAccountProfile: (await remotePromise)})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateRelationship(accountId, instanceName, accessToken) {
|
||||||
|
let localPromise = database.getRelationship(instanceName, accountId)
|
||||||
|
let remotePromise = getRelationship(instanceName, accessToken, accountId).then(relationship => {
|
||||||
|
database.setRelationship(instanceName, relationship)
|
||||||
|
return relationship
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
store.set({currentAccountRelationship: (await localPromise)})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
store.set({currentAccountRelationship: (await remotePromise)})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateProfileAndRelationship(accountId) {
|
||||||
|
store.set({
|
||||||
|
currentAccountProfile: null,
|
||||||
|
currentAccountRelationship: null
|
||||||
|
})
|
||||||
|
let instanceName = store.get('currentInstance')
|
||||||
|
let accessToken = store.get('accessToken')
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
updateAccount(accountId, instanceName, accessToken),
|
||||||
|
updateRelationship(accountId, instanceName, accessToken)
|
||||||
|
])
|
||||||
}
|
}
|
|
@ -63,5 +63,5 @@
|
||||||
--deemphasized-text-color: #666;
|
--deemphasized-text-color: #666;
|
||||||
--focus-outline: $focus-outline;
|
--focus-outline: $focus-outline;
|
||||||
|
|
||||||
--status-direct-background: darken($body-bg-color, 5%)
|
--status-direct-background: darken($body-bg-color, 5%);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue