Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/scratch-gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"core-js": "2.6.12",
"css-loader": "5.2.7",
"dapjs": "2.3.0",
"driver.js": "1.3.6",
"es6-object-assign": "1.1.0",
"fastestsmallesttextencoderdecoder": "1.0.22",
"get-float-time-domain-data": "0.1.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
@import "../../css/units.css";
@import "../../css/colors.css";
@import "../../css/z-index.css";

.extension-button-container {
width: 3.75rem;
height: 3.25rem;
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: $z-index-extension-button;
background: $looks-secondary;

border: 1px solid $looks-secondary;
box-sizing: content-box; /* To match scratch-block vertical toolbox borders */
}

$fade-out-distance: 15px;

.extension-button-container:before {
content: "";
position: absolute;
top: calc(calc(-1 * $fade-out-distance) - 1px);
left: -1px;
background: linear-gradient(rgba(0, 0, 0, 0),rgba(0, 0, 0, 0.15));
height: $fade-out-distance;
width: calc(100% + 0.5px);
}


.extension-button {
background: none;
border: none;
outline: none;
width: 100%;
height: 100%;
cursor: pointer;
--radiate-color: 133, 92, 214; /* $looks-secondary */
}

.extension-button-icon {
width: 1.75rem;
height: 1.75rem;
}

[dir="rtl"] .extension-button-icon {
transform: scaleX(-1);
}

.extension-button > div {
margin-top: 0;
}

$radiate-distance: 20px;

.radiate:before,
.radiate:after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;

z-index: -1;
animation: radiate 2.5s infinite;
clip-path: inset(-$radiate-distance -$radiate-distance 0 0);
}

.radiate:after {
animation-delay: 0.7s;
}

@keyframes radiate {
0% {
box-shadow: 0 0 0 0 rgba(var(--radiate-color), 0.7);
}
100% {
box-shadow: 0 0 0 $radiate-distance rgba(var(--radiate-color), 0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React, {useEffect, useCallback, useState, useRef} from 'react';
import classNames from 'classnames';
// eslint-disable-next-line import/no-unresolved
import {driver} from 'driver.js';
import 'driver.js/dist/driver.css';
import {defineMessages, injectIntl, intlShape} from 'react-intl';
import PropTypes from 'prop-types';

import Box from '../box/box.jsx';
import {BLOCKS_TAB_INDEX} from '../../reducers/editor-tab';
import {getLocalStorageValue, setLocalStorageValue} from '../../lib/local-storage.js';
import addExtensionIcon from '../gui/icon--extensions.svg';
import styles from './extension-button.css';
import './extension-button.raw.css';

const messages = defineMessages({
addExtension: {
id: 'gui.gui.addExtension',
description: 'Button to add an extension in the target pane',
defaultMessage: 'Add Extension'
},
faceSensingCalloutTitle: {
id: 'gui.gui.faceSensingCalloutTitle',
description: 'Hey there! \u{1F44B}',
defaultMessage: 'Hey there! \u{1F44B}'
},
faceSensingCalloutDescription: {
id: 'gui.gui.faceSensingCalloutDescription',
description: 'There is a new extension!',
defaultMessage: 'There is a new extension!'
}
});

const localStorageAvailable =
'localStorage' in window && window.localStorage !== null;

// Default to true to make sure we don't end up showing the feature
// callouts multiple times if localStorage isn't available.
const hasIntroducedFaceSensing = (username = 'guest') => {
if (!localStorageAvailable) return true;
return getLocalStorageValue('hasIntroducedFaceSensing', username) === true;
};

const setHasIntroducedFaceSensing = (username = 'guest') => {
if (!localStorageAvailable) return;
setLocalStorageValue('hasIntroducedFaceSensing', username, true);
};

const hasUsedFaceSensing = (username = 'guest') => {
if (!localStorageAvailable) return true;
return getLocalStorageValue('hasUsedFaceSensing', username) === true;
};

const ExtensionButton = props => {
const {
activeTabIndex,
intl,
showNewFeatureCallouts,
onExtensionButtonClick,
username
} = props;

const driverRef = useRef(null);
// Keep in a state to avoid reads from localStorage on every render.
const [shouldShowFaceSensingCallouts, setShouldShowFaceSensingCallouts] =
useState(showNewFeatureCallouts && !hasIntroducedFaceSensing(username) && !hasUsedFaceSensing(username));
const [clicked, setClicked] = useState(false);

useEffect(() => {
if (!shouldShowFaceSensingCallouts) return;

const onFirstClick = () => {
const isExtensionButtonVisible = document.querySelector('div[class*="extension-button-container"]');
if (!isExtensionButtonVisible) return;

const tooltip = driver({
allowClose: false,
allowInteraction: true,
overlayColor: 'transparent',
popoverOffset: -3,
steps: [{
element: 'div[class*="extension-button-container"]',
popover: {
title: intl.formatMessage(messages.faceSensingCalloutTitle),
description: intl.formatMessage(messages.faceSensingCalloutDescription),
side: 'right',
align: 'center',
popoverClass: 'tooltip-face-sensing',
showButtons: []
}
}]
});
setClicked(true);
driverRef.current = tooltip;
tooltip.drive();
};
window.addEventListener('click', onFirstClick, {once: true});

return () => {
if (driverRef.current) {
driverRef.current.destroy();
driverRef.current = null;
}
};
}, []);

useEffect(() => {
if (!driverRef.current) return;

if (!shouldShowFaceSensingCallouts && driverRef.current) {
driverRef.current.destroy();
}

if (!shouldShowFaceSensingCallouts || !clicked) return;

const isExtensionButtonVisible = document.querySelector('div[class*="extension-button-container"]');

if (!isExtensionButtonVisible || activeTabIndex !== BLOCKS_TAB_INDEX) {
driverRef.current.destroy();
}

if (isExtensionButtonVisible && activeTabIndex === BLOCKS_TAB_INDEX) {
driverRef.current.drive();
}
}, [shouldShowFaceSensingCallouts, activeTabIndex, clicked]);

const handleExtensionButtonClick = useCallback(() => {
if (driverRef.current) {
driverRef.current.destroy();
driverRef.current = null;
}

if (shouldShowFaceSensingCallouts) {
setHasIntroducedFaceSensing(username);
setShouldShowFaceSensingCallouts(false);
}
onExtensionButtonClick?.();
}, [shouldShowFaceSensingCallouts]);

return (
<Box className={styles.extensionButtonContainer}>
<button
className={
classNames(styles.extensionButton,
shouldShowFaceSensingCallouts && styles.radiate
)}
title={intl.formatMessage(messages.addExtension)}
onClick={handleExtensionButtonClick}
>
<img
className={styles.extensionButtonIcon}
draggable={false}
src={addExtensionIcon}
/>
</button>
</Box>
);
};

ExtensionButton.propTypes = {
activeTabIndex: PropTypes.number,
intl: intlShape.isRequired,
onExtensionButtonClick: PropTypes.func,
showNewFeatureCallouts: PropTypes.bool,
username: PropTypes.string
};

const ExtensionButtonIntl = injectIntl(ExtensionButton);

export default ExtensionButtonIntl;
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@import "../../css/units.css";
@import "../../css/colors.css";
@import "../../css/z-index.css";

/* Make sure driver.js doesn't block interactions with page elements */
.driver-active * {
pointer-events: revert;
}

.driver-active .driver-overlay {
pointer-events: none !important;
}

.driver-active:has(.tooltip-face-sensing) > .driver-overlay {
visibility: hidden;
}

/* Fallback, if :has is not supported */
.tooltip-face-sensing ~ .driver-overlay {
visibility: hidden;
}

.driver-popover.tooltip-face-sensing {
padding: 1rem;
background-color: $looks-secondary;
color: $ui-white;
z-index: 100;
min-width: 12rem;
height: 5rem;
border-radius: 0.5rem;
border: 1px solid $looks-secondary;
transform: translate(0, -0.8rem);
}

.driver-popover.tooltip-face-sensing .driver-popover-title {
font-weight: 700;
line-height: 1.25rem;
font-size: 0.875rem;
}

.driver-popover.tooltip-face-sensing .driver-popover-description {
font-weight: 400;
line-height: 1.25rem;
font-size: 0.875rem;
}

.driver-popover.tooltip-face-sensing .driver-popover-arrow-side-right {
border-right-color: $looks-secondary;
border-width: 0.5rem;
}
49 changes: 0 additions & 49 deletions packages/scratch-gui/src/components/gui/gui.css
Original file line number Diff line number Diff line change
Expand Up @@ -228,55 +228,6 @@
/* overflow: hidden; */
}

.extension-button-container {
width: 3.75rem;
height: 3.25rem;
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: $z-index-extension-button;
background: $looks-secondary;

border: 1px solid $looks-secondary;
box-sizing: content-box; /* To match scratch-block vertical toolbox borders */
}

$fade-out-distance: 15px;

.extension-button-container:before {
content: "";
position: absolute;
top: calc(calc(-1 * $fade-out-distance) - 1px);
left: -1px;
background: linear-gradient(rgba(0, 0, 0, 0),rgba(0, 0, 0, 0.15));
height: $fade-out-distance;
width: calc(100% + 0.5px);
}


.extension-button {
background: none;
border: none;
outline: none;
width: 100%;
height: 100%;
cursor: pointer;
}

.extension-button-icon {
width: 1.75rem;
height: 1.75rem;
}

[dir="rtl"] .extension-button-icon {
transform: scaleX(-1);
}

.extension-button > div {
margin-top: 0;
}

/* Sprite Selection Watermark */
.watermark {
position: absolute;
Expand Down
Loading
Loading