schon/storefront/app/components/demo/settings.vue
2026-02-27 21:59:51 +03:00

317 lines
No EOL
7.2 KiB
Vue

<template>
<div v-if="isDemoMode && isOpen" class="modal">
<div
class="modal__wrapper"
>
<demo-ui-button
@click="appStore.setDemoSettings(false)"
class="modal__close"
>
<Icon name="material-symbols:close" size="30" />
</demo-ui-button>
<h2 class="modal__title">{{ toConstantCase(t('demo.settings.title')) }}</h2>
<div class="modal__inner">
<div class="modal__block">
<h3 class="modal__block-title">{{ toConstantCase(t('demo.settings.ui')) }}</h3>
<div
v-for="(flag, idx) in availableFlags"
:key="flag.key"
class="modal__block-item"
>
<demo-ui-checkbox
:label="toConstantCase(flag.label)"
v-model="localFlags[flag.key]"
:id="idx"
/>
<p>{{ flag.description }}</p>
</div>
</div>
<demo-ui-button @click="showCodePreview = !showCodePreview">{{ toConstantCase(t('demo.buttons.generateCode')) }}</demo-ui-button>
<div v-if="showCodePreview" class="modal__preview">
<div class="modal__preview-wrapper">
<pre class="modal__preview-code"><code class="language-typescript">{{ codePreview }}</code></pre>
<demo-ui-button
@click="copyConfig"
class="modal__preview-button"
>
<Icon name="material-symbols:content-copy" size="16" />
</demo-ui-button>
</div>
<p class="modal__preview-text">{{ t('demo.preview.text') }}</p>
</div>
</div>
<div class="modal__buttons">
<demo-ui-button @click="resetToDefault">
<Icon name="material-symbols:refresh" size="20" />
{{ toConstantCase(t('demo.buttons.reset')) }}
</demo-ui-button>
<demo-ui-button @click="saveChanges">
<Icon name="material-symbols:save" size="20" />
{{ toConstantCase(t('demo.buttons.save')) }}
</demo-ui-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useProjectConfig } from '@composables/config';
const appStore = useAppStore();
const {t} = useI18n();
const isOpen = computed(() => appStore.isDemoSettings);
const {
isDemoMode,
availableFlags,
setDemoFlag,
resetDemoFlags,
uiConfig,
generateConfigCode,
copyToClipboard
} = useProjectConfig();
const showCodePreview = ref<boolean>(false);
const localFlags = ref<Record<string, boolean>>({});
const previewConfig = computed(() => {
const config: Record<string, boolean> = {};
availableFlags.value.forEach(flag => {
config[flag.key] = localFlags.value[flag.key] !== undefined
? localFlags.value[flag.key]
: flag.value;
});
return config;
});
const formatPreviewConfig = (config: Record<string, boolean>): string => {
const entries = Object.entries(config)
.map(([key, value]) => ` ${key}: ${value}`)
.join(',\n');
return `ui: {\n${entries}\n}`;
};
function toConstantCase(text: string): string {
const placeholders: string[] = [];
let placeholderIndex = 0;
const textWithPlaceholders = text.replace(/(['"])(.*?)\1/g, (match) => {
placeholders.push(match);
return `__QUOTE_PLACEHOLDER_${placeholderIndex++}__`;
});
let result = textWithPlaceholders
.toUpperCase()
.replace(/\s+/g, '_')
.replace(/[^A-Z0-9_]/g, '');
placeholders.forEach((placeholder, index) => {
result = result.replace(`__QUOTE_PLACEHOLDER_${index}__`, placeholder);
});
return result;
}
watch(isOpen, (newVal) => {
if (newVal) {
const flags: Record<string, boolean> = {};
availableFlags.value.forEach(flag => {
flags[flag.key] = flag.value;
});
localFlags.value = flags;
}
}, { immediate: true });
const saveChanges = () => {
availableFlags.value.forEach(flag => {
const newValue = localFlags.value[flag.key];
const oldValue = flag.value;
if (newValue !== oldValue) {
setDemoFlag(flag.key, newValue);
}
});
appStore.setDemoSettings(false);
};
const resetToDefault = () => {
resetDemoFlags();
appStore.setDemoSettings(false);
};
const copyConfig = async () => {
const code = codePreview.value;
await copyToClipboard(code);
};
const codePreview = computed(() => {
return formatPreviewConfig(previewConfig.value);
});
</script>
<style scoped lang="scss">
.modal {
position: fixed;
z-index: 3;
width: 100vw;
height: 100vh;
top: 0;
right: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(3px);
&__wrapper {
position: absolute;
z-index: 2;
inset: 100px;
background-color: #0d0d0d;
padding: 50px;
border-radius: $default_border_radius;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 50px;
}
&__close {
position: absolute;
z-index: 1;
top: 15px;
right: 15px;
padding: 7px !important;
}
&__title {
text-align: center;
font-size: 40px;
font-weight: 500;
text-shadow: 0 0 5px $accentNeon;
text-transform: uppercase;
color: $accentNeon;
}
&__inner {
height: 100%;
width: 100%;
overflow: auto;
display: flex;
flex-direction: column;
align-items: center;
gap: 50px;
padding: 10px 20px;
scrollbar-color: $accentNeon transparent;
}
&__block {
align-self: flex-start;
display: flex;
flex-direction: column;
gap: 15px;
&-title {
width: fit-content;
margin-bottom: 20px;
padding-bottom: 5px;
border-bottom: 2px solid $accentNeon;
color: $accentNeon;
font-weight: 500;
text-shadow: 0 0 5px $accentNeon;
text-transform: uppercase;
font-size: 22px;
}
&-item {
display: flex;
flex-direction: column;
gap: 5px;
& p {
padding-left: 50px;
color: #8c8c8c;
font-weight: 500;
font-size: 16px;
}
}
}
&__preview {
width: 100%;
display: flex;
flex-direction: column;
gap: 20px;
&-wrapper {
position: relative;
background-color: #1a1a1a;
border-radius: $default_border_radius;
padding: 20px;
border: 1px solid rgba($accentNeon, 0.3);
}
&-code {
margin: 0;
color: #f8f8f2;
font-family: 'Fira Code', 'Courier New', monospace;
font-size: 14px;
line-height: 1.5;
white-space: pre-wrap;
overflow-x: auto;
.language-typescript {
color: #f8f8f2;
.keyword {
color: lch(69.03% 60.03 345.46);
}
.string {
color: #f1fa8c;
}
.number {
color: #bd93f9;
}
.boolean {
color: #ff79c6;
}
.property {
color: #8be9fd;
}
}
}
&-button {
position: absolute;
top: 10px;
right: 10px;
padding: 6px 12px !important;
font-size: 12px !important;
gap: 6px !important;
}
&-text {
color: #8c8c8c;
font-weight: 500;
font-size: 14px;
text-align: center;
}
}
&__buttons {
border-top: 1px solid $white;
padding-top: 25px;
display: flex;
align-items: center;
justify-content: center;
gap: 25px;
}
}
</style>