Commit a4880a85 authored by Ken Hawkins's avatar Ken Hawkins

Refactor: vf-frctl-theme

With the recent changes in the fractal theming layer (see: #1260) our old vf-frctl-theme is no longer sustainable and needs a complete refactor.

The best way to do this is to take the revised frctl-mandlebrot theme and merge in our old code.

This presents and opportunity to reasses vf-frctl-theme to be more vf-core development focused. Especially as the theme is no longer deployed to the web.

High level ideas:

- something closer to the default mandlebrot theme (future sustainability)
- preserve some of the mandlebrot features (searching, resizing)
- apply VF things where it matters (fonts, colours, key principles)

This commit starts that work.
parent 329ca01a
Pipeline #112088 passed with stage
in 11 minutes and 16 seconds
module.exports = {
parser: 'babel-eslint',
env: {
jquery: true
},
};
......@@ -4,7 +4,7 @@
### 1.0.1
- Makes use of 'full width' which is useful for componets using `vf-u-fullbleed` or similar
- Makes use of 'full width' which is useful for componets using `vf-u-fullbleed` or similar.
### 1.0.0
......
<svg width="16" height="3" viewBox="0 0 16 3" xmlns="http://www.w3.org/2000/svg">
<path d="M16 2v1H0V2h16zm0-2v1H0V0h16z" fill="#ccc" fill-rule="evenodd"/>
</svg>
<svg width="3" height="16" viewBox="0 0 3 16" xmlns="http://www.w3.org/2000/svg">
<path d="M2 0h1v16H2V0zM0 0h1v16H0V0z" fill="#ccc" fill-rule="evenodd"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 160 160">
<path d="M88 80l-48 48 16 16 64-64-64-64-16 16 48 48z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 160 160">
<path d="M72 80l48 48-16 16-64-64 64-64 16 16-48 48z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 160 160">
<path d="M125.89 32L80.51 80 35.126 32 20 48l60.51 64 60.508-64-15.127-16z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 240 240">
<path d="M120 72H40v128h128v-80l-16 16v48H56V88h48l16-16zm52-16l-74 74 12 12 74-74v68h16V40h-96v16h68z"/>
</svg>
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg width="44" height="44" viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg" stroke="#999">
<g fill="none" fill-rule="evenodd" stroke-width="2">
<circle cx="22" cy="22" r="1">
<animate attributeName="r"
begin="0s" dur="1.8s"
values="1; 20"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.165, 0.84, 0.44, 1"
repeatCount="indefinite" />
<animate attributeName="stroke-opacity"
begin="0s" dur="1.8s"
values="1; 0"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="indefinite" />
</circle>
<circle cx="22" cy="22" r="1">
<animate attributeName="r"
begin="-0.9s" dur="1.8s"
values="1; 20"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.165, 0.84, 0.44, 1"
repeatCount="indefinite" />
<animate attributeName="stroke-opacity"
begin="-0.9s" dur="1.8s"
values="1; 0"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="indefinite" />
</circle>
</g>
</svg>
'use strict';
import 'select2';
import storage from '../storage';
export default class Browser {
constructor(el) {
const self = this;
this._el = $(el);
this._tabs = this._el.find('[data-role="tab"]');
this._tabPanels = this._el.find('[data-role="tab-panel"]');
this._fileSwitcher = this._el.find('[data-role="switcher"]');
this._codeViews = this._el.find('[data-role="code"]');
this._resourcePreview = this._el.find('[data-role="resource-preview"]');
this._activeClass = 'is-active';
this._initTabs();
$('.FileBrowser-select')
.select2({
minimumResultsForSearch: Infinity,
})
.on('change', function () {
$(this).closest('.FileBrowser').find('[data-role="resource-preview"]').removeClass(self._activeClass);
$(`#${this.value}`).addClass(self._activeClass);
});
this._initFileSwitcher();
}
_initTabs() {
const ac = this._activeClass;
const tabs = this._tabs;
const selectedIndex = Math.min(tabs.length - 1, storage.get(`browser.selectedTabIndex`, 0));
tabs.on('click', (e) => {
const link = $(e.target).closest('a');
const tab = link.parent();
tabs.removeClass(ac);
storage.set(`browser.selectedTabIndex`, tabs.index(tab));
tab.addClass(ac);
this._tabPanels.removeClass(ac);
this._tabPanels.filter(link.attr('href')).addClass(ac);
return false;
});
tabs.removeClass('is-active');
tabs.eq(selectedIndex).find('a').trigger('click');
}
_initFileSwitcher() {}
// _initFileSwitcher() {
// const ac = this._activeClass;
// const switcher = this._fileSwitcher;
// const defaultSelected = storage.get(`browser.selectedCodeView`, 'code-html');
// switcher.on('change', e => {
// const selected = $(e.target).val();
// storage.set(`browser.selectedCodeView`, selected);
// this._codeViews.removeClass(ac);
// this._codeViews.filter(`#${selected}`).addClass(ac);
// });
// if (switcher.find(`option[value="${defaultSelected}"]`).length) {
// switcher.val(defaultSelected);
// }
// switcher.trigger('change');
// }
}
'use strict';
import storage from '../storage';
import utils from '../utils';
import events from '../events';
export default function (element) {
const win = $(window);
const doc = $(document);
const el = $(element);
const dir = $('html').attr('dir');
const body = el.find('> [data-role="body"]');
const toggle = el.find('[data-action="toggle-sidebar"]');
const sidebar = body.children('[data-role="sidebar"]');
const main = body.children('[data-role="main"]');
const handle = body.children('[data-role="frame-resize-handle"]');
const sidebarMin = parseInt(sidebar.css('min-width'), 10);
let sidebarWidth = utils.isSmallScreen() ? sidebarMin : storage.get(`frame.sidebar`, sidebar.outerWidth());
let sidebarState = utils.isSmallScreen() ? 'closed' : storage.get(`frame.state`, 'open');
let dragOccuring = false;
let isInitialClose = false;
let handleClicks = 0;
sidebar.outerWidth(sidebarWidth);
if (sidebarState === 'closed') {
isInitialClose = true;
closeSidebar();
}
handle.on('mousedown', (e) => {
handleClicks++;
setTimeout(function () {
handleClicks = 0;
}, 400);
if (handleClicks === 2) {
dragOccuring = false;
toggleSidebar();
handleClicks = 0;
e.stopImmediatePropagation();
return;
}
});
sidebar.resizable({
handleSelector: '[data-role="frame-resize-handle"]',
resizeHeight: false,
onDragStart: () => {
el.addClass('is-resizing');
events.trigger('start-dragging');
},
onDragEnd: () => {
setSidebarWidth(sidebar.outerWidth());
el.removeClass('is-resizing');
events.trigger('end-dragging');
if (sidebarState === 'closed') {
dragOccuring = false;
openSidebar();
}
},
resizeWidthFrom: dir === 'rtl' ? 'left' : 'right',
});
sidebar.on(
'scroll',
utils.debounce(() => {
storage.set(`frame.scrollPos`, sidebar.scrollTop());
}, 50)
);
toggle.on('click', toggleSidebar);
win.on('resize', () => {
if (sidebarState == 'open' && doc.outerWidth() < sidebarWidth + 50) {
// setSidebarWidth(doc.outerWidth() - 50);
}
});
// Global event listeners
events.on('toggle-sidebar', toggleSidebar);
events.on('start-dragging', () => (dragOccuring = true));
events.on('end-dragging', function () {
setTimeout(function () {
dragOccuring = false;
}, 200);
});
events.on('scroll-sidebar', function () {
const scrollPos = storage.get(`frame.scrollPos`, 0);
sidebar.scrollTop(scrollPos);
});
events.on('data-changed', function () {
// TODO: make this smarter?
document.location.reload(true);
});
function closeSidebar() {
if (dragOccuring || (!isInitialClose && sidebarState == 'closed')) return;
const w = sidebar.outerWidth();
let translate = dir == 'rtl' ? w + 'px' : -1 * w + 'px';
let sidebarProps = {
transform: `translate3d(${translate}, 0, 0)`,
};
if (dir == 'rtl') {
sidebarProps.marginLeft = -1 * w + 'px';
} else {
sidebarProps.marginRight = -1 * w + 'px';
}
sidebarProps.transition = isInitialClose ? 'none' : '.3s ease all';
body.css(sidebarProps);
sidebarState = 'closed';
el.addClass('is-closed');
storage.set(`frame.state`, sidebarState);
isInitialClose = false;
}
function openSidebar() {
if (dragOccuring || sidebarState == 'open') return;
if (utils.isSmallScreen()) {
setSidebarWidth(sidebarMin);
}
body.css({
marginRight: 0,
marginLeft: 0,
transition: '0.3s ease all',
transform: `translate3d(0, 0, 0)`,
});
sidebarState = 'open';
el.removeClass('is-closed');
storage.set(`frame.state`, sidebarState);
}
function toggleSidebar() {
sidebarState == 'open' ? closeSidebar() : openSidebar();
return false;
}
function setSidebarWidth(width) {
sidebarWidth = width;
sidebar.outerWidth(width);
storage.set(`frame.sidebar`, width);
}
return {
closeSidebar: closeSidebar,
openSidebar: openSidebar,
startLoad: function () {
main.addClass('is-loading');
},
endLoad: function () {
main.removeClass('is-loading');
},
};
}
'use strict';
import Tree from './tree';
import Search from './search';
export default class Navigation {
constructor(el) {
this._el = $(el);
this.navTrees = $.map(this._el.find('[data-behaviour="tree"]'), (t) => new Tree(t));
$.map(this._el.find('[data-behaviour="search"]'), (s) => new Search(s, this.navTrees));
}
}
'use strict';
import 'jquery-resizable-dom';
import storage from '../storage';
import events from '../events';
import Preview from './preview';
import Browser from './browser';
export default class Pen {
constructor(el) {
this._el = $(el);
this._id = this._el[0].id;
this._previewPanel = this._el.find('[data-behaviour="preview"]');
this._browser = this._el.find('[data-behaviour="browser"]');
this._handle = this._el.children('[data-role="resize-handle"]');
this._init();
}
_init() {
const initialHeight = storage.get(`pen.previewHeight`, this._el.outerHeight() / 2);
const preview = new Preview(this._previewPanel);
new Browser(this._browser);
let state = storage.get(`pen.previewState`, 'open');
let handleClicks = 0;
let dblClick = false;
if (state === 'open') {
this._previewPanel.outerHeight(initialHeight);
storage.set(`pen.previewHeight`, initialHeight);
} else {
this._previewPanel.css('height', '100%');
}
this._handle.on('mousedown', () => {
dblClick = false;
handleClicks++;
setTimeout(function () {
handleClicks = 0;
}, 400);
if (handleClicks === 2) {
dblClick = true;
handleClicks = 0;
return false;
}
});
this._previewPanel.resizable({
handleSelector: '.Pen-handle[data-role="resize-handle"]',
resizeWidth: false,
onDragStart: () => {
this._el.addClass('is-resizing');
preview.disableEvents();
events.trigger('start-dragging');
},
onDragEnd: () => {
this._el.removeClass('is-resizing');
preview.enableEvents();
events.trigger('end-dragging');
if (dblClick) {
if (state === 'closed') {
this._previewPanel.css('height', storage.get(`pen.onClosedHeight`, initialHeight));
state = 'open';
storage.set(`pen.previewState`, 'open');
} else {
storage.set(`pen.onClosedHeight`, this._previewPanel.outerHeight());
this._previewPanel.css({
height: '100%',
transition: '.3s ease all',
});
state = 'closed';
storage.set(`pen.previewState`, 'closed');
}
} else {
if (state !== 'closed') {
storage.set(`pen.previewHeight`, this._previewPanel.outerHeight());
} else {
setTimeout(() => {
if (!dblClick) {
state = 'open';
storage.set(`pen.previewState`, 'open');
storage.set(`pen.previewHeight`, this._previewPanel.outerHeight());
}
}, 400);
}
}
},
});
}
}
'use strict';
import 'jquery-resizable-dom';
import storage from '../storage';
import events from '../events';
export default class Preview {
constructor(el) {
this._el = $(el);
this._id = this._el[0].id;
this._handle = this._el.find('[data-role="resize-handle"]');
this._iframe = this._el.children('[data-role="window"]');
this._resizer = this._el.children('[data-role="resizer"]');
this._init();
}
_init() {
const dir = $('html').attr('dir');
const initialWidth = storage.get(`preview.width`, this._resizer.outerWidth());
let handleClicks = 0;
if (initialWidth == this._el.outerWidth()) {
this._resizer.css('width', '100%');
} else {
this._resizer.outerWidth(initialWidth);
}
this._handle.on('mousedown', () => {
handleClicks++;
setTimeout(function () {
handleClicks = 0;
}, 400);
if (handleClicks === 2) {
this._resizer.css('width', 'calc(100% + 0.75rem)');
return false;
}
});
this._resizer.resizable({
handleSelector: '> [data-role="resize-handle"]',
resizeHeight: false,
onDragStart: () => {
this._el.addClass('is-resizing');
this.disableEvents();
events.trigger('start-dragging');
},
onDragEnd: () => {
if (this._resizer.outerWidth() == this._el.outerWidth()) {
this._resizer.css('width', '100%');
}
storage.set(`preview.width`, this._resizer.outerWidth());
this._el.removeClass('is-resizing');
this.enableEvents();
events.trigger('end-dragging');
},
resizeWidthFrom: dir === 'rtl' ? 'left' : 'right',
});
}
disableEvents() {
this._el.addClass('is-disabled');
}
enableEvents() {
this._el.removeClass('is-disabled');
}
}
'use strict';
import Mark from 'mark.js';
export default class Search {
constructor(el, trees) {
this._el = $(el);
this._trees = trees;
this._input = this._el.find('[data-role="input"]');
this._clearButton = this._el.find('[data-behaviour="clear-search"]');
this._marker = new Mark($.map(this._trees, (t) => t.getElement()[0]));
this._el.on('submit', (event) => {
event.preventDefault();
});
this._input.on('input', this.handleInput.bind(this));
if (this._clearButton) {
this._clearButton.on('click', () => {
this._input.val('').trigger('input').focus();
});
}
}
handleInput(event) {
const key = event.currentTarget.value.toUpperCase();
if (this._clearButton) {
if (key) {
this._clearButton.removeAttr('hidden');
} else {
this._clearButton.attr('hidden', true);
}
}
this._marker.unmark();
this._marker.mark(key);
this._trees.forEach((tree) => {
this.search(tree._el.children('ul'), key, tree._collections);
// If no item match in the group, hide it completely
const $treeEl = $(tree._el);
const $treeGroup = $treeEl.parent('.Navigation-group');
if ($treeEl.find('> ul > li:not([hidden])').length === 0) {
$treeGroup.attr('hidden', true);
} else {