added 260907 version of worker toolkit

This commit is contained in:
2026-09-08 20:30:51 -04:00
parent cbd3f0f8ca
commit 97aca37663
1283 changed files with 142951 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
import Ember from 'ember';
import { translationMacro as t } from "ember-i18n";
import moment from 'moment-timezone';
const {
get,
set,
computed,
computed: {
alias,
},
inject: {
service,
},
Component,
} = Ember;
export default Component.extend({
i18n: service(),
profile: alias('model'),
classNames: ['checkinReminder'],
checkinReminderEnabled: alias('profile.checkinReminder'),
reminderOff: t("step.onboarding.reminder.reminderOff"),
reminderOn: t("step.onboarding.reminder.reminderOn"),
title: null,
timezones: computed(function() {
let originNames = moment.tz.names();
if(originNames) {
return originNames.map((timezone) => {
return timezone.split('_').join(' ');
})
} else {
return [];
}
}),
options: computed(function() {
return {
hour: get(this, 'profile.checkinReminderAt.hours'),
mins: get(this, 'profile.checkinReminderAt.minutes'),
format: 'hh:i A'
}
}),
actions: {
changeReminder(boolParam) {
set(this, 'profile.checkinReminder', boolParam);
},
timeChanged(param) {
let parsedParam = { hours: param.getHours(), minutes: param.getMinutes() };
set(this, 'profile.checkinReminderAt', parsedParam);
}
},
});

View File

@@ -0,0 +1,46 @@
import Ember from 'ember';
import TrackablesFromType from 'flaredown/mixins/trackables-from-type';
const {
get,
} = Ember;
export default Ember.Component.extend(TrackablesFromType, {
tracking: Ember.inject.service(),
setupTracking: Ember.on('init', function() {
this.get('tracking').setup({
at: new Date(),
trackableType: this.get('trackableType').capitalize()
});
}),
existingTrackings: Ember.computed.alias('tracking.existingTrackings'),
newTrackings: Ember.computed.alias('tracking.newTrackings'),
actions: {
trackSelected(selectedTrackable) {
this.track(selectedTrackable);
},
remove(tracking) {
this.get('tracking').untrack({
tracking: tracking
});
}
},
track: function(trackable) {
const trackableType = get(this, 'trackableType').capitalize();
const trackableIds = this.get('store').peekAll('tracking').filterBy('trackableType', trackableType).map((tr) => {
return get(tr, 'trackable.id');
});
if (!trackableIds.includes(get(trackable, 'id'))) {
this.get('tracking').track(trackable, null, () => {
this.set('selectedTrackable', null);
});
}
}
});

View File

@@ -0,0 +1,6 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['flex-container flaredown-additional-info'],
tagName: ['div'],
});

View File

@@ -0,0 +1,39 @@
import Ember from 'ember';
const {
get,
inject: {
service,
},
TextArea
} = Ember;
export default TextArea.extend({
ajax: service(),
placeholder: 'Leave a comment...',
classNames: ['atwho-inputor'],
didInsertElement() {
this._super(...arguments);
const ajax = get(this, 'ajax');
const postId = get(this, 'postId');
ajax.request('/profiles', { data: { post_id: postId } }).then(({ profiles }) => {
let userList = profiles.map(function(item) {
return { screen_name: item.screen_name, slug_name: item.slug_name };
});
if(!get(this, 'isDestroyed')) {
this.$().atwho({
at: "@",
insertTpl: "@${slug_name}",
displayTpl: '<li>${screen_name}</li>',
searchKey: "screen_name",
data: userList,
});
}
});
}
});

View File

@@ -0,0 +1,54 @@
import Component from '@ember/component';
import { get, computed } from '@ember/object';
import moment from 'moment';
export default Component.extend({
classNames: ['birthDate'],
startYear: 1920,
months: computed(() => {
let monthArray = [];
for(let i = 1; i<= 12; i++) {
monthArray.push(i.toString());
}
return monthArray;
}),
yearsArray: computed('birthDate.date', function() {
let yearsArray = [];
const endYear = parseInt(moment().subtract(10, 'years').format('YYYY'));
const startYear = this.get('startYear');
for (let i=endYear; i>=startYear; i--) {
yearsArray.push(i.toString());
}
return yearsArray;
}),
disabledDays: computed('birthDate.{year,month}', function() {
return !(get(this, 'birthDate.year') && get(this, 'birthDate.month'));
}),
daysArray: computed('birthDate.{year,month}', function() {
let days = [];
if (!get(this, 'disabledDays')) {
const year = get(this, 'birthDate.year');
const month = get(this, 'birthDate.month');
for (let d = 1; d <= this.daysInMonth(month, year); d ++) {
days.push(d.toString());
}
}
return days;
}),
daysInMonth(month, year) {
return new Date(year, month, 0).getDate();
},
});

View File

@@ -0,0 +1,25 @@
import Ember from 'ember';
import CheckinByDate from 'flaredown/mixins/checkin-by-date';
const {
get,
computed,
Component,
inject: {
service,
},
} = Ember;
export default Component.extend(CheckinByDate, {
classNames: ['bottom-nav'],
notifications: service(),
isCheckinPath: computed('router.url', function() {
return get(this, 'router.url').split('/')[1] === 'checkin';
}),
checkinNavClass: computed('isCheckinPath', function() {
return `bottom-link ${get(this, 'isCheckinPath') ? 'active' : ''}`;
}),
});

View File

@@ -0,0 +1,103 @@
import Ember from 'ember';
import moment from 'moment';
const {
$,
Component,
computed,
get,
set,
computed: { alias },
observer,
run: { scheduleOnce }
} = Ember;
export default Component.extend({
date: new Date(),
dateFormat: 'MMM D',
classNames: ['chart-navigation'],
classNameBindings: ['isAuthenticated::isBottom'],
isAuthenticated: alias('session.isAuthenticated'),
patternNavbar: false,
rateOfDuration: 0.2,
visibleChartsCount: alias('chartsVisibilityService.visibleChartsCount'),
pickadateOptionsStart: {
container: 'body > .ember-view',
max: moment().toDate(),
},
pickadateOptionsEnd: {
container: 'body > .ember-view',
max: moment().toDate(),
},
pickadateOptions: {
container: 'body > .ember-view'
},
startDate: computed('startAt', function(){
return get(this, 'startAt').toDate();
}),
endDate: computed('endAt', function(){
return get(this, 'endAt').toDate();
}),
endAtLabel: computed('endAt', function() {
return get(this, 'endAt').format(get(this, 'dateFormat'));
}),
startAtLabel: computed('startAt', function() {
return get(this, 'startAt').format(get(this, 'dateFormat'));
}),
startDateLimitationObserver: observer('endAt', function() {
set(this, 'pickadateOptionsStart', $.extend({}, get(this, 'pickadateOptionsStart'), {
max: get(this, 'endAt').toDate()
}));
}),
rigthArrowVisibile: computed('endAt', function() {
return get(this, 'endAt').endOf('day') < moment();
}),
daysDuration: computed('startAt', 'endAt', function() {
let duration = moment.duration(get(this, 'endAt') - get(this, 'startAt')).asDays();
let rate = Number(duration*get(this, 'rateOfDuration')).toFixed(0);
return rate > 1 ? rate : 1;
}),
actions: {
openPicker() {
const arg = arguments[0];
const selector = arg ? `.calendar-opener-${arg}` : '.calendar-opener';
$(`${selector} .picker__input`).first().pickadate('picker').open();
},
navigateLeft() {
this.sendAction('onNavigate', - 1); // - daysDuration
},
navigateRight() {
this.sendAction('onNavigate', 1); // daysDuration
},
startChanged(date) {
scheduleOnce('afterRender', this, () => {
this.sendAction('onChangeStartAt', moment(date));
});
},
endChanged(date) {
scheduleOnce('afterRender', this, () => {
this.sendAction('onChangeEndAt', moment(date));
});
},
},
});

View File

@@ -0,0 +1,76 @@
import Ember from 'ember';
import CheckinByDate from 'flaredown/mixins/checkin-by-date';
import DatesRetriever from 'flaredown/mixins/chart/dates-retriever';
import DS from 'ember-data';
import moment from 'moment';
const {
get,
set,
computed,
computed: { alias },
Component,
inject: { service },
setProperties,
} = Ember;
const STEPS = {
INITIAL: 1,
CREATE: 2,
INDEX: 3
};
export default Component.extend(CheckinByDate, DatesRetriever, {
i18n: service(),
patternsLoading: service(),
STEPS,
selectedPattern: null,
currentStep: null,
dateRange: 2,
serieHeight: 75,
loadingPatterns: alias('patternsLoading.loadingPatterns'),
patterns: computed(function() {
return DS.PromiseArray.create({
promise: get(this, 'store').findAll('pattern')
});
}),
/*eslint-disable no-dupe-keys*/
currentStep: computed('patterns.length', function(){
return get(this, 'patterns.length') > 0 ? STEPS.INDEX : STEPS.INITIAL;
}),
/*eslint-enable no-dupe-keys*/
checkins: computed(function() {
return this
.store
.peekAll('checkin')
.toArray()
.sort(
(a, b) => moment(get(a, 'date')).diff(get(b, 'date'), 'days')
);
}),
actions: {
create() {
setProperties(this, { selectedPattern: null, currentStep: STEPS.CREATE });
},
edit(pattern) {
setProperties(this, { selectedPattern: pattern, currentStep: STEPS.CREATE });
},
requestData(page) {
get(this, 'store').query('pattern', { page: page }).then((record) => {
get(this, 'patterns').toArray().pushObject(record);
set(this, 'loadingPatterns', false);
})
},
nextStep() {
set(this, 'currentStep', STEPS.CREATE);
},
},
});

View File

@@ -0,0 +1,26 @@
import Ember from 'ember';
import Graphable from 'flaredown/components/chart/graphable';
import * as d3 from "d3";
const {
get,
computed,
Component,
computed: { alias },
} = Ember;
export default Component.extend(Graphable, {
name: alias('model.name'),
halfHeight: computed('height', function() {
return get(this, 'yScale')(0);
}),
yScale: computed('data', function() {
return d3.scale.linear().range([get(this, 'height') , 0]).domain([-1, 1]);
}),
data: computed('timeline', function() {
return (this.get('timeline') || []).map(day => ({ x: day, y: null })).sortBy('x');
}),
});

View File

@@ -0,0 +1,23 @@
import Ember from 'ember';
import Graphable from 'flaredown/components/chart/graphable';
import * as d3 from "d3";
const {
computed,
Component,
computed: { alias },
} = Ember;
export default Component.extend(Graphable, {
yRulers: [0, 1, 2, 3, 4],
name: alias('model.name'),
yScale: computed('data', function() {
return d3.scale.linear().range([this.get('height') , 0]).domain([-1, 5]);
}),
data: computed('timeline', function() {
return (this.get('timeline') || []).map(day => ({ x: day, y: null })).sortBy('x');
}),
});

View File

@@ -0,0 +1,97 @@
import Ember from 'ember';
import moment from 'moment';
import Graphable from 'flaredown/components/chart/graphable';
import * as d3 from "d3";
const {
get,
set,
computed,
Component,
computed: { alias },
} = Ember;
export default Component.extend(Graphable, {
markerRadius: 7,
name: alias('model.name'),
halfHeight: computed('height', function() {
return get(this, 'yScale')(0);
}),
markers: computed('data', function() {
return(
get(this, 'data')
.filter(item => item.y === true)
.map(item => ({
x: get(this, 'xScale')(item.x),
y: get(this, 'yScale')(0) - Math.ceil(get(this, 'markerRadius') / 2),
tip: {
label: item.label,
x: get(this, 'xScale')(item.x) - 15,
y: get(this, 'yScale')(item.y) - get(this, 'markerRadius') / 3,
}
}))
);
}),
xAxisElementId: computed('model', function() {
return `${get(this, 'model.constructor.modelName')}-${get(this, 'model.id')}`;
}),
yScale: computed('data', function() {
return d3.scale.linear().range([get(this, 'height') , 0]).domain([-1, 1]);
}),
data: computed('checkins', function() {
var type = get(this, 'model.constructor.modelName');
var key = type.pluralize();
var timeline = get(this, 'timeline') || [];
const modelId = get(this, 'model.id');
const modelName = get(this, 'name');
return timeline.map(day => {
var filteredCheckins = get(this, 'checkins').filter((checkin) => (
get(checkin, 'formattedDate') === moment(day).format("YYYY-MM-DD")
))
var coordinate = { x: day, y: null };
if(filteredCheckins.length) {
var items = key === 'tags' || key === 'foods' ?
filteredCheckins.map(item => (
get(item, key).findBy('name', modelName)
)).filter(Boolean)
:
filteredCheckins.map(item => (
get(item, key).findBy(`${type}.id`, modelId)
)).filter(Boolean)
var takenItems = items.filter((item) => (get(item, 'isTaken')))
if (items.length && (takenItems.length || key === 'tags' || key === 'foods')) {
var itemValues = [...takenItems, ...items].map(item => (get(item, 'value'))).filter(Boolean)
var label = itemValues.join(", ")
coordinate.label = label;
coordinate.y = true;
}
}
return coordinate;
}).sortBy('x');
}),
actions: {
openMarkerTooltip(marker) {
set(this, 'openMarkerTooltip', true);
set(this, 'currentMarker', marker);
},
closeMarkerTooltip() {
set(this, 'openMarkerTooltip', false);
set(this, 'currentMarker', null);
}
},
});

View File

@@ -0,0 +1,157 @@
import Ember from 'ember';
import moment from 'moment';
import Colorable from 'flaredown/mixins/colorable';
import Graphable from 'flaredown/components/chart/graphable';
import * as d3 from "d3";
const {
$,
get,
computed,
Component,
getProperties,
} = Ember;
export default Component.extend(Colorable, Graphable, {
colorId: '35',
rangeDevider: 4,
yRulersMax: computed.max('yRulers'),
yRulersMin: computed.min('yRulers'),
dataValuesYMax: computed.max('dataValuesY'),
dataValuesYMin: computed.min('dataValuesY'),
rulerStep: computed('dataValuesYMin', 'dataValuesYMax', function() {
const {
rangeDevider,
dataValuesYMax,
dataValuesYMin,
} = getProperties(this, 'dataValuesYMax', 'dataValuesYMin', 'rangeDevider');
const result = Math.ceil((dataValuesYMax - dataValuesYMin) / rangeDevider);
return result || 1;
}),
domainPadding: computed('yRulersMax', 'yRulersMin', function() {
const { rangeDevider, yRulersMax, yRulersMin } = getProperties(this, 'rangeDevider', 'yRulersMax', 'yRulersMin');
return (yRulersMax - yRulersMin) / rangeDevider;
}),
domain: computed('yRulersMax', 'yRulersMin', 'domainPadding', function() {
const {
yRulersMax,
yRulersMin,
domainPadding,
} = getProperties(this, 'yRulersMax', 'yRulersMin', 'domainPadding');
return [
yRulersMin - domainPadding,
yRulersMax + domainPadding,
];
}),
points: computed('data', function() {
const { data, xScale, yScale } = getProperties(this, 'data', 'xScale', 'yScale');
return(
data
.filter(item => $.isNumeric(item.y))
.map(item => {
const x = xScale(item.x);
const y = yScale(item.y) - 4; // minus radius
return {
x,
y,
tip: {
label: item.y,
x: x - 15,
y: y - 10
}
};
})
);
}),
yRulers: computed('data', function() {
const {
rulerStep,
dataValuesYMax,
dataValuesYMin,
} = getProperties(this, 'dataValuesYMax', 'dataValuesYMin', 'rulerStep');
let result = [];
const max = dataValuesYMax + rulerStep;
for (let i = dataValuesYMin; i < max; i += rulerStep) {
result.pushObject(i);
}
result = result.uniq();
if (result.length < 4) {
result.pushObjects([dataValuesYMax + rulerStep, dataValuesYMin - rulerStep]);
let offset = dataValuesYMin - rulerStep;
offset = offset >= 0 ? 0 : offset;
return result.uniq().map(v => v - offset);
} else {
return result;
}
}),
dataValuesY: computed('data', function() {
const values = get(this, 'data')
.filter(item => item.y || item.y === 0)
.map(item => item.y)
.compact();
return values.length ? values : [0, 100];
}),
yScale: computed('data', function() {
const { height, domain } = getProperties(this, 'height', 'domain');
return(
d3
.scale
.linear()
.range([height, 0])
.domain(domain)
);
}),
data: computed('checkins', function() {
const {
field,
checkins,
timeline,
} = getProperties(this, 'field', 'timeline', 'checkins');
return (timeline || []).map(day => {
let filteredCheckins = checkins.filter((checkin) => (
get(checkin, 'formattedDate') === moment(day).format("YYYY-MM-DD")
))
let coordinate = { x: day, y: null };
if (filteredCheckins.length) {
let items = filteredCheckins.map((checkin) => (
get(checkin, 'harveyBradshawIndex')
)).filter(Boolean)
if (items.length) {
let itemValues = items.map(checkin => get(checkin, field)).filter(Boolean)
let value = itemValues.reduce((acc, val) => (acc + val), 0) / itemValues.length
coordinate.y = value
}
}
return coordinate;
});
}),
});

View File

@@ -0,0 +1,45 @@
import Ember from 'ember';
let {
set,
computed,
Component,
getProperties,
} = Ember;
export default Component.extend({
unit: '',
tagName: 'g',
labelSize: 12,
linesOffset: 0,
init() {
this._super(...arguments);
Ember.run.scheduleOnce('afterRender', () => {
const labelsBox = this.$().find('.ruler-labels')[0].getBBox();
set(this, 'linesOffset', labelsBox.x + labelsBox.width);
});
},
rulers: computed('yRulers', function() {
const {
unit,
yScale,
labelSize,
yRulers,
} = getProperties(this, 'unit', 'yScale', 'labelSize', 'yRulers');
return (yRulers || [])
.map(item => {
const lineY = yScale(item);
return {
lineY,
label: `${item}${unit}`,
labelY: lineY + labelSize / 2 - 2,
};
});
}),
});

View File

@@ -0,0 +1,67 @@
import Ember from 'ember';
import moment from 'moment';
import * as d3 from "d3";
export default Ember.Component.extend( {
tagName: 'g',
classNames: 'timeline',
attributeBindings: ['transform'],
xAxisElementId: Ember.computed(function() {
return "x-axis-timeline";
}),
transform: Ember.computed('xScale', 'startAt', 'totalSeriesHeight', function() {
return `translate(${ - this.get('xScale')( this.get('startAt') )},${this.get('totalSeriesHeight')})`;
}),
drawAxis: Ember.on('didInsertElement', Ember.observer('xAxis', function() {
d3.select(`g#${this.get('xAxisElementId')}`)
.call(this.get('xAxis'))
.selectAll('.tick')
.on('click', this.handleDataClick.bind(this))
.on('mouseover', this.handleMouseHover.bind(this))
.on('mouseleave', this.handleMouseLeave.bind(this));
})),
xAxis: Ember.computed('xScale', function() {
return d3.svg.axis().scale(this.get('xScale')).orient("bottom").ticks(this.get('timelineLength')).tickFormat(function(d){
return moment(d).format("MMM DD");
});
}),
xScale: Ember.computed('xDomain', function() {
return d3.time.scale()
.range([0, this.get('width')])
.domain(this.get('xDomain'));
}),
xDomain: Ember.computed('timeline', function() {
return d3.extent(this.get('timeline'));
}),
handleDataClick(date) {
this.get('onDateClicked')(moment(date).format("YYYY-MM-DD"));
},
handleMouseHover(date) {
var formattedDate = moment(date).format("MMM DD");
var position = null;
d3.select(`g#${this.get('xAxisElementId')}`).selectAll('.tick').get(0).forEach( function(elem) {
if(Ember.isEqual(elem.textContent, formattedDate)) {
var clientRect = Ember.$(elem).get(0).getBoundingClientRect();
position = clientRect.left + ( clientRect.width / 2);
}
});
this.get('onMouseHover')(formattedDate, position );
},
handleMouseLeave(date) {
this.get('onMouseLeave')(moment(date).format("YYYY-MM-DD") );
}
});

View File

@@ -0,0 +1,74 @@
import Ember from 'ember';
import moment from 'moment';
import Graphable from 'flaredown/components/chart/graphable';
import * as d3 from "d3";
const {
$,
get,
computed,
Component,
isPresent,
computed: { alias },
} = Ember;
export default Component.extend(Graphable, {
yRulers: [0, 1, 2, 3, 4],
name: alias('model.name'),
points: computed('data', function() {
return get(this, 'data').filter( (item) => {
return $.isNumeric(item.y);
}).map( (item) => {
let x = get(this, 'xScale')(item.x);
let y = get(this, 'yScale')(item.y) - 4; // minus radius
return {
x,
y,
tip: {
label: item.label,
x: x - 15,
y: y - 10
}
};
});
}),
yScale: computed('data', function() {
return d3.scale.linear().range([get(this, 'height') , 0]).domain([-1, 5]);
}),
data: computed('checkins', function() {
var trackable = get(this, 'model');
var type = get(trackable, 'constructor.modelName');
var key = type.pluralize();
var timeline = get(this, 'timeline') || [];
return timeline.map( (day) => {
var filteredCheckins = get(this, 'checkins').filter((checkin) => (
get(checkin, 'formattedDate') === moment(day).format("YYYY-MM-DD")
))
var coordinate = { x: day, y: null };
if(filteredCheckins.length) {
var items = filteredCheckins.map(item => (
get(item, key).findBy(`${type}.id`, get(trackable, 'id'))
)).filter(Boolean)
if (items.length) {
var allItemValues = items.map(item => (get(item, 'value')))
var averageItemValue = allItemValues.reduce((acc, val) => (acc + val), 0) / allItemValues.length
if (isPresent(averageItemValue) && $.isNumeric(averageItemValue)) {
coordinate.label = averageItemValue
coordinate.y = averageItemValue
}
}
}
return coordinate;
}).sortBy('x');
}),
});

View File

@@ -0,0 +1,121 @@
import Ember from 'ember';
import moment from 'moment';
import * as d3 from "d3";
import Colorable from 'flaredown/mixins/colorable';
import Graphable from 'flaredown/components/chart/graphable';
const { $, Component, computed, get } = Ember;
export default Component.extend(Colorable, Graphable, {
colorId: '35',
rangeDevider: 4,
offset: -10,
dataValuesYMin: computed.min('dataValuesY'),
dataValuesYMax: computed.max('dataValuesY'),
roundValuesYMin: computed('dataValuesYMin', function() {
return Math.floor(get(this, 'dataValuesYMin'));
}),
roundValuesYMax: computed('dataValuesYMax', function() {
return Math.ceil(get(this, 'dataValuesYMax'));
}),
isDecimalStep: computed('roundValuesYMin', 'roundValuesYMax', function() {
return get(this, 'roundValuesYMax') - get(this, 'roundValuesYMin') < get(this, 'rangeDevider');
}),
chosenYMin: computed('isDecimalStep', function() {
return get(this, get(this, 'isDecimalStep') ? 'dataValuesYMin' : 'roundValuesYMin');
}),
chosenYMax: computed('isDecimalStep', function() {
return get(this, get(this, 'isDecimalStep') ? 'dataValuesYMax' : 'roundValuesYMax');
}),
rulerStep: computed('roundValuesYMin', 'roundValuesYMax', function() {
return (get(this, 'chosenYMax') - get(this, 'chosenYMin')) / get(this, 'rangeDevider');
}),
points: computed('data', function() {
return(
get(this, 'data')
.filter(item => $.isNumeric(item.y))
.map(item => {
const x = get(this, 'xScale')(item.x);
const y = get(this, 'yScale')(item.y) - 4; // minus radius
return {
x,
y,
tip: {
label: `${item.y}${get(this, 'unit')}`,
x: x - 15,
y: y - 10
}
};
})
);
}),
yRulers: computed('data', function() {
let result = [];
const finalizeNumber = get(this, 'isDecimalStep') ?
number => Math.round(number * 100) / 100
:
number => Math.floor(number);
for (let i = get(this, 'chosenYMin'); i < get(this, 'chosenYMax'); i += get(this, 'rulerStep')) {
result.pushObject(finalizeNumber(i));
}
result.pushObject(get(this, 'chosenYMax'));
return result.uniq();
}),
dataValuesY: computed('data', function() {
const values = get(this, 'data')
.filter(item => item.y || item.y === 0)
.map(item => item.y)
.compact();
return values.length ? values : [0, 100];
}),
yScale: computed('data', function() {
return(
d3
.scale
.linear()
.range([get(this, 'height') + get(this, 'offset'), 0])
.domain([
get(this, 'chosenYMin') - get(this, 'rulerStep'),
get(this, 'chosenYMax') + get(this, 'rulerStep')
])
);
}),
data: computed('checkins', function() {
return (get(this, 'timeline') || []).map(day => {
let filteredCheckins = get(this, 'checkins').filter((checkin) => (
get(checkin, 'formattedDate') === moment(day).format("YYYY-MM-DD")
))
let coordinate = { x: day, y: null };
if (filteredCheckins.length) {
let itemWeathers = filteredCheckins.map(checkin => get(checkin, 'weather')).filter(Boolean)
if (itemWeathers.length) {
let itemsValues = itemWeathers.map(checkin => get(checkin, get(this, 'field')))
let weatherValue = itemsValues.reduce((acc, val) => (acc + val), 1) / itemsValues.length
coordinate.y = weatherValue
}
}
return coordinate;
});
}),
});

View File

@@ -0,0 +1,89 @@
import Ember from 'ember';
import * as d3 from "d3";
/* no-unexpected-multiline: "error" */
const {
get,
set,
Mixin,
isEmpty,
computed,
getProperties,
} = Ember;
export default Mixin.create({
tagName: 'g',
classNames: 'chart',
attributeBindings: ['transform'],
transform: computed('height', 'padding', 'chartOffset', function() {
return `translate(0,${get(this, 'chartOffset')})`;
}),
nestedTransform: computed('height', 'startAt', 'data', function() {
return `translate(${ - parseFloat(get(this, 'xScale')( get(this, 'startAt') ) )}, 5)`;
}),
xDomain: computed('data', function() {
return d3.extent(get(this, 'data'), d => d.x);
}),
xScale: computed('data', function() {
return(
d3
.time
.scale()
.range([0, get(this, 'width')])
.domain(get(this, 'xDomain'))
);
}),
lineData: computed('data', function() {
return get(this, 'data').reject(item => isEmpty(item.y));
}),
/*eslint-disable no-unexpected-multiline*/
lineFunction: computed('lineData', function() {
return(
d3
.svg
.line()
.x(this.getX.bind(this))
.y(this.getY.bind(this))
.interpolate('linear')
(get(this, 'lineData'))
);
}),
/*eslint-enable no-unexpected-multiline*/
getX(d) {
return get(this, 'xScale')(d.x);
},
getY(d) {
return get(this, 'yScale')(d.y) - 4; // minus radius
},
actions: {
openPointTooltip(point) {
set(this, 'openPointTooltip', true);
set(this, 'currentPoint', point);
},
closePointTooltip() {
set(this, 'openPointTooltip', false);
set(this, 'currentPoint', null);
},
hideChart() {
const {
name,
category,
chartsVisibilityService,
} = getProperties(this, 'name', 'category', 'chartsVisibilityService');
chartsVisibilityService.setVisibility(false, category, name);
},
}
});

View File

@@ -0,0 +1,37 @@
import Ember from 'ember';
export default Ember.Mixin.create( {
resizeEndDelay: 350,
resizing: false,
didInsertElement(){
this._super(...arguments);
this.installHandlers();
},
willDestroyElement(){
this._super(...arguments);
this.removeHandlers();
},
installHandlers() {
return Ember.$(window).on("resize." + this.elementId, this.handlerManager.bind(this));
},
removeHandlers() {
Ember.$(window).off("resize." + this.elementId);
},
endResize(event) {
this.set('resizing', false);
Ember.tryInvoke(this, 'onResizeEnd', [event]);
},
handlerManager(event) {
if( !this.get('resizing') ) {
this.set('resizing', true);
Ember.tryInvoke(this, 'onResizeStart', [event]);
Ember.run.debounce(this, this.get('endResize'), event, this.get('resizeEndDelay'));
}
}
});

View File

@@ -0,0 +1,26 @@
import Ember from 'ember';
import StepControl from 'flaredown/mixins/step-control';
const {
computed: {
alias,
equal,
},
} = Ember;
export default Ember.Component.extend(StepControl, {
classNames: ['checkin-summary'],
routeAfterCompleted: 'chart',
checkin: alias('model.checkin'),
stepName: alias('step.stepName'),
isStart: equal('stepName', 'start'),
isSummary: equal('stepName', 'summary'),
isSymptoms: equal('stepName', 'symptoms'),
isConditions: equal('stepName', 'conditions'),
isTreatments: equal('stepName', 'treatments'),
isHeathFactors: equal('stepName', 'health_factors'),
isHarveyBradshawIndex: equal('stepName', 'harvey_bradshaw'),
isPromotionRate: equal('stepName', 'promotion_rate'),
});

View File

@@ -0,0 +1,47 @@
import Ember from 'ember';
const {
$,
get,
computed,
Component,
run: { scheduleOnce },
computed: { alias },
} = Ember;
export default Component.extend({
classNames: ['checkin-navigation'],
checkinId: alias('checkin.id'),
steps: computed('stepsService.steps', function() {
let result = [];
const steps = get(this, 'stepsService.steps');
Object.keys(steps).forEach(key => {
let step = steps[key];
if (step.priority && step.prefix === 'checkin.show') {
result.pushObject(step);
}
});
return result.sortBy('priority');
}),
didRender() {
scheduleOnce('afterRender', this, () => {
const $activeLink = $('.checkin-navigation .active');
if (!$activeLink.length) {
return;
}
const $checkinNavigation = $('.checkin-navigation');
const offsetedCenter = ($checkinNavigation.width() - $activeLink.width()) / 2;
const absoluteScrollTo = - (offsetedCenter - $checkinNavigation.scrollLeft() - $activeLink.offset().left);
$checkinNavigation.animate({ scrollLeft: absoluteScrollTo }, 0);
});
},
});

View File

@@ -0,0 +1,65 @@
import Ember from 'ember';
import moment from 'moment';
import CheckinByDate from 'flaredown/mixins/checkin-by-date';
const {
get,
computed,
Component,
} = Ember;
export default Component.extend(CheckinByDate, {
classNames: ['checkin-head-nav'],
classNameBindings: ['big'],
pickadateOptions: {
container: 'body > .ember-view',
max: moment().toDate(),
},
currentDate: Ember.computed.alias('date'),
checkinDate: computed('currentDate', function() {
return moment(get(this, 'currentDate')).toDate();
}),
monthAndDay: Ember.computed('currentDate', function() {
return moment(this.get('currentDate')).format("MMMM Do");
}),
isToday: Ember.computed('currentDate', function() {
return moment(this.get('currentDate')).isSame(new Date(), 'day');
}),
isntToday: Ember.computed.not('isToday'),
checkinDateLabel: computed('checkinDate', function() {
return moment(get(this, 'checkinDate')).format('MMM D');
}),
actions: {
goToYesterday() {
const yesterday = moment(this.get('currentDate')).subtract(1, 'days');
this.routeToCheckinsForDate(yesterday.format("YYYY-MM-DD"));
},
goToTomorrow() {
if (this.get('isntToday')) {
const tomorrow = moment(this.get('currentDate')).add(1, 'days');
this.routeToCheckinsForDate(tomorrow.format("YYYY-MM-DD"));
}
},
openPicker() {
this.$('.calendar-opener .picker__input').first().pickadate('picker').open();
},
checkinDateChanged(date) {
const checkinDate = moment(date);
this.routeToCheckinsForDate(checkinDate.format("YYYY-MM-DD"));
},
}
});

View File

@@ -0,0 +1,44 @@
import Ember from 'ember';
const {
get,
set,
Component,
computed: { alias },
} = Ember;
export default Component.extend({
classNames: ['hbi-step'],
checkin: alias('parentView.model.checkin'),
didReceiveAttrs() {
this._super(...arguments);
const checkin = get(this, 'checkin');
const hbi = get(checkin, 'harveyBradshawIndex');
set(this, 'hbi', hbi || get(this, 'store').createRecord('harveyBradshawIndex', { checkin }));
},
actions: {
save() {
get(this, 'hbi')
.save()
.then(() => this.get('chartsVisibilityService').refresh());
},
goBack() {
this.get('onGoBack')();
},
selectOption(field, option) {
set(this, `hbi.${field}`, option.value);
},
completeStep() {
this.get('onStepCompleted')();
},
},
});

View File

@@ -0,0 +1,40 @@
import Ember from 'ember';
const {
Component,
computed: { alias },
inject: { service },
} = Ember;
export default Component.extend({
store: service(),
model: alias('parentView.model'),
checkin: alias('model.checkin'),
findFactors(factorClassName, name) {
const factors = this.get('store').peekAll(factorClassName);
const existingFactor = factors.findBy('name', name);
if (existingFactor) {
this.get('store').unloadRecord(existingFactor);
}
},
actions: {
completeStep() {
this.get('onStepCompleted')();
},
goBack() {
this.get('onGoBack')();
},
onBeforeAddTag(name) {
this.findFactors('tag', name);
},
onBeforeAddFood(name) {
this.findFactors('food', name);
},
},
});

View File

@@ -0,0 +1,53 @@
import Ember from 'ember';
import SearchableDropdown from 'flaredown/mixins/searchable-dropdown';
export default Ember.Component.extend(SearchableDropdown, {
isSelectFocused: false,
selectPlaceholder: Ember.computed('isSelectFocused', function() {
if (this.get('isSelectFocused')) {
return 'Start typing a ' + this.get('modelName');
} else {
return 'Add a ' + this.get('modelName');
}
}),
actions: {
toggleSelectFocused() {
this.toggleProperty('isSelectFocused');
},
handleFocus() {
// TODO remove debug log when this is fixed:
// https://github.com/cibernox/ember-power-select/issues/739
Ember.Logger.debug('focus');
this.set('isSelectFocused', true);
},
handleBlur() {
// TODO remove debug log when this is fixed:
// https://github.com/cibernox/ember-power-select/issues/739
Ember.Logger.debug('blur');
this.set('isSelectFocused', false);
},
createObject(name) {
this.sendAction('onBeforeAdd', name);
this.createAndSave(this.get('modelName'), name).then(obj => this.get('onSelected')(obj));
},
handleChange(obj) {
this.get('onSelected')(obj);
},
clicked(obj) {
this.get('onClicked')(obj);
},
},
performSearch(term, resolve, reject) {
this
.searchByTerm(this.get('modelName'), term)
.then(function() { resolve(...arguments); }, reject);
},
});

View File

@@ -0,0 +1,54 @@
import Ember from 'ember';
const {
get,
set,
computed,
Component,
Logger: { debug },
computed: { alias },
} = Ember;
export default Component.extend({
checkin: alias('parentView.checkin'),
relatedObjects: computed('checkin', function() {
return get(this, 'checkin.' + get(this, 'relationName'));
}),
actions: {
add(obj) {
get(this, 'checkin').addObj(obj, get(this, 'idsKey'), get(this, 'relationName'));
this.saveCheckin();
},
remove(obj) {
get(this, 'checkin').removeObj(obj, get(this, 'idsKey'), get(this, 'relationName'));
this.saveCheckin();
},
onBeforeAdd(name) {
this.sendAction('onBeforeAdd', name);
},
},
saveCheckin() {
if (get(this, 'checkin.hasDirtyAttributes')) {
Ember.run.next(this, function() {
get(this, 'checkin')
.save()
.then(
() => {
debug('Checkin successfully saved');
set(this, 'checkin.hasDirtyAttributes', false);
},
(error) => {
get(this, 'checkin').handleSaveError(error);
}
)
.then(() => get(this, 'chartsVisibilityService').refresh());
});
}
},
});

View File

@@ -0,0 +1,23 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['flaredown-white-box'],
saveScheduled: false,
keyUp() {
this.set('saveScheduled', true);
this.get('checkin').set('hasDirtyAttributes', true);
Ember.run.debounce(this, this.save, 2000);
},
save() {
this.get('checkin').save().then(() => {
this.set('saveScheduled', false);
this.get('checkin').set('hasDirtyAttributes', false);
}, (error) => {
this.get('checkin').handleSaveError(error);
});
}
});

View File

@@ -0,0 +1,69 @@
import Ember from 'ember';
import { translationMacro as t } from "ember-i18n";
const {
get,
set,
computed,
computed: {
alias,
},
inject: {
service,
},
Component
} = Ember;
export default Component.extend({
i18n: service(),
ajax: service(),
classNames: ['checkinPrStep'],
checkin: alias('parentView.model.checkin'),
didReceiveAttrs() {
this._super(...arguments);
const checkin = get(this, 'checkin');
const pr = get(this, 'checkin.promotionRate');
const store = get(this, 'store');
if(store) {
set(this, 'pr', pr || store.createRecord('promotionRate', { checkin }));
}
},
feedBackLabel: t("step.checkin.promotion_rate.feedBackLabel"),
prBody: t("step.checkin.promotion_rate.rateBody"),
prRates: [1,2,3,4,5,6,7,8,9,10],
showRatePage: computed('pr.score', function() {
const score = get(this, 'pr.score');
return typeof score == 'undefined';
}),
actions: {
sendFeedback() {
const pr = get(this, 'pr');
set(pr, 'score', get(this, 'selectedRate'));
pr.save();
set(this, 'showRatePage', false);
},
skipStep() {
let checkin = get(this, 'checkin');
set(checkin, 'promotionSkippedAt', get(checkin, 'date'));
checkin.save();
get(this, 'onStepCompleted')();
},
goBack() {
get(this, 'onGoBack')();
},
}
});

View File

@@ -0,0 +1,20 @@
import Ember from 'ember';
const {
get,
computed,
inject: {
service,
},
Component,
} = Ember;
export default Component.extend({
store: service(),
session: service(),
classNames: ['summary-posts'],
model: computed(function(){
return get(this, 'store').query('post', { summary: true });
}),
});

View File

@@ -0,0 +1,17 @@
import Ember from 'ember';
export default Ember.Component.extend({
model: Ember.computed.alias('parentView.model'),
checkin: Ember.computed.alias('model.checkin'),
actions: {
completeStep() {
this.get('onStepCompleted')();
},
goBack() {
this.get('onGoBack')();
}
}
});

View File

@@ -0,0 +1,288 @@
import Ember from 'ember';
import DS from 'ember-data';
import moment from 'moment';
import TrackablesFromType from 'flaredown/mixins/trackables-from-type';
const {
get,
set,
computed,
computed: { alias },
inject: { service },
Component,
isNone,
run: { next },
RSVP: { all }
} = Ember;
export default Component.extend(TrackablesFromType, {
classNames: ['trackables-step'],
model: alias('parentView.model'),
tracking: service(),
setupTracking() {
this.get('tracking').setup({
at: new Date(this.get('checkin.date')),
trackableType: this.get('trackableType').capitalize()
});
},
checkin: alias('model.checkin'),
isTodaysCheckin: computed('checkin', function() {
return moment(this.get('checkin.date')).isSame(new Date(), 'day');
}),
sortedTrackeds: computed('trackeds.{[],@each.position}', function() {
return this.get('trackeds').toArray().sortBy('position');
}),
didReceiveAttrs() {
this._super(...arguments);
this.setupTracking();
},
removedTracked: null,
addedTracked: null,
actions: {
remove(tracked) {
tracked.prepareForDestroy();
this.set('removedTracked', tracked);
this.saveCheckin();
},
add(selectedTrackable) {
const trackeds = get(this, 'trackeds');
if (get(trackeds, 'isFulfilled')) {
this.addTrackable(get(trackeds, 'content'), selectedTrackable);
} else {
this.addTrackable(trackeds, selectedTrackable);
}
},
move(draggedId, droppedId) {
// Return if the item has been dropped onto its own dropzone
if (Ember.isEqual(draggedId, droppedId)) { return; }
let sortedTrackeds = this.get('sortedTrackeds');
let draggedItem = sortedTrackeds.findBy('id', draggedId);
// draggedItem might not be found if it's dragged from a different
// trackables' collection (this might happen in summary screen)
if (Ember.isNone(draggedItem)) { return; }
let draggedPosition = draggedItem.get('position');
let droppedPosition = 0;
if (Ember.isEqual(droppedId, 'after-last')) {
droppedPosition = sortedTrackeds.get('length');
} else {
let droppedItem = sortedTrackeds.findBy('id', droppedId);
droppedPosition = droppedItem.get('position');
}
// Return if the item has been dropped onto its next item's dropzone
if (Ember.isEqual(droppedPosition, draggedPosition + 1)) { return; }
if (draggedPosition > droppedPosition) {
// Case 1: Item Moved Up
// Shift down items between droppedPosition and draggedPosition
for (let i = droppedPosition; i < draggedPosition; i++) {
sortedTrackeds[i].set('position', i + 1);
}
draggedItem.set('position', droppedPosition);
} else {
// Case 2: Move Down
// Shift up items between draggedPosition and droppedPosition
for (let i = draggedPosition + 1; i < droppedPosition; i++) {
sortedTrackeds[i].set('position', i - 1);
}
draggedItem.set('position', droppedPosition - 1);
}
this.set('clickedTrackableId', null);
this.saveCheckin();
},
onTrackableClicked(trackableId) {
if (Ember.isPresent(this.get('clickedTrackableId'))) {
if (Ember.isEqual(trackableId, this.get('clickedTrackableId'))) {
this.set('clickedTrackableId', null);
} else {
this.set('clickedTrackableId', trackableId);
}
} else {
this.set('clickedTrackableId', trackableId);
}
},
saveChanges() {
this.saveCheckin();
},
completeStep() {
this.get('onStepCompleted')();
},
goBack() {
this.get('onGoBack')();
}
},
addTrackable(trackeds, trackable) {
const trackableType = get(this, 'trackableType');
const foundTrackable = trackeds.findBy(`${trackableType}.id`, trackable.get('id'));
if (isNone(foundTrackable)) {
const recordAttrs = {
colorId: this.computeNewColorId(),
position: trackeds.get('length'),
[trackableType]: trackable
};
const recordType = `checkin_${trackableType}`.camelize();
const tracked = this.store.createRecord(recordType, recordAttrs);
trackeds.pushObject(tracked);
set(this, 'addedTracked', tracked);
this.saveCheckin();
}
set(this, 'selectedTrackable', null);
},
computeNewColorId() {
var nColors = 32;
let usedColorIds = this.get('checkin.allColorIds');
// if all colors have been used
if (usedColorIds.length >= nColors) {
// re-use them again in the same order
return usedColorIds[(usedColorIds.length % nColors)];
} else {
// return a random unused color
let randomColor = '';
do {
randomColor = Math.floor(Math.random()*nColors)+'';
} while (usedColorIds.includes(randomColor));
return randomColor;
}
},
saveCheckin() {
set(this, 'checkin.hasDirtyAttributes', true);
next(this, () => {
set(this, 'isSaving', true);
all([
this.untrackRemovedTracked(),
this.trackAddedTracked()
])
.then(() => this.get('checkin').save())
.then(
() => {
Ember.Logger.info('Checkin successfully saved');
return this.onCheckinSaved();
},
(error) => {
this.get('checkin').handleSaveError(error);
}
)
.then(() => this.get('chartsVisibilityService').refresh())
.finally(() => {
set(this, 'isSaving', false);
});
});
},
onCheckinSaved() {
const checkin = this.get('checkin');
this.deleteAddedTracked();
checkin.set('hasDirtyAttributes', false);
checkin.deleteTrackablesPreparedForDestroy();
this.set('addedTracked', null);
this.set('removedTracked', null);
},
untrackRemovedTracked() {
// untrack() removedTracked if isTodaysCheckin
return new Ember.RSVP.Promise(resolve => {
let removedTracked = this.get('removedTracked');
if (this.get('isTodaysCheckin') && Ember.isPresent(removedTracked)) {
let rawTrackable = removedTracked.get(this.get('trackableType'));
if (DS.PromiseObject.detectInstance(rawTrackable)) {
let trackablePromise = rawTrackable;
if (trackablePromise.get('isFulfilled')) {
this.untrack(trackablePromise.get('content'), resolve);
} else {
trackablePromise.then(trackable => {
this.untrack(trackable, resolve);
});
}
} else {
Ember.Logger.debug('rawTrackable is not a DS.PromiseObject');
if (Ember.isPresent(rawTrackable)) {
this.untrack(rawTrackable, resolve);
}
}
} else {
resolve();
}
});
},
untrack(trackable, resolve) {
this.get('tracking').untrack(
{
trackable: trackable,
trackableType: this.get('trackableType').capitalize()
},
resolve
);
},
trackAddedTracked() {
// track() addedTracked if isTodaysCheckin
return new Ember.RSVP.Promise(resolve => {
var addedTracked = this.get('addedTracked');
if (this.get('isTodaysCheckin') && Ember.isPresent(addedTracked)) {
let rawTrackable = addedTracked.get(this.get('trackableType'));
if (DS.PromiseObject.detectInstance(rawTrackable)) {
let trackablePromise = rawTrackable;
if (trackablePromise.get('isFulfilled')) {
this.get('tracking').track(
trackablePromise.get('content'),
addedTracked.get('colorId'),
resolve
);
} else {
trackablePromise.then(trackable => {
this.get('tracking').track(
trackable,
addedTracked.get('colorId'),
resolve
);
});
}
} else {
Ember.Logger.debug('rawTrackable is not a DS.PromiseObject');
if (Ember.isPresent(rawTrackable)) {
this.get('tracking').track(
rawTrackable,
addedTracked.get('colorId'),
resolve
);
}
}
} else {
resolve();
}
});
},
deleteAddedTracked() {
// remove addedTracked to avoid 'ghost' record
let addedTracked = this.get('addedTracked');
if (Ember.isPresent(addedTracked)) {
addedTracked.deleteRecord();
}
}
});

View File

@@ -0,0 +1,53 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['tracked-level'],
label: Ember.computed.alias('model.label'),
pips: [
{ value: 0 },
{ value: 1 },
{ value: 2 },
{ value: 3 },
{ value: 4 },
],
onDidInsertElement: Ember.on('didInsertElement', function() {
Ember.run.scheduleOnce('afterRender', this, () => {
Ember.RSVP.resolve(this.get('model')).then(model => {
var value = model.get(this.get('valueKey'));
if (Ember.isPresent(value)) {
this.set('selectedValue', value);
}
});
});
}),
actions: {
titleClicked() {
this.get('onTitleClicked')(this.get('model.id'));
},
setSelectedValue(pipValue) {
this.set('selectedValue', pipValue);
this.get('model').set(this.get('valueKey'), pipValue);
this.get('onValueChanged')();
},
setCurrentValue(pipValue) {
this.set('currentValue', pipValue);
},
unsetCurrentValue() {
this.set('currentValue', null);
},
xClick() {
var model = this.get('model');
this.get('onRemove')(model);
}
}
});

View File

@@ -0,0 +1,86 @@
import Ember from 'ember';
import SearchableDropdown from 'flaredown/mixins/searchable-dropdown';
export default Ember.Component.extend(SearchableDropdown, {
classNames: ['treatment-taker'],
isEditMode: false,
treatment: Ember.computed.alias('model.treatment'),
dose: Ember.computed.alias('model.dose'),
isTaken: Ember.computed.alias('model.isTaken'),
isTakenObserver: Ember.observer('isTaken', function() {
this.get('onIsTakenChanged')();
}),
allTreatmentDoses: Ember.computed('treatment', function() {
return this.customSearch({
resource: 'dose',
query: {
treatment_id: this.get('treatment.id')
}
});
}),
changeDose(newDose) {
this.get('model').set('dose', newDose);
this.set('isEditMode', false);
},
actions: {
titleClicked() {
this.get('onTitleClicked')(this.get('model.id'));
},
removeTreatment() {
this.get('onRemove')(this.get('model'));
},
edit() {
this.set('isEditMode', true);
// TODO: auto-focus the select
},
clearDose() {
this.changeDose(null);
this.get('onDoseChanged')();
},
handleChange(dose) {
this.changeDose(dose);
this.get('onDoseChanged')(dose);
},
createDose(name) {
let dose = this.createRecord('dose', name);
this.changeDose(dose);
this.get('onDoseChanged')(dose);
},
handleFocus() {
// TODO remove debug log when this is fixed:
// https://github.com/cibernox/ember-power-select/issues/739
Ember.Logger.debug('focus');
this.set('isSelectFocused', true);
},
handleBlur() {
// TODO remove debug log when this is fixed:
// https://github.com/cibernox/ember-power-select/issues/739
Ember.Logger.debug('blur');
this.set('isSelectFocused', false);
},
},
performSearch(term, resolve, reject) {
this
.customSearch({
resource: 'dose',
query: {
treatment_id: this.get('treatment.id'),
name: term,
}
})
.then(function() { resolve(...arguments); }, reject);
},
});

View File

@@ -0,0 +1,107 @@
import Ember from 'ember';
let { Component, computed, computed: { alias, notEmpty }, get, set, setProperties } = Ember;
export default Component.extend({
classNames: ['centered'],
weatherTypes: [
'clear-day',
'clear-night',
'cloudy',
'fog',
'partly-cloudy-day',
'partly-cloudy-night',
'rain',
'sleet',
'snow',
'wind',
],
newPostalCode: '',
inputVisible: false,
validPostalCode: true,
checkin: alias('parentView.model.checkin'),
hasWeather: notEmpty('weather'),
pressureUnits: alias('session.currentUser.profile.pressureUnits'),
temperatureUnits: alias('session.currentUser.profile.temperatureUnits'),
weather: alias('checkin.weather'),
formattedPrecipitation: computed('weather.precipIntensity', function() {
return get(this, 'weather.precipIntensity').toString().split('.')[1];
}),
shownTemperatureMin: computed('weather.temperatureMin', 'temperatureUnits', function() {
return get(this, 'weather').temperatureMinByUnits(get(this, 'temperatureUnits'));
}),
shownTemperatureMax: computed('weather.temperatureMax', 'temperatureUnits', function() {
return get(this, 'weather').temperatureMaxByUnits(get(this, 'temperatureUnits'));
}),
shownPressure: computed('weather.pressure', 'pressureUnits', function() {
return get(this, 'weather').pressureByUnits(get(this, 'pressureUnits'));
}),
iconType: computed('weather.icon', function() {
let icon = get(this, 'weather.icon');
return get(this, 'weatherTypes').includes(icon) ? icon : 'default';
}),
willRender() {
this._super(...arguments);
if (!get(this, 'hasWeather')) {
set(this, 'inputVisible', true);
}
},
actions: {
updatePostalCode() {
const date = get(this, 'checkin.date');
const newPostalCode = get(this, 'newPostalCode');
if(get(newPostalCode, 'length') === 0) {
set(this, 'validPostalCode', false);
} else {
this
.store
.queryRecord('weather', { date: date, postal_code: newPostalCode })
.then(record => {
let checkin = get(this, 'checkin');
setProperties(checkin, { postalCode: newPostalCode, weather: record });
if(!record) {
set(this, 'validPostalCode', false);
}
return checkin.save();
})
.then(() => set(this, 'inputVisible', false));
}
},
showInput() {
setProperties(this, { inputVisible: true, newPostalCode: get(this, 'checkin.postalCode') });
},
toggleTemperatureUnits() {
this.updateProfileFieldFromMap('temperatureUnits', { c: 'f', f: 'c' });
},
togglePressureUnits() {
this.updateProfileFieldFromMap('pressureUnits', { mb: 'in', in: 'mb' });
},
},
updateProfileFieldFromMap(field, map) {
get(this, 'session.currentUser.profile').then(profile => {
set(profile, field, map[get(this, field)]);
return profile.save();
});
},
});

View File

@@ -0,0 +1,20 @@
import Ember from 'ember';
import PickATime from 'ember-cli-pickadate/components/pick-a-time';
const {
get,
set,
} = Ember;
export default PickATime.extend({
didInsertElement() {
this._super(...arguments);
let newDate = new Date();
let options = get(this, 'options') || {};
newDate.setHours(options.hour, options.mins, 0, 0);
set(this, 'date', newDate);
this.updateInputText();
},
});

View File

@@ -0,0 +1,32 @@
import Ember from 'ember';
import ENV from 'flaredown/config/environment';
let { Component, computed , get, set, $: { ajax } } = Ember;
export default Component.extend({
tagName: '',
apiHost: ENV.apiHost,
authHeader: computed('session.{data.authenticated.token,currentUser.email}', function() {
return `Token token="${get(this, 'session.data.authenticated.token')}", ` +
`email="${get(this, 'session.currentUser.email')}"`;
}),
actions: {
scheduleDataExport() {
ajax({
url: `${get(this, 'apiHost')}/api/data_export_schedules`,
method: 'POST',
dataType: 'json',
headers: {
Authorization: get(this, 'authHeader'),
},
statusCode: {
201: () => set(this, 'exportIsScheduled', true),
},
});
},
},
});

View File

@@ -0,0 +1,27 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['draggable-dropzone'],
classNameBindings: ['dragClass'],
dragClass: 'deactivated',
dragLeave(event) {
event.preventDefault();
this.set('dragClass', 'deactivated');
},
dragEnter(event) {
event.preventDefault();
this.set('dragClass', 'activated');
},
dragOver(event) {
event.preventDefault();
},
drop(event) {
let draggedData = event.dataTransfer.getData('text/plain');
this.get('onDropped')(draggedData, this.get('itemId'));
this.set('dragClass', 'deactivated');
}
});

View File

@@ -0,0 +1,19 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['draggable-item'],
classNameBindings: ['dragging:activated'],
dragging: false,
attributeBindings: ['draggable'],
draggable: 'true',
dragStart(event) {
this.set('dragging', true);
return event.dataTransfer.setData('text/plain', this.get('itemId'));
},
dragEnd() {
this.set('dragging', false);
}
});

View File

@@ -0,0 +1,60 @@
import Ember from 'ember';
const {
get,
set,
computed,
Component,
setProperties,
Logger: { debug },
inject: { service },
} = Ember;
export default Component.extend({
visible: false,
emojis: service(),
selectedGroup: computed('selected', function() {
return get(this, `emojis.groupedStyledMap.${get(this, 'selected')}`);
}),
init() {
this._super(...arguments);
this.setGroupsSelection();
},
setGroupsSelection(groupName = 'smile') {
let result = {};
get(this, 'emojis.groups').forEach(group => {
result[`${group}Class`] = `icon-${group}${group === groupName ? '-selected' : ''}`;
});
result.selected = groupName;
setProperties(this, result);
},
onSelect(emojiTitle) {
debug(emojiTitle, "was selected. Pass 'onSelect' callback to take some action...");
},
actions: {
noAction() {},
toggleMenu() {
set(this, 'visible', !get(this, 'visible'));
},
selectGroup(groupName) {
this.setGroupsSelection(groupName);
},
selectIcon(emojiTitle) {
this.onSelect(emojiTitle);
set(this, 'visible', false);
},
},
});

View File

@@ -0,0 +1,48 @@
import Ember from 'ember';
const {
get,
computed,
Component,
getProperties,
inject: { service },
computed: {
alias,
},
} = Ember;
export default Component.extend({
tagName: 'a',
classNames: ['emoji-reaction'],
classNameBindings: ['isParticipated'],
emojis: service(),
session: service(),
isParticipated: alias('reaction.participated'),
style: computed('reaction.value', function() {
return get(this, `emojis.styledMap.${get(this, 'reaction.value')}`);
}),
click() {
if(!get(this, 'session.isAuthenticated')) {
return;
}
let { store, reaction } = getProperties(this, 'store', 'reaction');
let {
participated,
reactable_id,
reactable_type,
} = getProperties(reaction, 'participated', 'reactable_id', 'reactable_type');
if (participated) {
reaction
.destroyRecord()
.then(() => store.findRecord(reactable_type.toLowerCase(), reactable_id));
} else {
this.onParticipate(get(reaction, 'value'));
}
},
});

View File

@@ -0,0 +1,38 @@
import Ember from 'ember';
const {
get,
Component,
getProperties,
} = Ember;
export default Component.extend({
classNames: ['emoji-reactions'],
actions: {
onEmojiSelect(emojiTitle) {
let reactions = get(this, 'reactable.reactions');
const existentReaction = reactions.findBy('value', emojiTitle);
if (existentReaction && get(existentReaction, 'participated')) {
return;
}
const { reactable, store } = getProperties(this, 'reactable', 'store');
let record = existentReaction || store.createRecord('reaction', {
value: emojiTitle,
reactable_id: get(reactable, 'id'),
reactable_type: get(reactable, 'modelType'),
});
record
.save()
.then(reaction => {
if (!existentReaction) {
reactions.pushObject(reaction);
}
});
},
},
});

View File

@@ -0,0 +1,34 @@
import Ember from 'ember';
import layout from 'ember-social-share/templates/components/fb-share-button';
const {
get,
computed,
Component
} = Ember;
export default Component.extend({
layout,
shareURL: 'https://facebook.com/sharer.php',
tagName: 'a',
classNames: ['fb-share-button', 'share-button'],
classNameBindings: ['adaptive:adaptive-button'],
attributeBindings: ['fbUrl:href', 'target'],
adaptive: true,
target: '_blank',
fbUrl: computed('shareURL', function() {
let url = get(this, 'shareURL');
url += '?display=popup';
url += '&u=' + encodeURIComponent(this.getCurrentUrl());
url += this.get('title') ? '&title=' + this.get('title') : '';
url += this.get('image') ? '&picture=' + this.get('image') : '';
url += this.get('text') ? '&description=' + this.get('text') : '';
return url;
}),
getCurrentUrl() {
return get(this, 'url') ? get(this, 'url') : document.location.href;
},
});

View File

@@ -0,0 +1,42 @@
import Ember from 'ember';
import FormForComponent from './form-for';
export default Ember.Component.extend({
classNames: 'form-field input',
type: 'text',
model: Ember.computed.alias('parentView.for'),
didInsertElement() {
Ember.run.scheduleOnce('afterRender', () => {
var parentView = this.get('parentView');
var elementId = this.get('elementId');
Ember.assert("FormFieldComponent (element ID: " + elementId + ") must have a parent view in order to yield.", parentView);
Ember.assert("FormFieldComponent (element ID: " + elementId + ") should be inside a FormForComponent.", FormForComponent.detectInstance(parentView));
});
},
hasError: Ember.computed('model.errors.[]', function() {
var ref;
if ((ref = this.get('model.errors')) != null) {
return ref.has(this.get('for'));
} else {
return false;
}
}),
errorClass: Ember.computed('hasError', function() {
if (this.get('hasError')) {
return 'error';
} else {
return '';
}
}),
errors: Ember.computed('model.errors.[]', function() {
var errors = this.get('model.errors');
return errors.errorsFor(this.get('for')).mapBy('message').join(', ');
})
});

View File

@@ -0,0 +1,12 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'form',
classNames: 'form',
submit: function() {
this.sendAction('onSubmit', this.get('for'));
return false;
}
});

View File

@@ -0,0 +1,375 @@
import Ember from 'ember';
import Moment from 'moment';
import { extendMoment } from 'moment-range';
import Resizable from './chart/resizable';
import FieldsByUnits from 'flaredown/mixins/fields-by-units';
import DatesRetriever from 'flaredown/mixins/chart/dates-retriever';
import * as d3 from "d3";
const moment = extendMoment(Moment);
const {
get,
set,
computed,
observer,
Component,
getProperties,
setProperties,
run: {
debounce,
scheduleOnce,
},
inject: {
service,
},
computed: {
alias,
},
} = Ember;
export default Component.extend(Resizable, FieldsByUnits, DatesRetriever, {
chartSelectedDates: service(),
classNames: ['health-chart'],
checkins: [],
trackables: [],
flatHeight: 30,
serieHeight: 75,
seriePadding: 20,
pixelsPerDate: 32,
timelineHeight: 25,
lastChartOffset: 0,
lastChartHeight: 0,
centeredDate: alias('chartSelectedDates.centeredDate'),
hiddenCharts: alias('chartsVisibilityService.hiddenCharts'),
pressureUnits: alias('session.currentUser.profile.pressureUnits'),
timelineLength: alias('timeline.length'),
visibilityFilter: alias('chartsVisibilityService.visibilityFilter'),
updateTrackables: observer(
'centeredDate',
'chartLoaded',
'chartsVisibilityService.payload.tags.@each.visible',
'chartsVisibilityService.payload.foods.@each.visible',
'chartsVisibilityService.payload.symptoms.@each.visible',
'chartsVisibilityService.payload.conditions.@each.visible',
'chartsVisibilityService.payload.treatments.@each.visible',
'chartsVisibilityService.payload.weathersMeasures.@each.visible',
'chartsVisibilityService.payload.harveyBradshawIndices.@each.visible',
function() {
debounce(this, this.fetchDataChart, 1000);
}
),
showChart: observer('chartToShow', function() {
const { chartToShow, chartsVisibilityService } = getProperties(this, 'chartToShow', 'chartsVisibilityService');
chartsVisibilityService.setVisibility(true, chartToShow.category, chartToShow.label);
}),
isChartEnablerDisabled: computed('hiddenCharts.length', function() {
return get(this, 'hiddenCharts.length') === 0;
}),
chartEnablerPlaceholder: computed('isChartEnablerDisabled', function() {
return get(this, 'isChartEnablerDisabled') ? "No charts to add" : "Add a chart";
}),
seriesLength: computed('series.weathersMeasures.length', 'trackables.length', function() {
return get(this, 'trackables.length') + get(this, 'series.weathersMeasures.length');
}),
seriesWidth: computed('SVGWidth', function() {
return get(this, 'SVGWidth') * 2;
}),
totalSeriesHeight: computed('lastChartOffset', 'lastChartHeight', function() {
const {
lastChartOffset,
lastChartHeight,
seriePadding,
} = getProperties(this, 'lastChartOffset', 'lastChartHeight', 'seriePadding');
return lastChartOffset + lastChartHeight + seriePadding;
}),
timeline: computed('checkins', 'startAt', 'endAt', function() {
let timeline = Ember.A();
moment.range(get(this, 'startAtWithCache'), get(this, 'endAtWithCache')).by('days', function(day) {
timeline.pushObject(
d3.time.format('%Y-%m-%d').parse(day.format("YYYY-MM-DD"))
);
});
return timeline;
}),
SVGHeight: computed('timelineLength', 'totalSeriesHeight', function() {
if(Ember.isPresent(get(this, 'totalSeriesHeight'))) {
return get(this, 'totalSeriesHeight') + get(this, 'timelineHeight') + get(this, 'seriePadding');
} else {
return get(this, 'timelineHeight') + get(this, 'seriePadding');
}
}),
SVGWidth: computed('checkins',function() {
return this.$().width();
}),
observeFilterAndTrackables: observer(
'trackables',
'pressureUnits',
'chartsVisibilityService.payload.tags.@each.visible',
'chartsVisibilityService.payload.foods.@each.visible',
'chartsVisibilityService.payload.symptoms.@each.visible',
'chartsVisibilityService.payload.conditions.@each.visible',
'chartsVisibilityService.payload.treatments.@each.visible',
'chartsVisibilityService.payload.weathersMeasures.@each.visible',
'chartsVisibilityService.payload.harveyBradshawIndices.@each.visible',
function() {
const {
flatHeight,
serieHeight,
seriePadding,
visibilityFilter,
} = getProperties(this, 'flatHeight', 'serieHeight', 'seriePadding', 'visibilityFilter');
let lastChartHeight = serieHeight;
let chartOffset = 0 - lastChartHeight - seriePadding;
let series = this.seriesWithBlanks(
this.unpositionedSeries(visibilityFilter),
visibilityFilter
);
series.conditions.forEach(item => {
item.chartOffset = chartOffset += lastChartHeight + seriePadding;
});
series.symptoms.forEach(item => {
item.chartOffset = chartOffset += lastChartHeight + seriePadding;
});
series.treatments.forEach((item, index) => {
item.chartOffset = chartOffset += (index === 0 ? serieHeight : flatHeight) + seriePadding;
});
series.tags.forEach((item, index) => {
let previousHeight = series.treatments.length === 0 && index === 0 ? serieHeight : flatHeight;
item.chartOffset = chartOffset += previousHeight + seriePadding;
});
series.foods.forEach((item, index) => {
let previousHeight = series.tags.length === 0 && series.treatments.length === 0 && index === 0 ?
serieHeight
:
flatHeight;
item.chartOffset = chartOffset += previousHeight + seriePadding;
});
if (series.treatments.length || series.tags.length || series.foods.length) {
lastChartHeight = flatHeight;
}
const hbiCategory = visibilityFilter.harveyBradshawIndices;
if (hbiCategory && hbiCategory['Harvey Bradshaw Index']) {
series.harveyBradshawIndices.pushObject({
name: 'Harvey Bradshaw Index',
field: 'score',
chartOffset: chartOffset += lastChartHeight + seriePadding,
});
lastChartHeight = serieHeight;
}
const weatherCategory = visibilityFilter.weathersMeasures;
if (weatherCategory && weatherCategory['Avg daily humidity']) {
series.weathersMeasures.pushObject({
name: 'Avg daily humidity',
unit: '%',
field: 'humidity',
chartOffset: chartOffset += lastChartHeight + seriePadding,
});
lastChartHeight = serieHeight;
}
if (weatherCategory && weatherCategory['Avg daily atmospheric pressure']) {
series.weathersMeasures.pushObject({
name: 'Avg daily atmospheric pressure',
unit: get(this, 'pressureUnits'),
field: this.pressureFieldByUnits(get(this, 'pressureUnits')),
chartOffset: chartOffset += lastChartHeight + seriePadding,
});
lastChartHeight = serieHeight;
}
set(this, 'series', series);
set(this, 'lastChartOffset', chartOffset);
set(this, 'lastChartHeight', lastChartHeight);
}
),
unpositionedSeries(visibilityFilter) {
let series = {
conditions: [],
symptoms: [],
treatments: [],
tags: [],
foods: [],
harveyBradshawIndices: [],
weathersMeasures: [],
};
get(this, 'trackables').forEach(item => {
let name = get(item, 'name');
let category = get(item, 'constructor.modelName').pluralize();
let visibleCategory = visibilityFilter[category];
if (visibleCategory && visibleCategory[name]) {
series[category].pushObject({ chartOffset: 0, model: item });
}
});
return series;
},
seriesWithBlanks(series, visibilityFilter) {
let result = {
conditions: [],
symptoms: [],
treatments: [],
tags: [],
foods: [],
harveyBradshawIndices: [],
weathersMeasures: [],
};
Object.keys(visibilityFilter).forEach(categoryName => {
const category = get(visibilityFilter, categoryName);
Object.keys(category).forEach(chartName => {
if (category[chartName] && series[categoryName]) {
const chartFromSeries = series[categoryName].length && series[categoryName].findBy('model.name', chartName);
result[categoryName].pushObject(chartFromSeries || {
blank: true,
chartOffset: 0,
model: Ember.Object.create({
name: chartName,
fillClass: 'colorable-fill-35',
strokeClass: 'colorable-stroke-35',
}),
});
}
});
});
return result;
},
didInsertElement() {
this._super(...arguments);
scheduleOnce('afterRender', this, this.fetchDataChart);
},
fetchDataChart() {
const { endAt, startAt } = getProperties(this, 'endAt', 'startAt');
const checkins = this.peekSortedCheckins();
let payloadVersion = get(this, 'chartsVisibilityService.payloadVersion');
let payloadDirection = get(this, 'chartsVisibilityService.payloadDirection');
let oldPayloadVersion = get(this, 'oldPayloadVersion');
if (
(!oldPayloadVersion || !payloadDirection || payloadVersion === oldPayloadVersion) &&
checkins.length &&
endAt.isSameOrBefore(checkins.get('lastObject.date'), 'day') &&
startAt.isSameOrAfter(checkins.get('firstObject.date'), 'day')
) {
return this.isDestroyed ||
(this.setChartsData() && setProperties(this, { chartLoaded: true, oldPayloadVersion: payloadVersion }));
} else {
const fetchOnlyQuery = get(this, 'chartsVisibilityService.fetchOnlyQuery');
if (!Object.keys(fetchOnlyQuery).length) {
return;
}
const { endAtWithCache, startAtWithCache } = getProperties(this, 'endAtWithCache', 'startAtWithCache');
const query = {
id: 'health',
end_at: endAtWithCache.format("YYYY-MM-DD"),
includes: fetchOnlyQuery,
start_at: startAtWithCache.format("YYYY-MM-DD"),
};
return this
.store
.queryRecord('chart', query)
.then(() => this.setChartsData())
.then(() => this.isDestroyed || setProperties(this, { chartLoaded: true, oldPayloadVersion: payloadVersion }));
}
},
setChartsData() {
const tags = this.store.peekAll('tag').toArray();
const foods = this.store.peekAll('food').toArray();
const checkins = this.peekSortedCheckins();
const symptoms = this.store.peekAll('symptom').toArray();
const conditions = this.store.peekAll('condition').toArray();
const treatments = this.store.peekAll('treatment').toArray();
const harveyBradshawIndices = this.store.peekAll('harveyBradshawIndex').toArray();
const trackables = [...conditions, ...symptoms, ...treatments, ...tags, ...foods, ...harveyBradshawIndices];
return this.isDestroyed || setProperties(this, {checkins, trackables});
},
peekSortedCheckins() {
return this
.store
.peekAll('checkin')
.toArray()
.sort(
(a, b) => moment(get(a, 'date')).diff(get(b, 'date'), 'days')
);
},
onResizeEnd() {
this.fetchDataChart();
},
actions: {
navigate(days) {
let centeredDate = get(this, 'centeredDate');
centeredDate = centeredDate ? moment(centeredDate) : get(this, 'endAt').subtract(get(this, 'daysRadius'), 'days');
set(this, 'centeredDate', centeredDate.add(days, 'days'));
},
setCurrentDate(date) {
get(this, 'onDateClicked')(date);
},
openInfoWindow(date, xPosition) {
set(this, 'xPosition', xPosition);
set(this, 'openInfoWindow', true);
},
closeInfoWindow() {
set(this, 'xPosition', null);
set(this, 'openInfoWindow', false);
},
},
});

View File

@@ -0,0 +1,37 @@
import Ember from 'ember';
import CheckinByDate from 'flaredown/mixins/checkin-by-date';
const {
set,
Component,
inject: { service },
computed: { alias },
setProperties,
} = Ember;
export default Component.extend(CheckinByDate, {
patternsVisible: false,
chartVisible: true,
journalIsVisible: alias('chartJournalSwitcher.journalIsVisible'),
chartJournalSwitcher: service(),
i18n: service(),
actions: {
routeToCheckin(date) {
this.routeToCheckin(date);
},
showJournal() {
setProperties(this, { journalIsVisible: true, chartVisible: false, patternsVisible: false });
},
showChart() {
set(this, 'journalIsVisible', false);
setProperties(this, { journalIsVisible: false, chartVisible: true, patternsVisible: false });
},
showPatterns() {
setProperties(this, { journalIsVisible: false, chartVisible: false, patternsVisible: true });
},
},
});

View File

@@ -0,0 +1,16 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['invitation-form'],
actions: {
acceptInvitation() {
var model = this.get('model');
model.set('passwordConfirmation', model.get('password'));
model.save().then( () => {
this.get('session').authenticate('authenticator:devise', model.get('email'), model.get('password') );
});
}
}
});

View File

@@ -0,0 +1,20 @@
import Ember from 'ember';
const {
get,
inject: {
service,
},
Component,
} = Ember;
export default Component.extend({
tagName: ['div'],
classNames: ['flex-container', 'footer', 'join-footer'],
_routing: service('-routing'),
click() {
get(this, '_routing').transitionTo('signup');
},
});

View File

@@ -0,0 +1,43 @@
import Ember from 'ember';
import moment from 'moment';
const {
get,
set,
computed,
Component,
run: { scheduleOnce },
String: { htmlSafe },
} = Ember;
export default Component.extend({
symptoms: [],
classNames: ["flaredown-white-box"],
symptomAverage: 0,
date: computed('checkin.date', function() {
return moment(get(this, 'checkin.date')).format("MMM D");
}),
style: computed('symptomAverage', function() {
return htmlSafe(`width: ${get(this, 'symptomAverage') * 100 / 5}%`);
}),
checkinId: computed('checkin.id', function() {
return get(this, 'checkin.id');
}),
symptomCeil: computed('symptomAverage', function() {
return Math.round(get(this, 'symptomAverage'));
}),
didInsertElement() {
this._super(...arguments);
scheduleOnce('afterRender', this, () => {
get(this, 'checkin.symptoms').then(symptoms =>
set(this, 'symptomAverage', symptoms.reduce((acc, s) => acc + get(s, 'value'), 0) / symptoms.length)
);
});
},
});

View File

@@ -0,0 +1,78 @@
import Ember from 'ember';
import moment from 'moment';
const {
get,
set,
computed,
Component,
isPresent,
setProperties,
run: {
scheduleOnce,
},
} = Ember;
export default Component.extend({
checkins: [],
loadingCheckins: true,
page: computed('checkins.[]', function() {
return Math.floor(get(this, 'checkins.length') / 10) + 1;
}),
didInsertElement() {
this._super(...arguments);
scheduleOnce('afterRender', this, () => {
if (this.peekReverseSortedCheckins().length >= 10) {
this.loadCheckins();
} else {
this.fetchMore().then(() => this.loadCheckins());
}
});
},
fetchMore() {
return this.store.query('checkin', { page: get(this, 'page') });
},
loadCheckins() {
const checkins = this.peekReverseSortedCheckins();
if (isPresent(checkins)) {
setProperties(this, {
checkins,
loadingCheckins: false,
});
} else {
setProperties(this, {
loadingCheckins: false,
noCheckinsPresent: true,
});
}
},
peekReverseSortedCheckins() {
return this
.store
.peekAll('checkin')
.toArray()
.filter(
c => isPresent(get(c, 'note'))
)
.sort(
(b, a) => moment(get(a, 'date')).diff(get(b, 'date'), 'days')
);
},
actions: {
crossedTheLine(above) {
if (above) {
set(this, 'loadingCheckins', true);
this.fetchMore().then(() => this.loadCheckins());
}
},
},
});

View File

@@ -0,0 +1,6 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'label',
classNames: ['label-switch']
});

View File

@@ -0,0 +1,21 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['login-form'],
actions: {
authenticateWithDevise() {
let { identification, password } = this.getProperties('identification', 'password');
this.get('session').authenticate('authenticator:devise', identification, password).catch((reason) => {
this.set('errorMessage', reason.errors);
});
},
authenticateWithFacebook() {
this.get('session').authenticate('authenticator:facebook').catch((reason) => {
this.set('errorMessage', reason);
});
}
}
});

View File

@@ -0,0 +1,15 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['tags-group'],
objects: Ember.computed('scope', function() {
return this.store.query(this.get('modelName'), { scope: this.get('scope') });
}),
actions: {
clickObj(obj) {
this.get('onItemClicked')(obj);
}
}
});

View File

@@ -0,0 +1,19 @@
import Ember from 'ember';
const {
Component,
inject: {
service,
},
computed: {
alias,
},
} = Ember;
export default Component.extend({
tagName: '',
notifications: service(),
postId: alias('notifications.first.postId'),
});

View File

@@ -0,0 +1,6 @@
import Ember from 'ember';
import CheckinByDate from 'flaredown/mixins/checkin-by-date';
export default Ember.Component.extend(CheckinByDate, {
classNames: 'navigation-bar',
});

View File

@@ -0,0 +1,87 @@
import Ember from 'ember';
import SearchableDropdown from 'flaredown/mixins/searchable-dropdown';
const {
get,
$,
computed,
inject: {
service,
},
computed: {
alias,
},
Component
} = Ember;
export default Component.extend(SearchableDropdown, {
tagName: 'header',
classNames: ['flex-container', 'nav'],
classNameBindings: ['isAuthenticatedUser::unauthenticatedNav'],
showSearch: false,
optionObjects: null,
searchObjects: null,
notifications: service(),
routeHistory: service(),
_routing: service('-routing'),
postId: alias('notifications.first.postId'),
click() {
if(!get(this, 'isAuthenticatedUser')) {
get(this, '_routing').transitionTo('signup');
}
},
didInsertElement(){
const state = {
direction: 0,
pos: $(window).scrollTop(),
};
$(window).on('scroll', () => {
let currentDirection = $(window).scrollTop();
if(currentDirection > state.pos) {
$('.nav').addClass('hideNavBar');
} else {
$('.nav').removeClass('hideNavBar');
}
state.pos = currentDirection;
});
},
didDestroyElement() {
$(window).off('scroll');
},
navigateBackHistory: computed('routeHistory.history', function() {
return get(this, 'routeHistory.history.length') >= 2;
}),
actions: {
toggleSearchBar() {
this.toggleProperty('showSearch');
},
searchObjects(query) {
this.sendAction('onSearch', query);
},
goToSelectedTopic(selected) {
if(selected) {
this.sendAction('onGo', selected);
}
},
navigateBack() {
const routing = get(this, '_routing');
const previous = get(this, 'routeHistory').popEntry();
if (previous) {
routing.transitionTo(...previous);
}
},
}
});

View File

@@ -0,0 +1,24 @@
import Ember from 'ember';
const {
get,
set,
Component
} = Ember;
export default Component.extend({
showReminder: false,
actions: {
saveProfile() {
set(this, 'isLoading', true);
get(this, 'model').save().then(profile => {
this.sendAction('onProfileSaved', profile);
}).finally(() => {
set(this, 'isLoading', false);
});
},
},
});

View File

@@ -0,0 +1,25 @@
import Ember from 'ember';
import StepControl from 'flaredown/mixins/step-control';
const {
computed: {
alias,
equal,
},
} = Ember;
export default Ember.Component.extend(StepControl, {
classNames: ['flaredown-white-box'],
isOnboarding: true,
routeAfterCompleted: 'index',
stepName: alias('step.stepName'),
isPersonal: equal('stepName', 'personal'),
isSymptoms: equal('stepName', 'symptoms'),
isCompleted: equal('stepName', 'completed'),
isConditions: equal('stepName', 'conditions'),
isTreatments: equal('stepName', 'treatments'),
isDemographic: equal('stepName', 'demographic'),
isReminder: equal('stepName', 'reminder'),
});

View File

@@ -0,0 +1,19 @@
import Ember from 'ember';
import CheckinByDate from 'flaredown/mixins/checkin-by-date';
import moment from 'moment';
export default Ember.Component.extend(CheckinByDate, {
model: Ember.computed.alias('parentView.model'),
actions: {
completeStep() {
this.routeToCheckinsForDate(moment(new Date()).format("YYYY-MM-DD"));
},
goBack() {
this.get('onGoBack')();
}
}
});

View File

@@ -0,0 +1,16 @@
import Ember from 'ember';
export default Ember.Component.extend({
model: Ember.computed.alias('parentView.model'),
actions: {
completeStep() {
this.get('onStepCompleted')();
},
goBack() {
this.get('onGoBack')();
}
}
});

View File

@@ -0,0 +1,42 @@
import Ember from 'ember';
export default Ember.Component.extend({
model: Ember.computed.alias('parentView.model'),
profile: Ember.computed.alias('model.profile'),
educationLevels: Ember.computed(function() {
return this.store.findAll('educationLevel');
}),
ethnicities: Ember.computed(function() {
return this.store.findAll('ethnicity');
}),
dayHabits: Ember.computed(function() {
return this.store.findAll('dayHabit');
}),
actions: {
validateNumber(event) {
const inputVar = document.getElementById("input-num");
if(event.target.value > 24 || event.target.value < 0) {
alert("Must be a number between 0 - 24");
inputVar.value = "";
return;
}
inputVar.disabled = false;
},
completeStep() {
this.get('profile').save().then( () => {
this.get('onStepCompleted')();
});
},
goBack() {
this.get('onGoBack')();
}
}
});

View File

@@ -0,0 +1,16 @@
import Ember from 'ember';
export default Ember.Component.extend({
model: Ember.computed.alias('parentView.model'),
actions: {
completeStep() {
var profile = this.get('model.profile');
profile.save().then( () => {
this.get('onStepCompleted')();
});
}
}
});

View File

@@ -0,0 +1,29 @@
import Ember from 'ember';
const {
get,
set,
computed: {
alias,
},
Component,
} = Ember;
export default Component.extend({
profile: alias('model.profile'),
actions: {
goBack() {
this.get('onGoBack')();
},
completeStep() {
const profile = get(this, 'profile');
set(profile, 'onboardingReminder', true);
profile.save().then( () => {
this.get('onStepCompleted')();
});
},
}
});

View File

@@ -0,0 +1,17 @@
import Ember from 'ember';
export default Ember.Component.extend({
model: Ember.computed.alias('parentView.model'),
actions: {
completeStep() {
this.get('onStepCompleted')();
},
goBack() {
this.get('onGoBack')();
}
}
});

View File

@@ -0,0 +1,17 @@
import Ember from 'ember';
export default Ember.Component.extend({
model: Ember.computed.alias('parentView.model'),
actions: {
completeStep() {
this.get('onStepCompleted')();
},
goBack() {
this.get('onGoBack')();
}
}
});

View File

@@ -0,0 +1,44 @@
import Ember from 'ember';
const {
get,
set,
Component,
inject: {
service,
},
getProperties,
computed: {
alias,
},
} = Ember;
export default Component.extend({
minSpinner: 3000,
maxSpinner: 5000,
selectableData: service(),
oracleSpinnerVisible: false,
sexes: alias('selectableData.sexes'),
actions: {
askOracle(oracleRequest) {
set(this, 'oracleSpinnerVisible', true);
const begin = + new Date();
oracleRequest
.askOracle()
.then(() => oracleRequest.save())
.then((result) => {
localStorage.oracleToken = get(result, 'token');
const { minSpinner, maxSpinner } = getProperties(this, 'minSpinner', 'maxSpinner');
let timeout = Math.random() * (maxSpinner - minSpinner) + minSpinner - new Date() + begin;
setTimeout(() => get(this, 'transitionToResult')(oracleRequest), timeout > 0 ? timeout : 0);
});
},
},
});

View File

@@ -0,0 +1,27 @@
import Ember from 'ember';
const {
get,
computed,
Component,
computed: {
not,
alias,
},
} = Ember;
export default Component.extend({
name: alias('item.name'),
disabled: not('model.canEdit'),
correction: alias('item.correction'),
confidence: computed('item.confidence', function() {
return Math.round(get(this, 'item.confidence') * 100) / 100;
}),
actions: {
correctionChanged() {
get(this, 'model').save();
},
},
});

View File

@@ -0,0 +1,43 @@
import Ember from 'ember';
import SearchableDropdown from 'flaredown/mixins/searchable-dropdown';
const {
get,
computed,
Component,
computed: {
bool,
}
} = Ember;
export default Component.extend(SearchableDropdown, {
isResponce: bool('model.responce.length'),
randomTrackables: computed(function() {
return this.randomSearch('condition');
}),
performSearch(term, resolve, reject) {
this
.searchByTerm('condition', term)
.then(function() { resolve(...arguments); }, reject);
},
actions: {
addCondition(condition) {
const name = get(condition, 'name');
const model = get(this, 'model');
const responce = get(model, 'responce');
if (!responce.findBy('name', name)) {
responce.pushObject({
name,
correction: 'selfAdded',
confidence: 100,
});
model.save();
}
}
},
});

View File

@@ -0,0 +1,35 @@
import Ember from 'ember';
import SearchableDropdown from 'flaredown/mixins/searchable-dropdown';
const {
get,
computed,
Component,
computed: {
alias,
},
} = Ember;
export default Component.extend(SearchableDropdown, {
objects: alias('model.symptoms'),
randomTrackables: computed(function() {
return this.randomSearch('symptom');
}),
performSearch(term, resolve, reject) {
this
.searchByTerm('symptom', term)
.then(function() { resolve(...arguments); }, reject);
},
actions: {
add(object) {
get(this, 'model').addSymptom(object);
},
remove(object) {
get(this, 'model').removeSymptom(object);
},
},
});

View File

@@ -0,0 +1,17 @@
import Ember from 'ember';
import { translationMacro as t } from "ember-i18n";
const {
inject: { service },
Component,
} = Ember;
export default Component.extend({
i18n: service(),
tagName: 'div',
classNames: ['expired-box'],
textBeforeLink: t("password.update.textBeforeLink"),
textAfterLink: t("password.update.textAfterLink"),
});

View File

@@ -0,0 +1,225 @@
import Ember from 'ember';
import moment from 'moment';
import * as d3 from "d3";
const {
get,
set,
computed,
observer,
Component,
setProperties,
A,
} = Ember;
export default Component.extend({
classNames: ['flaredown-svg-group'],
svg: null,
data: null,
svgChartWidth: 500,
svgLineAreaHeight: 150,
svgLineOffset: 20,
svgLineHeight: 3,
startAt: null,
endAt: null,
margin: { top: 20, right: 0, bottom: 5, left: 0 }, //margin for charts
backgroundMargin: { right: 10, left: 10 , top: 0 }, // for init-svg
colorIds: A(),
hasLines: false,
svgInitial: false,
init(){
this._super(...arguments);
const xScale = d3.time.scale().range([0, get(this, 'svgWidth')]);
const yScaleStatic = d3.scale.linear().range([get(this, 'svgLineAreaHeight'), 0]);
setProperties(this, { xScale, yScaleStatic });
},
didInsertElement() {
this._super(...arguments);
const svgCanvas = d3.select(this.element).select('svg');
const svgBackgroundArea = svgCanvas.append('g').attr('class', 'background-area');
const svg = svgCanvas.append('g').attr('class', 'offset');
const margin = get(this, 'margin');
svg.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // svg references to g
const resize = () => {
let svgSelection = this.$();
if(svgSelection) {
const width = this.$().width();
const backgroundMargin = get(this, 'backgroundMargin');
set(this, 'svgWidth', width);
set(this, 'svgChartWidth', width - backgroundMargin.left - backgroundMargin.right);
}
};
setProperties(this, { svgBackgroundArea, svg });
window.addEventListener("resize", resize);
resize();
},
xScaleObserver: observer('svgWidth', 'svgChartWidth', function(){
const svgInitial = get(this, 'svgInitial');
const svgWidth = get(this, 'svgWidth');
const width = svgInitial ? svgWidth - 2*get(this, 'backgroundMargin.left') : svgWidth;
get(this, 'xScale').range([0, width]);
this.renderBackground();
if(get(this, 'hasLines')) {
this.renderYGrid();
}
}),
svgBackgroundObserver: observer('svgBackgroundArea', function() {
const svgBackgroundArea = get(this, 'svgBackgroundArea');
if(!svgBackgroundArea) {
return;
}
if(svgBackgroundArea.select('rect').empty()) {
svgBackgroundArea.append('rect').attr('class', 'svg-background');
}
}),
svgGroupObserver: observer('svg', function() {
const svg = get(this, 'svg');
if(!svg) {
return;
}
if(svg.select('.grid-area').empty()) {
svg.append('g').attr('class', 'grid-area');
}
if(svg.select('.lines-area').empty()) {
const xOffset = get(this, 'svgInitial') ? get(this, 'backgroundMargin.left') : 0;
svg.append('g').attr('class', 'lines-area')
.attr('transform', `translate(${xOffset},0)`);
}
if(svg.select('.lines-dots-area').empty()) {
const xOffset = get(this, 'svgInitial') ? get(this, 'backgroundMargin.left') : 0;
svg.append('g').attr('class', 'lines-dots-area')
.attr('transform', `translate(${xOffset},0)`);
}
if(svg.select('.dots-area').empty()) {
const xOffset = get(this, 'svgInitial') ? get(this, 'backgroundMargin.left') : 0;
svg.append('g').attr('class', 'dots-area')
.attr('transform', `translate(${xOffset},0)`);
}
}),
startAtOffset: computed('startAt', 'daysRangeOffset', function() {
return moment(get(this, 'startAt')).subtract(get(this, 'daysRangeOffset'), 'days');
}),
svgHeight: computed('data.series.[]', function() {
let height = 0;
let filteredSeries = A();
const series = get(this, 'data.series') || [];
const dynamicSeries = series.filterBy('subtype', 'dynamic');
const lines = series.filterBy('type', 'line')
const markers = series.filterBy('type', 'marker');
const margin = get(this, 'margin');
filteredSeries.pushObjects([lines, dynamicSeries, markers]).map((itemArray) => {
return this.addChartAttributes(itemArray);
});
if(lines.length > 0 || dynamicSeries.length > 0) {
height += get(this, 'svgLineAreaHeight') + get(this, 'svgLineOffset');
set(this, 'hasLines', true);
} else {
height += get(this, 'svgLineOffset');
set(this, 'svgLineAreaHeight', 0);
}
if(markers.length > 0) {
markers.forEach(() => {
height += get(this, 'svgLineHeight') + get(this, 'svgLineOffset');
});
}
get(this, 'xScale').domain([
get(this, 'startAtOffset').startOf('day').toDate().getTime(),
get(this, 'endAt').endOf('day').toDate().getTime(),
]);
get(this, 'yScaleStatic').domain([0, 4]);
height += margin.top + margin.bottom;
return height || 0;
}),
svgChartHeigth: computed('svgHeight', function() {
const margin = get(this, 'margin');
return (get(this, 'svgHeight') - margin.top - margin.bottom);
}),
addChartAttributes(item_array) {
let index = get(item_array, 'firstObject.subtype') === 'dynamic' ? 10 : 0;
item_array.map((i) => {
i.index = index;
index += 1;
})
},
renderBackground() {
const backgroundMargin = get(this, 'backgroundMargin');
const svgBackgroundArea = get(this, 'svgBackgroundArea');
svgBackgroundArea.attr("transform", "translate(" + backgroundMargin.left + "," + backgroundMargin.top + ")");
svgBackgroundArea
.select('rect')
.attr('class', 'svg-background')
.attr('width', get(this, 'svgChartWidth'))
.attr('height', get(this, 'svgHeight'));
},
renderYGrid() {
const svg = get(this, 'svg');
const yScaleGrid = get(this, 'yScaleStatic');
const gridArea = svg.select('g.grid-area');
const backgroundMargin = get(this, 'backgroundMargin')
let attr = {
'class': 'yGrid',
'x1' : backgroundMargin.left,
'x2' : (get(this, 'svgWidth') - backgroundMargin.right),
'y1' : (d) => { return yScaleGrid(d) },
'y2' : (d) => { return yScaleGrid(d) },
};
let grid = gridArea.selectAll('.yGrid')
.data(yScaleGrid.ticks(5))
.attr(attr);
grid.exit().remove();
return grid.enter()
.append('line')
.attr(attr);
},
});

View File

@@ -0,0 +1,242 @@
import Ember from 'ember';
import moment from 'moment';
const {
$,
get,
set,
run,
observer,
Component,
A,
} = Ember;
export default Component.extend({
classNames: ['chart-hover-container'],
svg: null,
data: null,
width: null,
height: null,
dateFormat: 'MMM D, YYYY',
tooltipTopOffset: 11,
tooltipLeftOffset: 5,
initObserver: observer('svg', 'height', 'width', function() {
this._super(...arguments);
const svg = get(this, 'svg');
const height = get(this, 'height');
if(!svg || !height) {
return;
}
this.renderContainer(svg);
}),
widthObserver: observer('width', function() {
const width = get(this, 'width');
const backgroundMargin = get(this, 'backgroundMargin');
const hoverArea = get(this, 'hoverArea');
hoverArea
.attr("width", width)
.attr("transform", "translate(" + backgroundMargin.left + "," + backgroundMargin.top + ")");
}),
didInsertElement() {
this._super(...arguments);
$('body').on('click touchstart', this.hideAll.bind(this));
},
willDestroyElement() {
this._super(...arguments);
$('body').off('click touchstart', this.hideAll.bind(this));
},
hideAll(event) {
if($(event.target).closest('.flaredown-svg-group').length === 0){
get(this, 'line').style("display", "none");
get(this, 'tooltipArea').css('display', 'none');
}
},
renderContainer(svg){
if(!svg.select('.hover-area').empty()){
return;
}
const svgHight = get(this, 'height');
const line = svg.append('line')
.attr('class', 'hover-line')
.attr('style', 'display:none;')
.attr('y1', 0)
.attr('y2', svgHight)
.attr('transform', 'translate(0, -10)');
set(this, 'line', line);
const tooltipArea = this.$('.tooltip-area');
set(this, 'tooltipArea', tooltipArea);
const hoverArea = svg.append("rect")
.attr("class", "hover-area")
.attr("width", get(this, 'width'))
.attr("height", svgHight)
.style("fill", "none")
.style("pointer-events", "all")
.on("mouseover", function() {
line.style("display", 'block');
tooltipArea.css('display', 'block');
})
.on("mouseout", function() {
if ($(event.toElement).closest('.tooltip-area').length === 0) {
line.style("display", "none");
tooltipArea.css("display", "none");
}
})
.on("mousemove", () => {
run(this, this.mouseMove, event);
})
.on("touchmove", () => {
run(this, this.mouseMove, event);
})
.on("touchstart", () => {
line.style("display", 'block');
tooltipArea.css("display", "block");
});
set(this, 'hoverArea', hoverArea);
},
mouseMove(e) {
if (e instanceof $.Event) {
return;
}
const xScale = get(this, 'xScale');
const mouseX = e.offsetX || e.changedTouches[0].screenX; //d3.mouse(hoverArea.node())[0];
const xValue = moment(xScale.invert(mouseX));
if(this.isOutOfRangeDates(xValue)) {
return;
}
if(xValue.hours() >= 12){
xValue.add(1, 'days').startOf('day');
} else {
xValue.startOf('day');
}
const x = xScale(xValue.toDate().getTime());
get(this, 'line')
.attr('x1', x)
.attr('x2', x);
this.showTooltip(xValue, x);
e.stopPropagation();
},
isOutOfRangeDates(date) {
const startAt = get(this, 'startAt').startOf('day');
const endAt = get(this, 'endAt');
return date < startAt || date > endAt;
},
showTooltip(xValue, x) {
const xValueFormatted = xValue.format('YYYY-MM-DD');
const tooltipData = this.tooltipData(xValueFormatted);
if(tooltipData.empty) {
return;
}
const lineItemList = tooltipData
.filter((item) => (!item.marker && $.isNumeric(item.y) || (item.category == 'treatments' && item.is_taken)))
.map((item) => {
let value = this.tooltipItemValue(item);
return `<div class="line-item">
<span class="colorable-clr-${item.color_id}">${item.label}</span>
<span>${value}</span>
</div>`
}).join(' ');
let markerItemList = tooltipData.filter((item) => item.marker && item.category !== 'treatments' ).map((item) => {
return `<span class="marker-item colorable-clr-${item.color_id}">${item.label},&nbsp;</span>`
}).join(' ');
const tooltipArea = get(this, 'tooltipArea');
const hoverCenter = get(this, 'width')/2;
const tooltipLeftOffset = get(this, 'tooltipLeftOffset');
let tooltipLeft = x <= hoverCenter ? (x + tooltipLeftOffset) : (x - tooltipLeftOffset - tooltipArea.width() - get(this, 'backgroundMargin.left'));
const isAuthor = get(this, 'session.email') === get(this, 'data.author_email');
const headerBody = (get(this, 'session.isAuthenticated') && isAuthor) ?
`<a href=/checkin?date=${xValueFormatted}>
<b>${xValue.format(get(this, 'dateFormat'))}</b>
<img src="/assets/nav_icons/arrow-right.svg">
</a>` :
`<b>${xValue.format(get(this, 'dateFormat'))}</b>`;
const tooltipHeader = `<div class="tooltip-header">${headerBody}</div>`
tooltipArea
.css('visibility', 'visible')
.html(() => {
return `${tooltipHeader}` + `<div class="tooltip-items">
${lineItemList}
<div class="marker-items">${markerItemList}</div>
</div>`;
})
.css('top', get(this, 'tooltipTopOffset'))
.css('left', tooltipLeft);
},
tooltipData(xValueFormatted) {
let filteredItems = A();
get(this, 'data.series').map((item) => {
let filteredByDate = item.data.filterBy('x', xValueFormatted);
if(filteredByDate.length == 0) {
return;
} else {
let element = filteredByDate[0];
element.label = item.label;
element.color_id = item.color_id;
element.marker = item.type == 'marker';
element.static = item.subtype == 'static';
element.category = item.category;
return filteredItems.pushObjects(filteredByDate);
}
});
return filteredItems.filter((item) => !item.average);
},
tooltipItemValue(item) {
if(item.static) {
if(item.marker) {
return item.category == 'treatments' ? `${item.y || '&#8203;'}` : '';
} else {
let y = item.y % 1 === 0 ? item.y : item.y.toFixed(1);
return `${y}/4`;
}
} else {
let units = "";
if(item.category == "weathersMeasures") {
units = item.label == "Avg daily humidity" ? '%' : 'mb';
}
return `${item.y}` + `${units}`;
}
},
});

View File

@@ -0,0 +1,199 @@
import Ember from 'ember';
import moment from 'moment';
import * as d3 from "d3";
const {
$,
get,
set,
computed,
observer,
Component,
run: { scheduleOnce }
} = Ember;
export default Component.extend({
data: null,
chart: null,
index: 1,
isRendered: false,
svgInitial: false,
renderObserver: observer('chart.svg', 'chart.width', 'chart.height', 'isRendered', 'onNavigate', function() {
if(get(this, 'chart.svg') && get(this, 'isRendered')) {
scheduleOnce('afterRender', this, this.renderChart);
}
}),
isCurrentEndDate: computed('chart.endAt', function() {
return get(this, 'chart.endAt').startOf('day').toDate().getTime() == moment().startOf('day').toDate().getTime();
}),
didInsertElement() {
this._super(...arguments);
set(this, 'isRendered', true);
set(this, 'yScaleDynamic', d3.scale.linear().range([get(this, 'chart.svgLineAreaHeight'), 0]));
},
renderChart() {
const data = get(this, 'data');
let chartData = data.data
if(chartData) {
switch(data.type) {
case 'marker': {
if (data.category == 'treatments') {
chartData = data.data.filter((item) => item.is_taken)
}
this.renderMarker(chartData, data.index, data.label);
break;
}
default: {
this.renderLine(chartData.filter((i) => $.isNumeric(i.y)), data.subtype, data.index, data.label);
break;
}
}
} else {
// TODO: render "No Data"
// console.log('No data!');
}
},
renderMarker(data, index, label) {
const svg = get(this, 'chart.svg');
const dotsAreas = svg.select('g.dots-area');
const svgInitial = get(this, 'chart.svgInitial');
const backgroundMarginRight = get(this, 'chart.backgroundMarginRight');
const width = svgInitial ? get(this, 'chart.width') - 2*backgroundMarginRight : get(this, 'chart.width');
const xScale = get(this, 'chart.xScale');
const colorId = get(this, 'data.color_id');
const y = get(this, 'chart.svgLineAreaHeight') + get(this, 'chart.svgLineOffset')*(index + 1) + get(this, 'chart.svgLineHeight')*index;
const lineWidth = get(this, 'isCurrentEndDate') ? get(this, 'chart.svgChartWidth') : width;
const lineData = [{x: 0, y: y, x2: width, y2: y}];
const paths = dotsAreas
.selectAll(`.dot-path-${index}`) // for adding new pathes
.data(lineData)
.attr('x1', 0)
.attr('y1', y)
.attr('x2', lineWidth)
.attr('y2', y);
paths.exit().remove();
paths.enter()
.append('line')
.attr('class', `line colorable-stroke-${colorId} dot-path-${index}`)
.attr('x1', 0)
.attr('y1', y)
.attr('x2', lineWidth)
.attr('y2', y)
.attr('stroke', `${colorId}`)
.style('stroke-dasharray', ('3, 3'));
const dots = dotsAreas
.selectAll(`.dot-${index}`)
.data(data)
.attr('cx', (d) => xScale(moment(d.x).toDate().getTime()))
.attr('cy', () => y);
dots.enter()
.append('circle')
.attr('class', `colorable-fill-${colorId} dot-${index}`)
.attr('r', 6)
.attr('cx', (d) => {
return xScale(moment(d.x).toDate().getTime());
// return d.y == null ? null : xScale(moment(d.x).toDate().getTime());
})
.attr('cy', () => y)
.attr('data-legend', () => {
return label;
});
this.renderLabel(dotsAreas, label, colorId, `translate(50, ${155 + 25*index})`);
dots.exit().remove();
},
renderLine(data, subtype, index, label) {
const svg = get(this, 'chart.svg');
const lineAreas = svg.selectAll('g.lines-area');
const isStatic = subtype === 'static';
const xScale = get(this, 'chart.xScale');
let yScaleDynamic = get(this, 'yScaleDynamic');
if(!isStatic) {
if(label == 'Avg daily humidity') {
yScaleDynamic.domain([0, 100]);
} else if(label == 'Harvey Bradshaw Index') {
yScaleDynamic.domain([0, 25]);
} else {
yScaleDynamic.domain([
d3.min(data, (d) => d.y ),
d3.max(data, (d) => d.y )
]);
}
}
const yScale = isStatic ? get(this, 'chart.yScaleStatic') : yScaleDynamic;
const colorId = get(this, 'data.color_id');
const line = d3.svg.line()
.x((d) => xScale(moment(d.x).toDate().getTime()))
.y((d) => yScale(d.y));
const lines = lineAreas
.selectAll(`.line-path-${index}`)
.data(data)
.attr('d', line(data));
lines.enter()
.append('path')
.attr('class', isStatic ? `line colorable-stroke-${colorId} line-path-${index}` : `line dynamic colorable-stroke-${colorId} line-path-${index}`)
.attr('d', line(data))
.attr('data-legend', () => {
return label;
});
this.renderLabel(lineAreas, label, colorId, "translate(265, 80)");
lines.exit().remove();
const lineDotsAreas = svg.selectAll('g.lines-dots-area');
const filteredData = data.filter((d) => !d.average);
const dots = lineDotsAreas
.selectAll(`.dot-line-${index}`)
.data(filteredData)
.attr('cx', (d) => xScale(moment(d.x).toDate().getTime()) )
.attr('cy', (d) => yScale(moment(d.y).toDate().getTime()) );
dots.enter()
.append('circle')
.attr('class', `colorable-stroke-${colorId} dot-line-${index}`)
.attr('r', 4)
.attr('stroke-width', 2)
.attr('fill', 'white')
.attr('cx', (d) => xScale(moment(d.x).toDate().getTime()) )
.attr('cy', (d) => yScale(moment(d.y).toDate().getTime()) );
dots.exit().remove();
},
renderLabel(selection, label, colorId, translate) {
if(get(this, 'initialSvg')) {
selection.append('text')
.attr('x', get(this, 'width'))
.attr("dy", ".35em")
.text(label)
.attr('transform', translate)
.attr('class', `colorable-fill-${colorId}`);
}
},
});

View File

@@ -0,0 +1,21 @@
import Ember from 'ember';
const {
get,
computed,
Component,
} = Ember;
export default Component.extend({
tagName: 'span',
classNames: ['trackableItem'],
classNameBindings: ['isLine:line:circle', 'colorableClass'],
isLine: computed('item.type', function() {
return get(this, 'item.type') == 'line';
}),
colorableClass: computed('item.color_id', function() {
return `colorable-clr-${get(this, 'item.color_id')}`;
}),
});

View File

@@ -0,0 +1,160 @@
import Ember from 'ember';
import ChartDataRetrieve from 'flaredown/mixins/chart/chart-data-retrieve';
import { translationMacro as t } from "ember-i18n";
const {
get,
set,
computed,
computed: {
not
},
inject: { service },
observer,
setProperties,
Component,
A,
} = Ember;
export default Component.extend(ChartDataRetrieve, {
i18n: service(),
store: service(),
model: null,
maxTrackables: 10,
showMessage: false,
classNames: ['flaredown-white-box'],
selectLabel: t("history.step.creation.selectLabel"),
deleteText: t("history.step.creation.deleteText"),
saveText: t("history.step.creation.buttonText"),
maxTrackablesText: t("history.step.creation.maxTrackablesText"),
disableSaveBtn: not('model.includes.length'),
chartEnablerPlaceholder: computed('isChartEnablerDisabled', function() {
return get(this, 'isChartEnablerDisabled') ? 'No items to add' : 'Add symptoms, treatments and more...';
}),
init() {
this._super(...arguments);
get(this, 'store')
.queryRecord('chart-list', {})
.then((res) => {
set(this, 'payload', get(res, 'payload'));
});
},
didReceiveAttrs() {
this._super(...arguments);
const model = get(this, 'model');
const store = get(this, 'store');
if(model === null && store) {
set(this, 'model', store.createRecord('pattern'));
}
},
maxTrackablesObserver: observer('model.includes.length', function() {
let length = get(this, 'model.includes.length');
const maxTrackables = get(this, 'maxTrackables');
if(length >= maxTrackables) {
setProperties(this, { showMessage: true, disableSaveBtn: true });
} else {
setProperties(this, { showMessage: false, disableSaveBtn: false });
}
}),
patternIncludesObserver: observer('payload', function() {
const patternIncludes = A([]);
const payload = get(this, 'payload');
Object
.keys(payload)
.forEach(category => {
const categoryCharts = payload[category];
categoryCharts.forEach(chart => {
let chart_id = chart[0];
if(category === 'weathersMeasures') {
chart_id = chart_id == 1 ? 'humidity' : 'pressure';
}
patternIncludes.pushObject({
id: chart_id,
category,
label: chart[1],
});
});
});
set(this, 'patternIncludes', patternIncludes);
}),
patternOptionsObserver: observer('patternIncludes', 'model.includes', function() {
const includes = get(this, 'model.includes');
const patternIncludes = get(this, 'patternIncludes');
set(this, 'options', A(patternIncludes.filter((pattern) => {
return includes.filter((inc) => {
return inc.id == pattern.id && inc.category == pattern.category;
}).length === 0;
})));
}),
actions: {
handleChange(obj) {
const maxTrackables = get(this, 'maxTrackables');
const includes = get(this, 'model.includes');
const options = get(this, 'options');
const exist = includes.find((i) => {
return get(i, 'id') == get(obj, 'id') && get(i, 'category') == get(obj, 'category');
});
options.removeObject(obj);
if(exist == null && includes.length < maxTrackables) {
includes.pushObject(obj);
} else {
set(this, 'showMessage', true);
}
},
clicked(obj) {
get(this, 'model.includes').removeObject(obj);
let options = get(this, 'options');
const exist = options.find((i) => {
return get(i, 'id') == get(obj, 'id') && get(i, 'category') == get(obj, 'category');
});
if(exist == null && obj.category == 'weathersMeasures') {
options.pushObject(obj);
}
},
savePattern() {
get(this, 'model').save().then(() => {
this.sendAction('onSaved');
});
},
deletePattern() {
get(this, 'model').destroyRecord().then(() => {
this.sendAction('onSaved');
});
},
cancel() {
get(this, 'model').deleteRecord();
this.sendAction('onCanceled');
}
},
});

View File

@@ -0,0 +1,151 @@
import Ember from 'ember';
import DS from 'ember-data';
import moment from 'moment';
const {
get,
set,
computed,
computed: { alias },
Component,
inject: {
service,
},
setProperties,
A,
} = Ember;
export default Component.extend({
ajax: service('ajax'),
patternsLoading: service('patterns-loading'),
logoVisiability: service(),
page: 1,
loadingPatterns: alias('patternsLoading.loadingPatterns'),
patterns: [],
colorIds: A(),
backgroundMargin: { right: 10, left: 10 , top: 0 },
startAt: moment().subtract(14, 'days'), // 7 daysRadius * 2
endAt: moment(),
chartData: computed('patterns.@each.id', 'startAt', 'endAt', 'daysRangeOffset', function() {
const patterns = get(this, 'patterns');
if(!patterns) {
return;
}
const ids = patterns.mapBy('id');
const chartDataPromise = get(this, 'chartDataPromise');
const promise = get(this, 'ajax').request('/charts_pattern', {
data: {
pattern_ids: ids,
start_at: get(this, 'startAt').format('YYYY-MM-DD'),
end_at: get(this, 'endAt').format('YYYY-MM-DD'),
offset: get(this, 'daysRangeOffset') || 1,
}
}).then((data) => data.charts_pattern);
set(chartDataPromise, 'promise', promise);
return chartDataPromise;
}),
init() {
this._super(...arguments);
set(this, 'chartDataPromise', DS.PromiseArray.create({}));
},
didInsertElement() {
this._super(...arguments);
// needs to be loaded
get(this, 'chartData');
set(this, 'logoVisiability.showHeaderPath', false);
const resize = () => {
const selection = this.$();
if (!selection) {
return;
}
const width = selection.width();
set(this, 'indexPageWidth', width);
};
window.addEventListener("resize", resize);
resize();
},
willDestroyElement(){
set(this, '_isDestroyed', true);
},
authorName: computed('patterns.@each.authorName', function() {
const names = get(this, 'patterns').mapBy('authorName').compact();
const notEmpty = names.length > 0 ? names.filter((i) => i.length > 0).length > 0 : null;
return notEmpty ? get(names, 'firstObject') : 'User';
}),
daysRangeOffset: computed('indexPageWidth', 'daysRange', function() {
const backgroundMargin = get(this, 'backgroundMargin');
const backgroundWidth = get(this, 'indexPageWidth') - backgroundMargin.left - backgroundMargin.right;
const daysRange = get(this, 'daysRange');
return Math.ceil((daysRange*backgroundMargin.left)/backgroundWidth);
}),
daysRange: computed('startAt', 'endAt', function() {
return moment.duration(get(this, 'endAt') - get(this, 'startAt')).asDays();
}),
fixPatternColors(chartData) {
chartData.map((chart) => {
return this.addChartAttributes(chart.series);
});
},
addChartAttributes(series) {
let index = 0;
series.map((i) => { // setColorId()
i.index = index;
index += 1;
})
},
setColorId() {
return get(this, 'colorIds').shiftObject();
},
actions: {
newPattern() {
this.sendAction('onCreate');
},
edit(pattern){
this.sendAction('onEdit', pattern);
},
crossedTheLine() {
set(this, 'loadingPatterns', true);
this.sendAction('onRequest', this.incrementProperty('page', 1));
},
navigate(days) {
const startAt = moment(get(this, 'startAt')).add(days, 'days');
const endAt = moment(get(this, 'endAt')).add(days, 'days');
setProperties(this, { startAt: startAt, endAt: endAt });
},
sharePattern() {
this.transitionTo('patterns');
},
}
});

View File

@@ -0,0 +1,14 @@
import Ember from 'ember';
import { translationMacro as t } from "ember-i18n";
const {
inject: { service },
Component
} = Ember;
export default Component.extend({
i18n: service(),
classNames: ['flaredown-transparent-box'],
buttonLabel: t("history.step.initial.buttonText"),
});

View File

@@ -0,0 +1,72 @@
import Ember from 'ember';
import DatesRetriever from 'flaredown/mixins/chart/dates-retriever';
import moment from 'moment';
const {
get,
computed,
Component,
} = Ember;
export default Component.extend(DatesRetriever, {
yLine: [1, 1, 3, 3, 3, 1, 1, 0, 0, 0],
yMarkerIndexes: { 'B12': [3, 4, 5, 6], 'Alcohol': [2, 3] },
dates: computed('yLine', function() {
const daysCount = (get(this, 'yLine.length'));
let res = [];
for(let i = 0; i < daysCount; i++) {
res.push(moment().add(i+1, 'days').format('YYYY-MM-DD'));
}
return res.sort();
}),
lineData: computed('yLine', 'dates', function() {
const dates = get(this, 'dates');
const yLine = get(this, 'yLine');
let datesHash = dates.map((date) => {
return { x: date, y: yLine[dates.indexOf(date)] };
})
get(datesHash, 'firstObject').average = true;
get(datesHash, 'lastObject').average = true;
return datesHash;
}),
markerFirstData(key) {
const dates = get(this, 'dates');
const yMarkerIndexes = get(this, 'yMarkerIndexes');
let yValues = yMarkerIndexes[key];
let filteredDates = dates.filter((date) => {
if(yValues.includes(dates.indexOf(date))) {
return date;
}
});
return filteredDates.map((i) => {
return {x: i}
})
},
data: computed('dataLine', function() {
return { pattern_name: 'initial', series: [
{ type: 'line', subtype: 'static', label: 'Fatigue', category: 'conditions', color_id: 1, data: get(this, 'lineData') },
{ type: 'marker', subtype: 'static', label: 'B12 supplement', category: 'treatments', color_id: 2, data: this.markerFirstData('B12') },
{ type: 'marker', subtype: 'static', label: 'Alcohol', category: 'treatments', color_id: 3, data: this.markerFirstData('Alcohol') }
]}
}),
startAt: computed('dates', function() {
return moment(get(this, 'dates.firstObject'))
}),
endAt: computed('dates', function() {
return moment(get(this, 'dates.lastObject')).subtract(1, 'days');
}),
});

View File

@@ -0,0 +1,44 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'li',
classNameBindings: ['colorClass'],
classNames: ['pip-elem basic'],
colorClass: Ember.computed('isActive', function() {
if(this.get('isActive')) {
return `highlight ${this.get('modelBgClass')}`;
} else {
return "no-highlight";
}
}),
isActive: Ember.computed('value', 'selected', 'current', function() {
if(Ember.isEqual(this.get('value'), 0)) {
if(Ember.isPresent(this.get('current'))) {
return Ember.isEqual(this.get('value'), this.get('current')) || false;
} else {
return Ember.isEqual(this.get('value'), this.get('selected')) || false;
}
} else if(Ember.isPresent(this.get('current'))) {
return this.get('value') <= this.get('current');
} else {
return this.get('value') <= this.get('selected');
}
}),
click() {
return this.get('onClick')(this.get('value'));
},
mouseEnter() {
return this.get('onMouseEnter')(this.get('value'));
},
mouseLeave() {
return this.get('onMouseLeave')(this.get('value'));
}
});

View File

@@ -0,0 +1,46 @@
import Ember from 'ember';
const {
get,
computed,
computed: {
alias,
},
Component,
} = Ember;
export default Component.extend({
classNames: ['flaredown-white-box'],
classNameBindings: ['unreadClass:unreadNotification'],
notificationAnchor: computed('notification.kind', function() {
if( get(this, 'notification.kind') === 'reaction' && get(this, 'notification.notificateableType') === 'post') {
return 'emoji-reactions';
} else {
return `anchor-${get(this, 'notification.notificateableId')}`;
}
}),
people: computed('notification.count', function() {
const count = get(this, 'notification.count');
return `${count} ${count > 1 ? 'people' : 'person'}`;
}),
didWhat: computed('notification.kind', function() {
return get(this, 'notification.kind') === 'comment' ? 'responded' : 'reacted';
}),
fullResponse: computed('people', 'notification.{kind,notifier_username}', function() {
if(get(this, 'notification.kind') === 'mention') {
return (`<b>${get(this, 'notification.notifier_username')}</b> mentioned you in a comment` ).htmlSafe();
} else {
const response = get(this, 'notification.kind') === 'comment' ? 'responded' : 'reacted';
return (`<b>${get(this, 'people')}</b> ${response} to ${get(this, 'notification.postTitle')}`).htmlSafe();
}
}),
unreadClass: alias('notification.unread'),
});

View File

@@ -0,0 +1,69 @@
import Ember from 'ember';
import InViewportMixin from 'ember-in-viewport';
const {
get,
set,
observer,
Component,
getProperties,
run: {
schedule,
},
inject: {
service,
},
} = Ember;
export default Component.extend(InViewportMixin, {
init() {
this._super(...arguments);
const commentId = get(this, 'comment.id');
if (commentId) {
set(this, 'elementId', `anchor-${commentId}`);
}
},
visited: false,
ajax: service(),
notifications: service(),
didEnterViewport() {
schedule('afterRender', this, this.setVisited);
},
setVisited() {
set(this, 'visited', true);
},
destroyNotifications: observer('visited', 'comment.body', function() {
const {
ajax,
comment,
visited,
} = getProperties(this, 'ajax', 'comment', 'visited');
const { id, hasNotifications } = getProperties(comment, 'id', 'hasNotifications');
if (visited && hasNotifications) {
const store = get(this, 'store');
ajax
.put(`notifications/comment/${id}`)
.then(({notifications}) => {
notifications.forEach((n) => {
const model = store.peekRecord('notification', n.id);
if(model){
set(model, 'unread', false);
}
});
set(comment, 'notifications', {});
});
}
}),
});

View File

@@ -0,0 +1,8 @@
import Ember from 'ember';
const {
Component,
} = Ember;
export default Component.extend({
});

View File

@@ -0,0 +1,14 @@
import Ember from 'ember';
import UserNameable from 'flaredown/mixins/user-nameable';
const {
computed: {
alias
},
Component,
} = Ember;
export default Component.extend(UserNameable, {
classNames: ['profile-circle'],
pictureLetter: alias('session.actualUser.profile.screenName.0'),
});

View File

@@ -0,0 +1,28 @@
import Ember from 'ember';
import InViewportMixin from 'ember-in-viewport';
const {
set,
Component,
getProperties,
inject: {
service,
},
} = Ember;
export default Component.extend(InViewportMixin, {
classNames: ['flaredown-white-box'],
ajax: service(),
didEnterViewport() {
const { ajax, comment } = getProperties(this, 'ajax', 'comment');
const { id, hasNotifications } = getProperties(comment, 'id', 'hasNotifications');
if (hasNotifications) {
ajax
.put(`notifications/comment/${id}`)
.then(() => set(comment, 'notifications', {}));
}
},
});

View File

@@ -0,0 +1,26 @@
import Ember from 'ember';
import InViewportMixin from 'ember-in-viewport';
import UpdateNotifications from 'flaredown/mixins/update-notifications';
const {
get,
Component,
run: {
schedule,
},
inject: {
service,
},
} = Ember;
export default Component.extend(InViewportMixin, UpdateNotifications, {
classNames: ['flaredown-white-box post'],
ajax: service(),
notifications: service(),
elipsis: 125,
didEnterViewport() {
schedule('afterRender', this, this.updatePostNotifications, get(this, 'post'));
},
});

View File

@@ -0,0 +1,7 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['power-select-dropdown-name-desc']
});

View File

@@ -0,0 +1,7 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['power-select-dropdown-trackable', 'create']
});

View File

@@ -0,0 +1,7 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['power-select-dropdown-trackable', 'option']
});

View File

@@ -0,0 +1,8 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'span',
classNames: ['ember-power-select-selected-item']
});

View File

@@ -0,0 +1,57 @@
import Ember from 'ember';
const {
on,
get,
set,
compare,
isPresent,
Component,
inject: {
service,
},
computed: {
sort,
alias,
},
} = Ember;
export default Component.extend({
selectableData: service('selectable-data'),
sexes: alias('selectableData.sexes'),
countries: sort('selectableData.countries', function(itemA, itemB) {
const nameA = get(itemA, 'name');
const nameB = get(itemB, 'name');
const us = 'United States';
const gb = 'United Kingdom';
return nameA === us ? -1 : nameB === us ? 1 : nameA === gb ? -1 : nameB === gb ? 1 : compare(nameA, nameB);
}),
setSelectedSexId: on('didInsertElement', function() {
get(this, 'model.sex').then(sex => {
if (isPresent(sex)) {
set(this, 'selectedSexId', get(sex, 'id'));
}
});
}),
actions: {
sexChanged(newId) {
set(this, 'model.sex', get(this, 'sexes').findBy('id', newId));
},
saveProfile() {
set(this, 'isLoading', true);
get(this, 'model').save().then(profile => {
this.sendAction('onProfileSaved', profile);
}).finally(() => {
set(this, 'isLoading', false);
});
},
},
});

View File

@@ -0,0 +1,68 @@
import Ember from 'ember';
import { translationMacro as t } from "ember-i18n";
import config from 'flaredown/config/environment';
const {
get,
computed,
computed: {
alias,
},
inject: {
service,
},
Component,
} = Ember;
export default Component.extend({
i18n: service(),
_routing: service('-routing'),
classNames: ['checkinReviewBody'],
prScore: alias('pr.score'),
showReviewPage: computed('prScore', function() {
const score = get(this, 'prScore');
return score >= 9;
}),
appStoreURL: config.review.appStore,
googlePlayUrl: config.review.googlePlay,
highRateHeader: t("step.checkin.promotion_rate.reviewHeader"),
lowRateHeader: t("step.checkin.promotion_rate.feedBackHeader"),
fbText: t("step.checkin.promotion_rate.shareTitleFb"),
twText: t("step.checkin.promotion_rate.shareTitleTw"),
reviewHeader: computed('showReviewPage', function() {
return get(this, 'showReviewPage') ? get(this, 'highRateHeader') : get(this, 'lowRateHeader');
}),
shareTitles: computed(function() {
return { fb: get(this, 'fbText'), tw: get(this, 'twText') };
}),
shareUrl: ' ', // to not show current url
actions: {
sendFeedback() {
get(this, 'pr').save();
get(this, 'onForward')();
},
openAppStore() {
window.open(get(this, 'appStoreURL'));
},
openGooglePlay() {
let url = get(this, 'googlePlayUrl');
if(navigator.userAgent.toLowerCase().indexOf("android") === -1) {
url += '#details-reviews';
}
window.open(url);
},
}
});

View File

@@ -0,0 +1,48 @@
import Ember from 'ember';
import { translationMacro as t } from "ember-i18n";
const {
get,
set,
inject: { service },
Component
} = Ember;
export default Component.extend({
classNames: ['reset-password-form'],
i18n: service(),
successfulResetMsg: t("password.reset.successMsg"),
errorResetMsg: t("password.reset.errorMsg"),
setupModel: Ember.on('init', function(){
this.set('model', this.store.createRecord('password'));
}),
onWillDestroyElement: Ember.on('willDestroyElement', function(){
this.discardChanges();
}),
discardChanges() {
var model = this.get('model');
if( model.get('isNew') ) {
var statePath = model.get('stateManager.currentPath');
if (!Ember.isEqual(statePath,'created.inFlight')) {
model.transitionTo('created.uncommitted');
}
model.deleteRecord();
}
},
actions: {
resetPassword() {
get(this, 'model').save().then( () => {
set(this, 'model', this.store.createRecord('password'));
set(this, 'resetPassMessage', get(this, 'successfulResetMsg'));
}, () => {
set(this, 'resetPassMessage', get(this, 'errorResetMsg'));
});
}
}
});

View File

@@ -0,0 +1,19 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['save-status'],
saveFailed: Ember.computed('record.errors.[]', function() {
let errors = this.get('record.errors');
if (Ember.isPresent(errors)) {
return errors.has('saveFailure');
} else {
return false;
}
}),
saveFailureMessage: Ember.computed('record.errors.[]', function() {
let errors = this.get('record.errors');
return errors.errorsFor('saveFailure').mapBy('message').join(', ');
})
});

View File

@@ -0,0 +1,29 @@
import Ember from 'ember';
const {
$,
get,
Component,
run: { schedule }
} = Ember;
export default Component.extend({
didRender() {
this._super(...arguments);
schedule('afterRender', this, this.scrollToAnchor);
},
scrollToAnchor() {
const anchor = get(this, 'anchor');
if(anchor) {
const $anchorId = $(`#${anchor}`);
const $anchorClass = $(`.${anchor}`);
if($anchorId.length > 0) { $('body').animate({ scrollTop: $anchorId.offset().top }, 'slow'); }
if($anchorClass.length > 0) { $('body').animate({ scrollTop: $anchorClass.offset().top }, 'slow'); }
}
},
});

View File

@@ -0,0 +1,90 @@
import Ember from 'ember';
export default Ember.Component.extend({
items: [],
selection: null,
resource: null,
defaultQueryParams: null,
placeholder: 'select item',
async: true,
create: false,
openOnFocus: true,
multiple: false,
classNames: ['select-field'],
createItemAction: Ember.computed('create', function() {
return this.get('create') ? 'createItem' : false;
}),
findAll() {
this.store.findAll(this.get('resource')).then( (items) => {
this.refreshItems(items);
});
},
findByQuery(params = { }, autoAddSelection=false) {
this.store.queryRecord('search', this.queryParams(params)).then( (items) => {
this.refreshItems(items.get('searchables'), autoAddSelection);
});
},
refreshItems(items, autoAddSelection=false) {
this.set('items', items);
let selectedItem = this.get('selection');
if (autoAddSelection && Ember.isPresent(selectedItem)) {
let foundItem = items.toArray().findBy('name', selectedItem.get('name'));
if (!Ember.isPresent(foundItem)) {
items.addObject(selectedItem);
}
}
},
queryParams(params) {
var defaults = this.get('defaultQueryParams');
if (Ember.isPresent(defaults)) {
if (Ember.isNone(params['query'])) {
params['query'] = {};
}
for (var key in defaults) { params['query'][key] = defaults[key]; }
}
return params;
},
randomItemsOnInit: false,
actions: {
fetchItems() {
if (this.get('randomItemsOnInit')) {
this.findByQuery({
resource: this.get('resource'),
scope: 'random',
}, true);
} else {
if(this.get('async')) {
this.findByQuery({ resource: this.get('resource') }, true);
} else if (Ember.isEmpty(this.get('items'))) {
this.findAll();
}
}
},
selectItem(item) {
Ember.tryInvoke(this, 'onSelect', [item]);
},
createItem(value) {
var item = this.store.createRecord(this.get('resource'), { name: value });
this.set('selection', item);
Ember.tryInvoke(this, 'onSelect', [item]);
},
updateFilter(value) {
if( this.get('async') && value.length >= 1 ) {
this.findByQuery({ resource: this.get('resource'), query: { name: value } });
}
}
}
});

View File

@@ -0,0 +1,17 @@
import Ember from 'ember';
const {
computed,
Component,
} = Ember;
export default Component.extend({
classNames: ['shareSocials'],
divideTitles: false,
baseUrl: computed(function() {
let location = window.location;
return location.protocol + "//" + location.host;
}),
});

View File

@@ -0,0 +1,62 @@
import Ember from 'ember';
import config from 'flaredown/config/environment';
import CryptoJS from 'cryptojs';
const {
get,
set,
computed,
inject: {
service,
},
setProperties,
Component,
} = Ember;
export default Component.extend({
i18n: service(),
classNames: ['flaredown-white-box', 'sharedPatternsDialog'],
secretPhrase: config.encryptionSecret,
staticUrl: config.staticUrl,
loadingPatterns: false,
page: 2,
checkedPatterns: computed('patterns.@each.checked', function() {
return get(this, 'patterns').filter((pattern) => pattern.checked);
}),
nothingChecked: computed('checkedPatterns.[]', function() {
return get(this, 'checkedPatterns.length') === 0;
}),
encryptedUrl: computed('checkedPatterns.[]', function() {
const checkedPatternIds = get(this, 'checkedPatterns.[]').map((pattern) => pattern.id);
const encryptedParams = CryptoJS.AES.encrypt(checkedPatternIds.join(', '), get(this, 'secretPhrase'));
return `${get(this, 'staticUrl')}/patterns/${encodeURIComponent(encryptedParams)}`;
}),
actions: {
sharedPatterns() {
const encryptedUrl = encodeURIComponent(get(this, 'encryptedUrl'));
const currentUser = get(this, 'session.currentUser');
const screenName = currentUser ? get(currentUser, 'profile.screenName') : "";
const subject = `${screenName} would like to share their health patterns with you`;
window.location.href = `mailto:?subject=${subject}&body=${encryptedUrl}`;
},
requestData() {
let page = get(this, 'page');
set(this, 'loadingPatterns', true);
get(this, 'store').query('pattern', { page: page }).then((record) => {
setProperties(this, { loadingPatterns: false, page: page + 1 });
get(this, 'patterns').toArray().pushObject(record);
})
},
},
});

View File

@@ -0,0 +1,70 @@
import Ember from 'ember';
import moment from 'moment';
const {
get,
set,
Component,
getProperties,
computed
} = Ember;
export default Component.extend({
classNames: ['signup-form'],
isShowError: computed('model.birthDate.{year}', function() {
return moment().year() - get(this, 'model.birthDate.year') < 16 || get(this, 'model.birthDate.year') === null;
}),
className: computed('isDisabled', function() {
return get(this, 'isDisabled') ? 'btn btn-default left disabled' : 'btn btn-default left';
}),
isDisabled: computed('model.birthDate.{year,month,day}', 'model.acceptPrivacyTerms', function() {
const year = get(this, 'model.birthDate.year');
const month = get(this, 'model.birthDate.month');
const day = get(this, 'model.birthDate.day');
return get(this, 'isError') && (get(this, 'isShowError') || !year || !month || !day) || !get(this, 'model.acceptPrivacyTerms');
}),
isBlankError: computed('model.birthDate.{year,month,day}', function() {
const year = get(this, 'model.birthDate.year');
const month = get(this, 'model.birthDate.month');
const day = get(this, 'model.birthDate.day');
return year === null || month === null || day === null;
}),
actions: {
onCaptchaResolved(reCaptchaResponse) {
set(this, 'model.captchaResponse', reCaptchaResponse);
},
save() {
const model = get(this, 'model');
const year = get(this, 'model.birthDate.year');
const month = get(this, 'model.birthDate.month');
const day = get(this, 'model.birthDate.day');
!year && set(this, 'model.birthDate.year', null);
!month && set(this, 'model.birthDate.month', null);
!day && set(this, 'model.birthDate.day', null);
!get(this, 'model.acceptPrivacyTerms') && set(this, 'model.acceptPrivacyTerms', false);
if (get(this, 'isDisabled')) {
return;
}
const { email, password } = getProperties(model, 'email', 'password');
set(model, 'passwordConfirmation', password);
model
.save()
.then(() => get(this, 'session').authenticate('authenticator:devise', email, password))
.catch(() => get(this, 'gRecaptcha').resetReCaptcha());
},
},
});

View File

@@ -0,0 +1,41 @@
import Ember from 'ember';
const {
get,
inject: {
service,
},
Component,
computed: { bool },
} = Ember;
export default Component.extend({
classNames: ['step-controls'],
showBack: true,
linkForward: false,
backLabel: 'Back',
forwardLabel: 'Continue',
_routing: service('-routing'),
stepHasPrev: bool('step.prevId'),
actions: {
forward() {
if (!this.get('disabled')) {
this.get('onForward')();
}
},
backward() {
if (!this.get('disabled')) {
this.get('onBackward')();
}
},
discussion() {
get(this, '_routing').transitionTo('posts');
},
},
});

View File

@@ -0,0 +1,35 @@
import Ember from 'ember';
const {
Component,
computed,
get,
computed: {
alias,
},
inject: {
service,
},
} = Ember;
export default Component.extend({
logoVisiability: service(),
i18n: service(),
showHeaderLogo: alias('logoVisiability.showHeaderLogo'),
showHeaderPath: alias('logoVisiability.showHeaderPAth'),
currentPathTitle: computed('currentPath', 'i18n.locale', function() {
const currentPath = get(this, 'currentPath');
const i18n = get(this, 'i18n');
switch(currentPath) {
case 'password.update':
return i18n.t('password.update.header').toString();
case 'password.reset':
return i18n.t('password.reset.header').toString();
default:
return currentPath;
}
}),
});

View File

@@ -0,0 +1,43 @@
import Ember from 'ember';
import SearchableDropdown from 'flaredown/mixins/searchable-dropdown';
const {
get,
Component,
computed,
} = Ember;
export default Component.extend(SearchableDropdown, {
placeholder: computed('trackableType', function() {
return `Add a ${get(this, 'trackableType')}`;
}),
randomTrackables: computed('trackableType', function() {
return this.randomSearch(get(this, 'trackableType'));
}),
actions: {
createTrackable(name) {
const type = get(this, 'trackableType');
this.createAndSave(type, name).then((trackable) => {
this.sendAction('onSelect', trackable);
});
},
handleChange(trackable) {
this.sendAction('onSelect', trackable);
},
handleBuildSuggestion(typedText) {
// TODO Return rendered component: power-select/dropdown-trackable-create
return `Add ${this.get('trackableType')}: "${typedText}"`;
},
},
performSearch(term, resolve, reject) {
this
.searchByTerm(this.get('trackableType'), term)
.then(resolve, reject);
},
});

Some files were not shown because too many files have changed in this diff Show More