From 954d964384d38803f00776e825a4dd28f2c9e007 Mon Sep 17 00:00:00 2001 From: Ilya Y <75578537+ilya-bkv@users.noreply.github.com> Date: Thu, 23 Nov 2023 21:15:06 +0300 Subject: [PATCH 01/12] Fix/profile update (#327) Profile settings refactoring --- public/locales/en/translation.json | 2 + public/locales/ru/translation.json | 2 + .../ProfileSettings/ProfileSettings.tsx | 359 ++++++++++++++++++ src/components/ProfileSettings/index.ts | 1 + .../GrowingTextarea/GrowingTextarea.tsx | 8 +- .../SocialNetworkInput/SocialNetworkInput.tsx | 4 +- src/context/profile.tsx | 71 ++-- src/pages/profile/Settings.module.scss | 22 ++ src/pages/profile/profileSettings.page.tsx | 323 +--------------- src/stores/zine/authors.ts | 3 +- src/utils/apiClient.ts | 10 + 11 files changed, 450 insertions(+), 355 deletions(-) create mode 100644 src/components/ProfileSettings/ProfileSettings.tsx create mode 100644 src/components/ProfileSettings/index.ts diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 35a11b7d..d93d00b4 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -103,6 +103,7 @@ "Discussion rules": "Discussion rules", "Discussion rules in social networks": "Discussion rules", "Discussions": "Discussions", + "Do you really want to reset all changes?": "Do you really want to reset all changes?", "Dogma": "Dogma", "Draft successfully deleted": "Draft successfully deleted", "Drafts": "Drafts", @@ -329,6 +330,7 @@ "Terms of use": "Site rules", "Text checking": "Text checking", "Thank you": "Thank you", + "The address is already taken": "The address is already taken", "Theory": "Theory", "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?", "There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?", diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index a4daed51..de85f893 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -106,6 +106,7 @@ "Discussion rules": "Правила дискуссий", "Discussion rules in social networks": "Правила сообществ самиздата в соцсетях", "Discussions": "Дискуссии", + "Do you really want to reset all changes?": "Вы действительно хотите сбросить все изменения?", "Dogma": "Догма", "Draft successfully deleted": "Черновик успешно удален", "Drafts": "Черновики", @@ -347,6 +348,7 @@ "Terms of use": "Правила сайта", "Text checking": "Проверка текста", "Thank you": "Благодарности", + "The address is already taken": "Адрес уже занят", "Theory": "Теории", "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?": "В настройках вашего профиля есть несохраненные изменения. Уверены, что хотите покинуть страницу без сохранения?", "There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?": "В настройках публикации есть несохраненные изменения. Уверены, что хотите покинуть страницу без сохранения?", diff --git a/src/components/ProfileSettings/ProfileSettings.tsx b/src/components/ProfileSettings/ProfileSettings.tsx new file mode 100644 index 00000000..08e5aeb7 --- /dev/null +++ b/src/components/ProfileSettings/ProfileSettings.tsx @@ -0,0 +1,359 @@ +import { createFileUploader } from '@solid-primitives/upload' +import { clsx } from 'clsx' +import deepEqual from 'fast-deep-equal' +import { createEffect, createSignal, For, lazy, Match, onCleanup, onMount, Show, Switch } from 'solid-js' +import { createStore } from 'solid-js/store' + +import { useConfirm } from '../../context/confirm' +import { useLocalize } from '../../context/localize' +import { useProfileForm } from '../../context/profile' +import { useSession } from '../../context/session' +import { useSnackbar } from '../../context/snackbar' +import { clone } from '../../utils/clone' +import { getImageUrl } from '../../utils/getImageUrl' +import { handleImageUpload } from '../../utils/handleImageUpload' +import { profileSocialLinks } from '../../utils/profileSocialLinks' +import { validateUrl } from '../../utils/validateUrl' +import { Button } from '../_shared/Button' +import { Icon } from '../_shared/Icon' +import { Loading } from '../_shared/Loading' +import { Popover } from '../_shared/Popover' +import { SocialNetworkInput } from '../_shared/SocialNetworkInput' +import { ProfileSettingsNavigation } from '../Nav/ProfileSettingsNavigation' + +import styles from '../../pages/profile/Settings.module.scss' + +const SimplifiedEditor = lazy(() => import('../../components/Editor/SimplifiedEditor')) +const GrowingTextarea = lazy(() => import('../../components/_shared/GrowingTextarea/GrowingTextarea')) + +export const ProfileSettings = () => { + const { t } = useLocalize() + const [prevForm, setPrevForm] = createStore({}) + const [isFormInitialized, setIsFormInitialized] = createSignal(false) + const [social, setSocial] = createSignal([]) + const [addLinkForm, setAddLinkForm] = createSignal(false) + const [incorrectUrl, setIncorrectUrl] = createSignal(false) + const [isUserpicUpdating, setIsUserpicUpdating] = createSignal(false) + const [uploadError, setUploadError] = createSignal(false) + const [isFloatingPanelVisible, setIsFloatingPanelVisible] = createSignal(false) + const [hostname, setHostname] = createSignal(null) + const [slugError, setSlugError] = createSignal() + const [nameError, setNameError] = createSignal() + + const { + form, + actions: { submit, updateFormField, setForm }, + } = useProfileForm() + + const { + actions: { showSnackbar }, + } = useSnackbar() + + const { + actions: { loadSession }, + } = useSession() + + const { + actions: { showConfirm }, + } = useConfirm() + + createEffect(() => { + if (Object.keys(form).length > 0 && !isFormInitialized()) { + setPrevForm(form) + setSocial(form.links) + setIsFormInitialized(true) + } + }) + + const slugInputRef: { current: HTMLInputElement } = { current: null } + const nameInputRef: { current: HTMLInputElement } = { current: null } + + const handleChangeSocial = (value: string) => { + if (validateUrl(value)) { + updateFormField('links', value) + setAddLinkForm(false) + } else { + setIncorrectUrl(true) + } + } + + const handleSubmit = async (event: Event) => { + event.preventDefault() + if (nameInputRef.current.value.length === 0) { + setNameError(t('Required')) + nameInputRef.current.focus() + return + } + if (slugInputRef.current.value.length === 0) { + setSlugError(t('Required')) + slugInputRef.current.focus() + return + } + try { + await submit(form) + setPrevForm(clone(form)) + showSnackbar({ body: t('Profile successfully saved') }) + } catch (error) { + if (error.code === 'duplicate_slug') { + setSlugError(t('The address is already taken')) + slugInputRef.current.focus() + return + } + showSnackbar({ type: 'error', body: t('Error') }) + } + loadSession() + } + + const handleCancel = async () => { + const isConfirmed = await showConfirm({ + confirmBody: t('Do you really want to reset all changes?'), + confirmButtonVariant: 'primary', + declineButtonVariant: 'secondary', + }) + if (isConfirmed) { + setForm(clone(prevForm)) + } + } + + const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' }) + + const handleUploadAvatar = async () => { + selectFiles(async ([uploadFile]) => { + try { + setUploadError(false) + setIsUserpicUpdating(true) + const result = await handleImageUpload(uploadFile) + updateFormField('userpic', result.url) + setIsUserpicUpdating(false) + } catch (error) { + setUploadError(true) + console.error('[upload avatar] error', error) + } + }) + } + + onMount(() => { + setHostname(window?.location.host) + + // eslint-disable-next-line unicorn/consistent-function-scoping + const handleBeforeUnload = (event) => { + if (!deepEqual(form, prevForm)) { + event.returnValue = t( + 'There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?', + ) + } + } + + window.addEventListener('beforeunload', handleBeforeUnload) + onCleanup(() => window.removeEventListener('beforeunload', handleBeforeUnload)) + }) + + createEffect(() => { + if (!deepEqual(form, prevForm)) { + setIsFloatingPanelVisible(true) + } + }) + + const handleDeleteSocialLink = (link) => { + updateFormField('links', link, true) + } + + return ( + 0 && isFormInitialized()} fallback={}> + <> +
+
+
+
+ +
+
+
+
+
+

{t('Profile settings')}

+

{t('Here you can customize your profile the way you want.')}

+
+

{t('Userpic')}

+
+
+ + + + + +
+
+ + {(triggerRef: (el) => void) => ( + + )} + + + {(triggerRef: (el) => void) => ( + + )} + +
+ + + + {t('Here you can upload your photo')} + + +
+ +
{t('Upload error')}
+
+
+

{t('Name')}

+

+ {t( + 'Your name will appear on your profile page and as your signature in publications, comments and responses.', + )} +

+
+ updateFormField('name', event.currentTarget.value)} + value={form.name} + ref={(el) => (nameInputRef.current = el)} + /> + + +
+ {t(`${nameError()}`)} +
+
+
+ +

{t('Address on Discourse')}

+
+
+ +
+ updateFormField('slug', event.currentTarget.value)} + value={form.slug} + ref={(el) => (slugInputRef.current = el)} + class="nolabel" + /> + +

{t(`${slugError()}`)}

+
+
+
+
+ +

{t('Introduce')}

+ updateFormField('bio', value)} + initialValue={form.bio || ''} + allowEnterKey={false} + maxLength={120} + /> + +

{t('About')}

+ updateFormField('about', value)} + /> +
+
+

{t('Social networks')}

+ +
+ + handleChangeSocial(value)} + /> + +

{t('It does not look like url')}

+
+
+ + {(network) => ( + handleChangeSocial(value)} + isExist={!network.isPlaceholder} + slug={form.slug} + handleDelete={() => handleDeleteSocialLink(network.link)} + /> + )} + +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + ) +} diff --git a/src/components/ProfileSettings/index.ts b/src/components/ProfileSettings/index.ts new file mode 100644 index 00000000..af814349 --- /dev/null +++ b/src/components/ProfileSettings/index.ts @@ -0,0 +1 @@ +export { ProfileSettings } from './ProfileSettings' diff --git a/src/components/_shared/GrowingTextarea/GrowingTextarea.tsx b/src/components/_shared/GrowingTextarea/GrowingTextarea.tsx index 2c725f06..4541e1b2 100644 --- a/src/components/_shared/GrowingTextarea/GrowingTextarea.tsx +++ b/src/components/_shared/GrowingTextarea/GrowingTextarea.tsx @@ -28,8 +28,9 @@ const GrowingTextarea = (props: Props) => { setValue(props.initialValue ?? '') } }) - const handleChangeValue = (event) => { - setValue(event.target.value) + const handleChangeValue = (textareaValue) => { + setValue(textareaValue) + props.value(textareaValue) } const handleKeyDown = async (event) => { @@ -66,8 +67,7 @@ const GrowingTextarea = (props: Props) => { : props.initialValue } onKeyDown={props.allowEnterKey ? handleKeyDown : null} - onInput={(event) => handleChangeValue(event)} - onChange={(event) => props.value(event.target.value)} + onInput={(event) => handleChangeValue(event.target.value)} placeholder={props.placeholder} onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} diff --git a/src/components/_shared/SocialNetworkInput/SocialNetworkInput.tsx b/src/components/_shared/SocialNetworkInput/SocialNetworkInput.tsx index 98f228a6..c47b0597 100644 --- a/src/components/_shared/SocialNetworkInput/SocialNetworkInput.tsx +++ b/src/components/_shared/SocialNetworkInput/SocialNetworkInput.tsx @@ -10,7 +10,7 @@ type Props = { network?: string link?: string isExist: boolean - handleChange: (value: string) => void + handleInput: (value: string) => void handleDelete?: () => void slug?: string autofocus?: boolean @@ -33,7 +33,7 @@ export const SocialNetworkInput = (props: Props) => { class={styles.input} type="text" value={props.isExist ? props.link : null} - onChange={(event) => props.handleChange(event.currentTarget.value)} + onInput={(event) => props.handleInput(event.currentTarget.value)} placeholder={props.autofocus ? null : `${props.link}${props.slug}`} /> diff --git a/src/context/profile.tsx b/src/context/profile.tsx index 3050dbc0..1af825ad 100644 --- a/src/context/profile.tsx +++ b/src/context/profile.tsx @@ -1,6 +1,6 @@ import type { ProfileInput } from '../graphql/types.gen' -import { createEffect, createMemo, createSignal } from 'solid-js' +import { createContext, createEffect, createMemo, JSX, useContext } from 'solid-js' import { createStore } from 'solid-js/store' import { loadAuthor, useAuthorsStore } from '../stores/zine/authors' @@ -8,54 +8,58 @@ import { apiClient } from '../utils/apiClient' import { useSession } from './session' +type ProfileFormContextType = { + form: ProfileInput + actions: { + setForm: (profile: ProfileInput) => void + submit: (profile: ProfileInput) => Promise + updateFormField: (fieldName: string, value: string, remove?: boolean) => void + } +} + +const ProfileFormContext = createContext() + +export function useProfileForm() { + return useContext(ProfileFormContext) +} + const userpicUrl = (userpic: string) => { if (userpic.includes('assets.discours.io')) { return userpic.replace('100x', '500x500') } return userpic } -const useProfileForm = () => { +export const ProfileFormProvider = (props: { children: JSX.Element }) => { const { session } = useSession() + const [form, setForm] = createStore({}) + const currentSlug = createMemo(() => session()?.user?.slug) - const { authorEntities } = useAuthorsStore({ authors: [] }) - const currentAuthor = createMemo(() => authorEntities()[currentSlug()]) - const [slugError, setSlugError] = createSignal() const submit = async (profile: ProfileInput) => { - const response = await apiClient.updateProfile(profile) - if (response.error) { - setSlugError(response.error) - return response.error + try { + await apiClient.updateProfile(profile) + } catch (error) { + console.error('[ProfileFormProvider]', error) + throw error } - return response } - const [form, setForm] = createStore({ - name: '', - bio: '', - about: '', - slug: '', - userpic: '', - links: [], - }) - createEffect(async () => { if (!currentSlug()) return try { - await loadAuthor({ slug: currentSlug() }) + const currentAuthor = await loadAuthor({ slug: currentSlug() }) setForm({ - name: currentAuthor()?.name, - slug: currentAuthor()?.slug, - bio: currentAuthor()?.bio, - about: currentAuthor()?.about, - userpic: userpicUrl(currentAuthor()?.userpic), - links: currentAuthor()?.links, + name: currentAuthor.name, + slug: currentAuthor.slug, + bio: currentAuthor.bio, + about: currentAuthor.about, + userpic: userpicUrl(currentAuthor.userpic), + links: currentAuthor.links, }) } catch (error) { console.error(error) } }) - const updateFormField = (fieldName: string, value: string, remove?: boolean) => { if (fieldName === 'links') { if (remove) { @@ -73,7 +77,14 @@ const useProfileForm = () => { } } - return { form, submit, updateFormField, slugError } -} + const value: ProfileFormContextType = { + form, + actions: { + submit, + updateFormField, + setForm, + }, + } -export { useProfileForm } + return {props.children} +} diff --git a/src/pages/profile/Settings.module.scss b/src/pages/profile/Settings.module.scss index 50473218..22040ba6 100644 --- a/src/pages/profile/Settings.module.scss +++ b/src/pages/profile/Settings.module.scss @@ -279,3 +279,25 @@ h5 { .socialInput { margin-top: 1rem; } + +.formActions { + background: var(--background-color); + position: sticky; + z-index: 12; + bottom: 0; + border-top: 2px solid var(--black-100); + margin-bottom: -40px; + + .content { + display: flex; + align-items: center; + justify-content: space-around; + flex-direction: row; + padding: 1rem 0; + gap: 1rem; + } + + .cancel { + margin-right: auto; + } +} diff --git a/src/pages/profile/profileSettings.page.tsx b/src/pages/profile/profileSettings.page.tsx index a51b5084..81398499 100644 --- a/src/pages/profile/profileSettings.page.tsx +++ b/src/pages/profile/profileSettings.page.tsx @@ -1,331 +1,18 @@ -import { createFileUploader } from '@solid-primitives/upload' -import { clsx } from 'clsx' -import deepEqual from 'fast-deep-equal' -import { For, createSignal, Show, onMount, onCleanup, createEffect, Switch, Match, lazy } from 'solid-js' -import { createStore } from 'solid-js/store' - -import FloatingPanel from '../../components/_shared/FloatingPanel/FloatingPanel' -import { Icon } from '../../components/_shared/Icon' -import { Loading } from '../../components/_shared/Loading' import { PageLayout } from '../../components/_shared/PageLayout' -import { Popover } from '../../components/_shared/Popover' -import { SocialNetworkInput } from '../../components/_shared/SocialNetworkInput' import { AuthGuard } from '../../components/AuthGuard' -import { ProfileSettingsNavigation } from '../../components/Nav/ProfileSettingsNavigation' +import { ProfileSettings } from '../../components/ProfileSettings' import { useLocalize } from '../../context/localize' -import { useProfileForm } from '../../context/profile' -import { useSession } from '../../context/session' -import { useSnackbar } from '../../context/snackbar' -import { clone } from '../../utils/clone' -import { getImageUrl } from '../../utils/getImageUrl' -import { handleImageUpload } from '../../utils/handleImageUpload' -import { profileSocialLinks } from '../../utils/profileSocialLinks' -import { validateUrl } from '../../utils/validateUrl' - -import styles from './Settings.module.scss' - -const SimplifiedEditor = lazy(() => import('../../components/Editor/SimplifiedEditor')) -const GrowingTextarea = lazy(() => import('../../components/_shared/GrowingTextarea/GrowingTextarea')) +import { ProfileFormProvider } from '../../context/profile' export const ProfileSettingsPage = () => { const { t } = useLocalize() - const [addLinkForm, setAddLinkForm] = createSignal(false) - const [incorrectUrl, setIncorrectUrl] = createSignal(false) - - const [isUserpicUpdating, setIsUserpicUpdating] = createSignal(false) - const [uploadError, setUploadError] = createSignal(false) - const [isFloatingPanelVisible, setIsFloatingPanelVisible] = createSignal(false) - - const { - actions: { showSnackbar }, - } = useSnackbar() - - const { - actions: { loadSession }, - } = useSession() - - const { form, updateFormField, submit, slugError } = useProfileForm() - const [prevForm, setPrevForm] = createStore(clone(form)) - const [social, setSocial] = createSignal(form.links) - const handleChangeSocial = (value: string) => { - if (validateUrl(value)) { - updateFormField('links', value) - setAddLinkForm(false) - } else { - setIncorrectUrl(true) - } - } - - const handleSubmit = async (event: Event) => { - event.preventDefault() - try { - await submit(form) - setPrevForm(clone(form)) - showSnackbar({ body: t('Profile successfully saved') }) - } catch { - showSnackbar({ type: 'error', body: t('Error') }) - } - - loadSession() - } - - const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' }) - - const handleUploadAvatar = async () => { - selectFiles(async ([uploadFile]) => { - try { - setUploadError(false) - setIsUserpicUpdating(true) - const result = await handleImageUpload(uploadFile) - updateFormField('userpic', result.url) - setIsUserpicUpdating(false) - setIsFloatingPanelVisible(true) - } catch (error) { - setUploadError(true) - console.error('[upload avatar] error', error) - } - }) - } - - const [hostname, setHostname] = createSignal(null) - - onMount(() => { - setHostname(window?.location.host) - - // eslint-disable-next-line unicorn/consistent-function-scoping - const handleBeforeUnload = (event) => { - if (!deepEqual(form, prevForm)) { - event.returnValue = t( - 'There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?', - ) - } - } - - window.addEventListener('beforeunload', handleBeforeUnload) - onCleanup(() => window.removeEventListener('beforeunload', handleBeforeUnload)) - }) - - const handleSaveProfile = () => { - setIsFloatingPanelVisible(false) - setPrevForm(clone(form)) - } - - createEffect(() => { - if (!deepEqual(form, prevForm)) { - setIsFloatingPanelVisible(true) - } - }) - - const handleDeleteSocialLink = (link) => { - updateFormField('links', link, true) - } - - createEffect(() => { - setSocial(form.links) - }) return ( - -
-
-
-
- -
-
-
-
-
-

{t('Profile settings')}

-

{t('Here you can customize your profile the way you want.')}

-
-

{t('Userpic')}

-
-
- - - - - -
-
- - {(triggerRef: (el) => void) => ( - - )} - - - {(triggerRef: (el) => void) => ( - - )} - -
- - - - {t('Here you can upload your photo')} - - -
- -
{t('Upload error')}
-
-
-

{t('Name')}

-

- {t( - 'Your name will appear on your profile page and as your signature in publications, comments and responses.', - )} -

-
- updateFormField('name', event.currentTarget.value)} - value={form.name} - /> - -
- -

{t('Address on Discourse')}

-
-
- -
- updateFormField('slug', event.currentTarget.value)} - value={form.slug} - class="nolabel" - /> - -

{t(`${slugError()}`)}

-
-
-
-
- -

{t('Introduce')}

- updateFormField('bio', value)} - initialValue={form.bio} - allowEnterKey={false} - maxLength={120} - /> - -

{t('About')}

- updateFormField('about', value)} - /> - {/*Нет реализации полей на бэке*/} - {/*

{t('How can I help/skills')}

*/} - {/*
*/} - {/* */} - {/*
*/} - {/*

{t('Where')}

*/} - {/*
*/} - {/* */} - {/* */} - {/*
*/} - - {/*

{t('Date of Birth')}

*/} - {/*
*/} - {/* */} - {/*
*/} - -
-
-

{t('Social networks')}

- -
- - handleChangeSocial(value)} - /> - -

{t('It does not look like url')}

-
-
- - {(network) => ( - handleChangeSocial(value)} - isExist={!network.isPlaceholder} - slug={form.slug} - handleDelete={() => handleDeleteSocialLink(network.link)} - /> - )} - -
-
- setIsFloatingPanelVisible(false)} - /> - -
-
-
-
-
- + + + ) diff --git a/src/stores/zine/authors.ts b/src/stores/zine/authors.ts index 816a043b..e031bf69 100644 --- a/src/stores/zine/authors.ts +++ b/src/stores/zine/authors.ts @@ -57,9 +57,10 @@ const addAuthors = (authors: Author[]) => { ) } -export const loadAuthor = async ({ slug }: { slug: string }): Promise => { +export const loadAuthor = async ({ slug }: { slug: string }): Promise => { const author = await apiClient.getAuthor({ slug }) addAuthors([author]) + return author } export const addAuthorsByTopic = (newAuthorsByTopic: { [topicSlug: string]: Author[] }) => { diff --git a/src/utils/apiClient.ts b/src/utils/apiClient.ts index 8ba484f8..5256ee95 100644 --- a/src/utils/apiClient.ts +++ b/src/utils/apiClient.ts @@ -70,6 +70,7 @@ type ApiErrorCode = | 'user_already_exists' | 'token_expired' | 'token_invalid' + | 'duplicate_slug' export class ApiError extends Error { code: ApiErrorCode @@ -249,6 +250,15 @@ export const apiClient = { }, updateProfile: async (input: ProfileInput) => { const response = await privateGraphQLClient.mutation(updateProfile, { profile: input }).toPromise() + if (response.error) { + if ( + response.error.message.includes('duplicate key value violates unique constraint "user_slug_key"') + ) { + throw new ApiError('duplicate_slug', response.error.message) + } + throw new ApiError('unknown', response.error.message) + } + return response.data.updateProfile }, getTopic: async ({ slug }: { slug: string }): Promise => { From f0c9f85546a08c92c785656ed4580de6fc9acad2 Mon Sep 17 00:00:00 2001 From: kvakazyambra Date: Thu, 23 Nov 2023 22:02:55 +0300 Subject: [PATCH 02/12] Fixed blocks bottom paddings for mobile --- src/components/Feed/Group.scss | 6 +++++- src/components/Feed/Row3.tsx | 4 +++- src/styles/app.scss | 10 ++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/components/Feed/Group.scss b/src/components/Feed/Group.scss index 196b0584..b875ec05 100644 --- a/src/components/Feed/Group.scss +++ b/src/components/Feed/Group.scss @@ -36,7 +36,11 @@ .floor--group { background: #e8e5f0; - padding: 4rem 0 3rem; + padding: 4rem 0 0; + + @include media-breakpoint-up(md) { + padding-bottom: 3rem; + } @include media-breakpoint-down(sm) { .col-lg-12 { diff --git a/src/components/Feed/Row3.tsx b/src/components/Feed/Row3.tsx index e4896b92..52045fa0 100644 --- a/src/components/Feed/Row3.tsx +++ b/src/components/Feed/Row3.tsx @@ -17,7 +17,9 @@ export const Row3 = (props: {
-
{props.header}
+ +
{props.header}
+
{(a) => (
diff --git a/src/styles/app.scss b/src/styles/app.scss index 12b1f0eb..376ab52d 100644 --- a/src/styles/app.scss +++ b/src/styles/app.scss @@ -719,6 +719,10 @@ figure { } } +.floor-header { + margin-bottom: 0 !important; +} + .floor { @include media-breakpoint-up(md) { margin-bottom: 6.4rem; @@ -810,6 +814,12 @@ figure { } .row { + @include media-breakpoint-down(md) { + > * { + margin-bottom: 2.4rem; + } + } + @include media-breakpoint-down(sm) { margin-left: divide(-$container-padding-x, 2); margin-right: divide(-$container-padding-x, 2); From abd0ab7ccf019ed6bb666937197d8803cb55a059 Mon Sep 17 00:00:00 2001 From: kvakazyambra Date: Mon, 27 Nov 2023 23:56:26 +0300 Subject: [PATCH 03/12] Fixed header line on mobile --- src/components/Nav/Header/Header.module.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/Nav/Header/Header.module.scss b/src/components/Nav/Header/Header.module.scss index d4e0a5c1..b670f19e 100644 --- a/src/components/Nav/Header/Header.module.scss +++ b/src/components/Nav/Header/Header.module.scss @@ -61,6 +61,10 @@ border-bottom: 2px solid #000; } } + + > * { + margin-bottom: 0 !important; + } } .mainLogo { From 70adae46a89a91cbbffe9eb6a57378ec6e93e7ba Mon Sep 17 00:00:00 2001 From: kvakazyambra Date: Mon, 27 Nov 2023 23:59:07 +0300 Subject: [PATCH 04/12] Fixed hover color on in the popup buttons on the feed page --- src/components/Feed/FeedArticlePopup.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Feed/FeedArticlePopup.module.scss b/src/components/Feed/FeedArticlePopup.module.scss index 89aab48d..b2cfa6d3 100644 --- a/src/components/Feed/FeedArticlePopup.module.scss +++ b/src/components/Feed/FeedArticlePopup.module.scss @@ -19,7 +19,7 @@ &:hover { background: #000; - color: #fff; + color: #fff !important; } } } From dd2746e19eb1192a974d7819875323881d171af9 Mon Sep 17 00:00:00 2001 From: kvakazyambra Date: Tue, 28 Nov 2023 00:44:58 +0300 Subject: [PATCH 05/12] Fixed profile settings style --- .../Author/AuthorCard/AuthorCard.module.scss | 3 +- .../ProfileSettings/ProfileSettings.tsx | 35 +++++++++++-------- src/context/profile.tsx | 2 +- src/pages/profile/Settings.module.scss | 9 +++-- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/components/Author/AuthorCard/AuthorCard.module.scss b/src/components/Author/AuthorCard/AuthorCard.module.scss index eee554b7..98e1206e 100644 --- a/src/components/Author/AuthorCard/AuthorCard.module.scss +++ b/src/components/Author/AuthorCard/AuthorCard.module.scss @@ -420,7 +420,6 @@ flex-wrap: wrap; font-size: 1.4rem; margin-top: 1.5rem; - gap: 1rem; @include media-breakpoint-down(md) { justify-content: center; @@ -431,7 +430,7 @@ align-items: center; cursor: pointer; display: inline-flex; - margin-right: 3rem; + margin: 0 2% 1rem; vertical-align: top; border-bottom: unset !important; diff --git a/src/components/ProfileSettings/ProfileSettings.tsx b/src/components/ProfileSettings/ProfileSettings.tsx index 08e5aeb7..4d515194 100644 --- a/src/components/ProfileSettings/ProfileSettings.tsx +++ b/src/components/ProfileSettings/ProfileSettings.tsx @@ -315,19 +315,21 @@ export const ProfileSettings = () => {

{t('It does not look like url')}

- - {(network) => ( - handleChangeSocial(value)} - isExist={!network.isPlaceholder} - slug={form.slug} - handleDelete={() => handleDeleteSocialLink(network.link)} - /> - )} - + + + {(network) => ( + handleChangeSocial(value)} + isExist={!network.isPlaceholder} + slug={form.slug} + handleDelete={() => handleDeleteSocialLink(network.link)} + /> + )} + +
@@ -343,7 +345,12 @@ export const ProfileSettings = () => {
-
diff --git a/src/context/profile.tsx b/src/context/profile.tsx index 1af825ad..12a3d08d 100644 --- a/src/context/profile.tsx +++ b/src/context/profile.tsx @@ -24,7 +24,7 @@ export function useProfileForm() { } const userpicUrl = (userpic: string) => { - if (userpic.includes('assets.discours.io')) { + if (userpic && userpic.includes('assets.discours.io')) { return userpic.replace('100x', '500x500') } return userpic diff --git a/src/pages/profile/Settings.module.scss b/src/pages/profile/Settings.module.scss index 22040ba6..4ea3cb1d 100644 --- a/src/pages/profile/Settings.module.scss +++ b/src/pages/profile/Settings.module.scss @@ -291,13 +291,18 @@ h5 { .content { display: flex; align-items: center; - justify-content: space-around; + justify-content: space-between; flex-direction: row; padding: 1rem 0; gap: 1rem; } .cancel { - margin-right: auto; + color: #d00820; + padding: 0.8rem 0 !important; + } + + :global(.row) > * { + margin-bottom: 0; } } From 7ba3d767d76d0b1bfdd0fcc8d9120d1f85edc9e2 Mon Sep 17 00:00:00 2001 From: kvakazyambra Date: Tue, 28 Nov 2023 20:06:13 +0300 Subject: [PATCH 06/12] Profile head style fixes --- .../Author/AuthorCard/AuthorCard.module.scss | 17 ++++++++++++++++- src/components/Author/AuthorCard/AuthorCard.tsx | 11 +++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/components/Author/AuthorCard/AuthorCard.module.scss b/src/components/Author/AuthorCard/AuthorCard.module.scss index 98e1206e..a903b53b 100644 --- a/src/components/Author/AuthorCard/AuthorCard.module.scss +++ b/src/components/Author/AuthorCard/AuthorCard.module.scss @@ -43,8 +43,23 @@ } } + .authorActionsLabel { + @include media-breakpoint-down(sm) { + display: none; + } + } + + .authorActionsLabelMobile { + display: none; + + @include media-breakpoint-down(sm) { + display: block; + } + } + .authorDetails { display: block; + margin-bottom: 0; @include media-breakpoint-down(md) { flex: 1 100%; @@ -403,7 +418,7 @@ @include media-breakpoint-down(sm) { flex: 1 100%; justify-content: center; - margin-top: 1em; + margin-top: 1rem; } @include media-breakpoint-down(md) { diff --git a/src/components/Author/AuthorCard/AuthorCard.tsx b/src/components/Author/AuthorCard/AuthorCard.tsx index e38ef986..65f4353a 100644 --- a/src/components/Author/AuthorCard/AuthorCard.tsx +++ b/src/components/Author/AuthorCard/AuthorCard.tsx @@ -131,7 +131,9 @@ export const AuthorCard = (props: Props) => {
{name()}
-
+ +
+ 0) || @@ -237,7 +239,12 @@ export const AuthorCard = (props: Props) => {
- handleMediaDelete(index)} diff --git a/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx b/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx index ef63085e..9df151cf 100644 --- a/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx +++ b/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx @@ -36,7 +36,7 @@ export const ArticleCardSwiper = (props: Props) => { ref={(el) => (mainSwipeRef.current = el)} centered-slides={true} observer={true} - space-between={20} + space-between={10} breakpoints={{ 576: { spaceBetween: 20, slidesPerView: 1.5 }, 992: { spaceBetween: 52, slidesPerView: 1.5 }, @@ -44,13 +44,11 @@ export const ArticleCardSwiper = (props: Props) => { round-lengths={true} loop={true} speed={800} - /* autoplay={{ disableOnInteraction: false, delay: 6000, - pauseOnMouseEnter: true + pauseOnMouseEnter: true, }} -*/ > {(slide, index) => ( diff --git a/src/components/_shared/SolidSwiper/EditorSwiper.tsx b/src/components/_shared/SolidSwiper/EditorSwiper.tsx new file mode 100644 index 00000000..dc9f06e6 --- /dev/null +++ b/src/components/_shared/SolidSwiper/EditorSwiper.tsx @@ -0,0 +1,326 @@ +import { createFileUploader } from '@solid-primitives/upload' +import { clsx } from 'clsx' +import { createEffect, createSignal, For, Show, on, onMount, lazy } from 'solid-js' +import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper' + +import { useLocalize } from '../../../context/localize' +import { useSnackbar } from '../../../context/snackbar' +import { MediaItem, UploadedFile } from '../../../pages/types' +import { composeMediaItems } from '../../../utils/composeMediaItems' +import { getImageUrl } from '../../../utils/getImageUrl' +import { handleImageUpload } from '../../../utils/handleImageUpload' +import { validateFiles } from '../../../utils/validateFile' +import { DropArea } from '../DropArea' +import { Icon } from '../Icon' +import { Image } from '../Image' +import { Loading } from '../Loading' +import { Popover } from '../Popover' + +import { SwiperRef } from './swiper' + +import styles from './Swiper.module.scss' + +const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) + +type Props = { + images: MediaItem[] + onImagesAdd?: (value: MediaItem[]) => void + onImagesSorted?: (value: MediaItem[]) => void + onImageDelete?: (mediaItemIndex: number) => void + onImageChange?: (index: number, value: MediaItem) => void +} + +export const EditorSwiper = (props: Props) => { + const { t } = useLocalize() + const [loading, setLoading] = createSignal(false) + const [slideIndex, setSlideIndex] = createSignal(0) + const [slideBody, setSlideBody] = createSignal() + + const mainSwipeRef: { current: SwiperRef } = { current: null } + const thumbSwipeRef: { current: SwiperRef } = { current: null } + + const { + actions: { showSnackbar }, + } = useSnackbar() + + const handleSlideDescriptionChange = (index: number, field: string, value) => { + if (props.onImageChange) { + props.onImageChange(index, { ...props.images[index], [field]: value }) + } + } + const swipeToUploaded = () => { + setTimeout(() => { + mainSwipeRef.current.swiper.slideTo(props.images.length - 1) + }, 0) + } + const handleSlideChange = () => { + thumbSwipeRef.current.swiper.slideTo(mainSwipeRef.current.swiper.activeIndex) + setSlideIndex(mainSwipeRef.current.swiper.activeIndex) + } + + createEffect( + on( + () => props.images.length, + () => { + mainSwipeRef.current?.swiper.update() + thumbSwipeRef.current?.swiper.update() + }, + { defer: true }, + ), + ) + const handleDropAreaUpload = (value: UploadedFile[]) => { + props.onImagesAdd(composeMediaItems(value)) + swipeToUploaded() + } + + const handleDelete = (index: number) => { + props.onImageDelete(index) + + if (index === 0) { + mainSwipeRef.current.swiper.update() + } else { + mainSwipeRef.current.swiper.slideTo(index - 1) + } + } + + const { selectFiles } = createFileUploader({ + multiple: true, + accept: `image/*`, + }) + + const initUpload = async (selectedFiles) => { + const isValid = validateFiles('image', selectedFiles) + + if (!isValid) { + await showSnackbar({ type: 'error', body: t('Invalid file type') }) + setLoading(false) + return + } + + try { + setLoading(true) + const results: UploadedFile[] = [] + for (const file of selectedFiles) { + const result = await handleImageUpload(file) + results.push(result) + } + props.onImagesAdd(composeMediaItems(results)) + setLoading(false) + swipeToUploaded() + } catch (error) { + console.error('[runUpload]', error) + showSnackbar({ type: 'error', body: t('Error') }) + setLoading(false) + } + } + const handleUploadThumb = async () => { + selectFiles((selectedFiles) => { + initUpload(selectedFiles) + }) + } + + const handleChangeIndex = (direction: 'left' | 'right', index: number) => { + const images = [...props.images] + if (direction === 'left' && index > 0) { + const copy = images.splice(index, 1)[0] + images.splice(index - 1, 0, copy) + } else if (direction === 'right' && index < images.length - 1) { + const copy = images.splice(index, 1)[0] + images.splice(index + 1, 0, copy) + } + props.onImagesSorted(images) + setTimeout(() => { + mainSwipeRef.current.swiper.slideTo(direction === 'left' ? index - 1 : index + 1) + }, 0) + } + + const handleSaveBeforeSlideChange = () => { + handleSlideDescriptionChange(slideIndex(), 'body', slideBody()) + } + + onMount(async () => { + const { register } = await import('swiper/element/bundle') + register() + SwiperCore.use([Pagination, Navigation, Manipulation]) + }) + + return ( +
+
+ + + {t('You can upload up to 100 images in .jpg, .png format.')} +
+ {t('Each image must be no larger than 5 MB.')} +
+ } + /> + + 0}> +
+ (mainSwipeRef.current = el)} + slides-per-view={1} + thumbs-swiper={'.thumbSwiper'} + observer={true} + onSlideChange={handleSlideChange} + onBeforeSlideChangeStart={handleSaveBeforeSlideChange} + space-between={20} + > + + {(slide, index) => ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + +
+ {slide.title} + + + {(triggerRef: (el) => void) => ( +
handleDelete(index())} class={styles.action}> + +
+ )} +
+
+
+ )} +
+
+
mainSwipeRef.current.swiper.slidePrev()} + > + +
+
mainSwipeRef.current.swiper.slideNext()} + > + +
+
+ {slideIndex() + 1} / {props.images.length} +
+
+
+
+ (thumbSwipeRef.current = el)} + slides-per-view={'auto'} + space-between={20} + auto-scroll-offset={1} + watch-overflow={true} + watch-slides-visibility={true} + direction={'horizontal'} + slides-offset-after={160} + slides-offset-before={30} + > + + {(slide, index) => ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + +
+
+
handleDelete(index())}> + +
+
handleChangeIndex('left', index())} + > + +
+
handleChangeIndex('right', index())} + > + +
+
+
+
+ )} +
+ +
+
+ }> + + +
+
+
+
thumbSwipeRef.current.swiper.slidePrev()} + > + +
+
thumbSwipeRef.current.swiper.slideNext()} + > + +
+
+
+
+
+ + 0}> +
+ handleSlideDescriptionChange(slideIndex(), 'title', event.target.value)} + /> + handleSlideDescriptionChange(slideIndex(), 'source', event.target.value)} + /> + setSlideBody(value)} + /> +
+
+
+ ) +} diff --git a/src/components/_shared/SolidSwiper/ImageSwiper.tsx b/src/components/_shared/SolidSwiper/ImageSwiper.tsx index 695b0d6d..d8861f32 100644 --- a/src/components/_shared/SolidSwiper/ImageSwiper.tsx +++ b/src/components/_shared/SolidSwiper/ImageSwiper.tsx @@ -1,59 +1,34 @@ -import { createFileUploader } from '@solid-primitives/upload' import { clsx } from 'clsx' -import { createEffect, createSignal, For, Show, on, onMount, lazy } from 'solid-js' +import { createEffect, createSignal, For, Show, on, onMount, lazy, onCleanup } from 'solid-js' import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper' +import { throttle } from 'throttle-debounce' -import { useLocalize } from '../../../context/localize' -import { useSnackbar } from '../../../context/snackbar' -import { MediaItem, UploadedFile } from '../../../pages/types' -import { composeMediaItems } from '../../../utils/composeMediaItems' +import { MediaItem } from '../../../pages/types' import { getImageUrl } from '../../../utils/getImageUrl' -import { handleImageUpload } from '../../../utils/handleImageUpload' -import { validateFiles } from '../../../utils/validateFile' -import { DropArea } from '../DropArea' import { Icon } from '../Icon' import { Image } from '../Image' -import { Loading } from '../Loading' -import { Popover } from '../Popover' import { SwiperRef } from './swiper' import styles from './Swiper.module.scss' -const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) - type Props = { images: MediaItem[] - editorMode?: boolean onImagesAdd?: (value: MediaItem[]) => void onImagesSorted?: (value: MediaItem[]) => void onImageDelete?: (mediaItemIndex: number) => void onImageChange?: (index: number, value: MediaItem) => void } -export const ImageSwiper = (props: Props) => { - const { t } = useLocalize() - const [loading, setLoading] = createSignal(false) - const [slideIndex, setSlideIndex] = createSignal(0) - const [slideBody, setSlideBody] = createSignal() +const MIN_WIDTH = 540 +export const ImageSwiper = (props: Props) => { + const [slideIndex, setSlideIndex] = createSignal(0) + const [isMobileView, setIsMobileView] = createSignal(false) const mainSwipeRef: { current: SwiperRef } = { current: null } const thumbSwipeRef: { current: SwiperRef } = { current: null } + const swiperMainContainer: { current: HTMLDivElement } = { current: null } - const { - actions: { showSnackbar }, - } = useSnackbar() - - const handleSlideDescriptionChange = (index: number, field: string, value) => { - if (props.onImageChange) { - props.onImageChange(index, { ...props.images[index], [field]: value }) - } - } - const swipeToUploaded = () => { - setTimeout(() => { - mainSwipeRef.current.swiper.slideTo(props.images.length - 1) - }, 0) - } const handleSlideChange = () => { thumbSwipeRef.current.swiper.slideTo(mainSwipeRef.current.swiper.activeIndex) setSlideIndex(mainSwipeRef.current.swiper.activeIndex) @@ -69,99 +44,41 @@ export const ImageSwiper = (props: Props) => { { defer: true }, ), ) - const handleDropAreaUpload = (value: UploadedFile[]) => { - props.onImagesAdd(composeMediaItems(value)) - swipeToUploaded() - } - - const handleDelete = (index: number) => { - props.onImageDelete(index) - - if (index === 0) { - mainSwipeRef.current.swiper.update() - } else { - mainSwipeRef.current.swiper.slideTo(index - 1) - } - } - - const { selectFiles } = createFileUploader({ - multiple: true, - accept: `image/*`, - }) - - const initUpload = async (selectedFiles) => { - const isValid = validateFiles('image', selectedFiles) - if (isValid) { - try { - setLoading(true) - const results: UploadedFile[] = [] - for (const file of selectedFiles) { - const result = await handleImageUpload(file) - results.push(result) - } - props.onImagesAdd(composeMediaItems(results)) - setLoading(false) - swipeToUploaded() - } catch (error) { - await showSnackbar({ type: 'error', body: t('Error') }) - console.error('[runUpload]', error) - setLoading(false) - } - } else { - await showSnackbar({ type: 'error', body: t('Invalid file type') }) - setLoading(false) - return false - } - } - const handleUploadThumb = async () => { - selectFiles((selectedFiles) => { - initUpload(selectedFiles) - }) - } - - const handleChangeIndex = (direction: 'left' | 'right', index: number) => { - const images = [...props.images] - if (direction === 'left' && index > 0) { - const copy = images.splice(index, 1)[0] - images.splice(index - 1, 0, copy) - } else if (direction === 'right' && index < images.length - 1) { - const copy = images.splice(index, 1)[0] - images.splice(index + 1, 0, copy) - } - props.onImagesSorted(images) - setTimeout(() => { - mainSwipeRef.current.swiper.slideTo(direction === 'left' ? index - 1 : index + 1) - }, 0) - } - - const handleSaveBeforeSlideChange = () => { - handleSlideDescriptionChange(slideIndex(), 'body', slideBody()) - } onMount(async () => { const { register } = await import('swiper/element/bundle') register() - SwiperCore.use([Pagination, Navigation, Manipulation]) + SwiperCore.use([Pagination, Navigation, Manipulation, ResizeObserver]) + }) + + onMount(() => { + const updateDirection = () => { + const width = window.innerWidth + const direction = width > MIN_WIDTH ? 'vertical' : 'horizontal' + if (direction === 'horizontal') { + setIsMobileView(true) + } else { + setIsMobileView(false) + } + thumbSwipeRef.current?.swiper?.changeDirection(direction) + } + + updateDirection() + + const handleResize = throttle(100, () => { + updateDirection() + }) + + window.addEventListener('resize', handleResize) + + onCleanup(() => { + window.removeEventListener('resize', handleResize) + }) }) return ( -
-
- - - {t('You can upload up to 100 images in .jpg, .png format.')} -
- {t('Each image must be no larger than 5 MB.')} -
- } - /> - +
+
(swiperMainContainer.current = el)}> 0}>
{ thumbs-swiper={'.thumbSwiper'} observer={true} onSlideChange={handleSlideChange} - onBeforeSlideChangeStart={handleSaveBeforeSlideChange} - space-between={20} + space-between={isMobileView() ? 20 : 10} > {(slide, index) => ( @@ -180,19 +96,6 @@ export const ImageSwiper = (props: Props) => {
{slide.title} - - - {(triggerRef: (el) => void) => ( -
handleDelete(index())} - class={styles.action} - > - -
- )} -
-
)} @@ -224,13 +127,10 @@ export const ImageSwiper = (props: Props) => { class={'thumbSwiper'} ref={(el) => (thumbSwipeRef.current = el)} slides-per-view={'auto'} - space-between={20} + space-between={isMobileView() ? 20 : 10} auto-scroll-offset={1} watch-overflow={true} watch-slides-visibility={true} - direction={props.editorMode ? 'horizontal' : 'vertical'} - slides-offset-after={props.editorMode && 160} - slides-offset-before={props.editorMode && 30} > {(slide, index) => ( @@ -242,47 +142,10 @@ export const ImageSwiper = (props: Props) => { style={{ 'background-image': `url(${getImageUrl(slide.url, { width: 110, height: 75 })})`, }} - > - -
-
handleDelete(index())}> - -
-
handleChangeIndex('left', index())} - > - -
-
handleChangeIndex('right', index())} - > - -
-
-
-
+ /> )} - -
-
- }> - - -
-
-
{
- - -
{props.images[slideIndex()].title}
-
- -
{props.images[slideIndex()].source}
-
- -
- -
- } - > - 0}> -
- handleSlideDescriptionChange(slideIndex(), 'title', event.target.value)} - /> - handleSlideDescriptionChange(slideIndex(), 'source', event.target.value)} - /> - setSlideBody(value)} - /> -
+
+ +
{props.images[slideIndex()].title}
- + +
{props.images[slideIndex()].source}
+
+ +
+ +
) } diff --git a/src/components/_shared/SolidSwiper/Swiper.module.scss b/src/components/_shared/SolidSwiper/Swiper.module.scss index e953b565..6ae644b0 100644 --- a/src/components/_shared/SolidSwiper/Swiper.module.scss +++ b/src/components/_shared/SolidSwiper/Swiper.module.scss @@ -38,7 +38,6 @@ } .container { - // max-width: 800px; margin: auto; position: relative; padding: 24px 0; @@ -48,6 +47,7 @@ width: 100%; .thumbsHolder { + min-width: 110px; width: auto; } @@ -60,13 +60,6 @@ margin: 0; position: relative; - & > swiper-container { - position: absolute; - top: 52px; - bottom: 52px; - left: 0; - } - .thumbsNav { height: 52px; padding: 14px 0; @@ -92,6 +85,48 @@ } } } + &.mobileView { + .container { + flex-direction: column-reverse; + padding: 0; + + .thumbsHolder { + min-width: unset; + } + + .thumbs { + width: 100%; + height: 80px; + padding: 0; + + & swiper-slide { + //bind to html element + width: unset !important; + } + + .thumbsNav { + height: 100%; + padding: 0; + width: 40px; + + .icon { + transform: none; + } + + &.prev { + top: 0; + left: 0; + } + + &.next { + top: 0; + right: 0; + left: unset; + } + } + } + } + } } &.editorMode { diff --git a/src/components/_shared/SolidSwiper/index.ts b/src/components/_shared/SolidSwiper/index.ts index 54c84efc..b8d29439 100644 --- a/src/components/_shared/SolidSwiper/index.ts +++ b/src/components/_shared/SolidSwiper/index.ts @@ -1 +1,2 @@ export { ImageSwiper } from './ImageSwiper' +export { EditorSwiper } from './EditorSwiper' From 998b190f04f9594164699d36d0b080265200489e Mon Sep 17 00:00:00 2001 From: Ilya Y <75578537+ilya-bkv@users.noreply.github.com> Date: Mon, 4 Dec 2023 13:34:37 +0300 Subject: [PATCH 12/12] ResizeObserver remove (#328) --- src/components/_shared/SolidSwiper/ImageSwiper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/_shared/SolidSwiper/ImageSwiper.tsx b/src/components/_shared/SolidSwiper/ImageSwiper.tsx index d8861f32..888929c6 100644 --- a/src/components/_shared/SolidSwiper/ImageSwiper.tsx +++ b/src/components/_shared/SolidSwiper/ImageSwiper.tsx @@ -48,7 +48,7 @@ export const ImageSwiper = (props: Props) => { onMount(async () => { const { register } = await import('swiper/element/bundle') register() - SwiperCore.use([Pagination, Navigation, Manipulation, ResizeObserver]) + SwiperCore.use([Pagination, Navigation, Manipulation]) }) onMount(() => {