Skip to content

Commit

Permalink
Merge pull request #495 from greenpeace/wysiwyg-vars
Browse files Browse the repository at this point in the history
PLANET-5978: Theme creator experiment
  • Loading branch information
Inwerpsel authored Mar 1, 2021
2 parents 848d815 + ae644ef commit 2d08085
Show file tree
Hide file tree
Showing 23 changed files with 3,530 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .stylelintrc
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"selector-pseudo-element-case": "lower",
"selector-type-case": "lower",
"selector-type-no-unknown": [true, {
"ignoreTypes": ["/^--[a-z]\\w*(--?[a-z0-9]+)*--$/", "lite-youtube"]
"ignoreTypes": ["/^--[a-z]\\w*(--?[a-z0-9]+)*--$/", "_--", "lite-youtube"]
}],
"selector-max-empty-lines": 0,

Expand Down
119 changes: 119 additions & 0 deletions assets/src/styles/components/VarPicker.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
.var-picker * {
font-family: Roboto, sans-serif !important;
color: black !important;
line-height: initial !important;
}

.var-picker > ul {
padding: 0;
max-height: 80vh;
width: 300px;
overflow-y: scroll;
margin-bottom: 0;

&::-webkit-scrollbar {
width: 9px;
}

&::-webkit-scrollbar-track {
-webkit-border-radius: 5px;
border-radius: 5px;
background: rgba(140, 140, 140, 0.2);
}

&::-webkit-scrollbar-thumb {
-webkit-border-radius: 5px;
border-radius: 5px;
background: rgba(140, 140, 140, 0.4);
}

&::-webkit-scrollbar-thumb:hover {
background: rgba(140, 140, 140, 0.5);
}

&::-webkit-scrollbar-thumb:window-inactive {
background: rgba(140, 140, 140, 0.05);
}
}

.theme-editor-highlight {
border: 2px solid yellow !important;
box-sizing: border-box !important;
}

.var-picker .var-group {
background: white !important;
border: 1px solid black !important;
margin-bottom: 3px !important;
border-radius: 7px !important;
padding: 2px 7px 7px !important;

h5 {
margin-bottom: 2px;
}
}

.var-picker ul > li > ul {
padding-left: 8px;
}

.var-control {
_-- {
background-color: #cecece;
}
clear: both;
border: 1px solid black !important;
margin-bottom: 3px !important;
border-radius: 7px !important;
padding: 2px 7px 7px !important;

h5 {
margin-bottom: 2px;
}

&.var-control-in-theme _-- {
background-color: #fcfafa;
}
}

.usages-collapsed {
max-height: 32px;
overflow-y: hidden;
overflow-x: hidden;
}

.usages-open {
clear: both;
}

div[id^=font-picker] ul {
position: static !important;
}

#theme-editor-root {
position: fixed;
top: 64px;
left: 50px;
z-index: 99999999;
padding: 4px 1px 3px 4px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(1px);

&.dragging {
cursor: grabbing;
}
}

body.logged-in #theme-editor-root {
top: 92px;
}

.usages-wrapper {
position: relative;
max-height: 32px;
overflow: hidden;
}

.usages-wrapper:hover {
overflow: visible;
}
1 change: 1 addition & 0 deletions assets/src/styles/themeEditor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "components/VarPicker";
225 changes: 225 additions & 0 deletions assets/src/theme/VarPicker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import { useEffect, useState } from 'react';
import { VariableControl } from './VariableControl';
import { colorToState } from './colorToState';
import { THEME_ACTIONS, useThemeEditor } from './useThemeEditor';
import { whileHoverHighlight } from './highlight';
import { exportCss, exportJson } from './export';

export const LOCAL_STORAGE_KEY = 'p4-theme';

const byName = (a, b) => a.name > b.name ? 1 : (a.name === b.name ? 0 : -1);

export const VarPicker = (props) => {
const {
groups,
selectedVars,
allVars,
} = props;

const [activeVars, setActiveVars] = useState([]);

useEffect(() => {
const notAlreadyActive = cssVar => !activeVars.map(active => active.name).includes(cssVar.name);

const newOnes = selectedVars.filter(notAlreadyActive);

setActiveVars(([...activeVars, ...newOnes]));
}, [selectedVars]);

const deactivate = (cssVar) => {
const isOtherVar = active => active.name !== cssVar.name;
setActiveVars(activeVars.filter(isOtherVar));
};
const closeAll = () => setActiveVars([]);
const CloseAllButton = () => <span
style={ {
float: 'right',
fontSize: '14px',
padding: '3px 2px',
background: 'white',
border: '1px solid black',
borderRadius: '4px',
marginBottom: '8px'
} }
onClick={ closeAll }
> Close all </span>;

const [openGroups, setOpenGroups] = useState([]);
const toggleGroup = id => {
const newGroups = openGroups.includes(id)
? openGroups.filter(openId => openId !== id)
: [...openGroups, id];

setOpenGroups(newGroups);
};
useEffect(() => {
setOpenGroups([groups[0]?.label]);
}, [groups]);

// Todo: save state somewhere.
const config = {
allVars,
};
const [
{
theme,
defaultValues,
hasHistory,
hasFuture,
},
dispatch,
] = useThemeEditor(config);

const setProperty = (name, value) => {
dispatch({ type: THEME_ACTIONS.SET, payload: { name, value } });
};

const unsetProperty = (name) => {
dispatch({ type: THEME_ACTIONS.UNSET, payload: { name } });
};

const [collapsed, setCollapsed] = useState(false);
const toggleCollapsed = () => {
setCollapsed(!collapsed);
};

const [shouldGroup, setShouldGroup] = useState(true);

const [fileName, setFileName] = useState('');

return <div
className='var-picker'
>
<span
style={ {
fontSize: '10px',
border: '1px solid grey',
borderRadius: '3px',
margin: '0 8px',
padding: '2px 4px',
background: 'grey',
} }
onClick={ toggleCollapsed }
>
{ collapsed ? 'show' : 'hide' }
</span>
{ !collapsed && <label
htmlFor=""
onClick={ () => setShouldGroup(!shouldGroup) }
style={ { marginBottom: '2px' } }
>
<input type="checkbox" readOnly checked={ shouldGroup }/>
{ 'Group last clicked element' }
</label> }
{ !collapsed && <div
title='Click and hold to drag'
className="themer-controls">
<div>
<button
onClick={ () => exportJson(fileName) }
>JSON
</button>
<button
onClick={ () => exportCss(fileName) }
>CSS
</button>
<label style={{fontSize: '12px'}}>
<input style={ { width: '130px' } } placeholder='theme' type="text"
onChange={ event => setFileName(event.target.value) }/>
</label>
</div>
<div>
<label
// Only tested on Chrome
style={ {
display: 'inline-block',
maxWidth: '33%',
overflowX: 'hidden',
background: 'rgba(255,255,255,.3)',
cursor: 'copy'
} }
>
<input
type="file"
accept={ '.json' }
onChange={ event => {
const reader = new FileReader();
reader.onload = event => {
try {
const theme = JSON.parse(event.target.result);
dispatch({ type: THEME_ACTIONS.LOAD_THEME, payload: { theme } });
} catch (e) {
console.log('File contents is not valid JSON.', event.target.result, event);
}
};
reader.readAsText(event.target.files[0]);
} }
style={ { cursor: 'copy' } }
/>
</label>
</div>
</div> }
{ shouldGroup && !collapsed && <ul>
{ groups.map(({ element, label, vars }) => (
<li className={ 'var-group' } key={ label } style={ { marginBottom: '12px' } }>
<h4
style={ { fontWeight: 400, marginBottom: 0, cursor: 'pointer' } }
onClick={ () => toggleGroup(label) }
{ ...whileHoverHighlight(element) }
>
{ label } ({ vars.length })
</h4>
{ openGroups.includes(label) && <ul>
{ vars.map(cssVar => {
const defaultValue = defaultValues[cssVar.name];

return <VariableControl
{ ...{
theme,
cssVar,
defaultValue,
dispatch,
} }
key={ cssVar.name }
onChange={ (value) => {
setProperty(cssVar.name, value);
} }
onUnset={ () => unsetProperty(cssVar.name) }
/>;
}
) }
</ul> }
</li>
)) }
</ul> }

{ !shouldGroup && !collapsed && <ul>
<span>
showing { activeVars.length } propert{ activeVars.length === 1 ? 'y' : 'ies' }
</span>
{ activeVars.length > 0 && (
<CloseAllButton/>
) }

{ activeVars.sort(byName).map(cssVar => {
const defaultValue = defaultValues[cssVar.name];

return <VariableControl
{ ...{
cssVar,
defaultValue,
dispatch,
theme,
}
}
key={ cssVar.name }
onUnset={ () => unsetProperty(cssVar.name) }
onCloseClick={ deactivate }
onChange={ value => setProperty(cssVar.name, value) }
/>;
}
) }
</ul> }

</div>;
};
Loading

0 comments on commit 2d08085

Please sign in to comment.