diff --git a/src/app.common/actions/DisplaySettingsActions.ts b/src/app.common/actions/DisplaySettingsActions.ts index af00ccf..09f6759 100644 --- a/src/app.common/actions/DisplaySettingsActions.ts +++ b/src/app.common/actions/DisplaySettingsActions.ts @@ -43,4 +43,11 @@ export function change24HoursTimeFormatSetting(value: boolean): Action type: "DISPLAY_SETTINGS/TOGGLE_24_HOURS", payload: value }; +} + +export function changeTimeSelectionStepSetting(value: number): Action { + return { + type: "DISPLAY_SETTINGS/CHANGE_TIME_SELECTION_STEP", + payload: value + }; } \ No newline at end of file diff --git a/src/app.common/actions/ScrollPositionActions.ts b/src/app.common/actions/ScrollPositionActions.ts new file mode 100644 index 0000000..e13bf9d --- /dev/null +++ b/src/app.common/actions/ScrollPositionActions.ts @@ -0,0 +1,18 @@ +import { ActionCreator } from "react-redux"; +import { Action } from "./Action"; +import { DisplaySettingsInfo, DSTSetting } from "../models"; +import * as moment from "moment-timezone"; + +export function changeScrollPostion(position: number): Action { + return { + type: "SCROLL_POSITION/SET", + payload: position + }; +} + +export function resetScrollPostion(): Action { + return { + type: "SCROLL_POSITION/RESET", + payload: 0 + }; +} diff --git a/src/app.common/actions/SelectedTimeSpanActions.ts b/src/app.common/actions/SelectedTimeSpanActions.ts index af71dfa..439b504 100644 --- a/src/app.common/actions/SelectedTimeSpanActions.ts +++ b/src/app.common/actions/SelectedTimeSpanActions.ts @@ -45,11 +45,7 @@ export function changeEndMinute(minute: number) { }; } -export function changeSelectedTimeSpan(start: number, end: number): Action { - const startHour = Math.floor(start / 2); - const startMinute = (start % 2) * 30; - const endHour = Math.floor(end / 2); - const endMinute = (end % 2) * 30; +export function changeSelectedTimeSpan(startHour: number, startMinute: number, endHour: number, endMinute: number): Action { return { type: "SELECTED_TIMESPAN/CHANGE_SELECTED_TIMESPAN", payload: { startHour, startMinute, endHour, endMinute } diff --git a/src/app.common/actions/index.ts b/src/app.common/actions/index.ts index a0ba6e8..8efb129 100644 --- a/src/app.common/actions/index.ts +++ b/src/app.common/actions/index.ts @@ -3,4 +3,5 @@ export * from "./TimeLineActions"; export * from "./TimeLineFormActions"; export * from "./DisplaySettingsActions"; export * from "./ThemeSettingsActions"; -export * from "./SelectedTimeSpanActions"; \ No newline at end of file +export * from "./SelectedTimeSpanActions"; +export * from "./ScrollPositionActions"; \ No newline at end of file diff --git a/src/app.common/components/Range.tsx b/src/app.common/components/Range.tsx index 5247f43..8ab574b 100644 --- a/src/app.common/components/Range.tsx +++ b/src/app.common/components/Range.tsx @@ -23,13 +23,28 @@ export class Range extends React.Component { constructor(props: IRangeProps) { super(props); + const { rangeSize, valueMin, valueMax } = props; this.state = { - valueMin: props.rangeSize ? props.valueMin / props.rangeSize * 100 : props.valueMin, - valueMax: props.rangeSize ? props.valueMax / props.rangeSize * 100 : props.valueMax, + valueMin: rangeSize ? valueMin / rangeSize * 100 : valueMin, + valueMax: rangeSize ? valueMax / rangeSize * 100 : valueMax, hold: false }; } + componentWillReceiveProps(props: IRangeProps) { + if (props) { + this.updateState(props); + } + } + + updateState(props: IRangeProps) { + const { valueMax, valueMin, rangeSize } = props; + this.setState({ + valueMin: rangeSize ? valueMin / rangeSize * 100 : valueMin, + valueMax: rangeSize ? valueMax / rangeSize * 100 : valueMax, + }); + } + onChange(valueMin: number, valueMax: number) { if (this.props.onChange) { this.props.onChange(this.convertValues(valueMin, valueMax)); @@ -65,7 +80,15 @@ export class Range extends React.Component { event.preventDefault(); const { valueMin, valueMax } = this.state; const x = this.getCoordinateInRange(event); - const hold = Math.abs(x - valueMin) <= Math.abs(x - valueMax) ? "min" : "max"; + const distToMin = Math.abs(x - valueMin); + const distToMax = Math.abs(x - valueMax); + const hold = distToMin == distToMax + ? x > valueMax + ? "max" + : "min" + : distToMin < distToMax + ? "min" + : "max"; this.onHold(hold); this.movePointer(x, hold); } @@ -108,7 +131,6 @@ export class Range extends React.Component { } getRangeRect() { - // tslint:disable-next-line:no-string-literal return ReactDOM.findDOMNode(this.refs["rangeBase"]).getBoundingClientRect(); } @@ -122,9 +144,9 @@ export class Range extends React.Component {
this.onRangeBaseClick(event)}>
-
this.onHold("min")}>
-
-
this.onHold("max")}>
+
this.onHold("min")}>
+
+
this.onHold("max")}>
diff --git a/src/app.common/components/TimeLine.tsx b/src/app.common/components/TimeLine.tsx index 3b9c038..312d759 100644 --- a/src/app.common/components/TimeLine.tsx +++ b/src/app.common/components/TimeLine.tsx @@ -5,7 +5,7 @@ import Typography from "material-ui/Typography"; import { withTheme, Theme } from "material-ui/styles"; const style = require("./TimeLine.css"); -import { TimeZoneInfo, getOffset, DisplaySettingsInfo } from "../models"; +import { TimeZoneInfo, getOffset, DisplaySettingsInfo, ScrollPosition } from "../models"; interface TimeLineProps { timeLine: TimeZoneInfo; @@ -13,6 +13,7 @@ interface TimeLineProps { hours: number[]; displaySettings: DisplaySettingsInfo; theme: Theme; + scrollPosition: number; } interface TimeLineState { @@ -54,15 +55,12 @@ class TimeLineImpl extends React.Component { // classes.push(style.timeLineBorderRight); } if (h < 8 || h > 21) { - // classes.push(style.nightHour); background = theme.palette.primary[100]; } if (h === 0) { - // classes.push(style.hourMidnight); background = theme.palette.primary[200]; } if (h === currentHour) { - // classes.push(style.currentHour); background = theme.palette.primary[700]; } const color = theme.palette.getContrastText(background); @@ -93,8 +91,10 @@ class TimeLineImpl extends React.Component { const currentHour = +moment().utcOffset(offset).format("HH"); const uiOffset = (offset % 60) / 60; const oneDay = 100 / 3; + const oneHour = oneDay / 24; + const position = this.props.scrollPosition; const inlineStyle = { - transform: `translateX(${-oneDay - oneDay / 24 * uiOffset}%)` + transform: `translateX(${-oneDay + oneHour * position - oneHour * uiOffset}%)` }; return (
diff --git a/src/app.common/components/TimeSelector.tsx b/src/app.common/components/TimeSelector.tsx index c188a80..225f61e 100644 --- a/src/app.common/components/TimeSelector.tsx +++ b/src/app.common/components/TimeSelector.tsx @@ -5,15 +5,17 @@ const style = require("./TimeSelector.css"); import { TimeZoneInfo, TimeSpanInfo } from "../models"; interface TimeSelectorProps { - selectedTimeSpan: TimeSpanInfo; + valueMin: number; + valueMax: number; + rangeSize: number; color: string; } export class TimeSelector extends React.Component { render() { - const { selectedTimeSpan, color } = this.props; - const left = (selectedTimeSpan.startHour * 2 + selectedTimeSpan.startMinute / 30) / 48 * 100; - const right = (selectedTimeSpan.endHour * 2 + selectedTimeSpan.endMinute / 30) / 48 * 100; + const { valueMin, valueMax, rangeSize, color } = this.props; + const left = valueMin / rangeSize * 100; + const right = valueMax / rangeSize * 100; const visibilityLeft = left <= 0.001 ? "hidden" : "visible"; const visibilityRight = right >= 99.999 ? "hidden" : "visible"; return ( diff --git a/src/app.common/localstorage-enchancer.ts b/src/app.common/localstorage-enchancer.ts new file mode 100644 index 0000000..1583962 --- /dev/null +++ b/src/app.common/localstorage-enchancer.ts @@ -0,0 +1,27 @@ +import { compose } from "redux"; +import * as persistState from "redux-localstorage"; +import * as _ from "lodash"; +import { IAppState } from "./reducers"; + +function merge(initialState: IAppState, persistedState: IAppState) { + const mergedState = _.cloneDeep(initialState); + if (!persistedState) { + return initialState; + } + if (persistedState.timeLines) { + mergedState.timeLines = persistedState.timeLines; + } + if (persistedState.displaySettings) { + mergedState.displaySettings = _.merge(mergedState.displaySettings, persistedState.displaySettings); + } + if (persistedState.theme) { + mergedState.theme = _.merge(mergedState.theme, persistedState.theme); + } + return mergedState; +} + +export const localStorageEnchancer = [ + persistState("timeLines", { key: "timeLines@0.0.259", merge }), + persistState("displaySettings", { key: "displaySettings@0.1.265", merge }), + persistState("theme", { key: "theme@1.2.45", merge }), +]; \ No newline at end of file diff --git a/src/app.common/models/DisplaySettingsInfo.ts b/src/app.common/models/DisplaySettingsInfo.ts index 3920e74..bfa592a 100644 --- a/src/app.common/models/DisplaySettingsInfo.ts +++ b/src/app.common/models/DisplaySettingsInfo.ts @@ -5,6 +5,7 @@ export interface DisplaySettingsInfo { showControlPanel: boolean; useDarkTheme: boolean; use24HoursTime: boolean; + selectionStep: number; } export type DSTSetting = "hide" | "DST" | "Summer/Winter"; \ No newline at end of file diff --git a/src/app.common/models/ScrollPosition.ts b/src/app.common/models/ScrollPosition.ts new file mode 100644 index 0000000..19698bf --- /dev/null +++ b/src/app.common/models/ScrollPosition.ts @@ -0,0 +1,6 @@ +export interface ScrollPosition { + minLimit: number; + maxLimit: number; + position: number; + step: number; +} \ No newline at end of file diff --git a/src/app.common/models/index.ts b/src/app.common/models/index.ts index 797136e..51c8003 100644 --- a/src/app.common/models/index.ts +++ b/src/app.common/models/index.ts @@ -3,4 +3,5 @@ export * from "./TimeZoneInfo"; export * from "./TimeZoneShort"; export * from "./DisplaySettingsInfo"; export * from "./CalendarEvent"; -export * from "./AppTheme"; \ No newline at end of file +export * from "./AppTheme"; +export * from "./ScrollPosition"; \ No newline at end of file diff --git a/src/app.common/reducers/DisplaySettingsReducer.ts b/src/app.common/reducers/DisplaySettingsReducer.ts index 904768c..86c32b6 100644 --- a/src/app.common/reducers/DisplaySettingsReducer.ts +++ b/src/app.common/reducers/DisplaySettingsReducer.ts @@ -2,9 +2,21 @@ import { updateState } from "./UpdateStateHelper"; import { DisplaySettingsInfo } from "../models"; import { Action } from "../actions"; -export const displaySettings = function (state: DisplaySettingsInfo = {} as DisplaySettingsInfo, action: Action): DisplaySettingsInfo { - switch(action.type) { - case "DISPLAY_SETTINGS/SHOW_DST":{ +export type State = DisplaySettingsInfo; + +export const initialState: State = { + showDST: "hide", + showTimeZoneId: false, + showUTCOffset: true, + showControlPanel: true, + useDarkTheme: false, + use24HoursTime: true, + selectionStep: 30, +} + +export const reducer = function (state: State = initialState, action: Action): State { + switch (action.type) { + case "DISPLAY_SETTINGS/SHOW_DST": { return updateState(state, { showDST: action.payload }); } case "DISPLAY_SETTINGS/SHOW_UTC_OFFSET": { @@ -22,6 +34,9 @@ export const displaySettings = function (state: DisplaySettingsInfo = {} as Disp case "DISPLAY_SETTINGS/TOGGLE_24_HOURS": { return updateState(state, { use24HoursTime: action.payload }); } + case "DISPLAY_SETTINGS/CHANGE_TIME_SELECTION_STEP": { + return updateState(state, { selectionStep: action.payload }); + } default: return state; } diff --git a/src/app.common/reducers/EditTimeLineFormReducer.ts b/src/app.common/reducers/EditTimeLineFormReducer.ts index 4bf82bb..2546ae4 100644 --- a/src/app.common/reducers/EditTimeLineFormReducer.ts +++ b/src/app.common/reducers/EditTimeLineFormReducer.ts @@ -2,7 +2,14 @@ import { updateState } from "./UpdateStateHelper"; import { TimeZoneInfo } from "../models"; import { Action } from "../actions"; -export const editTimeLineForm = function (state: TimeZoneInfo = {} as TimeZoneInfo, action: Action): TimeZoneInfo { +export type State = TimeZoneInfo; + +export const initialState: State = { + name: "", + timeZoneId: "" +} as State; + +export const reducer = function (state: State = initialState, action: Action): State { switch(action.type) { case "EDIT_TIMELINE/CHANGE_DISPLAY_NAME": return updateState(state, { name: action.payload }) diff --git a/src/app.common/reducers/ScrollPositionReducer.ts b/src/app.common/reducers/ScrollPositionReducer.ts new file mode 100644 index 0000000..3b924a5 --- /dev/null +++ b/src/app.common/reducers/ScrollPositionReducer.ts @@ -0,0 +1,28 @@ +import { Action } from "../actions"; +import { AppTheme, ScrollPosition } from "../models"; +import { getColorById } from "../themes/themes"; +import { updateState } from "./UpdateStateHelper"; + +export type State = ScrollPosition; + +export const initialState: State = { + maxLimit: 23, + minLimit: -23, + position: 0, + step: 6, +}; + +export const reducer = function (state: State = initialState, action: Action): State { + switch (action.type) { + case "SCROLL_POSITION/RESET": + return updateState(state, { position: 0 }); + case "SCROLL_POSITION/SET": + if (action.payload > state.maxLimit || action.payload < state.minLimit) { + return state; + } else { + return updateState(state, { position: action.payload }); + } + default: + return state; + } +}; \ No newline at end of file diff --git a/src/app.common/reducers/SelectedTimeSpanReducer.ts b/src/app.common/reducers/SelectedTimeSpanReducer.ts index 9b6487c..cfab175 100644 --- a/src/app.common/reducers/SelectedTimeSpanReducer.ts +++ b/src/app.common/reducers/SelectedTimeSpanReducer.ts @@ -1,8 +1,18 @@ import { updateState } from "./UpdateStateHelper"; import { TimeSpanInfo } from "../models"; import { Action } from "../actions"; +import * as moment from "moment"; -export const selectedTimeSpan = function (state: TimeSpanInfo = {} as TimeSpanInfo, action: Action): TimeSpanInfo { +export type State = TimeSpanInfo; + +export const initialState: State = { + startHour: moment().hours(), + startMinute: moment().minutes(), + endHour: 24, + endMinute: 0 +}; + +export const reducer = function (state: State = initialState, action: Action): State { switch(action.type) { case "SELECTED_TIMESPAN/CHANGE_SELECTED_TIMESPAN": { return updateState(state, action.payload) diff --git a/src/app.common/reducers/ThemeReducer.ts b/src/app.common/reducers/ThemeReducer.ts index 101a71c..9e981cc 100644 --- a/src/app.common/reducers/ThemeReducer.ts +++ b/src/app.common/reducers/ThemeReducer.ts @@ -1,8 +1,14 @@ import { Action } from "../actions"; import { AppTheme } from "../models"; -import { getColorById } from "../themes/themes"; +import { getColorById, initialPalette } from "../themes/themes"; -export const theme = function (state: AppTheme = {} as AppTheme, action: Action): AppTheme { +export type State = AppTheme; + +export const initialState: State = { + palette: initialPalette, +}; + +export const reducer = function (state: State = initialState, action: Action): State { switch (action.type) { case "THEME/SET_PRIMARY_COLOR": const newState = Object.assign({}, state); diff --git a/src/app.common/reducers/TimeLineReducer.ts b/src/app.common/reducers/TimeLineReducer.ts index e6a0e19..e283881 100644 --- a/src/app.common/reducers/TimeLineReducer.ts +++ b/src/app.common/reducers/TimeLineReducer.ts @@ -1,8 +1,19 @@ -import { TimeZoneInfo } from "../models"; +import { TimeZoneInfo, createTimeZoneInfo } from "../models"; import { Action } from "../actions"; -export const timeLines = function (state: TimeZoneInfo[] = [], action: Action): TimeZoneInfo[] { - switch(action.type) { +export type State = TimeZoneInfo[]; + +export const initialState: State = [ + createTimeZoneInfo("Europe/Warsaw", "Kraków"), + createTimeZoneInfo("US/Pacific", "San Francisco"), + createTimeZoneInfo("Europe/Moscow", "Saint Petersburg"), + createTimeZoneInfo("Australia/Melbourne", "Melbourne"), + createTimeZoneInfo("Asia/Calcutta", "India") + // createTimeZoneInfo("Asia/Yekaterinburg", "Yekaterinburg") +]; + +export const reducer = function (state: State = initialState, action: Action): State { + switch (action.type) { case "REPLACE_TIMELINES": return action.payload; case "CREATE_OR_UPDATE": { @@ -13,7 +24,7 @@ export const timeLines = function (state: TimeZoneInfo[] = [], action: Action x.timeLineid === action.payload.timeLineid); const timeLines = i > -1 - ? state.slice(0, i).concat([ action.payload ]).concat(state.slice(i + 1)) + ? state.slice(0, i).concat([action.payload]).concat(state.slice(i + 1)) : state.concat([action.payload]); return timeLines; } diff --git a/src/app.common/reducers/index.ts b/src/app.common/reducers/index.ts index f02d30e..af10586 100644 --- a/src/app.common/reducers/index.ts +++ b/src/app.common/reducers/index.ts @@ -1,5 +1,46 @@ -export * from "./TimeLineReducer"; -export * from "./EditTimeLineFormReducer"; -export * from "./DisplaySettingsReducer"; -export * from "./ThemeReducer"; -export * from "./SelectedTimeSpanReducer"; \ No newline at end of file +import { combineReducers, Reducer, ReducersMapObject } from "redux"; + +import * as displaySettings from "./DisplaySettingsReducer"; +import * as editTimeLineForm from "./EditTimeLineFormReducer"; +import * as scrollPosition from "./ScrollPositionReducer"; +import * as selectedTimeSpan from "./SelectedTimeSpanReducer"; +import * as theme from "./ThemeReducer"; +import * as timeLines from "./TimeLineReducer"; + +export interface IAppStoreDispatcher extends ReducersMapObject { + timeLines: Reducer; + editTimeLineForm: Reducer; + displaySettings: Reducer; + selectedTimeSpan: Reducer; + theme: Reducer; + scrollPosition: Reducer; +} + +export interface IAppState { + timeLines: timeLines.State; + editTimeLineForm: editTimeLineForm.State; + displaySettings: displaySettings.State; + selectedTimeSpan: selectedTimeSpan.State; + theme: theme.State; + scrollPosition: scrollPosition.State; +} + +const reducers: IAppStoreDispatcher = { + timeLines: timeLines.reducer, + editTimeLineForm: editTimeLineForm.reducer, + displaySettings: displaySettings.reducer, + selectedTimeSpan: selectedTimeSpan.reducer, + theme: theme.reducer, + scrollPosition: scrollPosition.reducer, +}; + +export const initialState: IAppState = { + timeLines: timeLines.initialState, + editTimeLineForm: editTimeLineForm.initialState, + displaySettings: displaySettings.initialState, + selectedTimeSpan: selectedTimeSpan.initialState, + theme: theme.initialState, + scrollPosition: scrollPosition.initialState, +}; + +export const rootReducer = combineReducers(reducers); \ No newline at end of file diff --git a/src/app.common/store.ts b/src/app.common/store.ts index e02b9db..32d7c8c 100644 --- a/src/app.common/store.ts +++ b/src/app.common/store.ts @@ -1,72 +1,23 @@ -import { createStore, applyMiddleware, combineReducers, compose, Store, Reducer, ReducersMapObject, StoreEnhancer } from "redux"; -import { createLogger } from "redux-logger"; -import * as persistState from "redux-localstorage"; import * as moment from "moment"; +import { applyMiddleware, compose, createStore, Store, StoreEnhancer } from "redux"; +import { createLogger } from "redux-logger"; -import { timeLines, editTimeLineForm, displaySettings, selectedTimeSpan, theme } from "./reducers"; -import { TimeZoneInfo, createTimeZoneInfo, DisplaySettingsInfo, TimeSpanInfo, AppTheme } from "../app.common/models"; +import { + AppTheme, + createTimeZoneInfo, + DisplaySettingsInfo, + ScrollPosition, + TimeSpanInfo, + TimeZoneInfo, +} from "../app.common/models"; +import { localStorageEnchancer } from "./localstorage-enchancer"; +import { rootReducer, initialState, IAppState } from "./reducers"; import { initialPalette } from "./themes/themes"; - -export interface IAppState { - timeLines: TimeZoneInfo[]; - editTimeLineForm: TimeZoneInfo; - displaySettings: DisplaySettingsInfo; - selectedTimeSpan: TimeSpanInfo; - theme: AppTheme; -} - -export interface IAppStoreDispatcher extends ReducersMapObject { - timeLines: Reducer; - editTimeLineForm: Reducer; - displaySettings: Reducer; - selectedTimeSpan: Reducer; -} - -const initialState: IAppState = { - timeLines: [ - createTimeZoneInfo("Europe/Warsaw", "Kraków"), - createTimeZoneInfo("US/Pacific", "San Francisco"), - createTimeZoneInfo("Europe/Moscow", "Saint Petersburg"), - createTimeZoneInfo("Australia/Melbourne", "Melbourne"), - createTimeZoneInfo("Asia/Calcutta", "India") - // createTimeZoneInfo("Asia/Yekaterinburg", "Yekaterinburg") - ], - editTimeLineForm: { name: "", timeZoneId: "" } as TimeZoneInfo, - displaySettings: { - showDST: "hide", - showTimeZoneId: false, - showUTCOffset: true, - showControlPanel: true, - useDarkTheme: false, - use24HoursTime: true, - }, - selectedTimeSpan: { - startHour: moment().hours(), - startMinute: moment().minutes(), - endHour: 24, - endMinute: 0 - }, - theme: { - palette: initialPalette, - } -} as IAppState; - -const reducers: IAppStoreDispatcher = { - timeLines, - editTimeLineForm, - displaySettings, - selectedTimeSpan, - theme, -}; +export { IAppState, IAppStoreDispatcher } from "./reducers"; let enchancer: StoreEnhancer; -const localStorageState = [ - persistState("timeLines", { key: "timeLines@0.0.259" }), - persistState("displaySettings", { key: "displaySettings@0.1.265" }), - persistState("theme", { key: "theme@1.2.45" }), -]; if (process.env.NODE_ENV === "development") { const devEnchansers = [ applyMiddleware((createLogger)()) @@ -74,16 +25,16 @@ if (process.env.NODE_ENV === "development") { const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; enchancer = composeEnhancers( ...devEnchansers, - ...localStorageState + ...localStorageEnchancer ) as any; } else { enchancer = compose( - ...localStorageState + ...localStorageEnchancer ) as any; } export const store: Store = createStore( - combineReducers(reducers), + rootReducer, initialState, enchancer ); \ No newline at end of file diff --git a/src/app.common/themes/themes.ts b/src/app.common/themes/themes.ts index cd73418..aafd005 100644 --- a/src/app.common/themes/themes.ts +++ b/src/app.common/themes/themes.ts @@ -32,8 +32,8 @@ function getPalette(dark: boolean, overridePalette: Partial): Partial

; changeSecondaryColorSetting?: ActionCreator; change24HoursTimeFormatSetting?: ActionCreator; + changeTimeSelectionStepSetting?: ActionCreator; } interface DisplaySettingsStateProps { @@ -60,6 +63,7 @@ class DisplaySettingsImpl extends React.Component { changeDarkThemeSetting, changePrimaryColorSetting, changeSecondaryColorSetting, + changeTimeSelectionStepSetting, theme, } = this.props; @@ -70,103 +74,126 @@ class DisplaySettingsImpl extends React.Component {

- changeDarkThemeSetting(value)} + User interface +
+
+ changeShowUTCOffsetSetting(value)} + /> + } + label="Show UTC offset" /> - } - label="Use dark theme" - /> -
-
-
-
- changeShowUTCOffsetSetting(value)} +
+
+
+
+ changeShowTimezoneIdSetting(value)} + /> + } + label="Show Timezone name" /> - } - label="Show UTC offset" - /> -
-
-
-
- changeShowTimezoneIdSetting(value)} +
+
+
+
+ changeShowControlPanelSetting(value)} + /> + } + label="Show bottom panel" /> - } - label="Show Timezone name" - /> -
-
-
-
- changeShowControlPanelSetting(value)} +
+
+
+
+ change24HoursTimeFormatSetting(value)} + /> + } + label="Use 24 hours format" /> - } - label="Show bottom panel" - /> +
+
+
+
+ + Show DST (daylight saving time) DST + } + > + Hide + DST + Summer/Winter + + +
+
+
+
+ + Time selection step + } + > + 15 + 30 + + +
+
-
-
- change24HoursTimeFormatSetting(value)} + Theme +
+
+ changeDarkThemeSetting(value)} + /> + } + label="Use dark theme" /> - } - label="Use 24 hours format" - /> -
-
-
-
- - Show DST (daylight saving time) DST - } - > - hide - DST - Summer/Winter - - -
-
-
-
- changePrimaryColorSetting(color.id)} - /> -
-
-
-
- changeSecondaryColorSetting(color.id)} - /> +
+
+
+
+ changePrimaryColorSetting(color.id)} + /> +
+
+
+
+ changeSecondaryColorSetting(color.id)} + /> +
+
@@ -188,5 +215,6 @@ export const DisplaySettings = connect, changeSecondaryColorSetting: changeSecondaryColor as ActionCreator, change24HoursTimeFormatSetting: change24HoursTimeFormatSetting as ActionCreator, + changeTimeSelectionStepSetting: changeTimeSelectionStepSetting as ActionCreator, } )(DisplaySettingsImpl); diff --git a/src/app/components/Layout.tsx b/src/app/components/Layout.tsx index 3876c39..1cd2550 100644 --- a/src/app/components/Layout.tsx +++ b/src/app/components/Layout.tsx @@ -1,19 +1,22 @@ -import { formatTime } from "../../app.common/util/time"; -import * as React from "react"; -import { connect, ActionCreator } from "react-redux"; -import * as moment from "moment"; +import AdjustIcon from "material-ui-icons/Adjust"; +import KeyboardArrowLeftIcon from "material-ui-icons/KeyboardArrowLeft"; +import KeyboardArrowRightIcon from "material-ui-icons/KeyboardArrowRight"; +import SettingsIcon from "material-ui-icons/Settings"; import Button from "material-ui/Button"; import IconButton from "material-ui/IconButton"; -import Icon from "material-ui/Icon"; -import SettingsIcon from 'material-ui-icons/Settings'; import Paper from "material-ui/Paper"; import Typography from "material-ui/Typography"; -import { withTheme, Theme } from "material-ui/styles"; +import Tooltip from "material-ui/Tooltip"; +import * as moment from "moment"; +import * as React from "react"; +import { ActionCreator, connect } from "react-redux"; -import { TimeLine, Clock, Range, TimeSelector } from "../../app.common/components"; -import { changeSelectedTimeSpan } from "../../app.common/actions"; -import { getOffset, getHoursWithOffset, DisplaySettingsInfo, TimeSpanInfo, CalendarEvent } from "../../app.common/models"; +import { changeScrollPostion, changeSelectedTimeSpan, resetScrollPostion } from "../../app.common/actions"; +import { Clock, Range, TimeLine, TimeSelector } from "../../app.common/components"; +import { CalendarEvent, DisplaySettingsInfo, getOffset, ScrollPosition, TimeSpanInfo, getHoursWithOffset } from "../../app.common/models"; import { IAppState } from "../../app.common/store"; +import { formatTime } from "../../app.common/util/time"; + const style = require("./Layout.css"); interface ILayoutStateProps { @@ -21,20 +24,83 @@ interface ILayoutStateProps { displaySettings?: DisplaySettingsInfo; selectedTimeSpan?: TimeSpanInfo; rangeColor?: string; + scrollPosition?: ScrollPosition; } interface ILayoutDispatchProps { - changeSelectedTimeSpan?: ActionCreator + changeSelectedTimeSpan?: ActionCreator, + changeScrollPostion?: ActionCreator, + resetScrollPostion?: ActionCreator, } type ILayoutProps = ILayoutStateProps & ILayoutDispatchProps; class LayoutImpl extends React.Component { + get positionCentered(): boolean { + const { position } = this.props.scrollPosition; + return position === 0; + } + + get maxPositionReached(): boolean { + const { position, step, maxLimit } = this.props.scrollPosition; + return position + step > maxLimit; + } + + get rangeValue(): { valueMin: number, valueMax: number, rangeSize: number } { + const { position } = this.props.scrollPosition; + const { startHour, startMinute, endHour, endMinute } = this.props.selectedTimeSpan; + const { selectionStep } = this.props.displaySettings; + const stepsInHour = 60 / selectionStep; + const rangeSize = stepsInHour * 24; + + return { + valueMin: Math.min(rangeSize, Math.max((startHour + position) * stepsInHour + startMinute / selectionStep, 0)), + valueMax: Math.max(Math.min((endHour + position) * stepsInHour + endMinute / selectionStep, rangeSize), 0), + rangeSize, + }; + } + + get minPositionReached(): boolean { + const { position, step, minLimit } = this.props.scrollPosition; + return position - step < minLimit; + } + + incrementScrollPosition() { + const { position, step } = this.props.scrollPosition; + this.props.changeScrollPostion(position + step); + } + + decrementScrollPosition() { + const { position, step } = this.props.scrollPosition; + this.props.changeScrollPostion(position - step); + } + + updateSelectedTimeRange(start: number, end: number) { + const { position } = this.props.scrollPosition; + const { selectionStep } = this.props.displaySettings; + const stepsInHour = 60 / selectionStep; + const startHour = Math.floor(start / stepsInHour) - position; + const startMinute = (start % stepsInHour) * selectionStep; + const endHour = Math.floor(end / stepsInHour) - position; + const endMinute = (end % stepsInHour) * selectionStep; + this.props.changeSelectedTimeSpan(startHour, startMinute, endHour, endMinute); + } + + resetScrollPosition() { + this.props.resetScrollPostion(); + } + render(): React.ReactElement { - const { displaySettings, selectedTimeSpan, changeSelectedTimeSpan, timeLines, rangeColor } = this.props; - const valueMin = selectedTimeSpan.startHour * 2 + selectedTimeSpan.startMinute / 30; - const valueMax = selectedTimeSpan.endHour * 2 + selectedTimeSpan.endMinute / 30; + const { + displaySettings, + selectedTimeSpan, + timeLines, + rangeColor, + scrollPosition + } = this.props; + const scrollStep = scrollPosition.step; + const { valueMin, valueMax, rangeSize } = this.rangeValue; const startTime = moment().hours(selectedTimeSpan.startHour).minutes(selectedTimeSpan.startMinute); const endTime = moment().hours(selectedTimeSpan.endHour).minutes(selectedTimeSpan.endMinute); if (selectedTimeSpan.endHour === 24) { @@ -47,22 +113,50 @@ class LayoutImpl extends React.Component {
- - - + + this.incrementScrollPosition()} + > + + + + +
+ this.resetScrollPosition()} + > + + +
+
+ + this.decrementScrollPosition()} + > + + + + + + + +
- +
{timeLines.map(tl => - + )}
- changeSelectedTimeSpan(valueMin, valueMax)} /> + this.updateSelectedTimeRange(valueMin, valueMax)} />
{displaySettings.showControlPanel ? (
@@ -102,6 +196,11 @@ export const Layout = connect