Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions PinkSea.AtProto.Shared/Lexicons/AtProto/PutRecordResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ public class PutRecordResponse
/// The commit of the record.
/// </summary>
[JsonPropertyName("commit")]
public required Commit Commit { get; set; }
public Commit? Commit { get; set; }

/// <summary>
/// The validation result of the record.
/// </summary>
[JsonPropertyName("validationStatus")]
public required string ValidationStatus { get; set; }
public string? ValidationStatus { get; set; }
}
28 changes: 28 additions & 0 deletions PinkSea.Frontend/src/api/atproto/lexicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ declare module '@atcute/client/lexicons' {
}
}

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace ComShinolabsPinkseaPutPreference {
interface Input {
key: string
value: string
}
interface Output {
}
}

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace ComShinolabsPinkseaDeleteOekaki {
interface Input {
Expand Down Expand Up @@ -132,6 +142,16 @@ declare module '@atcute/client/lexicons' {
}
}

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace ComShinolabsPinkseaGetPreferences {
interface Output {
preferences: {
key: string,
value: string
}[]
}
}

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace ComShinolabsPinkseaGetSearchResults {
interface Params {
Expand Down Expand Up @@ -188,6 +208,10 @@ declare module '@atcute/client/lexicons' {
'com.shinolabs.pinksea.getSearchResults': {
params: ComShinolabsPinkseaGetSearchResults.Params,
output: ComShinolabsPinkseaGetSearchResults.Output
},
'com.shinolabs.pinksea.getPreferences': {
params: EmptyParams,
output: ComShinolabsPinkseaGetPreferences.Output
}
}

Expand Down Expand Up @@ -215,6 +239,10 @@ declare module '@atcute/client/lexicons' {
'com.shinolabs.pinksea.beginLoginFlow': {
input: ComShinolabsPinkseaBeginLoginFlow.Input,
output: ComShinolabsPinkseaBeginLoginFlow.Output
},
'com.shinolabs.pinksea.putPreference': {
input: ComShinolabsPinkseaPutPreference.Input,
output: ComShinolabsPinkseaPutPreference.Output
}
}
}
Expand Down
16 changes: 15 additions & 1 deletion PinkSea.Frontend/src/api/tegaki/tegaki.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import i18n from '@/intl/i18n.ts'
import { usePersistedStore } from '@/state/store.ts';
import { usePdsPreferencesStore } from '@/state/preferences.ts'

function TegakiStrings() {
let currentLanguage = usePersistedStore().lang
Expand Down Expand Up @@ -2847,6 +2848,8 @@ export var Tegaki = {
}
}

self.initTheme();

self.visible = true;
},

Expand Down Expand Up @@ -2886,6 +2889,14 @@ export var Tegaki = {
self.toolColor = $T.RgbToHex(...r.toolColor);
},

initTheme: function() {
console.log("initTheme")
const store = usePdsPreferencesStore()
console.log(store.editorDarkMode)
const theme = store.editorDarkMode === true ? 'dark' : 'light';
this.setTheme(theme)
},

initToolsFromReplay: function() {
var self, r, name, tool, rTool, prop, props;

Expand Down Expand Up @@ -3493,7 +3504,7 @@ export var Tegaki = {
},

//Toggles light/dark mode and changes icon of toggle button
onThemeToggle: function() {
onThemeToggle: async function() {


const toggleButton = document.getElementById('tegaki-theme-toggle');
Expand All @@ -3512,6 +3523,9 @@ export var Tegaki = {
htmlElement.dataset.theme = 'light';
}

const store = usePdsPreferencesStore()
await store.set('editorDarkMode', isLightMode)

},

setTheme: function(theme) {
Expand Down
8 changes: 4 additions & 4 deletions PinkSea.Frontend/src/components/TimeLineOekakiCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import { computed } from 'vue'
import type { Oekaki } from '@/models/oekaki'
import { useRouter } from 'vue-router'
import { usePersistedStore } from '@/state/store'
import { buildOekakiUrlFromOekakiObject } from '@/api/atproto/helpers'
import OekakiMetaContainer from './oekaki/OekakiMetaContainer.vue'
import { usePdsPreferencesStore } from '@/state/preferences'

const router = useRouter();
const persistedStore = usePersistedStore();
const preferencesStore = usePdsPreferencesStore()

const props = defineProps<{
oekaki: Oekaki
Expand All @@ -30,10 +30,10 @@ const openInNewTab = () => {
</script>

<template>
<div class="oekaki-card" v-if="!props.oekaki.nsfw || (props.oekaki.nsfw && !persistedStore.hideNsfw)">
<div class="oekaki-card" v-if="!props.oekaki.nsfw || (props.oekaki.nsfw && !preferencesStore.hideNsfw)">
<div class="oekaki-image" v-on:click.prevent="navigateToPost" v-on:mousedown.middle.stop.prevent="openInNewTab"
:title="altText">
<div class="oekaki-nsfw-blur" v-if="props.oekaki.nsfw && persistedStore.blurNsfw">NSFW</div>
<div class="oekaki-nsfw-blur" v-if="props.oekaki.nsfw && preferencesStore.blurNsfw">NSFW</div>
</div>
<OekakiMetaContainer :oekaki="props.oekaki" :show-substitute-on-no-tags="true" />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,20 @@
import { computed } from 'vue'
import type { Oekaki } from '@/models/oekaki'
import PostViewOekakiImageContainer from '@/components/oekaki/PostViewOekakiImageContainer.vue'
import { usePersistedStore } from '@/state/store'
import { useRouter } from 'vue-router'
import { xrpc } from '@/api/atproto/client'
import { formatDate, getRecordKeyFromAtUri } from '@/api/atproto/helpers'
import PostActions from '@/components/PostActions.vue'
import Username from '../profile/Username.vue'
import OekakiMetaContainer from './OekakiMetaContainer.vue'
import Avatar from '../profile/Avatar.vue'
import { usePdsPreferencesStore } from '@/state/preferences'

const props = defineProps<{
oekaki: Oekaki,
hideLineBar?: boolean
}>()

const router = useRouter();
const persistedStore = usePersistedStore();
const preferencesStore = usePdsPreferencesStore()

const authorProfileLink = computed(() => `/${props.oekaki.author.did}`);
const creationTime = computed(() => {
Expand All @@ -38,7 +36,7 @@ const redirectToParent = async () => {
</script>

<template>
<div :class="classList" v-if="!props.oekaki.nsfw || (props.oekaki.nsfw && !persistedStore.hideNsfw)">
<div :class="classList" v-if="!props.oekaki.nsfw || (props.oekaki.nsfw && !preferencesStore.hideNsfw)">
<div class="oekaki-child-info">
<Avatar :image="props.oekaki.author.avatar" :size="20" />
<div class="oekaki-info-text">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script setup lang="ts">
import { computed, useTemplateRef } from 'vue'
import type { Oekaki } from '@/models/oekaki'
import { usePersistedStore } from '@/state/store'
import { usePdsPreferencesStore } from '@/state/preferences';

const props = defineProps<{
oekaki: Oekaki
}>()

const persistedStore = usePersistedStore();
const preferencesStore = usePdsPreferencesStore()
const nsfwRef = useTemplateRef<HTMLDivElement>("nsfw-cover");
const imageLink = computed(() => `url(${props.oekaki.image})`)
const altText = computed(() => props.oekaki.alt ?? "");
Expand All @@ -21,7 +21,7 @@ const hideNSFWBlur = () => {
<div class="oekaki-image-container">
<div class="oekaki-image">
<img :src="props.oekaki.image" :alt="altText" :title="altText" />
<div class="oekaki-nsfw-cover" ref="nsfw-cover" :title="altText" v-if="props.oekaki.nsfw && persistedStore.blurNsfw">
<div class="oekaki-nsfw-cover" ref="nsfw-cover" :title="altText" v-if="props.oekaki.nsfw && preferencesStore.blurNsfw">
<div class="oekaki-nsfw-blur" v-on:click.prevent="hideNSFWBlur">
NSFW
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
<script setup lang="ts">
import type { Oekaki } from '@/models/oekaki'
import PostViewOekakiImageContainer from '@/components/oekaki/PostViewOekakiImageContainer.vue'
import { usePersistedStore } from '@/state/store'
import OekakiMetaContainer from './OekakiMetaContainer.vue'
import { usePdsPreferencesStore } from '@/state/preferences';

const props = defineProps<{
oekaki: Oekaki
}>()

const persistedStore = usePersistedStore();
const preferencesStore = usePdsPreferencesStore()
</script>

<template>
<div class="oekaki-card" v-if="!props.oekaki.nsfw || (props.oekaki.nsfw && !persistedStore.hideNsfw)">
<div class="oekaki-card" v-if="!props.oekaki.nsfw || (props.oekaki.nsfw && !preferencesStore.hideNsfw)">
<PostViewOekakiImageContainer :oekaki="props.oekaki" />
<OekakiMetaContainer :oekaki="props.oekaki" :show-post-actions="true" />
</div>
Expand Down
10 changes: 5 additions & 5 deletions PinkSea.Frontend/src/components/profile/Username.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script lang="ts" setup>
import type { Author } from '@/models/author';
import { UsernameDisplayType } from '@/models/username-display-type';
import { usePersistedStore } from '@/state/store';
import { usePdsPreferencesStore } from '@/state/preferences';
import { computed } from 'vue';

const store = usePersistedStore()
const preferencesStore = usePdsPreferencesStore()

const props = defineProps<{
author: Author
Expand All @@ -15,17 +15,17 @@ const username = computed(() => {
return `@${props.author.handle}`
}

if (store.usernameDisplayType == UsernameDisplayType.NicknameWithHandle) {
if (preferencesStore.usernameDisplayType == UsernameDisplayType.NicknameWithHandle) {
return `${props.author.nickname}`
} else if (store.usernameDisplayType == UsernameDisplayType.OnlyNickname) {
} else if (preferencesStore.usernameDisplayType == UsernameDisplayType.OnlyNickname) {
return props.author.nickname
} else {
return `@${props.author.handle}`
}
})

const displayHandle = computed(() => {
return store.usernameDisplayType == UsernameDisplayType.NicknameWithHandle &&
return preferencesStore.usernameDisplayType == UsernameDisplayType.NicknameWithHandle &&
props.author.nickname !== undefined
})

Expand Down
4 changes: 4 additions & 0 deletions PinkSea.Frontend/src/layouts/PanelLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { xrpc } from '@/api/atproto/client'
import i18next from 'i18next'
import I18n from '@/intl/i18n'
import { useTranslation } from 'i18next-vue'
import { usePdsPreferencesStore } from '@/state/preferences'

const identityStore = useIdentityStore()
const persistedStore = usePersistedStore()
Expand Down Expand Up @@ -50,6 +51,9 @@ onBeforeMount(async () => {
} catch {
persistedStore.token = null;
}

const preferencesStore = usePdsPreferencesStore()
await preferencesStore.fetch()
}
});

Expand Down
6 changes: 6 additions & 0 deletions PinkSea.Frontend/src/models/preferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Preferences {
values: {
key: string,
value: string
}[]
}
88 changes: 88 additions & 0 deletions PinkSea.Frontend/src/state/preferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { defineStore } from 'pinia'
import { xrpc } from '@/api/atproto/client';
import { usePersistedStore } from './store';
import type { Preferences } from '@/models/preferences';
import { UsernameDisplayType } from '@/models/username-display-type';
import { watch } from 'vue';

type FrontendPreferences = {
editorDarkMode: boolean,
blurNsfw: boolean,
hideNsfw: boolean,
usernameDisplayType: UsernameDisplayType
}

export const usePdsPreferencesStore = defineStore(
'preferencesStore',
{
state: () => {
return {
editorDarkMode: false,
blurNsfw: true,
hideNsfw: false,
usernameDisplayType: UsernameDisplayType.NicknameWithHandle
} as FrontendPreferences
},
actions: {
async fetch() {
const persistedStore = usePersistedStore()
const { data } = await xrpc.get("com.shinolabs.pinksea.getPreferences", {
params: {},
headers: {
"Authorization": `Bearer ${persistedStore.token}`
}
});

this.parse({ values: data.preferences })
},

parse(preferences: Preferences) {
preferences.values.forEach(({ key, value }) => {
const strippedKey = key.replace("frontend.", "")
if (strippedKey in this) {
const typedKey = strippedKey as keyof FrontendPreferences;
let parsedValue: any = value;

if (this[typedKey] === true || this[typedKey] === false) {
parsedValue = value === 'true';
} else if (typeof this[typedKey] === 'number') {
parsedValue = Number(value);
} else {
parsedValue = value;
}

(this as any)[strippedKey] = parsedValue;
}
});
},

async set<K extends keyof FrontendPreferences>(key: K, value: FrontendPreferences[K]) {
(this as any)[key] = value

const persistedStore = usePersistedStore()
await xrpc.call("com.shinolabs.pinksea.putPreference", {
data: {
key: `frontend.${key}`,
value: `${value}`
},
headers: {
"Authorization": `Bearer ${persistedStore.token}`
}
});
},

autoSync() {
watch(
() => JSON.parse(JSON.stringify(this.$state)),
(newState, oldState) => {
for (const key in newState) {
if (newState[key] !== oldState?.[key]) {
this.set(key as keyof typeof newState, newState[key])
}
}
},
{ deep: true }
)
}
}
});
5 changes: 0 additions & 5 deletions PinkSea.Frontend/src/state/store.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { UsernameDisplayType } from '@/models/username-display-type';
import { defineStore } from 'pinia'

export const usePersistedStore = defineStore(
Expand All @@ -7,10 +6,6 @@ export const usePersistedStore = defineStore(
state: () => {
return {
token: null as (string | null),
blurNsfw: true,
hideNsfw: false,
usernameDisplayType: UsernameDisplayType.NicknameWithHandle,

lang: null as (string | null)
}
},
Expand Down
Loading
Loading