feat: validate tts form items
This commit is contained in:
@@ -175,5 +175,7 @@ export default {
|
|||||||
'Transcript of Reference Audio (for ICL mode)',
|
'Transcript of Reference Audio (for ICL mode)',
|
||||||
'playground.params.refAudio.vectorMode':
|
'playground.params.refAudio.vectorMode':
|
||||||
'Use Speaker Embedding Only (no ICL)',
|
'Use Speaker Embedding Only (no ICL)',
|
||||||
'playground.params.streamMode': 'Enable Streaming'
|
'playground.params.streamMode': 'Enable Streaming',
|
||||||
|
'playground.speech.rules.refAudio':
|
||||||
|
'Either the reference audio transcript or speaker embedding must be set.'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -178,7 +178,9 @@ export default {
|
|||||||
'Transcript of Reference Audio (for ICL mode)',
|
'Transcript of Reference Audio (for ICL mode)',
|
||||||
'playground.params.refAudio.vectorMode':
|
'playground.params.refAudio.vectorMode':
|
||||||
'Use Speaker Embedding Only (no ICL)',
|
'Use Speaker Embedding Only (no ICL)',
|
||||||
'playground.params.streamMode': 'Enable Streaming'
|
'playground.params.streamMode': 'Enable Streaming',
|
||||||
|
'playground.speech.rules.refAudio':
|
||||||
|
'Either the reference audio transcript or speaker embedding must be set.'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -191,4 +193,5 @@ export default {
|
|||||||
// 7. 'playground.params.duration': 'Duration (seconds)',
|
// 7. 'playground.params.duration': 'Duration (seconds)',
|
||||||
// 8. 'playground.params.resolution': 'Resolution'
|
// 8. 'playground.params.resolution': 'Resolution'
|
||||||
// 9. 'playground.params.streamMode': 'Enable Streaming'
|
// 9. 'playground.params.streamMode': 'Enable Streaming'
|
||||||
|
// 10. 'playground.speech.rules.refAudio': 'Either the reference audio transcript or speaker embedding must be set.'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -172,7 +172,9 @@ export default {
|
|||||||
'Transcript of Reference Audio (for ICL mode)',
|
'Transcript of Reference Audio (for ICL mode)',
|
||||||
'playground.params.refAudio.vectorMode':
|
'playground.params.refAudio.vectorMode':
|
||||||
'Use Speaker Embedding Only (no ICL)',
|
'Use Speaker Embedding Only (no ICL)',
|
||||||
'playground.params.streamMode': 'Enable Streaming'
|
'playground.params.streamMode': 'Enable Streaming',
|
||||||
|
'playground.speech.rules.refAudio':
|
||||||
|
'Either the reference audio transcript or speaker embedding must be set.'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -181,5 +183,6 @@ export default {
|
|||||||
// 3. 'playground.uploadImage.url.button': 'Add Image from URL',
|
// 3. 'playground.uploadImage.url.button': 'Add Image from URL',
|
||||||
// 4. 'playground.params.duration': 'Duration (seconds)',
|
// 4. 'playground.params.duration': 'Duration (seconds)',
|
||||||
// 5. 'playground.params.resolution': 'Resolution',
|
// 5. 'playground.params.resolution': 'Resolution',
|
||||||
// 6. 'playground.params.streamMode': 'Enable Streaming'
|
// 6. 'playground.params.streamMode': 'Enable Streaming',
|
||||||
|
// 7. 'playground.speech.rules.refAudio': 'Either the reference audio transcript or speaker embedding must be set.'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -132,7 +132,8 @@ export default {
|
|||||||
'Sınıflandırıcısız rehberlik ölçeği, görselin isteme bağlılığını etkiler.',
|
'Sınıflandırıcısız rehberlik ölçeği, görselin isteme bağlılığını etkiler.',
|
||||||
'playground.image.strength.tip':
|
'playground.image.strength.tip':
|
||||||
'Değer ne kadar yüksek olursa, orijinal görsel üzerindeki değişiklik o kadar büyük olur.',
|
'Değer ne kadar yüksek olursa, orijinal görsel üzerindeki değişiklik o kadar büyük olur.',
|
||||||
'playground.image.edit.tips': 'Yüklemek için görseli tıklayın veya bu alana sürükleyin',
|
'playground.image.edit.tips':
|
||||||
|
'Yüklemek için görseli tıklayın veya bu alana sürükleyin',
|
||||||
'playground.image.saveMask': 'Maskeyi Kaydet',
|
'playground.image.saveMask': 'Maskeyi Kaydet',
|
||||||
'playground.image.negativeMask': 'Maskeyi Ters Çevir',
|
'playground.image.negativeMask': 'Maskeyi Ters Çevir',
|
||||||
'playground.image.brushSize': 'Fırça Boyutu',
|
'playground.image.brushSize': 'Fırça Boyutu',
|
||||||
@@ -154,14 +155,13 @@ export default {
|
|||||||
'playground.model.noavailable.tips2':
|
'playground.model.noavailable.tips2':
|
||||||
'Beklenen model görünmüyorsa, çalıştığından ve doğru kategoride olduğundan emin olun. Kategori yanlışsa, modelin ayarlarından manuel olarak düzeltebilirsiniz.',
|
'Beklenen model görünmüyorsa, çalıştığından ve doğru kategoride olduğundan emin olun. Kategori yanlışsa, modelin ayarlarından manuel olarak düzeltebilirsiniz.',
|
||||||
'playground.rerank.query.validate': 'Sorgu gereklidir.',
|
'playground.rerank.query.validate': 'Sorgu gereklidir.',
|
||||||
'playground.image.generate.error':
|
'playground.image.generate.error': 'Bir sorun oluştu. Görsel oluşturulamadı.',
|
||||||
'Bir sorun oluştu. Görsel oluşturulamadı.',
|
|
||||||
'playground.uploadfile.sizeError':
|
'playground.uploadfile.sizeError':
|
||||||
'Dosya boyutu sınırı aşıyor. İzin verilen maksimum: {size}.',
|
'Dosya boyutu sınırı aşıyor. İzin verilen maksimum: {size}.',
|
||||||
'playground.uploadImage.url.invalid':
|
'playground.uploadImage.url.invalid':
|
||||||
'Lütfen doğrudan bir görsel URL\'si girin (örn. https://.../image.png). İptal etmek için ESC tuşuna basın.',
|
"Lütfen doğrudan bir görsel URL'si girin (örn. https://.../image.png). İptal etmek için ESC tuşuna basın.",
|
||||||
'playground.uploadImage.url.holder': 'Görsel URL\'si girin',
|
'playground.uploadImage.url.holder': "Görsel URL'si girin",
|
||||||
'playground.uploadImage.url.button': 'URL\'den Görsel Ekle',
|
'playground.uploadImage.url.button': "URL'den Görsel Ekle",
|
||||||
'playground.params.duration': 'Süre (saniye)',
|
'playground.params.duration': 'Süre (saniye)',
|
||||||
'playground.params.resolution': 'Çözünürlük',
|
'playground.params.resolution': 'Çözünürlük',
|
||||||
'playground.params.taskType': 'Görev Türü',
|
'playground.params.taskType': 'Görev Türü',
|
||||||
@@ -170,10 +170,15 @@ export default {
|
|||||||
'playground.params.maxTokens': 'Maksimum Yeni Token',
|
'playground.params.maxTokens': 'Maksimum Yeni Token',
|
||||||
'playground.params.refAudio': 'Referans Ses',
|
'playground.params.refAudio': 'Referans Ses',
|
||||||
'playground.params.refAudio.tips':
|
'playground.params.refAudio.tips':
|
||||||
'Referans ses URL\'si girin veya bir ses dosyası yükleyin.',
|
"Referans ses URL'si girin veya bir ses dosyası yükleyin.",
|
||||||
'playground.params.refAudio.text':
|
'playground.params.refAudio.text': 'Referans Ses Transkripti (ICL modu için)',
|
||||||
'Referans Ses Transkripti (ICL modu için)',
|
|
||||||
'playground.params.refAudio.vectorMode':
|
'playground.params.refAudio.vectorMode':
|
||||||
'Yalnızca Konuşmacı Gömme Kullan (ICL yok)',
|
'Yalnızca Konuşmacı Gömme Kullan (ICL yok)',
|
||||||
'playground.params.streamMode': 'Akışı Etkinleştir'
|
'playground.params.streamMode': 'Akışı Etkinleştir',
|
||||||
|
'playground.speech.rules.refAudio':
|
||||||
|
'Either the reference audio transcript or speaker embedding must be set.'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
// 1. 'playground.speech.rules.refAudio': 'Either the reference audio transcript or speaker embedding must be set.'
|
||||||
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -167,5 +167,7 @@ export default {
|
|||||||
'playground.params.refAudio.text': '参考音频文本(用于上下文学习模式)',
|
'playground.params.refAudio.text': '参考音频文本(用于上下文学习模式)',
|
||||||
'playground.params.refAudio.vectorMode':
|
'playground.params.refAudio.vectorMode':
|
||||||
'仅使用说话人嵌入(不使用上下文学习)',
|
'仅使用说话人嵌入(不使用上下文学习)',
|
||||||
'playground.params.streamMode': '启用流式输出'
|
'playground.params.streamMode': '启用流式输出',
|
||||||
|
'playground.speech.rules.refAudio':
|
||||||
|
'设置参考音频文本或仅使用说话人嵌入的任意一项。'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const ModelSelect: React.FC<{
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
|
required
|
||||||
description={intl.formatMessage({
|
description={intl.formatMessage({
|
||||||
id: 'playground.model.noavailable.tips2'
|
id: 'playground.model.noavailable.tips2'
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import CheckboxField from '@/components/seal-form/checkbox-field';
|
import CheckboxField from '@/components/seal-form/checkbox-field';
|
||||||
|
import InputNumber from '@/components/seal-form/input-number';
|
||||||
import SealInput from '@/components/seal-form/seal-input';
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
import UploadAudio from '@/components/upload-audio';
|
import UploadAudio from '@/components/upload-audio';
|
||||||
import useAppUtils from '@/hooks/use-app-utils';
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
@@ -6,7 +7,7 @@ import { convertFileToBase64 } from '@/utils/load-audio-file';
|
|||||||
import { CloseCircleFilled } from '@ant-design/icons';
|
import { CloseCircleFilled } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { useFormContext } from '../../config/form-context';
|
import { useFormContext } from '../../config/form-context';
|
||||||
|
|
||||||
@@ -70,24 +71,74 @@ const TTSAdvanceConfig: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const atLeastOneValidator = (relateField: string) => () => ({
|
||||||
|
validator(rule: any, value: string) {
|
||||||
|
const type = form.getFieldValue('task_type');
|
||||||
|
const relateFieldValue = form.getFieldValue(relateField);
|
||||||
|
if (type !== 'Base') return Promise.resolve();
|
||||||
|
|
||||||
|
if (value || relateFieldValue) return Promise.resolve();
|
||||||
|
|
||||||
|
return Promise.reject(
|
||||||
|
new Error(
|
||||||
|
intl.formatMessage({ id: 'playground.speech.rules.refAudio' })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (taskType === 'Base') {
|
||||||
|
form.setFieldsValue({
|
||||||
|
x_vector_only_mode: true
|
||||||
|
});
|
||||||
|
onValuesChange?.(
|
||||||
|
{ x_vector_only_mode: true },
|
||||||
|
{ ...form.getFieldsValue(), x_vector_only_mode: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [taskType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Form.Item
|
||||||
|
name="instructions"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: taskType === 'VoiceDesign',
|
||||||
|
message: getRuleMessage('input', 'playground.params.instructions')
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInput.Input
|
||||||
|
allowClear
|
||||||
|
required={taskType === 'VoiceDesign'}
|
||||||
|
description={intl.formatMessage({
|
||||||
|
id: 'playground.params.instructions.tips'
|
||||||
|
})}
|
||||||
|
label={intl.formatMessage({ id: 'playground.params.instructions' })}
|
||||||
|
></SealInput.Input>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="max_new_tokens"
|
||||||
|
getValueProps={(value) => ({ value: value || null })}
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
min={0}
|
||||||
|
step={1}
|
||||||
|
max={4096}
|
||||||
|
label={intl.formatMessage({ id: 'playground.params.maxTokens' })}
|
||||||
|
></InputNumber>
|
||||||
|
</Form.Item>
|
||||||
<Container>
|
<Container>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="ref_audio"
|
name="ref_audio"
|
||||||
getValueProps={(value) => ({ value: fileName ? fileName : value })}
|
getValueProps={(value) => ({ value: fileName ? fileName : value })}
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: taskType === 'Base',
|
|
||||||
message: getRuleMessage('input', 'playground.params.refAudio')
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
dependencies={['task_type']}
|
dependencies={['task_type']}
|
||||||
>
|
>
|
||||||
<SealInput.Input
|
<SealInput.Input
|
||||||
allowClear
|
allowClear
|
||||||
readOnly={!!fileName}
|
readOnly={!!fileName}
|
||||||
required={taskType === 'Base'}
|
|
||||||
suffix={
|
suffix={
|
||||||
<SuffixWrapper>
|
<SuffixWrapper>
|
||||||
{fileName ? (
|
{fileName ? (
|
||||||
@@ -110,7 +161,12 @@ const TTSAdvanceConfig: React.FC = () => {
|
|||||||
></SealInput.Input>
|
></SealInput.Input>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Container>
|
</Container>
|
||||||
<Form.Item name="ref_text">
|
<Form.Item
|
||||||
|
name="ref_text"
|
||||||
|
style={{ marginBottom: 12 }}
|
||||||
|
dependencies={['task_type', 'x_vector_only_mode']}
|
||||||
|
rules={[atLeastOneValidator('x_vector_only_mode')]}
|
||||||
|
>
|
||||||
<SealInput.TextArea
|
<SealInput.TextArea
|
||||||
allowClear
|
allowClear
|
||||||
scaleSize={true}
|
scaleSize={true}
|
||||||
@@ -118,9 +174,11 @@ const TTSAdvanceConfig: React.FC = () => {
|
|||||||
></SealInput.TextArea>
|
></SealInput.TextArea>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
dependencies={['task_type', 'ref_text']}
|
||||||
|
style={{ marginBottom: 12 }}
|
||||||
name="x_vector_only_mode"
|
name="x_vector_only_mode"
|
||||||
valuePropName="checked"
|
valuePropName="checked"
|
||||||
style={{ marginBottom: 8 }}
|
rules={[atLeastOneValidator('ref_text')]}
|
||||||
>
|
>
|
||||||
<CheckboxField
|
<CheckboxField
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
|
|||||||
@@ -43,58 +43,5 @@ export const TTSAdvancedParamsConfig: ParamsSchema[] = [
|
|||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'Input',
|
|
||||||
name: 'instructions',
|
|
||||||
label: {
|
|
||||||
text: 'playground.params.instructions',
|
|
||||||
isLocalized: true
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
text: 'playground.params.instructions.tips',
|
|
||||||
isLocalized: true
|
|
||||||
},
|
|
||||||
attrs: {
|
|
||||||
allowClear: true
|
|
||||||
},
|
|
||||||
initAttrs: (meta: any) => {
|
|
||||||
return {
|
|
||||||
options: _.map(meta?.voices || [], (item: string) => ({
|
|
||||||
label: item,
|
|
||||||
value: item
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
},
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
required: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'InputNumber',
|
|
||||||
name: 'max_new_tokens',
|
|
||||||
label: {
|
|
||||||
text: 'playground.params.maxTokens',
|
|
||||||
isLocalized: true
|
|
||||||
},
|
|
||||||
attrs: {
|
|
||||||
step: 1,
|
|
||||||
min: 0,
|
|
||||||
max: 4096
|
|
||||||
},
|
|
||||||
formItemAttrs: {
|
|
||||||
getValueProps: (value: number) => {
|
|
||||||
return {
|
|
||||||
value: value || null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
required: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -189,9 +189,6 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
|
|
||||||
const submitMessage = async (current?: { role: string; content: string }) => {
|
const submitMessage = async (current?: { role: string; content: string }) => {
|
||||||
try {
|
try {
|
||||||
await formRef.current?.form.validateFields();
|
|
||||||
if (!parameters.model) return;
|
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setMessageId();
|
setMessageId();
|
||||||
setTokenResult(null);
|
setTokenResult(null);
|
||||||
@@ -207,7 +204,6 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
setParams(params);
|
setParams(params);
|
||||||
console.log('submitMessage params:', streamTTS.isPlaying);
|
|
||||||
|
|
||||||
// Choose stream or non-stream based on parameters
|
// Choose stream or non-stream based on parameters
|
||||||
if (parameters.stream) {
|
if (parameters.stream) {
|
||||||
@@ -230,7 +226,6 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
await nonStreamTTS.generate(params);
|
await nonStreamTTS.generate(params);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log('error:', error);
|
|
||||||
setPlayingStream(false);
|
setPlayingStream(false);
|
||||||
setTokenResult({
|
setTokenResult({
|
||||||
error: true,
|
error: true,
|
||||||
@@ -248,7 +243,7 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSendMessage = (message: Omit<MessageItem, 'uid'>) => {
|
const handleSendMessage = (message: Omit<MessageItem, 'uid'>) => {
|
||||||
submitMessage(message);
|
formRef.current?.form.submit();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseViewCode = () => {
|
const handleCloseViewCode = () => {
|
||||||
@@ -353,6 +348,7 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
<TTSDataForm
|
<TTSDataForm
|
||||||
ref={formRef}
|
ref={formRef}
|
||||||
modelList={modelList}
|
modelList={modelList}
|
||||||
|
onFinish={submitMessage}
|
||||||
updatateParams={updatateParams}
|
updatateParams={updatateParams}
|
||||||
/>
|
/>
|
||||||
</RightContainer>
|
</RightContainer>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: calc(
|
height: calc(
|
||||||
100vh - @HEADER_HEIGHT - var(--page-content-padding) -
|
100vh - @HEADER_HEIGHT - 4px - var(--page-content-padding) -
|
||||||
var(--page-content-padding) - var(--page-header-height)
|
var(--page-content-padding) - var(--page-header-height)
|
||||||
);
|
);
|
||||||
width: calc(100% - 390px);
|
width: calc(100% - 390px);
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
border-left: 1px solid var(--ant-color-split);
|
border-left: 1px solid var(--ant-color-split);
|
||||||
transition: width 0.3s ease;
|
transition: width 0.3s ease;
|
||||||
height: calc(
|
height: calc(
|
||||||
100vh - @HEADER_HEIGHT - var(--page-content-padding) -
|
100vh - @HEADER_HEIGHT - 2px - var(--page-content-padding) -
|
||||||
var(--page-header-height)
|
var(--page-content-padding) - var(--page-header-height)
|
||||||
);
|
);
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: calc(
|
height: calc(
|
||||||
100vh - var(--page-header-height) - var(--page-content-padding) -
|
100vh - var(--page-header-height) - var(--page-content-padding) -
|
||||||
@HEADER_HEIGHT
|
var(--page-content-padding) - @HEADER_HEIGHT
|
||||||
);
|
);
|
||||||
|
|
||||||
.collapse {
|
.collapse {
|
||||||
@@ -74,8 +74,8 @@
|
|||||||
.ant-divider {
|
.ant-divider {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: calc(
|
height: calc(
|
||||||
100vh - var(--page-header-height) - var(--page-content-padding) -
|
100vh - var(--page-header-height) - 2px - var(--page-content-padding) -
|
||||||
@HEADER_HEIGHT
|
var(--page-content-padding) - @HEADER_HEIGHT
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user