diff --git a/build/sw_plugin.js b/build/sw_plugin.js index 90ab856ad..a2c792b7d 100644 --- a/build/sw_plugin.js +++ b/build/sw_plugin.js @@ -11,6 +11,11 @@ const getSWMessagesAsText = async () => { } const projectRoot = dirname(dirname(fileURLToPath(import.meta.url))) +const swEnvName = 'virtual:pleroma-fe/service_worker_env' +const swEnvNameResolved = '\0' + swEnvName +const getDevSwEnv = () => `self.serviceWorkerOption = { assets: [] };` +const getProdSwEnv = ({ assets }) => `self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };` + export const devSwPlugin = ({ swSrc, swDest, @@ -32,12 +37,16 @@ export const devSwPlugin = ({ const name = id.startsWith('/') ? id.slice(1) : id if (name === swDest) { return swFullSrc + } else if (name === swEnvName) { + return swEnvNameResolved } return null }, async load (id) { if (id === swFullSrc) { return readFile(swFullSrc, 'utf-8') + } else if (id === swEnvNameResolved) { + return getDevSwEnv() } return null }, @@ -79,6 +88,21 @@ export const devSwPlugin = ({ contents: await getSWMessagesAsText() })) } + }, { + name: 'sw-env', + setup (b) { + b.onResolve( + { filter: new RegExp('^' + swEnvName + '$') }, + args => ({ + path: args.path, + namespace: 'sw-env' + })) + b.onLoad( + { filter: /.*/, namespace: 'sw-env' }, + () => ({ + contents: getDevSwEnv() + })) + } }] }) const text = res.outputFiles[0].text @@ -126,6 +150,30 @@ export const buildSwPlugin = ({ configFile: false } }, + generateBundle: { + order: 'post', + sequential: true, + async handler (_, bundle) { + const assets = Object.keys(bundle) + .filter(name => !/\.map$/.test(name)) + .map(name => '/' + name) + config.plugins.push({ + name: 'build-sw-env-plugin', + resolveId (id) { + if (id === swEnvName) { + return swEnvNameResolved + } + return null + }, + load (id) { + if (id === swEnvNameResolved) { + return getProdSwEnv({ assets }) + } + return null + } + }) + } + }, closeBundle: { order: 'post', sequential: true, diff --git a/changelog.d/action-button-extra-counter.add b/changelog.d/action-button-extra-counter.add new file mode 100644 index 000000000..7d5c77447 --- /dev/null +++ b/changelog.d/action-button-extra-counter.add @@ -0,0 +1 @@ +Display counter for status action buttons when they are on the menu diff --git a/changelog.d/akkoftermapth.skip b/changelog.d/akkoftermapth.skip new file mode 100644 index 000000000..e69de29bb diff --git a/changelog.d/akkoma-sharkey-net-support.add b/changelog.d/akkoma-sharkey-net-support.add new file mode 100644 index 000000000..4b4bff7fe --- /dev/null +++ b/changelog.d/akkoma-sharkey-net-support.add @@ -0,0 +1 @@ +Added support for Akkoma and IceShrimp.NET backend diff --git a/changelog.d/akkoma.skip b/changelog.d/akkoma.skip new file mode 100644 index 000000000..e69de29bb diff --git a/changelog.d/arithmetic-blend.add b/changelog.d/arithmetic-blend.add new file mode 100644 index 000000000..c579dca28 --- /dev/null +++ b/changelog.d/arithmetic-blend.add @@ -0,0 +1,2 @@ +Add arithmetic blend ISS function + diff --git a/changelog.d/bookmark-button-align.fix b/changelog.d/bookmark-button-align.fix new file mode 100644 index 000000000..64bc2c807 --- /dev/null +++ b/changelog.d/bookmark-button-align.fix @@ -0,0 +1 @@ +Fix bookmark button alignment in the extra actions menu diff --git a/changelog.d/csp.add b/changelog.d/csp.add new file mode 100644 index 000000000..260337b97 --- /dev/null +++ b/changelog.d/csp.add @@ -0,0 +1 @@ +Compatibility with stricter CSP (Akkoma backend) diff --git a/changelog.d/migrate-auth-flow-pinia.skip b/changelog.d/migrate-auth-flow-pinia.skip new file mode 100644 index 000000000..e69de29bb diff --git a/changelog.d/small-fixes.skip b/changelog.d/small-fixes.skip new file mode 100644 index 000000000..e69de29bb diff --git a/changelog.d/sw-cache-assets.add b/changelog.d/sw-cache-assets.add new file mode 100644 index 000000000..5f7414eee --- /dev/null +++ b/changelog.d/sw-cache-assets.add @@ -0,0 +1 @@ +Cache assets and emojis with service worker diff --git a/changelog.d/unify-show-hide-buttons.add b/changelog.d/unify-show-hide-buttons.add new file mode 100644 index 000000000..663bc38a5 --- /dev/null +++ b/changelog.d/unify-show-hide-buttons.add @@ -0,0 +1 @@ +Unify show/hide content buttons diff --git a/changelog.d/zoomlag.skip b/changelog.d/zoomlag.skip new file mode 100644 index 000000000..e69de29bb diff --git a/index.html b/index.html index a2f928361..fb92252c5 100644 --- a/index.html +++ b/index.html @@ -5,138 +5,16 @@ + - - - + + - +
diff --git a/package.json b/package.json index 2f9896d18..eea94a10e 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@fortawesome/free-regular-svg-icons": "6.7.2", "@fortawesome/free-solid-svg-icons": "6.7.2", "@fortawesome/vue-fontawesome": "3.0.8", - "@kazvmoe-infra/pinch-zoom-element": "1.2.0", + "@kazvmoe-infra/pinch-zoom-element": "1.3.0", "@kazvmoe-infra/unicode-emoji-json": "0.4.0", "@ruffle-rs/ruffle": "0.1.0-nightly.2025.1.13", "@vuelidate/core": "2.0.3", @@ -47,7 +47,7 @@ "url": "0.11.4", "utf8": "3.0.0", "uuid": "11.1.0", - "vue": "3.5.13", + "vue": "3.5.17", "vue-i18n": "11", "vue-router": "4.5.1", "vue-virtual-scroller": "^2.0.0-beta.7", @@ -66,7 +66,7 @@ "@vitest/ui": "^3.0.7", "@vue/babel-helper-vue-jsx-merge-props": "1.4.0", "@vue/babel-plugin-jsx": "1.4.0", - "@vue/compiler-sfc": "3.5.13", + "@vue/compiler-sfc": "3.5.17", "@vue/test-utils": "2.4.6", "autoprefixer": "10.4.21", "babel-plugin-lodash": "3.3.4", @@ -90,17 +90,17 @@ "http-proxy-middleware": "3.0.5", "iso-639-1": "3.1.5", "lodash": "4.17.21", - "msw": "2.7.6", + "msw": "2.10.2", "nightwatch": "3.12.1", "playwright": "1.52.0", "postcss": "8.5.3", "postcss-html": "^1.5.0", "postcss-scss": "^4.0.6", - "sass": "1.87.0", + "sass": "1.89.2", "selenium-server": "3.141.59", - "semver": "7.7.1", + "semver": "7.7.2", "serve-static": "2.2.0", - "shelljs": "0.9.2", + "shelljs": "0.10.0", "sinon": "20.0.0", "sinon-chai": "4.0.0", "stylelint": "16.19.1", diff --git a/public/static/splash.css b/public/static/splash.css new file mode 100644 index 000000000..abdc19fc2 --- /dev/null +++ b/public/static/splash.css @@ -0,0 +1,126 @@ +body { + margin: 0; + padding: 0; +} + +#splash { + --scale: 1; + width: 100vw; + height: 100vh; + display: grid; + grid-template-rows: auto; + grid-template-columns: auto; + align-content: center; + align-items: center; + justify-content: center; + justify-items: center; + flex-direction: column; + background: #0f161e; + font-family: sans-serif; + color: #b9b9ba; + position: absolute; + z-index: 9999; + font-size: calc(1vw + 1vh + 1vmin); +} + +#splash-credit { + position: absolute; + font-size: 14px; + bottom: 16px; + right: 16px; +} + +#splash-container { + align-items: center; +} + +#mascot-container { + display: flex; + align-items: flex-end; + justify-content: center; + perspective: 60em; + perspective-origin: 0 -15em; + transform-style: preserve-3d; +} + +#mascot { + width: calc(10em * var(--scale)); + height: calc(10em * var(--scale)); + object-fit: contain; + object-position: bottom; + transform: translateZ(-2em); +} + +#throbber { + display: grid; + width: calc(5em * 0.5 * var(--scale)); + height: calc(8em * 0.5 * var(--scale)); + margin-left: 4.1em; + z-index: 2; + grid-template-rows: repeat(8, 1fr); + grid-template-columns: repeat(5, 1fr); + grid-template-areas: "P P . L L" + "P P . L L" + "P P . L L" + "P P . L L" + "P P . . ." + "P P . . ." + "P P . E E" + "P P . E E"; + + --logoChunkSize: calc(2em * 0.5 * var(--scale)) +} + +.chunk { + background-color: #e2b188; + box-shadow: 0.01em 0.01em 0.1em 0 #e2b188; +} + +#chunk-P { + grid-area: P; + border-top-left-radius: calc(var(--logoChunkSize) / 2); +} + +#chunk-L { + grid-area: L; + border-bottom-right-radius: calc(var(--logoChunkSize) / 2); +} + +#chunk-E { + grid-area: E; + border-bottom-right-radius: calc(var(--logoChunkSize) / 2); +} + +#status { + margin-top: 1em; + line-height: 2; + width: 100%; + text-align: center; +} + +#statusError { + display: none; + margin-top: 1em; + font-size: calc(1vw + 1vh + 1vmin); + line-height: 2; + width: 100%; + text-align: center; +} + +#statusStack { + display: none; + margin-top: 1em; + font-size: calc((1vw + 1vh + 1vmin) / 2.5); + width: calc(100vw - 5em); + padding: 1em; + text-overflow: ellipsis; + overflow-x: hidden; + text-align: left; + line-height: 2; +} + +@media (prefers-reduced-motion) { + #throbber { + animation: none !important; + } +} diff --git a/src/App.js b/src/App.js index 013ea323c..a251682dc 100644 --- a/src/App.js +++ b/src/App.js @@ -14,6 +14,7 @@ import EditStatusModal from './components/edit_status_modal/edit_status_modal.vu import PostStatusModal from './components/post_status_modal/post_status_modal.vue' import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue' import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue' +import { getOrCreateServiceWorker } from './services/sw/sw' import { windowWidth, windowHeight } from './services/window_utils/window_utils' import { mapGetters } from 'vuex' import { defineAsyncComponent } from 'vue' @@ -77,6 +78,7 @@ export default { this.setThemeBodyClass() this.removeSplash() } + getOrCreateServiceWorker() }, unmounted () { window.removeEventListener('resize', this.updateMobileState) diff --git a/src/App.scss b/src/App.scss index 24afac8ab..d56306c9e 100644 --- a/src/App.scss +++ b/src/App.scss @@ -2,6 +2,9 @@ /* stylelint-disable no-descending-specificity */ @use "panel"; +@import '@fortawesome/fontawesome-svg-core/styles.css'; +@import '@kazvmoe-infra/pinch-zoom-element/dist/pinch-zoom.css'; + :root { --status-margin: 0.75em; --post-line-height: 1.4; @@ -30,6 +33,7 @@ body { font-family: sans-serif; font-family: var(--font); margin: 0; + padding: 0; color: var(--text); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 28ff1e530..3ef92fd11 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -6,6 +6,8 @@ import VueVirtualScroller from 'vue-virtual-scroller' import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome' +import { config } from '@fortawesome/fontawesome-svg-core'; +config.autoAddCss = false import App from '../App.vue' import routes from './routes' @@ -21,6 +23,7 @@ import { useOAuthStore } from 'src/stores/oauth' import { useI18nStore } from 'src/stores/i18n' import { useInterfaceStore } from 'src/stores/interface' import { useAnnouncementsStore } from 'src/stores/announcements' +import { useAuthFlowStore } from 'src/stores/auth_flow' let staticInitialResults = null @@ -63,10 +66,11 @@ const getInstanceConfig = async ({ store }) => { const textlimit = data.max_toot_chars const vapidPublicKey = data.pleroma.vapid_public_key + store.dispatch('setInstanceOption', { name: 'pleromaExtensionsAvailable', value: data.pleroma }) store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit }) store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required }) - store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required }) - store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 }) + store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma?.metadata.birthday_required }) + store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma?.metadata.birthday_min_age || 0 }) if (vapidPublicKey) { store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) @@ -78,6 +82,8 @@ const getInstanceConfig = async ({ store }) => { console.error('Could not load instance config, potentially fatal') console.error(error) } + // We should check for scrobbles support here but it requires userId + // so instead we check for it where it's fetched (statuses.js) } const getBackendProvidedConfig = async () => { @@ -153,7 +159,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { : config.logoMargin }) copyInstanceOption('logoLeft') - store.commit('authFlow/setInitialStrategy', config.loginMethod) + useAuthFlowStore().setInitialStrategy(config.loginMethod) copyInstanceOption('redirectRootNoLogin') copyInstanceOption('redirectRootLogin') @@ -242,7 +248,8 @@ const resolveStaffAccounts = ({ store, accounts }) => { const getNodeInfo = async ({ store }) => { try { - const res = await preloadFetch('/nodeinfo/2.1.json') + let res = await preloadFetch('/nodeinfo/2.1.json') + if (!res.ok) res = await preloadFetch('/nodeinfo/2.0.json') if (res.ok) { const data = await res.json() const metadata = data.metadata @@ -254,7 +261,12 @@ const getNodeInfo = async ({ store }) => { store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') }) store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') }) store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') }) - store.dispatch('setInstanceOption', { name: 'pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') }) + store.dispatch('setInstanceOption', { + name: 'pleromaCustomEmojiReactionsAvailable', + value: + features.includes('pleroma_custom_emoji_reactions') || + features.includes('custom_emoji_reactions') + }) store.dispatch('setInstanceOption', { name: 'pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders') }) store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') }) @@ -264,6 +276,7 @@ const getNodeInfo = async ({ store }) => { store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') }) store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') }) store.dispatch('setInstanceOption', { name: 'blockExpiration', value: features.includes('pleroma:block_expiration') }) + store.dispatch('setInstanceOption', { name: 'localBubbleInstances', value: metadata.localBubbleInstances ?? [] }) const uploadLimits = metadata.uploadLimits store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) }) @@ -282,7 +295,6 @@ const getNodeInfo = async ({ store }) => { const software = data.software store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version }) store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository }) - store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' }) const priv = metadata.private store.dispatch('setInstanceOption', { name: 'private', value: priv }) diff --git a/src/boot/routes.js b/src/boot/routes.js index da87c6c61..02abf8ce6 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -1,4 +1,5 @@ import PublicTimeline from 'components/public_timeline/public_timeline.vue' +import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue' import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue' import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue' import TagTimeline from 'components/tag_timeline/tag_timeline.vue' @@ -54,6 +55,7 @@ export default (store) => { { name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute }, { name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline }, { name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline }, + { name: 'bubble', path: '/bubble', component: BubbleTimeline }, { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, { name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline }, { diff --git a/src/components/auth_form/auth_form.js b/src/components/auth_form/auth_form.js index a86a3dca2..243cbf574 100644 --- a/src/components/auth_form/auth_form.js +++ b/src/components/auth_form/auth_form.js @@ -2,7 +2,8 @@ import { h, resolveComponent } from 'vue' import LoginForm from '../login_form/login_form.vue' import MFARecoveryForm from '../mfa_form/recovery_form.vue' import MFATOTPForm from '../mfa_form/totp_form.vue' -import { mapGetters } from 'vuex' +import { mapState } from 'pinia' +import { useAuthFlowStore } from 'src/stores/auth_flow' const AuthForm = { name: 'AuthForm', @@ -15,7 +16,7 @@ const AuthForm = { if (this.requiredRecovery) { return 'MFARecoveryForm' } return 'LoginForm' }, - ...mapGetters('authFlow', ['requiredTOTP', 'requiredRecovery']) + ...mapState(useAuthFlowStore, ['requiredTOTP', 'requiredRecovery']) }, components: { MFARecoveryForm, diff --git a/src/components/bubble_timeline/bubble_timeline.js b/src/components/bubble_timeline/bubble_timeline.js new file mode 100644 index 000000000..6f73dd2b8 --- /dev/null +++ b/src/components/bubble_timeline/bubble_timeline.js @@ -0,0 +1,18 @@ +import Timeline from '../timeline/timeline.vue' +const BubbleTimeline = { + components: { + Timeline + }, + computed: { + timeline () { return this.$store.state.statuses.timelines.bubble } + }, + created () { + this.$store.dispatch('startFetchingTimeline', { timeline: 'bubble' }) + }, + unmounted () { + this.$store.dispatch('stopFetchingTimeline', 'bubble') + } + +} + +export default BubbleTimeline diff --git a/src/components/bubble_timeline/bubble_timeline.vue b/src/components/bubble_timeline/bubble_timeline.vue new file mode 100644 index 000000000..4aefa2729 --- /dev/null +++ b/src/components/bubble_timeline/bubble_timeline.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue index 26b67cfe8..bcdf435fc 100644 --- a/src/components/color_input/color_input.vue +++ b/src/components/color_input/color_input.vue @@ -26,7 +26,7 @@ class="textColor unstyled" :class="{ disabled: !present || disabled }" type="text" - :value="modelValue || fallback" + :value="modelValue ?? fallback" :disabled="!present || disabled" @input="updateValue($event.target.value)" > diff --git a/src/components/component_preview/component_preview.js b/src/components/component_preview/component_preview.js new file mode 100644 index 000000000..9f830cd72 --- /dev/null +++ b/src/components/component_preview/component_preview.js @@ -0,0 +1,82 @@ +import Checkbox from 'src/components/checkbox/checkbox.vue' +import ColorInput from 'src/components/color_input/color_input.vue' + +import genRandomSeed from 'src/services/random_seed/random_seed.service.js' +import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js' + +export default { + components: { + Checkbox, + ColorInput + }, + props: [ + 'shadow', + 'shadowControl', + 'previewClass', + 'previewStyle', + 'previewCss', + 'disabled', + 'invalid', + 'noColorControl' + ], + emits: ['update:shadow'], + data () { + return { + colorOverride: undefined, + lightGrid: false, + zoom: 100, + randomSeed: genRandomSeed() + } + }, + mounted () { + this.update() + }, + computed: { + hideControls () { + return typeof this.shadow === 'string' + } + }, + watch: { + previewCss () { + this.update() + }, + previewStyle () { + this.update() + }, + zoom () { + this.update() + } + }, + methods: { + updateProperty (axis, value) { + this.$emit('update:shadow', { axis, value: Number(value) }) + }, + update () { + const sheet = createStyleSheet('style-component-preview', 90) + + sheet.clear() + + const result = [this.previewCss] + if (this.colorOverride) result.push(`--background: ${this.colorOverride}`) + + const styleRule = [ + '#component-preview-', this.randomSeed, ' {\n', + '.preview-block {\n', + `zoom: ${this.zoom / 100};`, + this.previewStyle, + '\n}', + '\n}' + ].join('') + + sheet.addRule(styleRule) + sheet.addRule([ + '#component-preview-', this.randomSeed, ' {\n', + ...result, + '\n}' + ].join('')) + + sheet.ready = true + adoptStyleSheets() + } + } +} diff --git a/src/components/component_preview/component_preview.scss b/src/components/component_preview/component_preview.scss new file mode 100644 index 000000000..bb83b7908 --- /dev/null +++ b/src/components/component_preview/component_preview.scss @@ -0,0 +1,151 @@ +.ComponentPreview { + display: grid; + grid-template-columns: 1em 1fr 1fr 1em; + grid-template-rows: 2em 1fr 1fr 1fr 1em 2em max-content; + grid-template-areas: + "header header header header " + "preview preview preview y-slide" + "preview preview preview y-slide" + "preview preview preview y-slide" + "x-slide x-slide x-slide . " + "x-num x-num y-num y-num " + "assists assists assists assists"; + grid-gap: 0.5em; + + &:not(.-shadow-controls) { + grid-template-areas: + "header header header header " + "preview preview preview y-slide" + "preview preview preview y-slide" + "preview preview preview y-slide" + "assists assists assists assists"; + grid-template-rows: 2em 1fr 1fr 1fr max-content; + } + + .header { + grid-area: header; + place-self: baseline center; + line-height: 2; + } + + .invalid-container { + position: absolute; + inset: 0; + display: grid; + place-items: center center; + background-color: rgb(100 0 0 / 50%); + + .alert { + padding: 0.5em 1em; + } + } + + .assists { + grid-area: assists; + display: grid; + grid-auto-flow: row; + grid-auto-rows: 2em; + grid-gap: 0.5em; + } + + .input-light-grid { + justify-self: center; + } + + .input-number { + min-width: 2em; + } + + .x-shift-number { + grid-area: x-num; + justify-self: right; + } + + .y-shift-number { + grid-area: y-num; + justify-self: left; + } + + .x-shift-number, + .y-shift-number { + input { + max-width: 4em; + } + } + + .x-shift-slider { + grid-area: x-slide; + height: auto; + align-self: start; + min-width: 10em; + } + + .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: rgb(102 102 102 / 20%); + --__grid-color2-disabled: rgb(153 153 153 / 20%); + + &.-light-grid { + --__grid-color1: rgb(205 205 205); + --__grid-color2: rgb(255 255 255); + --__grid-color1-disabled: rgb(205 205 205 / 20%); + --__grid-color2-disabled: rgb(255 255 255 / 20%); + } + + position: relative; + 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); + } + } +} diff --git a/src/components/component_preview/component_preview.vue b/src/components/component_preview/component_preview.vue index be9d25ac5..0d81128c7 100644 --- a/src/components/component_preview/component_preview.vue +++ b/src/components/component_preview/component_preview.vue @@ -1,14 +1,9 @@ - - +