2022-12-10 12:14:48 +03:00
import './settings.css' ;
2023-03-07 19:32:33 +03:00
import { useRef } from 'preact/hooks' ;
2023-01-14 14:42:04 +03:00
import { useSnapshot } from 'valtio' ;
2022-12-10 12:14:48 +03:00
2023-01-25 19:54:30 +03:00
import logo from '../assets/logo.svg' ;
2023-04-20 11:10:57 +03:00
import Icon from '../components/icon' ;
2023-01-05 05:50:27 +03:00
import RelativeTime from '../components/relative-time' ;
2023-03-07 17:38:06 +03:00
import targetLanguages from '../data/lingva-target-languages' ;
2023-07-09 11:32:09 +03:00
import { api } from '../utils/api' ;
2023-03-07 17:38:06 +03:00
import getTranslateTargetLanguage from '../utils/get-translate-target-language' ;
import localeCode2Text from '../utils/localeCode2Text' ;
2022-12-25 18:31:50 +03:00
import states from '../utils/states' ;
2022-12-10 12:14:48 +03:00
import store from '../utils/store' ;
2023-03-08 12:17:23 +03:00
const DEFAULT _TEXT _SIZE = 16 ;
const TEXT _SIZES = [ 16 , 17 , 18 , 19 , 20 ] ;
2022-12-16 08:27:04 +03:00
function Settings ( { onClose } ) {
2023-01-14 14:42:04 +03:00
const snapStates = useSnapshot ( states ) ;
2022-12-10 12:14:48 +03:00
const currentTheme = store . local . get ( 'theme' ) || 'auto' ;
const themeFormRef = useRef ( ) ;
2023-03-07 17:38:06 +03:00
const targetLanguage =
snapStates . settings . contentTranslationTargetLanguage || null ;
const systemTargetLanguage = getTranslateTargetLanguage ( ) ;
const systemTargetLanguageText = localeCode2Text ( systemTargetLanguage ) ;
2023-03-08 12:17:23 +03:00
const currentTextSize = store . local . get ( 'textSize' ) || DEFAULT _TEXT _SIZE ;
2023-03-07 17:38:06 +03:00
2023-07-09 11:32:09 +03:00
const [ prefs , setPrefs ] = useState ( store . account . get ( 'preferences' ) || { } ) ;
// Get preferences every time Settings is opened
// NOTE: Disabled for now because I don't expect this to change often. Also for some reason, the /api/v1/preferences endpoint is cached for a while and return old prefs if refresh immediately after changing them.
// useEffect(() => {
// const { masto } = api();
// (async () => {
// try {
// const preferences = await masto.v1.preferences.fetch();
// setPrefs(preferences);
// store.account.set('preferences', preferences);
// } catch (e) {
// // Silently fail
// console.error(e);
// }
// })();
// }, []);
2022-12-10 12:14:48 +03:00
return (
2022-12-29 11:11:58 +03:00
< div id = "settings-container" class = "sheet" tabIndex = "-1" >
2023-04-20 11:10:57 +03:00
{ ! ! onClose && (
< button type = "button" class = "sheet-close" onClick = { onClose } >
< Icon icon = "x" / >
< / button >
) }
2023-03-07 19:32:33 +03:00
< header >
2023-01-17 12:58:04 +03:00
< h2 > Settings < / h2 >
2023-03-07 19:32:33 +03:00
< / header >
< main >
2023-02-28 12:12:17 +03:00
< section >
< ul >
< li >
< div >
< label > Appearance < / label >
< / div >
< div >
< form
ref = { themeFormRef }
onInput = { ( e ) => {
console . log ( e ) ;
e . preventDefault ( ) ;
const formData = new FormData ( themeFormRef . current ) ;
const theme = formData . get ( 'theme' ) ;
const html = document . documentElement ;
2022-12-10 12:14:48 +03:00
2023-02-28 12:12:17 +03:00
if ( theme === 'auto' ) {
html . classList . remove ( 'is-light' , 'is-dark' ) ;
} else {
html . classList . toggle ( 'is-light' , theme === 'light' ) ;
html . classList . toggle ( 'is-dark' , theme === 'dark' ) ;
}
document
. querySelector ( 'meta[name="color-scheme"]' )
. setAttribute (
'content' ,
theme === 'auto' ? 'dark light' : theme ,
) ;
2022-12-10 12:14:48 +03:00
2023-02-28 12:12:17 +03:00
if ( theme === 'auto' ) {
store . local . del ( 'theme' ) ;
} else {
store . local . set ( 'theme' , theme ) ;
}
} }
>
< div class = "radio-group" >
< label >
< input
type = "radio"
name = "theme"
value = "light"
defaultChecked = { currentTheme === 'light' }
/ >
< span > Light < / span >
< / label >
< label >
< input
type = "radio"
name = "theme"
value = "dark"
defaultChecked = { currentTheme === 'dark' }
/ >
< span > Dark < / span >
< / label >
< label >
< input
type = "radio"
name = "theme"
value = "auto"
defaultChecked = {
currentTheme !== 'light' && currentTheme !== 'dark'
}
/ >
< span > Auto < / span >
< / label >
< / div >
< / form >
< / div >
< / li >
2023-03-08 12:17:23 +03:00
< li >
< div >
< label > Text size < / label >
< / div >
< div class = "range-group" >
< span style = { { fontSize : TEXT _SIZES [ 0 ] } } > A < / span > { ' ' }
< input
type = "range"
min = { TEXT _SIZES [ 0 ] }
max = { TEXT _SIZES [ TEXT _SIZES . length - 1 ] }
step = "1"
value = { currentTextSize }
list = "sizes"
onChange = { ( e ) => {
const value = parseInt ( e . target . value , 10 ) ;
const html = document . documentElement ;
// set CSS variable
html . style . setProperty ( '--text-size' , ` ${ value } px ` ) ;
// save to local storage
if ( value === DEFAULT _TEXT _SIZE ) {
store . local . del ( 'textSize' ) ;
} else {
store . local . set ( 'textSize' , e . target . value ) ;
}
} }
/ > { ' ' }
< span style = { { fontSize : TEXT _SIZES [ TEXT _SIZES . length - 1 ] } } >
A
< / span >
< datalist id = "sizes" >
{ TEXT _SIZES . map ( ( size ) => (
< option value = { size } / >
) ) }
< / datalist >
< / div >
< / li >
2023-03-07 19:32:33 +03:00
< / ul >
< / section >
2023-07-09 11:32:09 +03:00
< h3 > Posting < / h3 >
< section >
< ul >
< li >
< div >
< label for = "posting-privacy-field" > Default visibility < / label >
< / div >
< div >
< select
id = "posting-privacy-field"
value = { prefs [ 'posting:default:visibility' ] || 'public' }
onChange = { ( e ) => {
const { value } = e . target ;
const { masto } = api ( ) ;
( async ( ) => {
try {
await masto . v1 . accounts . updateCredentials ( {
source : {
privacy : value ,
} ,
} ) ;
setPrefs ( {
... prefs ,
'posting:default:visibility' : value ,
} ) ;
store . account . set ( 'preferences' , {
... prefs ,
'posting:default:visibility' : value ,
} ) ;
} catch ( e ) {
alert ( 'Failed to update posting privacy' ) ;
console . error ( e ) ;
}
} ) ( ) ;
} }
>
< option value = "public" > Public < / option >
< option value = "unlisted" > Unlisted < / option >
< option value = "private" > Followers only < / option >
< / select >
< / div >
< / li >
< / ul >
< / section >
2023-03-07 19:32:33 +03:00
< h3 > Experiments < / h3 >
< section >
< ul >
2023-05-05 12:53:16 +03:00
< li >
< label >
< input
type = "checkbox"
checked = { snapStates . settings . autoRefresh }
onChange = { ( e ) => {
states . settings . autoRefresh = e . target . checked ;
} }
/ > { ' ' }
Auto refresh timeline posts
< / label >
< / li >
2023-02-28 12:12:17 +03:00
< li >
< label >
< input
type = "checkbox"
checked = { snapStates . settings . boostsCarousel }
onChange = { ( e ) => {
states . settings . boostsCarousel = e . target . checked ;
} }
/ > { ' ' }
2023-03-07 19:32:33 +03:00
Boosts carousel
2023-02-28 12:12:17 +03:00
< / label >
< / li >
2023-03-07 17:38:06 +03:00
< li >
< label >
< input
type = "checkbox"
checked = { snapStates . settings . contentTranslation }
onChange = { ( e ) => {
2023-03-09 08:20:01 +03:00
const { checked } = e . target ;
states . settings . contentTranslation = checked ;
if ( ! checked ) {
states . settings . contentTranslationTargetLanguage = null ;
}
2023-03-07 17:38:06 +03:00
} }
/ > { ' ' }
2023-03-07 19:32:33 +03:00
Post translation
2023-03-07 17:38:06 +03:00
< / label >
2023-03-09 08:20:01 +03:00
< div
class = { ` sub-section ${
! snapStates . settings . contentTranslation
? 'more-insignificant'
: ''
} ` }
>
< label >
Translate to { ' ' }
< select
value = { targetLanguage || '' }
disabled = { ! snapStates . settings . contentTranslation }
onChange = { ( e ) => {
states . settings . contentTranslationTargetLanguage =
e . target . value || null ;
} }
>
< option value = "" >
System language ( { systemTargetLanguageText } )
< / option >
< option disabled > ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ < / option >
{ targetLanguages . map ( ( lang ) => (
< option value = { lang . code } > { lang . name } < / option >
) ) }
< / select >
< / label >
2023-03-28 14:04:52 +03:00
< p class = "checkbox-fieldset" >
< small >
Hide "Translate" button for
{ snapStates . settings . contentTranslationHideLanguages
. length > 0 && (
< >
{ ' ' }
(
{
snapStates . settings . contentTranslationHideLanguages
. length
}
)
< / >
) }
:
< / small >
< div class = "checkbox-fields" >
{ targetLanguages . map ( ( lang ) => (
< label >
< input
type = "checkbox"
checked = { snapStates . settings . contentTranslationHideLanguages . includes (
lang . code ,
) }
onChange = { ( e ) => {
const { checked } = e . target ;
if ( checked ) {
states . settings . contentTranslationHideLanguages . push (
lang . code ,
) ;
} else {
states . settings . contentTranslationHideLanguages =
snapStates . settings . contentTranslationHideLanguages . filter (
( code ) => code !== lang . code ,
) ;
}
} }
/ > { ' ' }
{ lang . name }
< / label >
) ) }
< / div >
< / p >
2023-03-09 08:20:01 +03:00
< p >
< small >
Note : This feature uses an external API to translate ,
powered by { ' ' }
< a
href = "https://github.com/thedaviddelta/lingva-translate"
target = "_blank"
2023-03-07 17:38:06 +03:00
>
2023-03-09 08:20:01 +03:00
Lingva Translate
< / a >
.
< / small >
< / p >
< / div >
2023-03-07 17:38:06 +03:00
< / li >
2023-04-23 07:08:41 +03:00
< li >
< label >
< input
type = "checkbox"
checked = { snapStates . settings . cloakMode }
onChange = { ( e ) => {
states . settings . cloakMode = e . target . checked ;
} }
/ > { ' ' }
2023-04-23 14:47:49 +03:00
Cloak mode { ' ' }
< span class = "insignificant" >
( < samp > Text < / samp > → < samp > █ █ █ █ < / samp > )
< / span >
2023-04-23 07:08:41 +03:00
< / label >
2023-04-23 14:47:49 +03:00
< div class = "sub-section insignificant" >
< small >
Replace text as blocks , useful when taking screenshots , for
privacy reasons .
< / small >
< / div >
2023-04-23 07:08:41 +03:00
< / li >
2023-03-07 19:32:33 +03:00
< li >
< button
type = "button"
class = "light"
onClick = { ( ) => {
states . showDrafts = true ;
states . showSettings = false ;
} }
>
Unsent drafts
< / button >
< / li >
2023-02-28 12:12:17 +03:00
< / ul >
< / section >
2023-03-07 19:32:33 +03:00
< h3 > About < / h3 >
2023-01-17 12:58:04 +03:00
< section >
2023-03-09 06:23:07 +03:00
< div
style = { {
display : 'flex' ,
gap : 8 ,
lineHeight : 1.25 ,
alignItems : 'center' ,
marginTop : 8 ,
} }
>
2023-01-25 19:54:30 +03:00
< img
src = { logo }
alt = ""
2023-03-09 06:23:07 +03:00
width = "64"
height = "64"
2023-01-25 19:54:30 +03:00
style = { {
aspectRatio : '1/1' ,
verticalAlign : 'middle' ,
2023-03-09 06:23:07 +03:00
background : '#b7cdf9' ,
borderRadius : 12 ,
2023-01-25 19:54:30 +03:00
} }
2023-03-09 06:23:07 +03:00
/ >
< div >
< b > Phanpy < / b > { ' ' }
< a
href = "https://hachyderm.io/@phanpy"
// target="_blank"
onClick = { ( e ) => {
e . preventDefault ( ) ;
states . showAccount = 'phanpy@hachyderm.io' ;
} }
>
@ phanpy
< / a >
< br / >
< a href = "https://github.com/cheeaun/phanpy" target = "_blank" >
Built
< / a > { ' ' }
by { ' ' }
< a
href = "https://mastodon.social/@cheeaun"
// target="_blank"
onClick = { ( e ) => {
e . preventDefault ( ) ;
states . showAccount = 'cheeaun@mastodon.social' ;
} }
>
@ cheeaun
< / a >
< / div >
< / div >
2023-01-30 18:16:00 +03:00
< p >
< a
href = "https://github.com/cheeaun/phanpy/blob/main/PRIVACY.MD"
target = "_blank"
>
Privacy Policy
< / a >
2022-12-25 13:01:01 +03:00
< / p >
2023-01-17 12:58:04 +03:00
{ _ _BUILD _TIME _ _ && (
< p >
2023-03-09 06:23:07 +03:00
< span class = "insignificant" > Last build : < / span > { ' ' }
< RelativeTime datetime = { new Date ( _ _BUILD _TIME _ _ ) } / > { ' ' }
2023-01-17 12:58:04 +03:00
{ _ _COMMIT _HASH _ _ && (
< >
(
< a
href = { ` https://github.com/cheeaun/phanpy/commit/ ${ _ _COMMIT _HASH _ _ } ` }
target = "_blank"
>
< code > { _ _COMMIT _HASH _ _ } < / code >
< / a >
)
< / >
) }
< / p >
) }
< / section >
2022-12-25 13:01:01 +03:00
< / main >
2022-12-10 12:14:48 +03:00
< / div >
) ;
2022-12-16 08:27:04 +03:00
}
export default Settings ;