added 260907 version of worker toolkit
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import ActiveModelAdapter from 'active-model-adapter';
|
||||
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';
|
||||
import ENV from 'flaredown/config/environment';
|
||||
|
||||
export default ActiveModelAdapter.extend(DataAdapterMixin, {
|
||||
host: ENV.apiHost,
|
||||
namespace: 'api',
|
||||
coalesceFindRequests: true,
|
||||
// defaults
|
||||
// identificationAttributeName: 'email'
|
||||
// tokenAttributeName: 'token'
|
||||
authorize(xhr) {
|
||||
let { email, token } = this.get('session.data.authenticated');
|
||||
let authData = `Token token="${token}", email="${email}"`;
|
||||
xhr.setRequestHeader('Authorization', authData);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import ApplicationAdapter from 'flaredown/adapters/application';
|
||||
|
||||
export default ApplicationAdapter.extend({
|
||||
ajaxOptions() {
|
||||
let hash = this._super(...arguments);
|
||||
|
||||
if (localStorage.oracleToken) {
|
||||
let { beforeSend } = hash;
|
||||
|
||||
hash.beforeSend = (xhr) => {
|
||||
xhr.setRequestHeader('X-Oracle-Token', localStorage.oracleToken);
|
||||
|
||||
if (beforeSend) {
|
||||
beforeSend(xhr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return hash;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import Ember from 'ember';
|
||||
import ApplicationAdapter from 'flaredown/adapters/application';
|
||||
|
||||
const {
|
||||
getProperties,
|
||||
$,
|
||||
} = Ember;
|
||||
|
||||
export default ApplicationAdapter.extend({
|
||||
urlForDeleteRecord(id, modelName, snapshot) {
|
||||
if (typeof FastBoot === 'undefined') {
|
||||
const params = $.param({ reaction: getProperties(snapshot.serialize(), 'value', 'reactable_id', 'reactable_type') });
|
||||
|
||||
return `${this._buildURL(modelName, id)}?${params}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
14
worker-toolkit-flaredown/repo/frontend/app/app.js
Normal file
14
worker-toolkit-flaredown/repo/frontend/app/app.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import Application from '@ember/application';
|
||||
import Resolver from './resolver';
|
||||
import loadInitializers from 'ember-load-initializers';
|
||||
import config from './config/environment';
|
||||
|
||||
const App = Application.extend({
|
||||
modulePrefix: config.modulePrefix,
|
||||
podModulePrefix: config.podModulePrefix,
|
||||
Resolver
|
||||
});
|
||||
|
||||
loadInitializers(App, config.modulePrefix);
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,7 @@
|
||||
import Devise from 'ember-simple-auth/authenticators/devise';
|
||||
import ENV from 'flaredown/config/environment';
|
||||
|
||||
|
||||
export default Devise.extend({
|
||||
serverTokenEndpoint: `${ENV.apiHost}/api/sessions`,
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import Ember from 'ember';
|
||||
import ToriiAuthenticator from 'ember-simple-auth/authenticators/torii';
|
||||
|
||||
export default ToriiAuthenticator.extend({
|
||||
ajax: Ember.inject.service(),
|
||||
torii: Ember.inject.service(),
|
||||
|
||||
restore: function(data) {
|
||||
var resolveData = data || {};
|
||||
this._provider = resolveData.provider;
|
||||
return new Ember.RSVP.Promise(function(resolve) { resolve(resolveData); });
|
||||
},
|
||||
|
||||
authenticate() {
|
||||
if (typeof FastBoot !== 'undefined') { return; }
|
||||
|
||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||
this._super('facebook-connect').then( (data) => {
|
||||
this.get('ajax').request('/auth/facebook/callback', {
|
||||
method: 'POST',
|
||||
dataType: 'json'
|
||||
}).then((response) => {
|
||||
resolve({
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
user_id: response.user_id,
|
||||
// jscs:enable requireCamelCaseOrUpperCaseIdentifiers
|
||||
email: response.email,
|
||||
token: response.token,
|
||||
provider: data.provider
|
||||
});
|
||||
}, reject);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
import Devise from 'ember-simple-auth/authorizers/devise';
|
||||
|
||||
export default Devise.extend();
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNames: ['flex-container flaredown-additional-info'],
|
||||
tagName: ['div'],
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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();
|
||||
},
|
||||
});
|
||||
@@ -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' : ''}`;
|
||||
}),
|
||||
});
|
||||
@@ -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));
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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');
|
||||
}),
|
||||
});
|
||||
@@ -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');
|
||||
}),
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
}),
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
}),
|
||||
});
|
||||
@@ -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") );
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
@@ -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');
|
||||
}),
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
}),
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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'),
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -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"));
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -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')();
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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')();
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -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 });
|
||||
}),
|
||||
});
|
||||
@@ -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')();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -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();
|
||||
},
|
||||
});
|
||||
@@ -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),
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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'));
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
@@ -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(', ');
|
||||
})
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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 });
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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') );
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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');
|
||||
},
|
||||
});
|
||||
@@ -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)
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -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());
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'label',
|
||||
classNames: ['label-switch']
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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'),
|
||||
});
|
||||
@@ -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',
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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'),
|
||||
});
|
||||
@@ -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')();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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')();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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')();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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')();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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')();
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -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')();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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')();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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();
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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"),
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
@@ -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}, </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 || '​'}` : '';
|
||||
} 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}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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')}`;
|
||||
}),
|
||||
});
|
||||
@@ -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');
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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');
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -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"),
|
||||
});
|
||||
@@ -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');
|
||||
}),
|
||||
});
|
||||
@@ -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'));
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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'),
|
||||
});
|
||||
@@ -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', {});
|
||||
});
|
||||
}
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
const {
|
||||
Component,
|
||||
} = Ember;
|
||||
|
||||
export default Component.extend({
|
||||
});
|
||||
@@ -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'),
|
||||
});
|
||||
@@ -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', {}));
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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'));
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
|
||||
classNames: ['power-select-dropdown-name-desc']
|
||||
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
|
||||
classNames: ['power-select-dropdown-trackable', 'create']
|
||||
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
|
||||
classNames: ['power-select-dropdown-trackable', 'option']
|
||||
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
|
||||
tagName: 'span',
|
||||
classNames: ['ember-power-select-selected-item']
|
||||
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -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'));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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(', ');
|
||||
})
|
||||
});
|
||||
@@ -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'); }
|
||||
}
|
||||
},
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user