Paul Shorey | Web app UI developer (and designer) hidden | hidden | Full resume
2018
After taking the holidays off, I'm testing out new techniques. Demo, code, and articles coming soon...
2016-2017 - Luxul
Designed and developed a new admin UI for Luxul brand electronic devices, using React, Redux, Webpack, Material-UI, and JSON-RPC.
Featuring:
* Mobile-first responsive UI design (open demo »)
* Styled-Components Material-UI form framework with state management and validation (mui-form NPM/Yarn package)
Global state management solution which is even simpler than MobX, used together with Redux (read about it)
* JSON-RPC streaming protocol, custom API and authentication library, unit-testing for Redux, and other goodies
Code samples below
 
Code samples:
* Custom <Select /> and <Toggle /> React components inheriting logic from the custom <Input />
* <Nav /> and <NavGroup /> using Styled-Components
* Multi-level routing and neat code-splitting (lazy-loading) of routes in a React/Redux app
* Unit testing automatically run before codebase committed to git
2016 - All Events .NYC
"Mobile-first" design all the way! Responsive AngularJS web app, made with Ionic, to work as a standalone iPhone and Android app too. An experiment in content curation and aggregation. PhantomJS bot constantly crawls the web, updating events in the database. After a busy workday, see what's going on in town. This crawling system and user interface can very quickly be adapted to any type of content or niche. Code samples below
This text will be replaced by Javascript. If you see this text, please let me know :(
ReactJS code on GitHub and NPM:
 
* Custom <Select /> and <Toggle /> React components inheriting logic from the custom <Input />
* <Nav /> and <NavGroup /> using Styled-Components
* Multi-level routing and neat code-splitting (lazy-loading) of routes in a React/Redux app
* Unit testing automatically run before codebase committed to git
 
 
 
 
 
AngularJS code samples:
Rock Paper Scissors
Clock hands UI »
 
How am I making this arrow? or any other icons?

	How am I making   this arrow? or any other icons?  
			
 
AngularJS meets ReactJS:

	

	angular.module('appNyc.components', [])
	
	.directive('reactEventslist', function (reactDirective, EventService,$timeout, $rootScope) {
	return {
		restrict: 'A',
		scope: {
			data: '='
		},
		link: function (scope, element, attrs) {
			scope.vm = {};
	
			// load template -- react component
			scope.list_ready = function(){
				var query = {};
				query.category = scope.data.category;
				query.scene = scope.data.scene;
				query.time = scope.data.time;
				EventService.getEvents(query)
				.then(function (response) {
					scope.vm.events = response.data.data;
	
					ReactDOM.render(  
					  React.createElement(React.html.eventslist, {vm:scope.vm,data:scope.data}), 
					  angular.element(element)[0]
					);
	
				}, function (error) {
					console.error(error);
				});
			}
	
			// loading animation -- default state
			scope.list_reset = function(){
				scope.vm = {};
				ReactDOM.render( 
					React.createElement(React.html.eventslist_loading, {data:scope.data}), 
					angular.element(element)[0]
				);
				$timeout(function(){
					if (element) {
						$(element).addClass('ready');
					}
				},1000);
			}
			scope.list_reset();
	
			// lazyload -- make API call only for content in view
			if (!$rootScope.lazyLoadedLists) {
				$rootScope.lazyLoadedLists = {};
			}
			scope.element = element;
			scope.$watch(
			function( scope ) {
				element = scope.element;
				var window_width = (window.innerWidth || document.documentElement.clientWidth);
				var rect = element[0].getBoundingClientRect();
				if (rect.left > 0 && rect.left < window_width) {
	
					if (!$rootScope.lazyLoadedLists[ scope.data.category ]) {
						$rootScope.lazyLoadedLists[ scope.data.category ] = scope;
						console.log('listing ',scope.data.category);
						scope.list_ready()
					}
	
				} else {
					if ($rootScope.lazyLoadedLists[ scope.data.category ]) {
						delete $rootScope.lazyLoadedLists[ scope.data.category ];
						scope.list_reset();
					}
				}
			});
	
		}
	}
	})
	
	;
			

	if (!React.html) {
	React.html = {};
	}
	React.html['eventslist'] = React.createClass({
	bindClick(e, link) {
		// Ionic opens href links in its own browser, this is to escape that. However, thinking of ditching Ionic for something faster like famo.us
		window.open(link, '_system');
		e.preventDefault();
	},
	render: function (events) {
	
		var rows = [];
		var old_timestring = '';
		for (var i=0; i < this.props.events.length; i++) {
			var event = this.props.events[i];
			if (!event.texts) {
				break;
			}
			var timestring = Date.create(event.timestamp).short();
			var todayEnd = moment().endOf('day').format('x');
			if (event.timestamp < todayEnd - 1) { // party must end before midnight, because we don't know when exactly tomorrow's dates are, most come in as 12:00am
				timestring = 'today';
			} else if (event.timestamp < todayEnd - 1 + 1000*60*60*24) {
				timestring = 'tomorrow';
			} else if (event.timestamp < todayEnd - 1 + 1000*60*60*24 *6) {
				timestring = 'this week';
			} else if (event.timestamp < todayEnd - 1 + 1000*60*60*24 *30) {
				timestring = 'this month';
			}
	
			// 
			if (timestring != old_timestring) {
				rows.push(
{timestring}
); old_timestring = timestring; } // var time = moment(event.timestamp).format('h:mma'); if (time=='12:00am') { time = ''; } var subtext = []; if (timestring.indexOf('week') != -1 || timestring.indexOf('month') != -1) { subtext.push( {moment(event.timestamp).format('MMM D') +' '+time}); } else if (time && time!='12:00am') { subtext.push( {time}); } subtext.push({event.source_host.substr(0, event.source_host.indexOf('.'))}); // rows.push(
{event.texts[0]} {event.texts[1]} {event.texts[2]}
{subtext}
); } return
{rows}
; } });
AngularJS directives. Custom carousel, and scroll style events. Used in allevents.nyc homepage:

	.directive('scrollable', function ($timeout) {
	return {
		restrict: 'A',
		scope: {
			which: '='
		},
		link: function (scope, element, attrs) {
	
			// to scroll or not to scroll
			scope.$watch(
				function () {
					if (scope.$parent.vm.listJustAdded) {
						return scope.$parent.vm.listJustAdded;
					} else {
						return false;
					}
				},
				function (newValue, oldValue) {
					// scroll to beginning
					if (newValue && newValue != oldValue) {
						var target = element[0];
						var duration = 400; // target.clientWidth / 2;
	
						var scrollTo = 0;
						target.doNotScroll = 'scroll--changed';
						$timeout(function () {
							$(target)
								.animate({
									scrollLeft: 0
								}, {
									duration: duration
								});
	
							$timeout(
								scope.scrollCheck,
								duration + 10
							);
	
						}, 100);
					}
				}
			);
	
			// the arrows
			$(element)
			.siblings('[scrollable-left]')
			.click(function () {
				var target = element[0];
				if (target.doNotScroll) {
					return;
				}
				var duration = 400;
	
				var scrollTo = target.scrollLeft - target.clientWidth;
				$(target)
					.animate({
						scrollLeft: scrollTo
					}, {
						duration: duration
					});
	
				target.doNotScroll = 'scrollable-left';
				$timeout(
					scope.scrollCheck,
					duration
				);
			});
			$(element)
			.siblings('[scrollable-right]')
			.click(function () {
				var target = element[0];
				if (target.doNotScroll) {
					return;
				}
				var duration = 400;
	
				var scrollTo = target.scrollLeft + target.clientWidth;
				$(target)
					.animate({
						scrollLeft: scrollTo
					}, {
						duration: duration
					});
	
				target.doNotScroll = 'scrollable-right';
				$timeout(
					scope.scrollCheck,
					duration
				);
			});
	
			// finish scroll position to nearest column or page
			scope.scrollfix = function(){
				var target = element[0];
				var duration = 200;
				if (target.doNotScroll) {
					return;
				}
				// what direction?
				var round = 'ceil';
				if (target.scrollLeft < target.scrollLeftLast) {
					round = 'floor';
				}
				// finish scrolling - to closest column
				var columns = Math[round](target.scrollLeft / target.firstElementChild.firstElementChild.clientWidth);
				var scrollTo = target.firstElementChild.firstElementChild.clientWidth * columns;
				// go
				$(target).animate({
					scrollLeft: scrollTo
				}, {
					duration: duration
				});
				// done
				target.doNotScroll = true;
				$timeout(
					scope.scrollCheck,
					duration
				);
			};
			$(element).scroll(function(){
				window.clearTimeout(scope.scrollfix_timeout);
				scope.scrollfix_timeout = window.setTimeout(function(){
					scope.scrollfix();
				},222); // timeout must be greater than duration of 
			});
	
			// ends - disable arrow when at beginning or end
			scope.scrollCheck = function () {
				var target = element[0];
				target.doNotScroll = false;
				target.scrollLeftLast = target.scrollLeft;
				if (target.scrollLeft<10) {
					$(element).siblings('[scrollable-left]').addClass('scrollEnd');
				} else {
					$(element).siblings('[scrollable-left]').removeClass('scrollEnd');
				}
				if (target.scrollLeft > ( target.scrollWidth - target.parentElement.scrollWidth - 10 ) ) {
					$(element).siblings('[scrollable-right]').addClass('scrollEnd');
				} else {
					$(element).siblings('[scrollable-right]').removeClass('scrollEnd');
				}
			};
			$timeout(
				scope.scrollCheck,
				500
			);
			$(window).on('resize',scope.scrollCheck);
			scope.$on('$destroy',function(){
				$(window).off('resize',scope.scrollCheck);
			});
		}
	}
	})
			

Hello world! More info coming soon. But here is a beautiful portrait of my face in case you wanted to see it. Unfortunately, I do not have a LinkedIn. I do have a Facebook but it is filled with silly pictures.

Besides programming, I enjoy creating other things - like electronics, product design, hydroponic greens (only legal stuff), and fine art.

I also enjoy skiing, hiking, and hang gliding, which is why I moved to and am now living in Utah!