Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
43 KB
Referenced Files
None
Subscribers
None
diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue
index b608d0ff18..85346e1745 100644
--- a/src/components/shadow_control/shadow_control.vue
+++ b/src/components/shadow_control/shadow_control.vue
@@ -1,238 +1,242 @@
<template>
<div class="shadow-control" :class="{ disabled: !present }">
<div class="shadow-preview-container">
<div :disabled="!present" class="y-shift-control">
<input
v-model="selected.y"
:disabled="!present"
class="input-number"
type="number">
<div class="wrap">
<input
v-model="selected.y"
:disabled="!present"
class="input-range"
type="range"
max="20"
min="-20">
</div>
</div>
<div class="preview-window">
<div class="preview-block" :style="style"></div>
</div>
<div :disabled="!present" class="x-shift-control">
<input
v-model="selected.x"
:disabled="!present"
class="input-number"
type="number">
<div class="wrap">
<input
v-model="selected.x"
:disabled="!present"
class="input-range"
type="range"
max="20"
min="-20">
</div>
</div>
</div>
<div class="shadow-tweak">
<div :disabled="usingFallback" class="id-control style-control">
<label for="shadow-switcher" class="select" :disabled="!ready || usingFallback">
<select
v-model="selectedId" class="shadow-switcher"
:disabled="!ready || usingFallback"
id="shadow-switcher">
<option v-for="(shadow, index) in cValue" :value="index">
{{$t('settings.style.shadows.shadow_id', { value: index })}}
</option>
</select>
<i class="icon-down-open"/>
</label>
<button class="btn btn-default" :disabled="!ready || !present" @click="del">
<i class="icon-cancel"/>
</button>
<button class="btn btn-default" :disabled="!moveUpValid" @click="moveUp">
<i class="icon-up-open"/>
</button>
<button class="btn btn-default" :disabled="!moveDnValid" @click="moveDn">
<i class="icon-down-open"/>
</button>
<button class="btn btn-default" :disabled="usingFallback" @click="add">
<i class="icon-plus"/>
</button>
</div>
<div :disabled="!present" class="inset-control style-control">
<label for="inset" class="label">
{{$t('settings.style.shadows.inset')}}
</label>
<input
v-model="selected.inset"
:disabled="!present"
name="inset"
id="inset"
class="input-inset"
type="checkbox">
<label class="checkbox-label" for="inset"></label>
</div>
<div :disabled="!present" class="blur-control style-control">
<label for="spread" class="label">
{{$t('settings.style.shadows.blur')}}
</label>
<input
v-model="selected.blur"
:disabled="!present"
name="blur"
id="blur"
class="input-range"
type="range"
max="20"
min="0">
<input
v-model="selected.blur"
:disabled="!present"
class="input-number"
type="number"
min="0">
</div>
<div :disabled="!present" class="spread-control style-control">
<label for="spread" class="label">
{{$t('settings.style.shadows.spread')}}
</label>
<input
v-model="selected.spread"
:disabled="!present"
name="spread"
id="spread"
class="input-range"
type="range"
max="20"
min="-20">
<input
v-model="selected.spread"
:disabled="!present"
class="input-number"
type="number">
</div>
<ColorInput
v-model="selected.color"
:disabled="!present"
:label="$t('settings.style.common.color')"
name="shadow"/>
<OpacityInput
v-model="selected.alpha"
:disabled="!present"/>
+ <div>
+ {{$t('settings.style.shadows.hint')}}
+ </div>
</div>
</div>
</template>
<script src="./shadow_control.js" ></script>
<style lang="scss">
@import '../../_variables.scss';
.shadow-control {
display: flex;
flex-wrap: wrap;
justify-content: center;
.shadow-preview-container,
.shadow-tweak {
margin: 5px 6px 0 0;
}
.shadow-preview-container {
flex: 0;
display: flex;
flex-wrap: wrap;
$side: 15em;
input[type=number] {
width: 5em;
min-width: 2em;
}
.x-shift-control,
.y-shift-control {
display: flex;
flex: 0;
&[disabled=disabled] *{
opacity: .5
}
}
.x-shift-control {
align-items: flex-start;
}
.x-shift-control .wrap,
input[type=range] {
margin: 0;
width: $side;
height: 2em;
}
.y-shift-control {
flex-direction: column;
align-items: flex-end;
.wrap {
width: 2em;
height: $side;
}
input[type=range] {
transform-origin: 1em 1em;
transform: rotate(90deg);
}
}
.preview-window {
flex: 1;
background-color: #999999;
display: flex;
align-items: center;
justify-content: center;
background-image:
linear-gradient(45deg, #666666 25%, transparent 25%),
linear-gradient(-45deg, #666666 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #666666 75%),
linear-gradient(-45deg, transparent 75%, #666666 75%);
background-size: 20px 20px;
background-position:0 0, 0 10px, 10px -10px, -10px 0;
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);
.preview-block {
width: 33%;
height: 33%;
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
}
}
}
.shadow-tweak {
flex: 1;
+ min-width: 280px;
.id-control {
align-items: stretch;
.select, .btn {
min-width: 1px;
margin-right: 5px;
}
.btn {
padding: 0 .4em;
margin: 0 .1em;
}
.select {
flex: 1;
select {
align-self: initial;
}
}
}
}
}
</style>
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 9e5cffbed2..f509032144 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -1,226 +1,229 @@
<template>
<div class="style-switcher">
<div class="presets-container">
<div class="save-load">
<div>
{{$t('settings.presets')}}
<label for="preset-switcher" class='select'>
<select id="preset-switcher" v-model="selected" class="preset-switcher">
<option v-for="style in availableStyles"
:value="style"
:style="{
backgroundColor: style[1] || style.theme.colors.bg,
color: style[3] || style.theme.colors.text
}">
{{style[0] || style.name}}
</option>
</select>
<i class="icon-down-open"/>
</label>
</div>
<div class="import-export">
<button class="btn" @click="exportCurrentTheme">{{ $t('settings.export_theme') }}</button>
<button class="btn" @click="importTheme">{{ $t('settings.import_theme') }}</button>
<p v-if="invalidThemeImported" class="import-warning">{{ $t('settings.invalid_theme_imported') }}</p>
</div>
</div>
<div class="save-load-options">
<span>
<input
id="keep-shadows"
type="checkbox"
v-model="keepShadows">
<label for="keep-shadows">{{$t('settings.style.switcher.keep_shadows')}}</label>
</span>
<span>
<input
id="keep-opacity"
type="checkbox"
v-model="keepOpacity">
<label for="keep-opacity">{{$t('settings.style.switcher.keep_opacity')}}</label>
</span>
<span>
<input
id="keep-roundness"
type="checkbox"
v-model="keepRoundness">
<label for="keep-roundness">{{$t('settings.style.switcher.keep_roundness')}}</label>
</span>
<p>{{$t('settings.style.switcher.save_load_hint')}}</p>
</div>
</div>
<div class="preview-container">
<div class="panel dummy" :style="previewRules">
<div class="panel-heading">Preview</div>
<div class="panel-body theme-preview-content">
<div class="avatar">
( ͡° ͜ʖ ͡°)
</div>
<h4>Content</h4>
<br>
A bunch of more content and
<a style="color: var(--link)">a nice lil' link</a>
<i style="color: var(--cBlue)" class="icon-reply"/>
<i style="color: var(--cGreen)" class="icon-retweet"/>
<i style="color: var(--cRed)" class="icon-cancel"/>
<i style="color: var(--cOrange)" class="icon-star"/>
<br>
<button class="btn">Button</button>
</div>
</div>
</div>
<keep-alive>
<tab-switcher key="style-tweak">
<div :label="$t('settings.style.common_colors._tab_label')" class="color-container">
<div class="tab-header">
- <p>{{$t('settings.theme_help')}}</p>
+ <p>{{$t('settings.theme_help')}}</p>
<button class="btn" @click="clearOpacity">{{$t('settings.style.switcher.clear_opacity')}}</button>
+ <button class="btn" @click="clearV1">{{$t('settings.style.switcher.clear_all')}}</button>
</div>
+ <p>{{$t('settings.theme_help_v2_1')}}</p>
<h4>{{ $t('settings.style.common_colors.main') }}</h4>
<div class="color-item">
<ColorInput name="bgColor" v-model="bgColorLocal" :label="$t('settings.background')"/>
<OpacityInput name="bgOpacity" v-model="bgOpacityLocal" :fallback="previewTheme.opacity.bg || 1"/>
<ColorInput name="textColor" v-model="textColorLocal" :label="$t('settings.text')"/>
<ContrastRatio :contrast="previewContrast.bgText"/>
<ColorInput name="linkColor" v-model="linkColorLocal" :label="$t('settings.links')"/>
<ContrastRatio :contrast="previewContrast.bgLink"/>
</div>
<div class="color-item">
<ColorInput name="fgColor" v-model="fgColorLocal" :label="$t('settings.foreground')"/>
<ColorInput name="fgTextColor" v-model="fgTextColorLocal" :label="$t('settings.text')" :fallback="previewTheme.colors.fgText"/>
<ColorInput name="fgLinkColor" v-model="fgLinkColorLocal" :label="$t('settings.links')" :fallback="previewTheme.colors.fgLink"/>
<p>{{ $t('settings.style.common_colors.foreground_hint') }}</p>
</div>
<h4>{{ $t('settings.style.common_colors.rgbo') }}</h4>
<div class="color-item">
<ColorInput name="cRedColor" v-model="cRedColorLocal" :label="$t('settings.cRed')"/>
<ContrastRatio :contrast="previewContrast.bgRed"/>
<ColorInput name="cBlueColor" v-model="cBlueColorLocal" :label="$t('settings.cBlue')"/>
<ContrastRatio :contrast="previewContrast.bgBlue"/>
</div>
<div class="color-item">
<ColorInput name="cGreenColor" v-model="cGreenColorLocal" :label="$t('settings.cGreen')"/>
<ContrastRatio :contrast="previewContrast.bgGreen"/>
<ColorInput name="cOrangeColor" v-model="cOrangeColorLocal" :label="$t('settings.cOrange')"/>
<ContrastRatio :contrast="previewContrast.bgOrange"/>
</div>
+ <p>{{$t('settings.theme_help_v2_2')}}</p>
</div>
<div :label="$t('settings.style.advanced_colors._tab_label')" class="color-container">
<div class="tab-header">
<p>{{$t('settings.theme_help')}}</p>
- <button class="btn" @click="clearV1">{{$t('settings.style.switcher.clear_all')}}</button>
<button class="btn" @click="clearOpacity">{{$t('settings.style.switcher.clear_opacity')}}</button>
+ <button class="btn" @click="clearV1">{{$t('settings.style.switcher.clear_all')}}</button>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.alert') }}</h4>
<ColorInput name="alertError" v-model="alertErrorColorLocal" :label="$t('settings.style.advanced_colors.alert_error')" :fallback="previewTheme.colors.alertError"/>
<ContrastRatio :contrast="previewContrast.alertError"/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.badge') }}</h4>
<ColorInput name="badgeNotification" v-model="badgeNotificationColorLocal" :label="$t('settings.style.advanced_colors.badge_notification')" :fallback="previewTheme.colors.badgeNotification"/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.panel_header') }}</h4>
<ColorInput name="panelColor" v-model="panelColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
<OpacityInput name="panelOpacity" v-model="panelOpacityLocal" :fallback="previewTheme.opacity.panel || 1"/>
<ColorInput name="panelTextColor" v-model="panelTextColorLocal" :fallback="previewTheme.colors.panelText" :label="$t('settings.links')"/>
<ContrastRatio :contrast="previewContrast.panelText" large="1"/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.top_bar') }}</h4>
<ColorInput name="topBarColor" v-model="topBarColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
<ColorInput name="topBarTextColor" v-model="topBarTextColorLocal" :fallback="previewTheme.colors.topBarText" :label="$t('settings.text')"/>
<ContrastRatio :contrast="previewContrast.topBarText"/>
<ColorInput name="topBarLinkColor" v-model="topBarLinkColorLocal" :fallback="previewTheme.colors.topBarLink" :label="$t('settings.links')"/>
<ContrastRatio :contrast="previewContrast.topBarLink"/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.inputs') }}</h4>
<ColorInput name="inputColor" v-model="inputColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
<OpacityInput name="inputOpacity" v-model="inputOpacityLocal" :fallback="previewTheme.opacity.input || 1"/>
<ColorInput name="inputTextColor" v-model="inputTextColorLocal" :fallback="previewTheme.colors.inputText" :label="$t('settings.text')"/>
<ContrastRatio :contrast="previewContrast.inputText"/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.buttons') }}</h4>
<ColorInput name="btnColor" v-model="btnColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
<OpacityInput name="btnOpacity" v-model="btnOpacityLocal" :fallback="previewTheme.opacity.btn || 1"/>
<ColorInput name="btnTextColor" v-model="btnTextColorLocal" :fallback="previewTheme.colors.btnText" :label="$t('settings.text')"/>
<ContrastRatio :contrast="previewContrast.btnText"/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.borders') }}</h4>
<ColorInput name="borderColor" v-model="borderColorLocal" :fallback="previewTheme.colors.border" label="Color"/>
<OpacityInput name="borderOpacity" v-model="borderOpacityLocal" :fallback="previewTheme.opacity.border || 1"/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.faint_text') }}</h4>
<ColorInput name="faintColor" v-model="faintColorLocal" :fallback="previewTheme.colors.faint || 1" :label="$t('settings.text')"/>
<ColorInput name="faintLinkColor" v-model="faintLinkColorLocal" :fallback="previewTheme.colors.faintLink" :label="$t('settings.links')"/>
<ColorInput name="panelFaintColor" v-model="panelFaintColorLocal" :fallback="previewTheme.colors.panelFaint" :label="$t('settings.style.advanced_colors.panel_header')"/>
<OpacityInput name="faintOpacity" v-model="faintOpacityLocal" :fallback="previewTheme.opacity.faint || 0.5"/>
</div>
</div>
<div :label="$t('settings.style.radii._tab_label')" class="radius-container">
<div>
<div class="tab-header">
<p>{{$t('settings.radii_help')}}</p>
<button class="btn" @click="clearRoundness">{{$t('settings.style.switcher.clear_all')}}</button>
</div>
<RangeInput name="btnRadius" :label="$t('settings.btnRadius')" v-model="btnRadiusLocal" :fallback="previewTheme.radii.btn" max="16" hardMin="0"/>
<RangeInput name="inputRadius" :label="$t('settings.inputRadius')" v-model="inputRadiusLocal" :fallback="previewTheme.radii.input" max="9" hardMin="0"/>
<RangeInput name="checkboxRadius" :label="$t('settings.checkboxRadius')" v-model="checkboxRadiusLocal" :fallback="previewTheme.radii.checkbox" max="16" hardMin="0"/>
<RangeInput name="panelRadius" :label="$t('settings.panelRadius')" v-model="panelRadiusLocal" :fallback="previewTheme.radii.panel" max="50" hardMin="0"/>
<RangeInput name="avatarRadius" :label="$t('settings.avatarRadius')" v-model="avatarRadiusLocal" :fallback="previewTheme.radii.avatar" max="28" hardMin="0"/>
<RangeInput name="avatarAltRadius" :label="$t('settings.avatarAltRadius')" v-model="avatarAltRadiusLocal" :fallback="previewTheme.radii.avatarAlt" max="28" hardMin="0"/>
<RangeInput name="attachmentRadius" :label="$t('settings.attachmentRadius')" v-model="attachmentRadiusLocal" :fallback="previewTheme.radii.attachment" max="50" hardMin="0"/>
<RangeInput name="tooltipRadius" :label="$t('settings.tooltipRadius')" v-model="tooltipRadiusLocal" :fallback="previewTheme.radii.tooltip" max="50" hardMin="0"/>
</div>
</div>
<div :label="$t('settings.style.shadows._tab_label')" class="shadow-container">
<div class="tab-header shadow-selector">
<div class="select-container">
{{$t('settings.style.shadows.component')}}
<label for="shadow-switcher" class="select">
<select id="shadow-switcher" v-model="shadowSelected" class="shadow-switcher">
<option v-for="shadow in shadowsAvailable"
:value="shadow">
{{$t('settings.style.shadows.components.' + shadow)}}
</option>
</select>
<i class="icon-down-open"/>
</label>
</div>
<div class="override">
<label for="override" class="label">
{{$t('settings.style.shadows.override')}}
</label>
<input
v-model="currentShadowOverriden"
name="override"
id="override"
class="input-override"
type="checkbox">
<label class="checkbox-label" for="override"></label>
</div>
<button class="btn" @click="clearShadows">{{$t('settings.style.switcher.clear_all')}}</button>
</div>
<shadow-control :ready="!!currentShadowFallback" :fallback="currentShadowFallback" v-model="currentShadow"/>
</div>
</tab-switcher>
</keep-alive>
<div class="apply-container">
<button class="btn submit" @click="setCustomTheme">{{$t('general.apply')}}</button>
<button class="btn" @click="clearAll">{{$t('settings.style.switcher.reset')}}</button>
</div>
</div>
</template>
<script src="./style_switcher.js"></script>
<style src="./style_switcher.scss" lang="scss"></style>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 15d57765eb..5bd1ddbb09 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -1,272 +1,275 @@
{
"chat": {
"title": "Chat"
},
"features_panel": {
"chat": "Chat",
"gopher": "Gopher",
"media_proxy": "Media proxy",
"scope_options": "Scope options",
"text_limit": "Text limit",
"title": "Features",
"who_to_follow": "Who to follow"
},
"finder": {
"error_fetching_user": "Error fetching user",
"find_user": "Find user"
},
"general": {
"apply": "Apply",
"submit": "Submit"
},
"login": {
"login": "Log in",
"logout": "Log out",
"password": "Password",
"placeholder": "e.g. lain",
"register": "Register",
"username": "Username"
},
"nav": {
"chat": "Local Chat",
"friend_requests": "Follow Requests",
"mentions": "Mentions",
"public_tl": "Public Timeline",
"timeline": "Timeline",
"twkn": "The Whole Known Network"
},
"notifications": {
"broken_favorite": "Unknown status, searching for it...",
"favorited_you": "favorited your status",
"followed_you": "followed you",
"load_older": "Load older notifications",
"notifications": "Notifications",
"read": "Read!",
"repeated_you": "repeated your status"
},
"post_status": {
"account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
"account_not_locked_warning_link": "locked",
"attachments_sensitive": "Mark attachments as sensitive",
"content_type": {
"plain_text": "Plain text"
},
"content_warning": "Subject (optional)",
"default": "Just landed in L.A.",
"direct_warning": "This post will only be visible to all the mentioned users.",
"posting": "Posting",
"scope": {
"direct": "Direct - Post to mentioned users only",
"private": "Followers-only - Post to followers only",
"public": "Public - Post to public timelines",
"unlisted": "Unlisted - Do not post to public timelines"
}
},
"registration": {
"bio": "Bio",
"email": "Email",
"fullname": "Display name",
"password_confirm": "Password confirmation",
"registration": "Registration",
"token": "Invite token"
},
"settings": {
"attachmentRadius": "Attachments",
"attachments": "Attachments",
"autoload": "Enable automatic loading when scrolled to the bottom",
"avatar": "Avatar",
"avatarAltRadius": "Avatars (Notifications)",
"avatarRadius": "Avatars",
"background": "Background",
"bio": "Bio",
"btnRadius": "Buttons",
"cBlue": "Blue (Reply, follow)",
"cGreen": "Green (Retweet)",
"cOrange": "Orange (Favorite)",
"cRed": "Red (Cancel)",
"change_password": "Change Password",
"change_password_error": "There was an issue changing your password.",
"changed_password": "Password changed successfully!",
"collapse_subject": "Collapse posts with subjects",
"confirm_new_password": "Confirm new password",
"current_avatar": "Your current avatar",
"current_password": "Current password",
"current_profile_banner": "Your current profile banner",
"data_import_export_tab": "Data Import / Export",
"default_vis": "Default visibility scope",
"delete_account": "Delete Account",
"delete_account_description": "Permanently delete your account and all your messages.",
"delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
"delete_account_instructions": "Type your password in the input below to confirm account deletion.",
"export_theme": "Save preset",
"filtering": "Filtering",
"filtering_explanation": "All statuses containing these words will be muted, one per line",
"follow_export": "Follow export",
"follow_export_button": "Export your follows to a csv file",
"follow_export_processing": "Processing, you'll soon be asked to download your file",
"follow_import": "Follow import",
"follow_import_error": "Error importing followers",
"follows_imported": "Follows imported! Processing them will take a while.",
"foreground": "Foreground",
"general": "General",
"hide_attachments_in_convo": "Hide attachments in conversations",
"hide_attachments_in_tl": "Hide attachments in timeline",
"import_followers_from_a_csv_file": "Import follows from a csv file",
"import_theme": "Load preset",
"inputRadius": "Input fields",
"checkboxRadius": "Checkboxes",
"instance_default": "(default: {value})",
"interfaceLanguage": "Interface language",
"invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
"limited_availability": "Unavailable in your browser",
"links": "Links",
"lock_account_description": "Restrict your account to approved followers only",
"loop_video": "Loop videos",
"loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
"name": "Name",
"name_bio": "Name & Bio",
"new_password": "New password",
"notification_visibility": "Types of notifications to show",
"notification_visibility_follows": "Follows",
"notification_visibility_likes": "Likes",
"notification_visibility_mentions": "Mentions",
"notification_visibility_repeats": "Repeats",
"no_rich_text_description": "Disable rich text support",
"nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
"panelRadius": "Panels",
"pause_on_unfocused": "Pause streaming when tab is not focused",
"presets": "Presets",
"profile_background": "Profile Background",
"profile_banner": "Profile Banner",
"profile_tab": "Profile",
"radii_help": "Set up interface edge rounding (in pixels)",
"replies_in_timeline": "Replies in timeline",
"reply_link_preview": "Enable reply-link preview on mouse hover",
"reply_visibility_all": "Show all replies",
"reply_visibility_following": "Only show replies directed at me or users I'm following",
"reply_visibility_self": "Only show replies directed at me",
"saving_err": "Error saving settings",
"saving_ok": "Settings saved",
"security_tab": "Security",
"set_new_avatar": "Set new avatar",
"set_new_profile_background": "Set new profile background",
"set_new_profile_banner": "Set new profile banner",
"settings": "Settings",
"stop_gifs": "Play-on-hover GIFs",
"streaming": "Enable automatic streaming of new posts when scrolled to the top",
"text": "Text",
"theme": "Theme",
"theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
+ "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
+ "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
"tooltipRadius": "Tooltips/alerts",
"user_settings": "User Settings",
"values": {
"false": "no",
"true": "yes"
},
"style": {
"switcher": {
"keep_shadows": "Keep shadows",
"keep_opacity": "Keep opacity",
"keep_roundness": "Keep roundness",
"save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme.",
"reset": "Reset",
"clear_all": "Clear all",
"clear_opacity": "Clear opacity"
},
"common": {
"color": "Color",
"opacity": "Opacity",
"contrast": {
"hint": "Contrast ratio is {ratio}, it {level} {context}",
"level": {
"aa": "meets Level AA guideline (minimal)",
"aaa": "meets Level AAA guideline (recommended)",
"bad": "doesn't meet any accessibility guidelines"
},
"context": {
"18pt": "for large (18pt+) text",
"text": "for text"
}
}
},
"common_colors": {
"_tab_label": "Common",
"main": "Common colors",
"foreground": "Panel header, top bar, buttons, text fields",
"foreground_hint": "See \"Advanced\" tab for more detailed control",
"rgbo": "Icons, accents, badges"
},
"advanced_colors": {
"_tab_label": "Advanced",
"alert": "Alert background",
"alert_error": "Error",
"badge": "Badge background",
"badge_notification": "Notification",
"panel_header": "Panel header",
"top_bar": "Top bar",
"borders": "Borders",
"buttons": "Buttons",
"inputs": "Input fields",
"faint_text": "Faded text"
},
"radii": {
"_tab_label": "Roundness"
},
"shadows": {
"_tab_label": "Shadow and lighting",
"component": "Component",
"override": "Override",
"shadow_id": "Shadow #{value}",
"blur": "Blur",
"spread": "Spread",
"inset": "Inset",
+ "hint": "For shadows you can also use --variable as a color value to use CSS3 variables. Please note that setting opacity won't work in this case.",
"components": {
"panel": "Panel",
"panelHeader": "Panel header",
"topBar": "Top bar",
- "avatar": "User avatar (in post display)",
- "avatarStatus": "User avatar (in profile view)",
+ "avatar": "User avatar (in profile view)",
+ "avatarStatus": "User avatar (in post display)",
"popup": "Popups and tooltips",
"button": "Button",
"buttonHover": "Button (hover)",
"buttonPressed": "Button (pressed)",
"buttonPressedHover": "Button (pressed+hover)",
"input": "Input field"
}
}
}
},
"timeline": {
"collapse": "Collapse",
"conversation": "Conversation",
"error_fetching": "Error fetching updates",
"load_older": "Load older statuses",
"no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
"repeated": "repeated",
"show_new": "Show new",
"up_to_date": "Up-to-date"
},
"user_card": {
"approve": "Approve",
"block": "Block",
"blocked": "Blocked!",
"deny": "Deny",
"follow": "Follow",
"followees": "Following",
"followers": "Followers",
"following": "Following!",
"follows_you": "Follows you!",
"mute": "Mute",
"muted": "Muted",
"per_day": "per day",
"remote_follow": "Remote follow",
"statuses": "Statuses"
},
"user_profile": {
"timeline_title": "User Timeline"
},
"who_to_follow": {
"more": "More",
"who_to_follow": "Who to follow"
}
}
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 680aadb436..5726cd6144 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -1,430 +1,431 @@
import { times } from 'lodash'
import { brightness, invertLightness, convert, contrastRatio } from 'chromatism'
import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend } from '../color_convert/color_convert.js'
// While this is not used anymore right now, I left it in if we want to do custom
// styles that aren't just colors, so user can pick from a few different distinct
// styles as well as set their own colors in the future.
const setStyle = (href, commit) => {
/***
What's going on here?
I want to make it easy for admins to style this application. To have
a good set of default themes, I chose the system from base16
(https://chriskempson.github.io/base16/) to style all elements. They
all have the base00..0F classes. So the only thing an admin needs to
do to style Pleroma is to change these colors in that one css file.
Some default things (body text color, link color) need to be set dy-
namically, so this is done here by waiting for the stylesheet to be
loaded and then creating an element with the respective classes.
It is a bit weird, but should make life for admins somewhat easier.
***/
const head = document.head
const body = document.body
body.style.display = 'none'
const cssEl = document.createElement('link')
cssEl.setAttribute('rel', 'stylesheet')
cssEl.setAttribute('href', href)
head.appendChild(cssEl)
const setDynamic = () => {
const baseEl = document.createElement('div')
body.appendChild(baseEl)
let colors = {}
times(16, (n) => {
const name = `base0${n.toString(16).toUpperCase()}`
baseEl.setAttribute('class', name)
const color = window.getComputedStyle(baseEl).getPropertyValue('color')
colors[name] = color
})
body.removeChild(baseEl)
const styleEl = document.createElement('style')
head.appendChild(styleEl)
// const styleSheet = styleEl.sheet
body.style.display = 'initial'
}
cssEl.addEventListener('load', setDynamic)
}
const rgb2rgba = function (rgba) {
return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`
}
const getTextColor = function (bg, text, preserve) {
const bgIsLight = convert(bg).hsl.l > 50
const textIsLight = convert(text).hsl.l > 50
if ((bgIsLight && textIsLight) || (!bgIsLight && !textIsLight)) {
const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
const result = Object.assign(base, invertLightness(text).rgb)
if (!preserve && getContrastRatio(bg, result) < 4.5) {
return contrastRatio(bg, text).rgb
}
return result
}
return text
}
const setColors = (input, commit) => {
const { rules, theme } = generatePreset(input)
const head = document.head
const body = document.body
body.style.display = 'none'
const styleEl = document.createElement('style')
head.appendChild(styleEl)
const styleSheet = styleEl.sheet
styleSheet.toString()
styleSheet.insertRule(`body { ${rules.radii} }`, 'index-max')
styleSheet.insertRule(`body { ${rules.colors} }`, 'index-max')
styleSheet.insertRule(`body { ${rules.shadows} }`, 'index-max')
body.style.display = 'initial'
// commit('setOption', { name: 'colors', value: htmlColors })
// commit('setOption', { name: 'radii', value: radii })
commit('setOption', { name: 'customTheme', value: input })
commit('setOption', { name: 'colors', value: theme.colors })
}
const getCssShadow = (input) => {
if (input.length === 0) {
return 'none'
}
return input.map((shad) => [
shad.x,
shad.y,
shad.blur,
shad.spread
].map(_ => _ + 'px').concat([
getCssColor(shad.color, shad.alpha),
shad.inset ? 'inset' : ''
]).join(' ')).join(', ')
}
const getCssColor = (input, a) => {
let rgb = {}
if (typeof input === 'object') {
rgb = input
} else if (typeof input === 'string') {
if (input.startsWith('#')) {
rgb = hex2rgb(input)
} else if (input.startsWith('--')) {
return `var(${input})`
} else {
return input
}
}
return rgb2rgba({ ...rgb, a })
}
const generateColors = (input) => {
const colors = {}
const opacity = Object.assign({
alert: 0.5,
input: 0.5,
faint: 0.5
}, Object.entries(input.opacity || {}).reduce((acc, [k, v]) => {
if (typeof v !== 'undefined') {
acc[k] = v
}
return acc
}, {}))
const col = Object.entries(input.colors || input).reduce((acc, [k, v]) => {
if (typeof v === 'object') {
acc[k] = v
} else {
acc[k] = hex2rgb(v)
}
return acc
}, {})
const isLightOnDark = convert(col.bg).hsl.l < convert(col.text).hsl.l
const mod = isLightOnDark ? 1 : -1
colors.text = col.text
colors.lightText = brightness(20 * mod, colors.text).rgb
colors.link = col.link
colors.faint = col.faint || Object.assign({}, col.text)
colors.bg = col.bg
colors.lightBg = col.lightBg || brightness(5, colors.bg).rgb
colors.fg = col.fg
colors.fgText = col.fgText || getTextColor(colors.fg, colors.text)
colors.fgLink = col.fgLink || getTextColor(colors.fg, colors.link, true)
colors.border = col.border || brightness(2 * mod, colors.fg).rgb
colors.btn = col.btn || Object.assign({}, col.fg)
colors.btnText = col.btnText || getTextColor(colors.btn, colors.fgText)
colors.input = col.input || Object.assign({}, col.fg)
colors.inputText = col.inputText || getTextColor(colors.input, colors.lightText)
colors.panel = col.panel || Object.assign({}, col.fg)
colors.panelText = col.panelText || getTextColor(colors.panel, colors.fgText)
colors.panelFaint = col.panelFaint || getTextColor(colors.panel, colors.faint)
colors.topBar = col.topBar || Object.assign({}, col.fg)
colors.topBarText = col.topBarText || getTextColor(colors.topBar, colors.fgText)
colors.topBarLink = col.topBarLink || getTextColor(colors.topBar, colors.fgLink)
colors.faintLink = col.faintLink || Object.assign({}, col.link)
colors.icon = mixrgb(colors.bg, colors.text)
colors.cBlue = col.cBlue
colors.cRed = col.cRed
colors.cGreen = col.cGreen
colors.cOrange = col.cOrange
colors.alertError = col.alertError || Object.assign({}, col.cRed)
colors.alertErrorText = getTextColor(alphaBlend(colors.alertError, opacity.alert, colors.bg), colors.text)
colors.alertErrorPanelText = getTextColor(alphaBlend(colors.alertError, opacity.alert, colors.panel), colors.panelText)
colors.badgeNotification = col.badgeNotification || Object.assign({}, col.cRed)
colors.badgeNotificationText = contrastRatio(colors.badgeNotification).rgb
Object.entries(opacity).forEach(([ k, v ]) => {
if (typeof v === 'undefined') return
if (k === 'alert') {
colors.alertError.a = v
return
}
if (k === 'faint') {
colors[k + 'Link'].a = v
colors['panelFaint'].a = v
}
if (k === 'bg') {
colors['lightBg'].a = v
}
if (colors[k]) {
colors[k].a = v
} else {
console.error('Wrong key ' + k)
}
})
const htmlColors = Object.entries(colors)
.reduce((acc, [k, v]) => {
if (!v) return acc
acc.solid[k] = rgb2hex(v)
acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgb2rgba(v)
return acc
}, { complete: {}, solid: {} })
return {
rules: {
colors: Object.entries(htmlColors.complete)
.filter(([k, v]) => v)
.map(([k, v]) => `--${k}: ${v}`)
.join(';')
},
theme: {
colors: htmlColors.solid,
opacity
}
}
}
const generateRadii = (input) => {
let inputRadii = input.radii || {}
// v1 -> v2
if (typeof input.btnRadius !== 'undefined') {
inputRadii = Object
.entries(input)
.filter(([k, v]) => k.endsWith('Radius'))
.reduce((acc, e) => { acc[e[0].split('Radius')[0]] = e[1]; return acc }, {})
}
const radii = Object.entries(inputRadii).filter(([k, v]) => v).reduce((acc, [k, v]) => {
acc[k] = v
return acc
}, {
btn: 4,
input: 4,
checkbox: 2,
panel: 10,
avatar: 5,
avatarAlt: 50,
tooltip: 2,
attachment: 5
})
return {
rules: {
radii: Object.entries(radii).filter(([k, v]) => v).map(([k, v]) => `--${k}Radius: ${v}px`).join(';')
},
theme: {
radii
}
}
}
const generateShadows = (input) => {
const border = (top, shadow) => ({
x: 0,
y: top ? 1 : -1,
blur: 0,
spread: 0,
color: shadow ? '#000000' : '#FFFFFF',
alpha: 0.2,
inset: true
})
const buttonInsetFakeBorders = [border(true, false), border(false, true)]
const inputInsetFakeBorders = [border(true, true), border(false, false)]
const hoverGlow = {
x: 0,
y: 0,
blur: 4,
spread: 0,
color: '--faint',
alpha: 1
}
const shadows = {
panel: [{
x: 1,
y: 1,
blur: 4,
spread: 0,
color: '#000000',
alpha: 0.6
}],
topBar: [{
x: 0,
y: 0,
blur: 4,
spread: 0,
color: '#000000',
alpha: 0.6
}],
popup: [{
x: 2,
y: 2,
blur: 3,
spread: 0,
color: '#000000',
alpha: 0.5
}],
avatar: [{
x: 0,
y: 1,
blur: 8,
spread: 0,
color: '#000000',
alpha: 0.7
}],
+ avatarStatus: [],
panelHeader: [],
button: [{
x: 0,
y: 0,
blur: 2,
spread: 0,
color: '#000000',
alpha: 1
}, ...buttonInsetFakeBorders],
buttonHover: [hoverGlow, ...buttonInsetFakeBorders],
buttonPressed: [hoverGlow, ...inputInsetFakeBorders],
input: [...inputInsetFakeBorders, {
x: 0,
y: 0,
blur: 2,
inset: true,
spread: 0,
color: '#000000',
alpha: 1
}],
...(input.shadows || {})
}
console.log(Object.entries(shadows).map(([k, v]) => `--${k}Shadow: ${getCssShadow(v)}`).join(';'))
return {
rules: {
shadows: Object.entries(shadows).map(([k, v]) => `--${k}Shadow: ${getCssShadow(v)}`).join(';')
},
theme: {
shadows
}
}
}
const composePreset = (colors, radii, shadows) => {
return {
rules: {
...shadows.rules,
...colors.rules,
...radii.rules
},
theme: {
...shadows.theme,
...colors.theme,
...radii.theme
}
}
}
const generatePreset = (input) => {
const shadows = generateShadows(input)
const colors = generateColors(input)
const radii = generateRadii(input)
return composePreset(colors, radii, shadows)
}
const setPreset = (val, commit) => {
window.fetch('/static/styles.json')
.then((data) => data.json())
.then((themes) => {
const theme = themes[val] ? themes[val] : themes['pleroma-dark']
const bgRgb = hex2rgb(theme[1])
const fgRgb = hex2rgb(theme[2])
const textRgb = hex2rgb(theme[3])
const linkRgb = hex2rgb(theme[4])
const cRedRgb = hex2rgb(theme[5] || '#FF0000')
const cGreenRgb = hex2rgb(theme[6] || '#00FF00')
const cBlueRgb = hex2rgb(theme[7] || '#0000FF')
const cOrangeRgb = hex2rgb(theme[8] || '#E3FF00')
const colors = {
bg: bgRgb,
fg: fgRgb,
text: textRgb,
link: linkRgb,
cRed: cRedRgb,
cBlue: cBlueRgb,
cGreen: cGreenRgb,
cOrange: cOrangeRgb
}
// This is a hack, this function is only called during initial load.
// We want to cancel loading the theme from config.json if we're already
// loading a theme from the persisted state.
// Needed some way of dealing with the async way of things.
// load config -> set preset -> wait for styles.json to load ->
// load persisted state -> set colors -> styles.json loaded -> set colors
if (!window.themeLoaded) {
setColors({ colors }, commit)
}
})
}
export {
setStyle,
setPreset,
setColors,
getTextColor,
generateColors,
generateRadii,
generateShadows,
generatePreset,
composePreset,
getCssShadow
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 31, 5:51 PM (20 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1801636
Default Alt Text
(43 KB)

Event Timeline