This commit is contained in:
2024-05-20 15:37:46 +03:00
commit 00b7dbd0b7
10404 changed files with 3285853 additions and 0 deletions

View File

@ -0,0 +1,342 @@
import './style.scss';
import { Button, ToggleControl } from '@wordpress/components';
import { useCallback, useEffect, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import ControlsRender from '../controls-render';
import StepsWizard from './steps-wizard';
const { plugin_name: pluginName } = window.VPGutenbergVariables;
const NOTICE_LIMIT = parseInt(
window.VPGutenbergVariables.items_count_notice_limit,
10
);
function renderControls(props, category) {
return <ControlsRender {...props} category={category} isSetupWizard />;
}
function hasLayoutElement(element, attributes) {
const { layout_elements: layoutElements } = attributes;
const checkIn = element === 'filter' ? 'top' : 'bottom';
return (
typeof layoutElements[checkIn] !== 'undefined' &&
layoutElements[checkIn]?.elements.includes(element)
);
}
function toggleLayoutElement(element, attributes) {
const { layout_elements: layoutElements } = attributes;
const checkIn = element === 'filter' ? 'top' : 'bottom';
if (
typeof layoutElements[checkIn] === 'undefined' ||
!layoutElements[checkIn]?.elements
) {
return layoutElements;
}
const result = JSON.parse(JSON.stringify(layoutElements));
if (hasLayoutElement(element, attributes)) {
result[checkIn].elements = [];
} else {
result[checkIn].elements = [element];
}
return result;
}
/**
* Component Class
*
* @param props
*/
export default function SetupWizard(props) {
const { attributes, setAttributes } = props;
const {
align,
content_source: contentSource,
items_click_action: clickAction,
layout,
images,
} = attributes;
const [step, setStep] = useState(0);
const [allowNextStep, setAllowNextStep] = useState(false);
const maxSteps = 2.5;
// Add startup attributes.
useEffect(() => {
if (!align && !contentSource) {
setAttributes({ align: 'wide' });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Set some starter attributes for different content sources.
// And hide the setup wizard.
const setStarterAttributes = useCallback(() => {
let newAttributes = {};
switch (contentSource) {
case 'images':
// Hide setup wizard once user select images.
if (images && images.length) {
newAttributes = {
...newAttributes,
items_count: -1,
items_click_action: 'popup_gallery',
};
// Add infinite scroll to the gallery when user adds a lot of images.
if (layout !== 'slider' && images.length > NOTICE_LIMIT) {
newAttributes = {
...newAttributes,
items_count: NOTICE_LIMIT,
layout_elements: {
top: {
elements: [],
align: 'center',
},
items: {
elements: ['items'],
},
bottom: {
elements: ['pagination'],
align: 'center',
},
},
pagination: 'infinite',
pagination_hide_on_end: true,
};
}
}
break;
case 'post-based':
case 'social-stream':
newAttributes = {
...newAttributes,
layout_elements: {
top: {
elements: [],
align: 'center',
},
items: {
elements: ['items'],
},
bottom: {
elements: ['pagination'],
align: 'center',
},
},
};
break;
// no default
}
// Prepare better default settings for Popup.
// We can't change defaults of registered controls because it may break existing user galleries.
// This is why we change it here, in the Setup Wizard.
newAttributes = {
...newAttributes,
items_click_action_popup_title_source:
contentSource === 'post-based' ? 'title' : 'item_title',
items_click_action_popup_description_source:
contentSource === 'post-based'
? 'description'
: 'item_description',
items_click_action_popup_deep_link_pid: 'filename',
};
setAttributes(newAttributes);
setAllowNextStep(true);
}, [contentSource, images, layout, setAttributes]);
useEffect(() => {
if (contentSource) {
setStarterAttributes();
}
// We have to check for contentSource change here because we don't want to run this on every render.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [contentSource]);
return (
<div className={`vpf-setup-wizard vpf-setup-wizard-step-${step}`}>
<StepsWizard step={step}>
{/* Step 0: Content Source */}
<StepsWizard.Step>
<div className="vpf-setup-wizard-title">{pluginName}</div>
<div
className="vpf-setup-wizard-description"
dangerouslySetInnerHTML={{
__html: __(
'Set the common settings in the setup wizard,<br />more options will be available in the block settings.<br />Select the Content Source first:',
'visual-portfolio'
),
}}
/>
{renderControls(props, 'content-source')}
{renderControls(props, 'content-source-images')}
{renderControls(props, 'content-source-social-stream')}
</StepsWizard.Step>
{/* Step 1: Items Style */}
<StepsWizard.Step>
<div className="vpf-setup-wizard-title">
{__('Items Style', 'visual-portfolio')}
</div>
<div
className="vpf-setup-wizard-description"
dangerouslySetInnerHTML={{
__html: __(
'Select one of the featured styles to get started.<br />More style settings will be available in the block settings.',
'visual-portfolio'
),
}}
/>
{renderControls(props, 'items-style')}
</StepsWizard.Step>
{/* Step 2: Layout Elements */}
<StepsWizard.Step>
<div className="vpf-setup-wizard-title">
{__('Additional Settings', 'visual-portfolio')}
</div>
<div className="vpf-setup-wizard-layout-elements">
<div>
<ToggleControl
label={__('Filter', 'visual-portfolio')}
checked={hasLayoutElement('filter', attributes)}
onChange={() => {
setAttributes({
layout_elements: toggleLayoutElement(
'filter',
attributes
),
});
}}
/>
</div>
<div>
<ToggleControl
label={__('Pagination', 'visual-portfolio')}
checked={hasLayoutElement(
'pagination',
attributes
)}
onChange={() => {
setAttributes({
layout_elements: toggleLayoutElement(
'pagination',
attributes
),
});
}}
/>
</div>
<div>
<ToggleControl
label={__('Popup Gallery', 'visual-portfolio')}
checked={clickAction === 'popup_gallery'}
onChange={() => {
setAttributes({
items_click_action:
clickAction === 'popup_gallery'
? 'url'
: 'popup_gallery',
});
}}
/>
</div>
</div>
</StepsWizard.Step>
</StepsWizard>
{/* Pagination */}
<div className="vpf-setup-wizard-pagination">
{contentSource ? (
<>
<div className="vpf-setup-wizard-pagination-button">
<Button
isLink
onClick={() => {
// Skip Setup
if (step === 0) {
setAttributes({
setup_wizard: '',
content_source:
attributes.contentSource ||
'images',
});
// Previous Step
} else {
setStep(step - 1);
}
}}
>
{step === 0
? __('Skip Setup', 'visual-portfolio')
: __('Previous Step', 'visual-portfolio')}
</Button>
</div>
<div className="vpf-setup-wizard-pagination-progress">
<div
style={{
width: `${Math.max(
15,
Math.min(100, (100 * step) / maxSteps)
)}%`,
}}
/>
</div>
<div className="vpf-setup-wizard-pagination-button vpf-setup-wizard-pagination-button-end">
<Button
isPrimary
disabled={!allowNextStep}
onClick={() => {
if (step === 2) {
setAttributes({ setup_wizard: '' });
} else {
setStep(step + 1);
}
}}
>
{__('Continue', 'visual-portfolio')}
<svg
width="14"
height="14"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
style={{ marginLeft: '5px' }}
>
<path
d="M3 10H17"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
/>
<path
d="M11 4L17 10L11 16"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
/>
</svg>
</Button>
</div>
</>
) : null}
</div>
</div>
);
}

View File

@ -0,0 +1,92 @@
import classnames from 'classnames/dedupe';
import { debounce } from 'throttle-debounce';
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
const { ResizeObserver, MutationObserver } = window;
function stepsWizard(props) {
const { step, children } = props;
const $ref = useRef();
const [newStep, setNewStep] = useState(step);
const [height, setHeight] = useState(0);
const maybeUpdateHeight = useCallback(() => {
let newHeight = 0;
$ref.current.childNodes.forEach(($child) => {
const styles = window.getComputedStyle($child);
const margin =
parseFloat(styles.marginTop) + parseFloat(styles.marginBottom);
newHeight += Math.ceil($child.offsetHeight + margin);
});
setHeight(`${newHeight}px`);
}, [$ref]);
useEffect(() => {
if (step !== newStep) {
setNewStep(step);
}
}, [step, newStep]);
useEffect(() => {
maybeUpdateHeight();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [newStep]);
useEffect(() => {
const $element = $ref.current;
const calculateHeight = debounce(100, () => {
maybeUpdateHeight();
});
// Resize observer is used to properly set height
// when selected images, saved post and reloaded page.
const resizeObserver = new ResizeObserver(calculateHeight);
const mutationObserver = new MutationObserver(calculateHeight);
resizeObserver.observe($element);
mutationObserver.observe($element, {
attributes: true,
characterData: true,
childList: true,
subtree: true,
attributeOldValue: true,
characterDataOldValue: true,
});
return () => {
resizeObserver.disconnect();
mutationObserver.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [$ref.current]);
return typeof children[newStep] !== 'undefined' ? (
<div
ref={$ref}
className={classnames(
'vpf-component-steps-wizard',
step !== newStep
? `vpf-component-steps-wizard-animate-${
newStep > step ? 'left' : 'right'
}`
: false
)}
style={height ? { height } : {}}
>
{children[newStep]}
</div>
) : null;
}
stepsWizard.Step = function (props) {
const { children } = props;
return children;
};
export default stepsWizard;

View File

@ -0,0 +1,286 @@
@use "sass:color";
$brand_color: #2540cc !default;
.vpf-setup-wizard {
--wp-admin-theme-color: #{$brand_color};
padding: 20px;
padding-top: 50px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
color: #000;
text-align: center;
background-color: #fff;
border: 1px solid #7e7e7e;
border-radius: 6px;
// Hide default Gutenberg outline and use our own.
.wp-block-visual-portfolio-block.is-selected:has(> &) {
&::after {
content: none !important;
}
.vpf-setup-wizard {
border-color: $brand_color;
}
}
> div {
max-width: 520px;
margin: 0 auto;
}
.vpf-setup-wizard-title {
margin-bottom: 15px;
font-size: 18px;
font-weight: 500;
}
.vpf-setup-wizard-description {
font-size: 14px;
opacity: 0.75;
}
.vpf-setup-wizard-panel {
margin-top: 25px;
}
// Pagination.
.vpf-setup-wizard-pagination {
display: flex;
align-items: center;
justify-content: space-between;
max-width: none;
margin-top: 40px;
}
.vpf-setup-wizard-pagination-progress {
width: 130px;
height: 2px;
background-color: #eaeaea;
> div {
height: 2px;
background-color: $brand_color;
transition: 0.3s width ease;
}
}
.vpf-setup-wizard-pagination-button {
display: flex;
justify-content: flex-start;
min-width: 100px;
.components-button {
height: 34px;
padding: 6px 20px;
&.is-link {
padding: 0;
font-weight: 400;
color: #b9b9b9;
text-decoration: none;
&:hover,
&:focus {
color: $brand_color;
}
}
}
.components-button.is-primary {
background-color: $brand_color;
&:disabled {
background-color: $brand_color;
border-color: $brand_color;
svg {
color: #fff;
opacity: 0.4;
}
}
&:hover:not(:disabled) {
background: color.adjust($brand_color, $lightness: -10%);
}
&:focus:not(:disabled) {
box-shadow: inset 0 0 0 1px #fff, 0 0 0 1.5px $brand_color;
}
}
}
.vpf-setup-wizard-pagination-button-end {
justify-content: flex-end;
}
// Icons Selector
.vpf-component-icon-selector {
display: flex;
justify-content: center;
.vpf-component-icon-selector-item {
padding: 15px 20px;
background: none !important;
border-color: #fff;
svg {
width: 44px;
max-width: 44px;
height: 44px;
color: #2b2b2b;
transition: 0.2s color;
}
span {
font-size: 0.95em;
font-weight: 500;
text-transform: initial;
transition: 0.2s color;
}
&:hover,
&:focus {
svg,
span {
color: #1e1e1e;
}
}
&.vpf-component-icon-selector-item-active {
border-color: $brand_color !important;
box-shadow: 0 0 0 1px $brand_color !important;
svg,
span {
color: $brand_color;
}
}
}
}
// Step 0: Content Source Selector
&-step-0 {
// Gallery Control
.vpf-component-gallery-control .vpf-component-gallery-control-items {
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
}
}
// Step 1: Items Style Selector
&-step-1 .vpf-component-icon-selector {
gap: 20px;
padding-bottom: 30px;
margin-top: 35px;
// Limit to 4 items.
> .vpf-component-icon-selector-item:nth-child(4) ~ * {
display: none;
}
.vpf-component-icon-selector-item {
position: relative;
flex: 1;
padding: 0;
background: #eee !important;
border: none !important;
border-radius: 6px;
&::before {
display: block;
width: 100%;
padding-top: 100%;
content: "";
}
img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
border-radius: 6px;
}
svg {
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
&:hover,
&:focus {
border-color: none !important;
box-shadow: 0 0 0 1.5px #fff, 0 0 0 3.5px #000 !important;
}
&.vpf-component-icon-selector-item-active {
border-color: none !important;
box-shadow: 0 0 0 1.5px #fff, 0 0 0 3.5px $brand_color !important;
}
> span {
position: absolute;
top: calc(100% + 5px);
opacity: 0;
transition: 0.2s ease;
transform: translateY(-4px);
}
&:hover,
&:focus,
&.vpf-component-icon-selector-item-active {
> span {
opacity: 1;
transform: translateY(0);
}
}
}
}
// Step 2: Layout Elements
&-step-2 {
.vpf-setup-wizard-layout-elements {
> div {
padding: 25px 0;
+ div {
border-top: 1px solid #ececec;
}
}
.components-toggle-control__label {
font-size: 16px;
font-weight: 500;
}
}
}
.spinner,
.components-base-control,
.components-base-control__field {
margin: 0;
}
}
// Steps Wizard.
.vpf-component-steps-wizard {
opacity: 1;
transition: 0.3s opacity, 0.3s transform, 0.3s height;
transform: translateX(0);
&-animate-right {
opacity: 0;
transition: 0.3s height;
transform: translateX(40px);
}
&-animate-left {
opacity: 0;
transition: 0.3s height;
transform: translateX(-40px);
}
}