reorganize packages, start converting overall app to react (very broken at the moment)

This commit is contained in:
Erik Ziegler committed 2018-12-12 18:06:25 +01:00
1 parent 024fe1a448
commit 5f30f7bd35
479 files changed
+5364 -11611

No files matched your search

@@ -0,0 +1,8 @@
// selectTree
import './selectTree/selectTree.html';
import './selectTree/selectTree.styl';
import './selectTree/selectTree.js';
// selectTreeCommon
import './selectTreeCommon/selectTreeCommon.html';
import './selectTreeCommon/selectTreeCommon.js';
@@ -0,0 +1,91 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import _ from 'underscore';
/*
* input: controls a tree selection component
*/
OHIF.mixins.selectTree = new OHIF.Mixin({
dependencies: 'group',
composition: {
onCreated() {
const instance = Template.instance();
const component = instance.component;
const rootComponent = instance.data.root || component;
const rootInstance = rootComponent.templateInstance;
// Store the component's current value
instance.currentValue = null;
// Create the component's value storage property
instance.data.currentNode = new ReactiveVar(null);
// Share the component in data property
instance.data.component = component;
// Override the default value method
component.value = value => {
const isGet = _.isUndefined(value);
// Return the current value
if (isGet) {
return component.parseData(rootInstance.currentValue);
}
// Change the current value
rootInstance.currentValue = value;
// Trigger the change event
component.$element.trigger('change');
};
// Method to manipulate nodes
component.node = node => {
const isGet = _.isUndefined(node);
// Return the current node
if (isGet) {
const currentNode = instance.data.currentNode.get();
return currentNode ? currentNode.value : null;
}
// get the items array
const items = component.templateInstance.data.items;
// Get the node for the selected value
const currentNode = _.findWhere(items, {
value: node
});
// Change the current node
instance.data.currentNode.set(currentNode);
};
// Return plain data for all the leaves inside current node
component.getLeaves = () => {
const recursiveSeek = (items, result=[]) => {
_.each(items, item => {
if (item.items) {
recursiveSeek(item.items, result);
} else {
result.push(item);
}
});
return result;
};
return recursiveSeek(instance.data.items);
};
},
onRendered() {
const instance = Template.instance();
const component = instance.component;
// Define the main element
component.$element = instance.$('.select-tree:first').first();
}
}
});
@@ -0,0 +1,89 @@
<template name="selectTree">
{{#group (extend this
class=(concat
'select-tree '
(valueIf this.root '' 'select-tree-root ')
this.class
)
mixins=(concat 'selectTree ' this.mixins)
)}}
{{>UI.contentBlock}}
<div class="tree-content">
{{#let currentNode=this.currentNode.get}}
{{#if (and (not this.root) (ne this.search false))}}
{{#inputText
label=this.label
labelClass='tree-search'
placeholder=(choose this.searchPlaceholder 'Search')
}}
<i class="fa fa-search"></i>
{{/inputText}}
{{/if}}
<div class="tree-options {{#if currentNode}}collapsed{{/if}}">
{{#if this.root}}
{{>selectTreeBreadcrumb component=this.component}}
{{/if}}
<div class="tree-inputs clearfix">
{{#each item in treeItems}}
{{>inputRadio
id=(concat this.storageKey '_' (encodeId item.value))
class=this.radioClass
name=this.key
itemData=item
value=item.value
label=item.label
labelClass=(valueIf (isArray item.items) 'tree-node' 'tree-leaf')
checked=(eq item.value (reactive this.value))
}}
{{/each}}
</div>
{{#if (and currentNode (isArray currentNode.items))}}
{{>selectTree (clone this
items=currentNode.items
root=(choose this.root this.component)
class='select-tree-node'
)}}
{{/if}}
</div>
{{/let}}
</div>
{{#if (and (not this.root) (or this.storageKey this.commonItems))}}
{{>selectTreeCommon (clone this component=this.component treeInstance=instance)}}
{{/if}}
{{/group}}
</template>
<template name="selectTreeBreadcrumb">
<div class="tree-breadcrumb clearfix">
{{>selectTreeBreadcrumbParent
component=this.component.parent
index=1
}}
{{#let currentNode=this.component.parent.templateInstance.data.currentNode.get}}
<span class="tree-current-node">{{currentNode.label}}</span>
{{/let}}
</div>
</template>
<template name="selectTreeBreadcrumbParent">
{{#let parent=this.component.parent}}
{{#let parentNode=(reactive parent.templateInstance.data.currentNode)}}
{{#if parentNode}}
{{>selectTreeBreadcrumbParent
component=parent
index=(sum this.index 1)
}}
{{/if}}
<span class="path-link">
<a href="#" class="tree-back" data-index="{{this.index}}">
{{#if parentNode}}
<span>{{parentNode.label}}</span>
{{else}}
<i class="fa fa-fast-backward"></i>
{{/if}}
</a>
{{#if parentNode}}<span>/</span>{{/if}}
</span>
{{/let}}
{{/let}}
</template>
@@ -0,0 +1,394 @@
import { OHIF } from 'meteor/ohif:core';
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { Tracker } from 'meteor/tracker';
import { ReactiveVar } from 'meteor/reactive-var';
import _ from 'underscore';
import $ from 'jquery';
// TODO: use npm dependency
import transition from 'meteor/ohif:core/client/lib/third-party/transition-to-from-auto';
Template.selectTree.onCreated(() => {
const instance = Template.instance();
// Return the common items
instance.getCommonItems = () => {
const instance = Template.instance();
// Return the common items if given
if (instance.data.commonItems) {
return instance.data.commonItems;
}
// Get all the tree leaves
const leaves = instance.data.component.getLeaves();
// Generate an object with encoded keys from the tree leaves
const leavesObject = {};
_.each(leaves, leaf => {
leavesObject[OHIF.string.encodeId(leaf.value)] = leaf;
});
// Get the current items ranking
const ranking = OHIF.user.getData(instance.data.storageKey);
// Sort the items based on how many times each one was used
const sorted = [];
_.each(ranking, (count, key) => sorted.push([key, count]));
sorted.sort((a, b) => b[1] - a[1]);
// Create the result and push every item respecting the ranking order
const result = [];
_.each(sorted, item => {
const current = leavesObject[item[0]];
if (current) {
result.push(current);
}
});
// Return the resulting array
return result;
};
// Create a reactive variable to control the search
instance.searchTerm = new ReactiveVar('');
});
Template.selectTree.onRendered(() => {
const instance = Template.instance();
const component = instance.$('.select-tree:first').data('component');
const rootComponent = instance.data.root || component;
const rootInstance = rootComponent.templateInstance;
const $treeRoot = rootInstance.$('.select-tree-root:first').first();
instance.component = component;
// Force to hardware acceleration to move element if browser supports translate property
instance.useTransform = OHIF.ui.styleProperty.check('transform', 'translate(1px, 1px)');
// Set the margin property and width
const isthreeColumns = instance.data.threeColumns;
if (!rootInstance.marginProperty) {
rootInstance.marginProperty = isthreeColumns ? 'margin-left' : 'margin-right';
rootInstance.marginWidth = isthreeColumns ? $treeRoot.width() / 2 : $treeRoot.width();
}
// Define a function to set the margin and toggle common section
instance.setMargin = isHidden => {
const marginWidth = isHidden ? '' : rootInstance.marginWidth;
$treeRoot.children('.tree-content').css(rootInstance.marginProperty, marginWidth);
};
// Set the margin to display the common section
if (!$treeRoot.hasClass('started')) {
instance.setMargin(false);
}
// Make the component respect the window boundaries
if (rootComponent === component) {
$treeRoot.bounded();
}
// Check if the component will be rendered on a specific position
const position = instance.data.position;
if (component === rootComponent && position) {
// Get the dimensions and move it with the given position as its center
const width = $treeRoot.outerWidth();
const height = $treeRoot.outerHeight();
position.left -= Math.round(width / 2);
position.top -= Math.round(height / 2);
// Change the component's position and trigger the bounded event
$treeRoot.css(_.extend({}, position, {
position: 'fixed'
})).trigger('spatialChanged');
}
Meteor.defer(() => {
// Start the component transitions
$treeRoot.addClass('started');
// Focus the search box
$treeRoot.find('.tree-search input').focus();
});
// Update the component's viewport height
instance.updateHeight = _.throttle(searchTerm => {
let height;
// Check if there's a search term
if (searchTerm) {
// Set the viewport height as same as listed options height
height = rootInstance.$('.tree-inputs').height();
} else {
// Set the viewport height as same as selected node's options height
height = rootInstance.$('.tree-options:last').data('height');
}
// Update the viewport's height
rootInstance.$('.tree-options:first').first().height(height);
}, 100, { trailing: false });
// Update the opened node
instance.updateOpen = () => {
rootInstance.$('.select-tree').removeClass('open');
rootInstance.$('.select-tree:last').addClass('open');
};
// Put the component in selected state
instance.setSelected = (flag) => {
if (flag) {
$treeRoot.addClass('selected');
} else {
$treeRoot.removeClass('selected');
}
};
// Store the element's initial height
const $treeOptions = instance.$('.tree-options:first').first();
const height = $treeOptions.height();
$treeOptions.data('height', height);
$treeOptions.off('transitionend').on('transitionend', event => {
if (event.target !== event.currentTarget) return;
$treeRoot.trigger('spatialChanged');
});
instance.autorun(() => {
// Run this computation everytime the current node is changed
instance.data.currentNode.dep.depend();
// Run this computation everytime the search term is changed
const searchTerm = instance.searchTerm.get();
// Clean the current node selection if the user started the search
if (searchTerm) {
rootInstance.data.currentNode.set(null);
}
// Update the viewport height
Tracker.afterFlush(() => {
instance.updateHeight(searchTerm);
instance.updateOpen();
});
});
const selector = '.select-tree:first>.tree-content>.tree-options>.tree-breadcrumb .path-link';
instance.$(selector).css('width', 0).each((index, element) => {
transition({
element: element,
prop: 'width',
style: 'width 0.3s ease',
val: 'auto'
});
});
});
Template.selectTree.events({
'input .tree-search input'(event, instance) {
const $target = $(event.currentTarget);
// Get the tree root
const $treeRoot = $target.closest('.select-tree-root');
// Get the search term
const searchTerm = $target.val();
// Change the search term to update the tree items
instance.searchTerm.set(searchTerm);
// Change the component state
const method = searchTerm ? 'addClass' : 'removeClass';
$treeRoot[method]('navigated');
instance.setMargin(!!searchTerm);
},
'keydown .tree-search input'(event, instance) {
// Get the search term
const searchTerm = $(event.currentTarget).val().trim();
const $labels = instance.$('.tree-inputs:first>label');
// Select the label if ENTER was pressed and there is only one result
if (searchTerm.length && $labels.length === 1 && event.which === 13) {
$labels.find('input').click();
}
},
'change .select-tree:first>.tree-content>.tree-options>.tree-inputs>label>input'(event, instance) {
const component = instance.component;
const $target = $(event.currentTarget);
const $treeRoot = $target.closest('.select-tree-root');
const $label = $target.closest('label');
const eventComponent = $target.data('component');
const rootComponent = instance.data.root || component;
const rootInstance = rootComponent.templateInstance;
const offsetTop = $target.closest('.wrapperLabel').offset().top;
// Change the component's node
component.node(eventComponent.value());
// Unset the active leaf
rootInstance.$('label').removeClass('active').css('width', '');
// Unset the selected state
instance.setSelected(false);
// Detect the first interaction with the component and do the animation
instance.setMargin(true);
$treeRoot.addClass('navigated');
// Check if the clicked element is a node or a leaf
const isLeaf = $label.hasClass('tree-leaf');
if (isLeaf) {
// Change the active item
$label.css('width', $label.outerWidth()).addClass('active');
// Ckeck if there's a storageKey defined
const storageKey = instance.data.storageKey;
if (storageKey) {
// Get the current stored data
const storedData = _.extend({}, OHIF.user.getData(storageKey));
// Increment or create a counter for the clicked leaf
const itemKey = OHIF.string.encodeId($target.val());
if (storedData[itemKey]) {
storedData[itemKey]++;
} else {
storedData[itemKey] = 1;
}
// Update the stored data with the new count
OHIF.user.setData(storageKey, storedData);
}
// Set the selected leaf value in the root component
const itemData = $target.data('component').templateInstance.data.itemData;
rootComponent.value(itemData);
// Mark the component as selected
instance.setSelected(true);
} else {
// Get the position of the clicked element
const position = $label.position();
const getPosition = $element => {
if (instance.useTransform) {
const matrixToArray = str => str.match(/(-?[0-9\.]+)/g);
const transformMatrix = matrixToArray($element.css('transform')) || [];
return {
x: parseFloat(transformMatrix[4]) || 0,
y: parseFloat(transformMatrix[5]) || 0
};
} else {
return {
x: parseFloat($element.css('left')),
y: parseFloat($element.css('top'))
};
}
};
const setPosition = ($element, position) => {
if (instance.useTransform) {
const translation = `translate(${position.x}px, ${position.y}px)`;
OHIF.ui.styleProperty.set($element[0], 'transform', translation);
} else {
$element.css('left', `${position.x}px`);
$element.css('top', `${position.y}px`);
}
};
Meteor.defer(() => {
const $treeNode = $label.parent().siblings('.select-tree');
// Do the transition from the clicked position to top
$treeNode.css(position);
setTimeout(() => $treeNode.css('top', 0));
const optionsTop = $target.closest('.tree-options').position().top;
const treeOffsetTop = $treeRoot.offset().top;
const treeRootPosition = getPosition($treeRoot);
treeRootPosition.y += offsetTop - treeOffsetTop - optionsTop;
setPosition($treeRoot, treeRootPosition);
});
}
},
'click .select-tree:first>.tree-content>.tree-options>.tree-breadcrumb .tree-back'(event, instance) {
const $target = $(event.currentTarget);
const rootComponent = instance.data.root || instance.component;
const rootInstance = rootComponent.templateInstance;
// Prevent the default element action
event.preventDefault();
// Reset the checked inputs to unchecked state
rootInstance.$('input').removeAttr('checked');
// Unset the selected state
instance.setSelected(false);
instance.setMargin(false);
$target.closest('.select-tree-root').removeClass('navigated');
// Get the index of the breadcrumb's clicked option
const index = parseInt($target.attr('data-index'));
// Set the current instance
let currentInstance = instance.component.parent.templateInstance;
// Iterate over all parents until gets into the clicked node
for (let i = 1; i <= index; i++) {
// Unset the selected node
currentInstance.data.currentNode.set(null);
// Check if it's not the root node
if (i < index) {
// Change the current instance
currentInstance = currentInstance.component.parent.templateInstance;
}
}
}
});
Template.selectTree.helpers({
treeItems() {
const instance = Template.instance();
// Run this computation everytime the search term is changed
const searchTerm = instance.searchTerm.get();
// Get all the items
let items = instance.data.items;
// Check if the search term was informed
if (searchTerm) {
// Search the items by the give search term
items = OHIF.string.search(items, searchTerm, 'label');
// Filter only the tree leaves
items = _.filter(items, item => !item.items);
} else if (instance.data.hideCommon) {
// Get the values of common items
const commonValues = _.pluck(instance.getCommonItems(), 'value');
// Filter only the items that are not common
items = _.filter(items, item => !_.contains(commonValues, item.value));
}
// Return the items sorted for tree columns
if (instance.data.threeColumns) {
const begin = items.splice(0, Math.ceil(items.length / 2));
const sortedItems = [];
_.each(begin, (item, index) => {
sortedItems.push(item);
if (items[index]) {
sortedItems.push(items[index]);
}
});
items = sortedItems;
}
// Return the tree items
return items;
}
});
@@ -0,0 +1,264 @@
@import "{ohif:viewerbase}/app"
// TODO: [design] can't we use colors that are already in common pallete?
$gray1 = #A3A3A3
$gray2 = #A0A0A0
$gray3 = #C4C4C4
$gray4 = #C0C0C0
$gray5 = #9B9B9B
$gray6 = #303030
.select-tree-root
.tree-content
background-color: white
position: relative
transition(transform 0.3s ease\, background-color 0.3s ease\, border-color 0.3s ease\, border-radius 0.3s ease\, margin-right 0.3s ease)
.tree-search
a.tree-back
.tree-breadcrumb span
.tree-node
.tree-leaf
transition(all 0.3s ease)
&.selected
label.tree-leaf.active
box-shadow: 0 0 0 10px white, inset 0 0 0 200px white
.tree-content
background-color: transparent
&>.tree-content
border-color: transparent
border-radius: 0
.tree-search
background-color: transparent
border-color: transparent
color: transparent
input,
i
opacity: 0
&, .select-tree
&:not(.open)>.tree-content
&>.tree-inputs
&>.tree-breadcrumb
display: none
&>.tree-content>.tree-options
&>.tree-inputs>label:not(.active)
&>.tree-breadcrumb a
&>.tree-breadcrumb span
color: transparent
&>.tree-inputs>label.tree-node:not(.active)
border-color: transparent
&.common-clicked .tree-leaf.active
background-color: transparent
box-shadow: none
color: transparent
.select-tree-root
display: inline-block
position: relative
z-index: 0
&>.tree-content
border: 1px solid $gray3
border-radius: 5px
overflow: hidden
&>.tree-content>.tree-options
transform(scale(1))
transition(height 0.3s ease)
.tree-search
background-color: $gray4
border-bottom: 1px solid $gray3
color: $gray6
display: flex
margin: 0
padding: 5px
position: relative
text-align: center
span
font-weight: bold
line-height: 24px
input
border-color: $gray5
font-weight: normal
padding-left: 34px
transition(opacity 0.3s ease)
& ~ i
color: $gray1
font-size: 18px
left: 16px
position: absolute
top: 37px
transition(opacity 0.3s ease)
.tree-breadcrumb
line-height: 32px
padding: 5px 12px
.path-link, .tree-current-node
display: inline-block
font-weight: normal
float: left
overflow: hidden
text-overflow: ellipsis
white-space: nowrap
i
margin-right: 5px
.select-tree-node
left: 0
overflow: hidden
position: fixed
width: 100%
z-index: 2
&
.tree-content
.tree-inputs
height: 100%
.tree-options
overflow: hidden
position: relative
z-index: 2
.tree-node, .tree-leaf
cursor: pointer
display: block
font-weight: normal
margin-bottom: 0
padding: 0 12px
position: relative
z-index: 1
&>input
display: none
.tree-node
height: 41px
border-bottom: 1px solid $gray3
border-top: 1px solid $gray3
line-height: 41px
margin-top: -1px
&:last-child
border-bottom: 0
.tree-leaf
box-shadow: 0 0 0 200px transparent, inset 0 0 0 200px transparent
line-height: 26px
&.active
box-shadow: 0 0 0 200px white, inset 0 0 0 200px white
transition(box-shadow 0.3s ease)
z-index: 0
&:last-child:after
display: block
content: ''
height: 10px
&.collapsed
z-index: 1
.select-tree-common
height: 100%
padding-left: 6px
position: absolute
right: 0
top: 0
width: 50%
.content
background-color: $gray6
border: 1px solid $gray5
border-radius: 5px
color: white
opacity: 0.9
padding: 0 15px 12px
position: absolute
top: 50%
transform(translateY(-50%))
width: calc(100% - 6px)
h5.title
color: $gray5
font-weight: normal
line-height: 20px
margin: 12px 0 16px
div.labels
position: relative
label
cursor: pointer
display: block
font-weight: 300
line-height: 26px
margin: 0
padding-left: 3px
transition(padding 0.3s ease)
&:hover
padding-left: 0
.select-tree-root
&>.tree-content
transform(scale(0))
transform-origin(100% 50%)
.select-tree-common
z-index: -1
.content
margin-left: calc(-100% - 6px)
transform(scale(0))
transition(margin-left 0.3s ease 0.3s\, transform 0s linear 0.3s)
&.started
&>.tree-content
transform(scale(1))
.select-tree-common
.content
margin-left: 0
transform(scale(1) translateY(-50%))
&.navigated, &.selected
.select-tree-common
.content
opacity: 0
transform(translateX(-100%) scale(0))
transition(transform 0.3s ease\, opacity 0.3s ease)
@keyframes selectTreeCommonOverflow {
from {
overflow: hidden
}
to {
overflow: visible
}
}
@@ -0,0 +1,12 @@
<template name="selectTreeCommon">
<div class="select-tree-common">
<div class="content">
<h5 class="title">Common</h5>
<div class="labels">
{{#each item in items}}
<label for="{{this.storageKey}}_{{encodeId item.value}}">{{item.label}}</label>
{{/each}}
</div>
</div>
</div>
</template>
@@ -0,0 +1,44 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { Tracker } from 'meteor/tracker';
Template.selectTreeCommon.events({
'click .select-tree-common label'(event, instance) {
event.preventDefault();
// Get the clicked label
const $target = $(event.currentTarget);
// Store the offset position for further use
$target.data('offset', $target.offset());
// Build the input selector based on the label target
const inputSelector = '#' + $target.attr('for');
// Get the tree instance
const treeInstance = instance.data.treeInstance;
// Check if the input is not rendered
if (!$(inputSelector).length) {
// Change the search terms with the clicked label string
treeInstance.$('.tree-search input').val($target.text()).trigger('input');
// Wait for options rerendering
Tracker.afterFlush(() => {
const $treeRoot = treeInstance.$('.select-tree-root:first').first();
$treeRoot.addClass('common-clicked');
// Wait for components initialization
Meteor.defer(() => $(inputSelector).click());
});
}
}
});
Template.selectTreeCommon.helpers({
// Get the common items
items() {
return Template.instance().data.treeInstance.getCommonItems();
}
});