Merge branch 'shadow-control-2.0' into 'develop'

Fixed and refined the shadow control in theme tab

See merge request pleroma/pleroma-fe!1939
This commit is contained in:
HJ 2024-10-03 19:44:24 +00:00
commit a1c3a7a742
18 changed files with 737 additions and 371 deletions

View file

@ -0,0 +1 @@
Updated shadow editor, hopefully fixed long-standing bugs, added ability to specify shadow's name.

View file

@ -96,6 +96,17 @@ export default {
textOpacity: 0.25, textOpacity: 0.25,
textOpacityMode: 'blend' textOpacityMode: 'blend'
} }
},
{
component: 'Icon',
parent: {
component: 'Button',
state: ['disabled']
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend'
}
} }
] ]
} }

View file

@ -3,6 +3,13 @@
class="checkbox" class="checkbox"
:class="{ disabled, indeterminate, 'indeterminate-fix': indeterminateTransitionFix }" :class="{ disabled, indeterminate, 'indeterminate-fix': indeterminateTransitionFix }"
> >
<span
v-if="!!$slots.before"
class="label -before"
:class="{ faint: disabled }"
>
<slot name="before" />
</span>
<input <input
type="checkbox" type="checkbox"
class="visible-for-screenreader-only" class="visible-for-screenreader-only"
@ -14,11 +21,13 @@
<i <i
class="input -checkbox checkbox-indicator" class="input -checkbox checkbox-indicator"
:aria-hidden="true" :aria-hidden="true"
:class="{ disabled }"
@transitionend.capture="onTransitionEnd" @transitionend.capture="onTransitionEnd"
/> />
<span <span
v-if="!!$slots.default" v-if="!!$slots.default"
class="label" class="label -after"
:class="{ faint: disabled }"
> >
<slot /> <slot />
</span> </span>
@ -93,14 +102,9 @@ export default {
box-sizing: border-box; box-sizing: border-box;
} }
&.disabled { .disabled {
.checkbox-indicator::before, .checkbox-indicator::before {
.label { background-color: var(--background);
opacity: 0.5;
}
.label {
color: var(--text);
} }
} }
@ -121,8 +125,14 @@ export default {
} }
} }
& > span { & > .label {
&.-after {
margin-left: 0.5em; margin-left: 0.5em;
} }
&.-before {
margin-right: 0.5em;
}
}
} }
</style> </style>

View file

@ -1,12 +1,15 @@
.color-input { .color-input {
display: inline-flex; display: inline-flex;
.label {
flex: 1 1 auto;
}
&-field.input { &-field.input {
display: inline-flex; display: inline-flex;
flex: 0 0 0; flex: 0 0 0;
max-width: 9em; max-width: 9em;
align-items: stretch; align-items: stretch;
padding: 0.2em 8px;
input { input {
color: var(--text); color: var(--text);
@ -25,6 +28,7 @@
.nativeColor { .nativeColor {
cursor: pointer; cursor: pointer;
flex: 0 0 auto; flex: 0 0 auto;
padding: 0;
input { input {
appearance: none; appearance: none;
@ -41,10 +45,10 @@
.invalidIndicator, .invalidIndicator,
.transparentIndicator { .transparentIndicator {
flex: 0 0 2em; flex: 0 0 2em;
margin: 0 0.5em; margin: 0.2em 0.5em;
min-width: 2em; min-width: 2em;
align-self: stretch; align-self: stretch;
min-height: 1.5em; min-height: 1.1em;
border-radius: var(--roundness); border-radius: var(--roundness);
} }
@ -81,9 +85,17 @@
border-bottom-right-radius: var(--roundness); border-bottom-right-radius: var(--roundness);
} }
} }
}
.label { &.disabled,
flex: 1 1 auto; &:disabled {
.nativeColor input,
.computedIndicator,
.validIndicator,
.invalidIndicator,
.transparentIndicator {
/* stylelint-disable-next-line declaration-no-important */
opacity: 0.25 !important;
}
}
} }
} }

View file

@ -6,6 +6,7 @@
<label <label
:for="name" :for="name"
class="label" class="label"
:class="{ faint: !present || disabled }"
> >
{{ label }} {{ label }}
</label> </label>
@ -14,16 +15,20 @@
:model-value="present" :model-value="present"
:disabled="disabled" :disabled="disabled"
class="opt" class="opt"
@update:modelValue="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)" @update:modelValue="update(typeof modelValue === 'undefined' ? fallback : undefined)"
/> />
<div class="input color-input-field"> <div
class="input color-input-field"
:class="{ disabled: !present || disabled }"
>
<input <input
:id="name + '-t'" :id="name + '-t'"
class="textColor unstyled" class="textColor unstyled"
:class="{ disabled: !present || disabled }"
type="text" type="text"
:value="modelValue || fallback" :value="modelValue || fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
@input="$emit('update:modelValue', $event.target.value)" @input="updateValue($event.target.value)"
> >
<div <div
v-if="validColor" v-if="validColor"
@ -51,7 +56,8 @@
type="color" type="color"
:value="modelValue || fallback" :value="modelValue || fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
@input="$emit('update:modelValue', $event.target.value)" :class="{ disabled: !present || disabled }"
@input="updateValue($event.target.value)"
> >
</label> </label>
</div> </div>
@ -60,6 +66,7 @@
<script> <script>
import Checkbox from '../checkbox/checkbox.vue' import Checkbox from '../checkbox/checkbox.vue'
import { hex2rgb } from '../../services/color_convert/color_convert.js' import { hex2rgb } from '../../services/color_convert/color_convert.js'
import { throttle } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -125,6 +132,11 @@ export default {
computedColor () { computedColor () {
return this.modelValue && this.modelValue.startsWith('--') return this.modelValue && this.modelValue.startsWith('--')
} }
},
methods: {
updateValue: throttle(function (value) {
this.$emit('update:modelValue', value)
}, 100)
} }
} }
</script> </script>

View file

@ -0,0 +1,212 @@
<template>
<div
class="ComponentPreview"
:class="{ '-shadow-controls': shadowControl }"
>
<label
class="header"
v-show="shadowControl"
:class="{ faint: disabled }"
>
{{ $t('settings.style.shadows.offset') }}
</label>
<input
v-show="shadowControl"
:value="shadow?.y"
:disabled="disabled"
:class="{ disabled }"
class="input input-number y-shift-number"
type="number"
@input="e => updateProperty('y', e.target.value)"
>
<input
v-show="shadowControl"
:value="shadow?.y"
:disabled="disabled"
:class="{ disabled }"
class="input input-range y-shift-slider"
type="range"
max="20"
min="-20"
@input="e => updateProperty('y', e.target.value)"
>
<div
class="preview-window"
:class="{ '-light-grid': lightGrid }"
>
<div
class="preview-block"
:style="previewStyle"
/>
</div>
<input
v-show="shadowControl"
:value="shadow?.x"
:disabled="disabled"
:class="{ disabled }"
class="input input-number x-shift-number"
type="number"
@input="e => updateProperty('x', e.target.value)"
>
<input
v-show="shadowControl"
:value="shadow?.x"
:disabled="disabled"
:class="{ disabled }"
class="input input-range x-shift-slider"
type="range"
max="20"
min="-20"
@input="e => updateProperty('x', e.target.value)"
>
<Checkbox
id="lightGrid"
v-model="lightGrid"
:disabled="shadow == null"
name="lightGrid"
class="input-light-grid"
>
{{ $t('settings.style.shadows.light_grid') }}
</Checkbox>
</div>
</template>
<style lang="scss">
.ComponentPreview {
display: grid;
grid-template-columns: 3em 1fr 3em;
grid-template-rows: 2em 1fr 2em;
grid-template-areas:
". header y-num "
". preview y-slide"
"x-num x-slide . "
"options options options";
grid-gap: 0.5em;
.header {
grid-area: header;
justify-self: center;
align-self: baseline;
line-height: 2;
}
.input-light-grid {
grid-area: options;
justify-self: center;
}
.input-number {
min-width: 2em;
}
.x-shift-number {
grid-area: x-num;
}
.x-shift-slider {
grid-area: x-slide;
height: auto;
align-self: start;
min-width: 10em;
}
.y-shift-number {
grid-area: y-num;
}
.y-shift-slider {
grid-area: y-slide;
writing-mode: vertical-lr;
justify-self: left;
min-height: 10em;
}
.x-shift-slider,
.y-shift-slider {
padding: 0;
}
.preview-window {
--__grid-color1: rgb(102 102 102);
--__grid-color2: rgb(153 153 153);
--__grid-color1-disabled: rgba(102 102 102 / 20%);
--__grid-color2-disabled: rgba(153 153 153 / 20%);
&.-light-grid {
--__grid-color1: rgb(205 205 205);
--__grid-color2: rgb(255 255 255);
--__grid-color1-disabled: rgba(205 205 205 / 20%);
--__grid-color2-disabled: rgba(255 255 255 / 20%);
}
grid-area: preview;
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
min-width: 10em;
min-height: 10em;
background-color: var(--__grid-color2);
background-image:
linear-gradient(45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1) 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
border-radius: var(--roundness);
&.disabled {
background-color: var(--__grid-color2-disabled);
background-image:
linear-gradient(45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1-disabled) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1-disabled) 75%);
}
.preview-block {
background: var(--background, var(--bg));
display: flex;
justify-content: center;
align-items: center;
min-width: 33%;
min-height: 33%;
max-width: 80%;
max-height: 80%;
border-width: 0;
border-style: solid;
border-color: var(--border);
border-radius: var(--roundness);
box-shadow: var(--shadow);
}
}
}
</style>
<script>
import Checkbox from 'src/components/checkbox/checkbox.vue'
export default {
props: [
'shadow',
'shadowControl',
'previewClass',
'previewStyle',
'disabled'
],
data () {
return {
lightGrid: false
}
},
emits: ['update:shadow'],
components: {
Checkbox
},
methods: {
updateProperty (axis, value) {
this.$emit('update:shadow', { axis, value })
}
}
}
</script>

View file

@ -10,17 +10,18 @@ const hoverGlow = {
export default { export default {
name: 'Input', name: 'Input',
selector: '.input', selector: '.input',
variant: { states: {
hover: ':hover:not(.disabled)',
focused: ':focus-within',
disabled: '.disabled'
},
variants: {
checkbox: '.-checkbox', checkbox: '.-checkbox',
radio: '.-radio' radio: '.-radio'
}, },
states: {
disabled: ':disabled',
hover: ':hover:not(:disabled)',
focused: ':focus-within'
},
validInnerComponents: [ validInnerComponents: [
'Text' 'Text',
'Icon'
], ],
defaultRules: [ defaultRules: [
{ {
@ -55,6 +56,34 @@ export default {
directives: { directives: {
shadow: [hoverGlow, '--defaultInputBevel'] shadow: [hoverGlow, '--defaultInputBevel']
} }
},
{
state: ['disabled'],
directives: {
background: '--parent'
}
},
{
component: 'Text',
parent: {
component: 'Input',
state: ['disabled']
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend'
}
},
{
component: 'Icon',
parent: {
component: 'Input',
state: ['disabled']
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend'
}
} }
] ]
} }

View file

@ -6,6 +6,7 @@
<label <label
:for="name" :for="name"
class="label" class="label"
:class="{ faint: !present || disabled }"
> >
{{ $t('settings.style.common.opacity') }} {{ $t('settings.style.common.opacity') }}
</label> </label>
@ -22,6 +23,7 @@
type="number" type="number"
:value="modelValue || fallback" :value="modelValue || fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
:class="{ disabled: !present || disabled }"
max="1" max="1"
min="0" min="0"
step=".05" step=".05"

View file

@ -6,13 +6,14 @@
<select <select
:disabled="disabled" :disabled="disabled"
:value="modelValue" :value="modelValue"
v-bind="attrs" v-bind="$attrs"
@change="$emit('update:modelValue', $event.target.value)" @change="$emit('update:modelValue', $event.target.value)"
> >
<slot /> <slot />
</select> </select>
{{ ' ' }} {{ ' ' }}
<FAIcon <FAIcon
v-if="!$attrs.size && !$attrs.multiple"
class="select-down-icon" class="select-down-icon"
icon="chevron-down" icon="chevron-down"
/> />
@ -39,6 +40,38 @@ label.Select {
z-index: 1; z-index: 1;
height: 2em; height: 2em;
line-height: 16px; line-height: 16px;
&[multiple],
&[size] {
height: 100%;
padding: 0.2em;
option {
background-color: transparent;
&.-active {
color: var(--selectionText);
background-color: var(--selectionBackground);
}
}
}
}
&.disabled,
&:disabled {
background-color: var(--background);
opacity: 1; /* override browser */
color: var(--faint);
select {
&[multiple],
&[size] {
option.-active {
color: var(--faint);
background: transparent;
}
}
}
} }
.select-down-icon { .select-down-icon {

View file

@ -314,7 +314,18 @@ export default {
}, },
set (val) { set (val) {
if (val) { if (val) {
this.shadowsLocal[this.shadowSelected] = this.currentShadowFallback.map(_ => Object.assign({}, _)) this.shadowsLocal[this.shadowSelected] = (this.currentShadowFallback || [])
.map(s => ({
name: null,
x: 0,
y: 0,
blur: 0,
spread: 0,
inset: false,
color: '#000000',
alpha: 1,
...s
}))
} else { } else {
delete this.shadowsLocal[this.shadowSelected] delete this.shadowsLocal[this.shadowSelected]
} }

View file

@ -25,7 +25,9 @@
margin-bottom: 5px; margin-bottom: 5px;
.label { .label {
margin-right: 1em;
flex: 1; flex: 1;
line-height: 2;
} }
.opt { .opt {
@ -48,15 +50,14 @@
&[type="range"] { &[type="range"] {
flex: 1; flex: 1;
min-width: 3em; min-width: 2em;
align-self: flex-start; align-self: center;
} margin: 0 0.5em;
} }
&.disabled { &[type="checkbox"] + i {
input, height: 1.1em;
select { align-self: center;
opacity: 0.5;
} }
} }
} }

View file

@ -123,10 +123,13 @@
</div> </div>
</div> </div>
<!-- eslint-disable vue/no-v-text-v-html-on-component --> <!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component -->
<component :is="'style'" v-html="themeV3Preview"/> <component
<!-- eslint-enable vue/no-v-text-v-html-on-component --> :is="'style'"
<preview id="theme-preview"/> v-html="themeV3Preview"
/>
<!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component -->
<preview id="theme-preview" />
<div> <div>
<button <button
@ -934,24 +937,14 @@
</Select> </Select>
</div> </div>
<div class="override"> <div class="override">
<label <Checkbox
for="override"
class="label"
>
{{ $t('settings.style.shadows.override') }}
</label>
{{ ' ' }}
<input
id="override" id="override"
v-model="currentShadowOverriden" v-model="currentShadowOverriden"
name="override" name="override"
class="input-override" class="input-override"
type="checkbox"
> >
<label {{ $t('settings.style.shadows.override') }}
class="checkbox-label" </Checkbox>
for="override"
/>
</div> </div>
<button <button
class="btn button-default" class="btn button-default"
@ -962,38 +955,10 @@
</div> </div>
<ShadowControl <ShadowControl
v-model="currentShadow" v-model="currentShadow"
:ready="!!currentShadowFallback" :separate-inset="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'"
:fallback="currentShadowFallback" :fallback="currentShadowFallback"
/> />
<div v-if="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'">
<i18n-t
scope="global"
keypath="settings.style.shadows.filter_hint.always_drop_shadow"
tag="p"
>
<code>filter: drop-shadow()</code>
</i18n-t>
<p>{{ $t('settings.style.shadows.filter_hint.avatar_inset') }}</p>
<i18n-t
scope="global"
keypath="settings.style.shadows.filter_hint.drop_shadow_syntax"
tag="p"
>
<code>drop-shadow</code>
<code>spread-radius</code>
<code>inset</code>
</i18n-t>
<i18n-t
scope="global"
keypath="settings.style.shadows.filter_hint.inset_classic"
tag="p"
>
<code>box-shadow</code>
</i18n-t>
<p>{{ $t('settings.style.shadows.filter_hint.spread_zero') }}</p>
</div> </div>
</div>
<div <div
:label="$t('settings.style.fonts._tab_label')" :label="$t('settings.style.fonts._tab_label')"
class="fonts-container" class="fonts-container"

View file

@ -1,9 +1,12 @@
import ColorInput from '../color_input/color_input.vue' import ColorInput from 'src/components/color_input/color_input.vue'
import OpacityInput from '../opacity_input/opacity_input.vue' import OpacityInput from 'src/components/opacity_input/opacity_input.vue'
import Select from '../select/select.vue' import Select from 'src/components/select/select.vue'
import { getCssShadow } from '../../services/theme_data/theme_data.service.js' import Checkbox from 'src/components/checkbox/checkbox.vue'
import { hex2rgb } from '../../services/color_convert/color_convert.js' import Popover from 'src/components/popover/popover.vue'
import ComponentPreview from 'src/components/component_preview/component_preview.vue'
import { getCssShadow, getCssShadowFilter } from '../../services/theme_data/theme_data.service.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { throttle } from 'lodash'
import { import {
faTimes, faTimes,
faChevronDown, faChevronDown,
@ -30,93 +33,100 @@ const toModel = (object = {}) => ({
}) })
export default { export default {
// 'modelValue' and 'Fallback' can be undefined, but if they are
// initially vue won't detect it when they become something else
// therefore i'm using "ready" which should be passed as true when
// data becomes available
props: [ props: [
'modelValue', 'fallback', 'ready' 'modelValue', 'fallback', 'separateInset', 'noPreview', 'disabled'
], ],
emits: ['update:modelValue'], emits: ['update:modelValue', 'subShadowSelected'],
data () { data () {
return { return {
selectedId: 0, selectedId: 0,
// TODO there are some bugs regarding display of array (it's not getting updated when deleting for some reason) // TODO there are some bugs regarding display of array (it's not getting updated when deleting for some reason)
cValue: (this.modelValue || this.fallback || []).map(toModel) cValue: (this.modelValue ?? this.fallback ?? []).map(toModel)
} }
}, },
components: { components: {
ColorInput, ColorInput,
OpacityInput, OpacityInput,
Select Select,
Checkbox,
Popover,
ComponentPreview
},
beforeUpdate () {
this.cValue = (this.modelValue ?? this.fallback ?? []).map(toModel)
},
computed: {
selected () {
const selected = this.cValue[this.selectedId]
if (selected) {
return { ...selected }
}
return null
},
present () {
return this.selected != null && !this.usingFallback
},
shadowsAreNull () {
return this.modelValue == null
},
currentFallback () {
return this.fallback?.[this.selectedId]
},
moveUpValid () {
return this.selectedId > 0
},
moveDnValid () {
return this.selectedId < this.cValue.length - 1
},
usingFallback () {
return this.modelValue == null
},
style () {
if (this.separateInset) {
return {
filter: getCssShadowFilter(this.cValue),
boxShadow: getCssShadow(this.cValue, true)
}
}
return {
boxShadow: getCssShadow(this.cValue)
}
}
},
watch: {
selected (value) {
this.$emit('subShadowSelected', this.selectedId)
}
}, },
methods: { methods: {
updateProperty: throttle(function (prop, value) {
this.cValue[this.selectedId][prop] = value
if (prop === 'inset' && value === false && this.separateInset) {
this.cValue[this.selectedId].spread = 0
}
this.$emit('update:modelValue', this.cValue)
}, 100),
add () { add () {
this.cValue.push(toModel(this.selected)) this.cValue.push(toModel(this.selected))
this.selectedId = this.cValue.length - 1 this.selectedId = Math.max(this.cValue.length - 1, 0)
this.$emit('update:modelValue', this.cValue)
}, },
del () { del () {
this.cValue.splice(this.selectedId, 1) this.cValue.splice(this.selectedId, 1)
this.selectedId = this.cValue.length === 0 ? undefined : Math.max(this.selectedId - 1, 0) this.selectedId = this.cValue.length === 0 ? undefined : Math.max(this.selectedId - 1, 0)
this.$emit('update:modelValue', this.cValue)
}, },
moveUp () { moveUp () {
const movable = this.cValue.splice(this.selectedId, 1)[0] const movable = this.cValue.splice(this.selectedId, 1)[0]
this.cValue.splice(this.selectedId - 1, 0, movable) this.cValue.splice(this.selectedId - 1, 0, movable)
this.selectedId -= 1 this.selectedId -= 1
this.$emit('update:modelValue', this.cValue)
}, },
moveDn () { moveDn () {
const movable = this.cValue.splice(this.selectedId, 1)[0] const movable = this.cValue.splice(this.selectedId, 1)[0]
this.cValue.splice(this.selectedId + 1, 0, movable) this.cValue.splice(this.selectedId + 1, 0, movable)
this.selectedId += 1 this.selectedId += 1
} this.$emit('update:modelValue', this.cValue)
},
beforeUpdate () {
this.cValue = this.modelValue || this.fallback
},
computed: {
anyShadows () {
return this.cValue.length > 0
},
anyShadowsFallback () {
return this.fallback.length > 0
},
selected () {
if (this.ready && this.anyShadows) {
return this.cValue[this.selectedId]
} else {
return toModel({})
}
},
currentFallback () {
if (this.ready && this.anyShadowsFallback) {
return this.fallback[this.selectedId]
} else {
return toModel({})
}
},
moveUpValid () {
return this.ready && this.selectedId > 0
},
moveDnValid () {
return this.ready && this.selectedId < this.cValue.length - 1
},
present () {
return this.ready &&
typeof this.cValue[this.selectedId] !== 'undefined' &&
!this.usingFallback
},
usingFallback () {
return typeof this.modelValue === 'undefined'
},
rgb () {
return hex2rgb(this.selected.color)
},
style () {
return this.ready
? {
boxShadow: getCssShadow(this.fallback)
}
: {}
} }
} }
} }

View file

@ -0,0 +1,105 @@
.settings-modal .settings-modal-panel .shadow-control {
display: flex;
flex-wrap: wrap;
justify-content: stretch;
grid-gap: 0.25em;
margin-bottom: 1em;
.shadow-switcher {
order: 1;
flex: 1 0 6em;
min-width: 6em;
margin-right: 0.125em;
display: flex;
flex-direction: column;
.shadow-list {
flex: 1 0 auto;
}
.arrange-buttons {
flex: 0 0 auto;
display: grid;
grid-auto-columns: 1fr;
grid-auto-flow: column;
margin-top: 0.25em;
.button-default {
margin: 0;
padding: 0;
}
}
}
.shadow-tweak {
order: 3;
flex: 2 0 10em;
min-width: 10em;
margin-left: 0.125em;
margin-right: 0.125em;
/* hack */
.input-boolean {
flex: 1;
display: flex;
.label {
flex: 1;
}
}
.input-string {
flex: 1 0 5em;
}
.id-control {
align-items: stretch;
.shadow-switcher,
.btn {
min-width: 1px;
margin-right: 5px;
}
.btn {
padding: 0 0.4em;
margin: 0 0.1em;
}
}
}
&.-no-preview {
.shadow-tweak {
order: 0;
flex: 2 0 8em;
max-width: 100%;
}
.input-range {
min-width: 5em;
}
}
.inset-alert {
padding: 0.25em 0.5em;
}
&.disabled {
.inset-alert {
opacity: 0.2;
}
}
.shadow-preview {
order: 2;
flex: 3 3 15em;
min-width: 10em;
margin-left: 0.125em;
align-self: start;
}
}
.inset-tooltip {
padding: 0.5em;
max-width: 30em;
}

View file

@ -1,91 +1,51 @@
<template> <template>
<div <div
class="shadow-control" class="label shadow-control"
:class="{ disabled: !present }" :class="{ disabled: disabled || !present, '-no-preview': noPreview }"
> >
<div class="shadow-preview-container"> <ComponentPreview
<div v-if="!noPreview"
:disabled="!present" class="shadow-preview"
class="y-shift-control" :shadow-control="true"
> :shadow="selected"
<input :preview-style="style"
v-model="selected.y" :disabled="disabled || !present"
:disabled="!present" @update:shadow="({ axis, value }) => updateProperty(axis, value)"
class="input input-number"
type="number"
>
<div class="wrap">
<input
v-model="selected.y"
:disabled="!present"
class="input input-range"
type="range"
max="20"
min="-20"
>
</div>
</div>
<div class="preview-window">
<div
class="preview-block"
:style="style"
/> />
</div> <div class="shadow-switcher">
<div
:disabled="!present"
class="x-shift-control"
>
<input
v-model="selected.x"
:disabled="!present"
class="input input-number"
type="number"
>
<div class="wrap">
<input
v-model="selected.x"
:disabled="!present"
class="input input-range"
type="range"
max="20"
min="-20"
>
</div>
</div>
</div>
<div class="shadow-tweak">
<div
:disabled="usingFallback"
class="id-control style-control"
>
<Select <Select
id="shadow-switcher" id="shadow-list"
v-model="selectedId" v-model="selectedId"
class="shadow-switcher" class="shadow-list"
:disabled="!ready || usingFallback" size="10"
:disabled="shadowsAreNull"
> >
<option <option
v-for="(shadow, index) in cValue" v-for="(shadow, index) in cValue"
:key="index" :key="index"
:value="index" :value="index"
:class="{ '-active': index === Number(selectedId) }"
> >
{{ $t('settings.style.shadows.shadow_id', { value: index }) }} {{ shadow?.name ?? $t('settings.style.shadows.shadow_id', { value: index }) }}
</option> </option>
</Select> </Select>
<div
class="id-control btn-group arrange-buttons"
>
<button <button
class="btn button-default" class="btn button-default"
:disabled="!ready || !present" :disabled="disabled || shadowsAreNull"
@click="del" @click="add"
> >
<FAIcon <FAIcon
fixed-width fixed-width
icon="times" icon="plus"
/> />
</button> </button>
<button <button
class="btn button-default" class="btn button-default"
:disabled="!moveUpValid" :disabled="disabled || !moveUpValid"
:class="{ disabled: disabled || !moveUpValid }"
@click="moveUp" @click="moveUp"
> >
<FAIcon <FAIcon
@ -95,7 +55,8 @@
</button> </button>
<button <button
class="btn button-default" class="btn button-default"
:disabled="!moveDnValid" :disabled="disabled || !moveDnValid"
:class="{ disabled: disabled || !moveDnValid }"
@click="moveDn" @click="moveDn"
> >
<FAIcon <FAIcon
@ -105,222 +66,191 @@
</button> </button>
<button <button
class="btn button-default" class="btn button-default"
:disabled="usingFallback" :disabled="disabled || !present"
@click="add" :class="{ disabled: disabled || !present }"
@click="del"
> >
<FAIcon <FAIcon
fixed-width fixed-width
icon="plus" icon="times"
/> />
</button> </button>
</div> </div>
</div>
<div class="shadow-tweak">
<div <div
:disabled="!present" :class="{ disabled: disabled || !present }"
class="inset-control style-control" class="name-control style-control"
> >
<label <label
for="inset" for="name"
class="label" class="label"
:class="{ faint: disabled || !present }"
> >
{{ $t('settings.style.shadows.inset') }} {{ $t('settings.style.shadows.name') }}
</label> </label>
<input <input
id="inset" id="name"
v-model="selected.inset" :value="selected?.name"
:disabled="!present" :disabled="disabled || !present"
name="inset" :class="{ disabled: disabled || !present }"
class="input -checkbox input-inset visible-for-screenreader-only" name="name"
type="checkbox" class="input input-string"
@input="e => updateProperty('name', e.target.value)"
> >
<label
class="checkbox-label"
for="inset"
:aria-hidden="true"
/>
</div> </div>
<div <div
:disabled="!present" :disabled="disabled || !present"
class="inset-control style-control"
>
<Checkbox
id="inset"
:value="selected?.inset"
:disabled="disabled || !present"
name="inset"
class="input-inset input-boolean"
@input="e => updateProperty('inset', e.target.checked)"
>
<template #before>
{{ $t('settings.style.shadows.inset') }}
</template>
</Checkbox>
</div>
<div
:disabled="disabled || !present"
:class="{ disabled: disabled || !present }"
class="blur-control style-control" class="blur-control style-control"
> >
<label <label
for="spread" for="blur"
class="label" class="label"
:class="{ faint: disabled || !present }"
> >
{{ $t('settings.style.shadows.blur') }} {{ $t('settings.style.shadows.blur') }}
</label> </label>
<input <input
id="blur" id="blur"
v-model="selected.blur" :value="selected?.blur"
:disabled="!present" :disabled="disabled || !present"
:class="{ disabled: disabled || !present }"
name="blur" name="blur"
class="input input-range" class="input input-range"
type="range" type="range"
max="20" max="20"
min="0" min="0"
@input="e => updateProperty('blur', e.target.value)"
> >
<input <input
v-model="selected.blur" :value="selected?.blur"
:disabled="!present" class="input input-number -small"
class="input input-number" :disabled="disabled || !present"
:class="{ disabled: disabled || !present }"
type="number" type="number"
min="0" min="0"
@input="e => updateProperty('blur', e.target.value)"
> >
</div> </div>
<div <div
:disabled="!present"
class="spread-control style-control" class="spread-control style-control"
:class="{ disabled: disabled || !present || (separateInset && !selected?.inset) }"
> >
<label <label
for="spread" for="spread"
class="label" class="label"
:class="{ faint: disabled || !present || (separateInset && !selected?.inset) }"
> >
{{ $t('settings.style.shadows.spread') }} {{ $t('settings.style.shadows.spread') }}
</label> </label>
<input <input
id="spread" id="spread"
v-model="selected.spread" :value="selected?.spread"
:disabled="!present" :disabled="disabled || !present || (separateInset && !selected?.inset)"
:class="{ disabled: disabled || !present || (separateInset && !selected?.inset) }"
name="spread" name="spread"
class="input input-range" class="input input-range"
type="range" type="range"
max="20" max="20"
min="-20" min="-20"
@input="e => updateProperty('spread', e.target.value)"
> >
<input <input
v-model="selected.spread" :value="selected?.spread"
:disabled="!present" class="input input-number -small"
class="input input-number" :class="{ disabled: disabled || !present || (separateInset && !selected?.inset) }"
:disabled="{ disabled: disabled || !present || (separateInset && !selected?.inset) }"
type="number" type="number"
@input="e => updateProperty('spread', e.target.value)"
> >
</div> </div>
<ColorInput <ColorInput
v-model="selected.color" :model-value="selected?.color"
:disabled="!present" :disabled="disabled || !present"
:label="$t('settings.style.common.color')" :label="$t('settings.style.common.color')"
:fallback="currentFallback.color" :fallback="currentFallback?.color"
:show-optional-tickbox="false" :show-optional-tickbox="false"
name="shadow" name="shadow"
@update:modelValue="e => updateProperty('color', e)"
/> />
<OpacityInput <OpacityInput
v-model="selected.alpha" :model-value="selected?.alpha"
:disabled="!present" :disabled="disabled || !present"
@update:modelValue="e => updateProperty('alpha', e)"
/> />
<i18n-t <i18n-t
scope="global" scope="global"
keypath="settings.style.shadows.hintV3" keypath="settings.style.shadows.hintV3"
:class="{ faint: disabled || !present }"
tag="p" tag="p"
> >
<code>--variable,mod</code> <code>--variable,mod</code>
</i18n-t> </i18n-t>
<Popover
v-if="separateInset"
trigger="hover"
>
<template #trigger>
<div
class="inset-alert alert warning"
>
<FAIcon icon="exclamation-triangle" />
&nbsp;
{{ $t('settings.style.shadows.filter_hint.avatar_inset_short') }}
</div>
</template>
<template #content>
<div class="inset-tooltip">
<i18n-t
scope="global"
keypath="settings.style.shadows.filter_hint.always_drop_shadow"
tag="p"
>
<code>filter: drop-shadow()</code>
</i18n-t>
<p>{{ $t('settings.style.shadows.filter_hint.avatar_inset') }}</p>
<i18n-t
scope="global"
keypath="settings.style.shadows.filter_hint.drop_shadow_syntax"
tag="p"
>
<code>drop-shadow</code>
<code>spread-radius</code>
<code>inset</code>
</i18n-t>
<i18n-t
scope="global"
keypath="settings.style.shadows.filter_hint.inset_classic"
tag="p"
>
<code>box-shadow</code>
</i18n-t>
<p>{{ $t('settings.style.shadows.filter_hint.spread_zero') }}</p>
</div>
</template>
</Popover>
</div> </div>
</div> </div>
</template> </template>
<script src="./shadow_control.js"></script> <script src="./shadow_control.js"></script>
<style lang="scss"> <style src="./shadow_control.scss" lang="scss"></style>
.shadow-control {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 1em;
.shadow-preview-container,
.shadow-tweak {
margin: 5px 6px 0 0;
}
.shadow-preview-container {
flex: 0;
display: flex;
flex-wrap: wrap;
input[type="number"] {
width: 5em;
min-width: 2em;
}
.x-shift-control,
.y-shift-control {
display: flex;
flex: 0;
&[disabled="disabled"] * {
opacity: 0.5;
}
}
.x-shift-control {
align-items: flex-start;
}
.x-shift-control .wrap,
input[type="range"] {
margin: 0;
width: 15em;
height: 2em;
}
.y-shift-control {
flex-direction: column;
align-items: flex-end;
.wrap {
width: 2em;
height: 15em;
}
input[type="range"] {
transform-origin: 1em 1em;
transform: rotate(90deg);
}
}
.preview-window {
flex: 1;
background-color: #999;
display: flex;
align-items: center;
justify-content: center;
background-image:
linear-gradient(45deg, #666 25%, transparent 25%),
linear-gradient(-45deg, #666 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #666 75%),
linear-gradient(-45deg, transparent 75%, #666 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
border-radius: var(--roundness);
.preview-block {
width: 33%;
height: 33%;
border-radius: var(--roundness);
}
}
}
.shadow-tweak {
flex: 1;
min-width: 280px;
.id-control {
align-items: stretch;
.shadow-switcher {
flex: 1;
}
.shadow-switcher,
.btn {
min-width: 1px;
margin-right: 5px;
}
.btn {
padding: 0 0.4em;
margin: 0 0.1em;
}
}
}
}
</style>

View file

@ -874,6 +874,9 @@
"component": "Component", "component": "Component",
"override": "Override", "override": "Override",
"shadow_id": "Shadow #{value}", "shadow_id": "Shadow #{value}",
"offset": "Shadow offset",
"light_grid": "Use light checkerboard",
"name": "Name",
"blur": "Blur", "blur": "Blur",
"spread": "Spread", "spread": "Spread",
"inset": "Inset", "inset": "Inset",
@ -881,6 +884,7 @@
"filter_hint": { "filter_hint": {
"always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.", "always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
"drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.", "drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",
"avatar_inset_short": "Separate inset shadow",
"avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.", "avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.",
"spread_zero": "Shadows with spread > 0 will appear as if it was set to zero", "spread_zero": "Shadows with spread > 0 will appear as if it was set to zero",
"inset_classic": "Inset shadows will be using {0}" "inset_classic": "Inset shadows will be using {0}"

View file

@ -452,7 +452,7 @@ export const getCssShadow = (input, usesDropShadow) => {
]).join(' ')).join(', ') ]).join(' ')).join(', ')
} }
const getCssShadowFilter = (input) => { export const getCssShadowFilter = (input) => {
if (input.length === 0) { if (input.length === 0) {
return 'none' return 'none'
} }

View file

@ -182,7 +182,7 @@ export const init = ({
const rulesetUnsorted = [ const rulesetUnsorted = [
...Object.values(components) ...Object.values(components)
.map(c => (c.defaultRules || []).map(r => ({ component: c.name, ...r, source: 'Built-in' }))) .map(c => (c.defaultRules || []).map(r => ({ source: 'Built-in', component: c.name, ...r })))
.reduce((acc, arr) => [...acc, ...arr], []), .reduce((acc, arr) => [...acc, ...arr], []),
...inputRuleset ...inputRuleset
].map(rule => { ].map(rule => {
@ -198,18 +198,33 @@ export const init = ({
const ruleset = rulesetUnsorted const ruleset = rulesetUnsorted
.map((data, index) => ({ data, index })) .map((data, index) => ({ data, index }))
.sort(({ data: a, index: ai }, { data: b, index: bi }) => { .toSorted(({ data: a, index: ai }, { data: b, index: bi }) => {
const parentsA = unroll(a).length const parentsA = unroll(a).length
const parentsB = unroll(b).length const parentsB = unroll(b).length
if (parentsA === parentsB) { let aScore = 0
if (a.component === 'Text') return -1 let bScore = 0
if (b.component === 'Text') return 1
aScore += parentsA * 1000
bScore += parentsB * 1000
aScore += a.variant !== 'normal' ? 100 : 0
bScore += b.variant !== 'normal' ? 100 : 0
aScore += a.state.filter(x => x !== 'normal').length * 1000
bScore += b.state.filter(x => x !== 'normal').length * 1000
aScore += a.component === 'Text' ? 1 : 0
bScore += b.component === 'Text' ? 1 : 0
// Debug
a.specifityScore = aScore
b.specifityScore = bScore
if (aScore === bScore) {
return ai - bi return ai - bi
} }
if (parentsA === 0 && parentsB !== 0) return -1 return aScore - bScore
if (parentsB === 0 && parentsA !== 0) return 1
return parentsA - parentsB
}) })
.map(({ data }) => data) .map(({ data }) => data)
@ -235,7 +250,10 @@ export const init = ({
// Inheriting all of the applicable rules // Inheriting all of the applicable rules
const existingRules = ruleset.filter(findRules(combination)) const existingRules = ruleset.filter(findRules(combination))
const computedDirectives = existingRules.map(r => r.directives).reduce((acc, directives) => ({ ...acc, ...directives }), {}) const computedDirectives =
existingRules
.map(r => r.directives)
.reduce((acc, directives) => ({ ...acc, ...directives }), {})
const computedRule = { const computedRule = {
...combination, ...combination,
directives: computedDirectives directives: computedDirectives