Skip to content

Commit

Permalink
Previous entries, SpeedDial UI and performance improvements (#56)
Browse files Browse the repository at this point in the history
* Add previouesEntries to Contentful edit URL

* Improve tree update performance and add speed dial as default UI
  • Loading branch information
maxtechera authored Apr 25, 2022
1 parent c0d5403 commit f234ff2
Show file tree
Hide file tree
Showing 13 changed files with 523 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
10.15.0
16.13.0
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "contentful-sidekick",
"version": "0.1.1",
"version": "1.0.0",
"description": "Chrome Extension that enables inline editing for websites created in Contentful",
"main": "index.js",
"browserslist": [
Expand All @@ -10,7 +10,7 @@
"test": "jest tests/*.test.js",
"build:clean": "rimraf dist",
"build:chrome": "npm run build:clean && webpack --progress --config webpack/webpack.config.chrome.js",
"start": "npm run build:clean && webpack --watch --progress --config webpack/webpack.config.chrome.js"
"dev": "npm run build:clean && webpack --watch --progress --config webpack/webpack.config.chrome.js"
},
"repository": {
"type": "git",
Expand All @@ -28,6 +28,11 @@
},
"homepage": "https://github.com/bradtaylorsf/contentful-sidekick#readme",
"dependencies": {
"@emotion/react": "^11.7.0",
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.2.1",
"@mui/material": "^5.2.3",
"@mui/system": "^5.2.3",
"common-tags": "^1.8.0",
"contentful": "^7.14.6",
"contentful-management": "^5.4.0",
Expand Down
2 changes: 1 addition & 1 deletion src/shared/css/content.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ body {
}
[data-init-csk] {
/* position: relative; */
padding-left: 20vw;
/* padding-left: 20vw; */
/* width: 100vw; */
}

Expand Down
10 changes: 6 additions & 4 deletions src/shared/js/components/Sidekick/ElementHighlighter.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import React from 'react';
import throttle from 'lodash/throttle';
import { useContextSelector } from 'use-context-selector';
import getContentfulItemUrl from '../../helpers/getContentfulItemUrl';
import { resetBlur, setBlur } from '../../helpers/blur';
import { CSK_ENTRY_ID_NAME, CSK_ENTRY_SELECTOR, CSK_ENTRY_UUID_NAME } from '../../helpers/constants';
import { useTreeUpdater } from './tree-context';
import { TreeStateContext, useTreeUpdater } from './tree-context';

const ElementHighlighter = () => {
const { setSelected } = useTreeUpdater();
const selectedPath = useContextSelector(TreeStateContext, (context) => context.selectedPath);
React.useEffect(() => {
const handleCskEntryMouseenter = throttle((e) => {
if (!e.target) return;
const $ct = $(e.target);
let id = $ct.data(CSK_ENTRY_ID_NAME);
let url = id ? getContentfulItemUrl(id) : null;
let url = id ? getContentfulItemUrl(id, selectedPath) : null;
let uuid = $(e.target).data(CSK_ENTRY_UUID_NAME);
if (!uuid) {
// The mouse enter target might not be the element with sidekick props
// So we look for it on the parents
const $parentEl = $(e.target).parents(`[data-${CSK_ENTRY_UUID_NAME}]`);
uuid = $($parentEl[0]).data(CSK_ENTRY_UUID_NAME);
id = $($parentEl[0]).data(CSK_ENTRY_ID_NAME);
url = id ? getContentfulItemUrl(id) : null;
url = id ? getContentfulItemUrl(id, selectedPath) : null;
}
setBlur($(e.target), url);
setSelected(uuid);
Expand Down Expand Up @@ -63,7 +65,7 @@ const ElementHighlighter = () => {
$('#csk-blur-actions').on('mouseleave', handleActionsMouseleave);
}
};
}, [setSelected]);
}, [setSelected, selectedPath]);

return (
<>
Expand Down
73 changes: 61 additions & 12 deletions src/shared/js/components/Sidekick/Sidekick.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,84 @@
import { SpeedDial, SpeedDialAction } from '@mui/material';
import HighlightIcon from '@mui/icons-material/HighlightAlt';
import ReadMoreIcon from '@mui/icons-material/ReadMore';
import { ThemeProvider } from '@mui/system';
import debounce from 'lodash/debounce';

import React, { useEffect, useState } from 'react';
import buildCskEntryTree from '../../helpers/buildCskEntryTree';
import { TreeProvider } from './tree-context';

import ElementHighlighter from './ElementHighlighter';
import Sidebar from './Sidebar';
import './Sidekick.css';
import theme from '../../theme';
import useStorageState from '../../helpers/useStorageState';

const Sidekick = ({ defaultTree }) => {
const [tree, setTree] = useState(defaultTree);
const [show, setShow] = useState(false);
const [show, setShow] = useStorageState(false, 'sidebarEnabled');
const [highlight, setHighlight] = useStorageState(true, 'highlightEnabled');

useEffect(() => {
const interval = setInterval(() => {
const callback = debounce(() => {
const newTree = buildCskEntryTree();
if (JSON.stringify(tree) !== JSON.stringify(newTree)) {
setTree(newTree);
}
}, 3000);
setTimeout(() => setShow(true), 300);
}, 300);
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
let interval;
let mutationObserver;
if (MutationObserver) {
mutationObserver = new MutationObserver(callback);
mutationObserver.observe(document.documentElement || document.body, { childList: true, subtree: true });
} else {
interval = setInterval(callback, 3000);
callback();
}
return () => {
clearInterval(interval);
if (interval) clearInterval(interval);
if (mutationObserver) mutationObserver.disconnect();
};
}, []);

return (
<TreeProvider tree={tree}>
<>
<Sidebar show={show} tree={tree} />
<ElementHighlighter />
</>
</TreeProvider>
<ThemeProvider theme={theme}>
<TreeProvider tree={tree}>
<>
<SpeedDial
size="medium"
color="black"
ariaLabel="LastRev Sidekick speed dial"
sx={{ position: 'fixed', bottom: 0, left: 0, zIndex: 9999999999999999 }}
FabProps={{
sx: { borderRadius: '0px 10px 0px 0px' }
}}
icon={
<svg version="1.0" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 64 64" style={{ padding: '8px', fill: 'white' }}>
<path d="M26.3 21.2c-1.2 1.4-7.8 14.1-10.5 20-1.3 2.8-1.4 3.8-.5 3.8 1.4 0 13.4-23.6 12.5-24.5-.3-.3-1 0-1.5.7zM32 33c0 4.7.4 8 1 8s1-1.5 1-3.4c0-7.2 4.2-12.3 7.4-9.1 1.6 1.7 3.6 2 3.6.6 0-1.6-3-4.1-4.8-4.1-1 0-2.8.7-4 1.6-2 1.4-2.2 1.4-2.2 0 0-.9-.4-1.6-1-1.6s-1 3.3-1 8zM5.8 29.8c-8.7 4.1-8.2 5 6 10.9.6.3 1.2 0 1.2-.6 0-.7-2-2.1-4.5-3.2S4 34.6 4 34.2c0-.5 2-1.8 4.5-3S13 28.6 13 28c0-1.4-.8-1.2-7.2 1.8zM49 28.1c0 .5 2.2 2 5 3.2 2.7 1.2 5 2.5 5 2.8 0 .4-2.2 1.6-5 2.7-2.7 1.2-5 2.6-5 3.1 0 1.5.5 1.4 7.1-1.6C59.7 36.7 62 35 62 34c0-.9-2.3-2.7-5.7-4.3-6.4-3-7.3-3.2-7.3-1.6z" />
</svg>
}>
<SpeedDialAction
icon={<HighlightIcon />}
tooltipTitle={`${!highlight ? 'Enable' : 'Disable'} inspect content`}
tooltipPlacement='right'
color={highlight ? 'primary' : 'secondary'}
onClick={() => setHighlight(!highlight)}
/>
<SpeedDialAction
icon={<ReadMoreIcon />}
tooltipTitle={`${!show ? 'Enable' : 'Disable'} sidebar`}
tooltipPlacement='right'
color={highlight ? 'primary' : 'secondary'}
onClick={() => setShow(!show)}
/>
</SpeedDial>

<Sidebar show={show} tree={tree} />
{highlight ? <ElementHighlighter /> : null}
</>
</TreeProvider>
</ThemeProvider >
);
};

Expand Down
8 changes: 5 additions & 3 deletions src/shared/js/components/Sidekick/TreeNode.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from 'react';
import { useContextSelector } from 'use-context-selector';
import getContentfulItemUrl from '../../helpers/getContentfulItemUrl';
import { CSK_ENTRY_UUID_NAME } from '../../helpers/constants';
import { resetBlur, setBlur } from '../../helpers/blur';
import ErrorTooltip from './ErrorTooltip';
import { useNode, useTreeUpdater } from './tree-context';
import { TreeStateContext, useNode, useTreeUpdater } from './tree-context';

const calcElScrollTop = (el) => {
if (el && el.offset()) {
Expand All @@ -16,6 +17,7 @@ const calcElScrollTop = (el) => {

const TreeNode = ({ id, field, type, displayText, uuid, childNodes, errors }) => {
const { isExpanded, isSelected } = useNode(uuid);
const selectedPath = useContextSelector(TreeStateContext, (context) => context.selectedPath);
const { setIsExpanded } = useTreeUpdater();
const handleExpandCollapseClick = React.useCallback(() => {
setIsExpanded(uuid, !isExpanded);
Expand Down Expand Up @@ -44,7 +46,7 @@ const TreeNode = ({ id, field, type, displayText, uuid, childNodes, errors }) =>
))}
</ul>
) : null,
[childNodes]
[childNodes, selectedPath]
);
const el = React.useMemo(() => $(`[data-${CSK_ENTRY_UUID_NAME}='${uuid}']`), [uuid]);

Expand All @@ -53,7 +55,7 @@ const TreeNode = ({ id, field, type, displayText, uuid, childNodes, errors }) =>
$('html, body').stop().animate({ scrollTop }, 300);
}, [el]);

const url = id ? getContentfulItemUrl(id) : null;
const url = id ? getContentfulItemUrl(id, selectedPath) : null;
const text = `${displayText || field || type || id}`;
const handleMouseEnter = () => {
setBlur(el);
Expand Down
8 changes: 4 additions & 4 deletions src/shared/js/components/Sidekick/tree-context.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { createContext, useContextSelector } from 'use-context-selector';

const TreeStateContext = createContext({
export const TreeStateContext = createContext({
selected: null,
expandedState: {},
selectedPath: []
Expand All @@ -14,13 +14,13 @@ const TreeUpdaterContext = createContext({

const getPath = ({ node, uuid }) => {
if (node.uuid == uuid) {
return [uuid];
return [node];
}
if (node.children && node.children.length) {
for (const child of node.children) {
const path = getPath({ node: child, uuid });
if (path.length) {
return [...path, node.uuid];
return [...path, node];
}
}
}
Expand Down Expand Up @@ -72,7 +72,7 @@ const useNode = (uuid) => {
const selectedPath = useContextSelector(TreeStateContext, (context) => context.selectedPath);
const selected = useContextSelector(TreeStateContext, (context) => context.selected);

const isExpanded = React.useMemo(() => expanded || (selected && selectedPath.includes(uuid)), [
const isExpanded = React.useMemo(() => expanded || (selected && selectedPath.find((n) => n.uuid === uuid)), [
uuid,
expanded,
selected,
Expand Down
6 changes: 3 additions & 3 deletions src/shared/js/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import buildCskEntryTree from './helpers/buildCskEntryTree';
import { CSK_ENTRY_SELECTOR } from './helpers/constants';

const shrinkContent = () => {
$('body').css('padding-left', '20vw');
// $('body').css('padding-left', '20vw');
// $('*').filter(function () {
// const $el = $(this);
// if ($el.css('position') == 'fixed') {
Expand Down Expand Up @@ -140,8 +140,8 @@ const init = async () => {
const { changedUrl } = request;
getIsSideKickEnabledFromStorage().then((enabled) => {
if (changedUrl && enabled) {
resetDom();
loadSidekick();
// resetDom();
// loadSidekick();
}
});
});
Expand Down
6 changes: 3 additions & 3 deletions src/shared/js/helpers/buildCskEntryTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import {

const parseErrors = ($el) => {
try {
const error = $el.data(CSK_ENTRY_ERROR);
const error = $el.data(CSK_ENTRY_ERROR);
return error.errors;
} catch(e) {
} catch (e) {
return null;
}
};
Expand All @@ -28,7 +28,7 @@ function traverseDomNode(jqObj, domEl, results) {
const uuid = prevUuid || uuidv4();

$el.attr(`data-${CSK_ENTRY_UUID_NAME}`, uuid);

results.push({
id: $el.data(CSK_ENTRY_ID_NAME),
field: $el.data(CSK_ENTRY_FIELD_NAME),
Expand Down
11 changes: 9 additions & 2 deletions src/shared/js/helpers/getContentfulItemUrl.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import getContentfulVars from './getContentfulVars';

export default (contentId) => {
export default (contentId, selectedPath = []) => {
const [CONTENTFUL_CURRENT_SPACE_ID, CONTENTFUL_ENVIRONMENT] = getContentfulVars();
return `https://app.contentful.com/spaces/${CONTENTFUL_CURRENT_SPACE_ID}/environments/${CONTENTFUL_ENVIRONMENT}/entries/${contentId}`;
const reversed = [...selectedPath].reverse();
const previousEntries = selectedPath
? `?previousEntries=${reversed
.filter((node) => node.id && node.id !== contentId)
.map((node) => node.id)
.join(',')}`
: '';
return `https://app.contentful.com/spaces/${CONTENTFUL_CURRENT_SPACE_ID}/environments/${CONTENTFUL_ENVIRONMENT}/entries/${contentId}${previousEntries}`;
};
26 changes: 26 additions & 0 deletions src/shared/js/helpers/useStorageState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
export default (defaultValue, key) => {
const [value, setValuestate] = React.useState(localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key)) : defaultValue);
// React.useEffect(() => {
// chrome.storage.sync.get([key], (storageValue) => {
// if (typeof storageValue[key] !== 'undefined') {
// setValuestate(storageValue[key]);
// }
// });

// chrome.storage.onChanged.addListener((changes) => {
// if (changes[key] && changes[key].newValue !== changes[key].oldValue) {
// setValuestate(changes[key].newValue);
// }
// });
// }, [defaultValue]);

const setValue = newValue => {
setValuestate(newValue);
localStorage.setItem(key, newValue);
// TODO: The next line makes the extension disappear without error
// chrome.storage.sync.set({ [key]: newValue });

};
return [value, setValue];
};
11 changes: 11 additions & 0 deletions src/shared/js/theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createTheme } from '@mui/material/styles';

const theme = createTheme({
palette: {
primary: {
main: '#9146ff'
}
}
});

export default theme;
Loading

0 comments on commit f234ff2

Please sign in to comment.