cronofy-elements
Advanced tools
Comparing version 1.25.1 to 1.25.2
{ | ||
"name": "cronofy-elements", | ||
"version": "1.25.1", | ||
"version": "1.25.2", | ||
"description": "Fast track scheduling with Cronofy's embeddable UI Elements", | ||
@@ -5,0 +5,0 @@ "main": "build/npm/CronofyElements.js", |
@@ -17,3 +17,3 @@ import React, { useContext } from "react"; | ||
if (drag.mouseDown) { | ||
toggleMultiple(drag.dragged, drag.toggleState); | ||
handleRuleChange(drag.dragged, drag.toggleState); | ||
setDrag({ | ||
@@ -20,0 +20,0 @@ mouseDown: false, |
@@ -1,2 +0,2 @@ | ||
import React, { useContext } from "react"; | ||
import React, { memo, useContext } from "react"; | ||
import { css, jsx } from "@emotion/core"; | ||
@@ -8,3 +8,3 @@ | ||
const DayColumnDisplay = ({ day }) => { | ||
let DayColumnDisplay = ({ day }) => { | ||
const [theme, setTheme] = useContext(ThemeContext); | ||
@@ -32,2 +32,4 @@ return ( | ||
DayColumnDisplay = memo(DayColumnDisplay); | ||
export default DayColumnDisplay; |
@@ -1,2 +0,2 @@ | ||
import React, { useContext } from "react"; | ||
import React, { memo, useContext } from "react"; | ||
import { css, jsx } from "@emotion/core"; | ||
@@ -6,3 +6,3 @@ | ||
const DayColumnWrapper = ({ children, layer = 1 }) => { | ||
let DayColumnWrapper = ({ children, layer = 1 }) => { | ||
const [theme, setTheme] = useContext(ThemeContext); | ||
@@ -28,2 +28,4 @@ return ( | ||
DayColumnWrapper = memo(DayColumnWrapper); | ||
export default DayColumnWrapper; |
@@ -1,2 +0,2 @@ | ||
import React, { useContext } from "react"; | ||
import React, { memo, useContext } from "react"; | ||
import { css, jsx } from "@emotion/core"; | ||
@@ -9,3 +9,5 @@ | ||
const Overlay = ({ day, columnCount, toggleMultiple }) => { | ||
import { overlayComparator } from "../../helpers/comparators.AvailabilityViewer"; | ||
let Overlay = ({ day, columnCount, toggleMultiple }) => { | ||
const [status, setStatus] = useContext(StatusContext); | ||
@@ -57,2 +59,4 @@ const [theme, setTheme] = useContext(ThemeContext); | ||
Overlay = memo(Overlay, overlayComparator); | ||
export default Overlay; |
@@ -21,3 +21,3 @@ import React, { useState, useContext, useEffect } from "react"; | ||
// Default slot info | ||
const [slotData, setSlotData] = useState({ | ||
const [slotData, setSlotData] = useState(() => ({ | ||
position: { y: 0, x: columnCount }, | ||
@@ -30,15 +30,14 @@ topSlot: topSlot, | ||
} | ||
}); | ||
})); | ||
const defaultTimeDisplayStart = i18n | ||
.f(moment(slot.start, "YYYY-MM-DDTHH:mm:00Z").tz(status.tzid), "LT") | ||
.replace(" ", ""); | ||
const defaultTimeDisplayEnd = i18n | ||
.f(moment(slot.end, "YYYY-MM-DDTHH:mm:00Z").tz(status.tzid), "LT") | ||
.replace(" ", ""); | ||
// Default time display | ||
const [timeDisplay, setTimeDisplay] = useState( | ||
`${defaultTimeDisplayStart} - ${defaultTimeDisplayEnd}` | ||
); | ||
const [timeDisplay, setTimeDisplay] = useState(() => { | ||
const defaultTimeDisplayStart = i18n | ||
.f(moment(slot.start, "YYYY-MM-DDTHH:mm:00Z").tz(status.tzid), "LT") | ||
.replace(" ", ""); | ||
const defaultTimeDisplayEnd = i18n | ||
.f(moment(slot.end, "YYYY-MM-DDTHH:mm:00Z").tz(status.tzid), "LT") | ||
.replace(" ", ""); | ||
return `${defaultTimeDisplayStart} - ${defaultTimeDisplayEnd}`; | ||
}); | ||
@@ -222,2 +221,3 @@ const updateSlotData = () => { | ||
}; | ||
export default Slot; |
@@ -1,2 +0,2 @@ | ||
import React, { useContext } from "react"; | ||
import React, { memo, useContext } from "react"; | ||
import { css, jsx } from "@emotion/core"; | ||
@@ -7,3 +7,3 @@ | ||
const TimeLines = () => { | ||
let TimeLines = () => { | ||
const labels = useContext(LabelsContext); | ||
@@ -73,2 +73,4 @@ const [theme, setTheme] = useContext(ThemeContext); | ||
TimeLines = memo(TimeLines); | ||
export default TimeLines; |
@@ -69,22 +69,23 @@ import React, { useContext, useState, useEffect } from "react"; | ||
const toggleMultiple = (IDs, toggleStatus) => { | ||
const updatedSlotData = slotData.map(day => ({ | ||
...day, | ||
slots: day.slots.map(slot => | ||
IDs.includes(slot.start) | ||
? { ...slot, selected: toggleStatus } | ||
: slot | ||
) | ||
})); | ||
setSlotData(oldSlotData => { | ||
const updatedSlotData = oldSlotData.map(day => ({ | ||
...day, | ||
slots: day.slots.map(slot => | ||
IDs.includes(slot.start) | ||
? { ...slot, selected: toggleStatus } | ||
: slot | ||
) | ||
})); | ||
const currentlySelectedSlots = getSelectedSlots(updatedSlotData); | ||
const queryPeriods = buildPeriodsFromSlots(currentlySelectedSlots); | ||
const callbackContent = { | ||
notification: { | ||
type: "query_periods_edited", | ||
query_periods: queryPeriods | ||
} | ||
}; | ||
status.notificationCallback(callbackContent); | ||
setSlotData(updatedSlotData); | ||
const currentlySelectedSlots = getSelectedSlots(updatedSlotData); | ||
const queryPeriods = buildPeriodsFromSlots(currentlySelectedSlots); | ||
const callbackContent = { | ||
notification: { | ||
type: "query_periods_edited", | ||
query_periods: queryPeriods | ||
} | ||
}; | ||
status.notificationCallback(callbackContent); | ||
return updatedSlotData; | ||
}); | ||
}; | ||
@@ -91,0 +92,0 @@ |
import { | ||
parseConnectionDomains, | ||
parseTarget, | ||
parseTimzone, | ||
parseTimezone, | ||
parseToken, | ||
@@ -72,3 +72,3 @@ parseTranslations | ||
const tzid = parseTimzone(options.tzid, "availability-rules", log); | ||
const tzid = parseTimezone(options.tzid, "availability-rules", log); | ||
@@ -75,0 +75,0 @@ const domains = parseConnectionDomains( |
import moment from "moment-timezone"; | ||
import { | ||
adjustInterval, | ||
parseConnectionDomains, | ||
parseGridTime, | ||
parseInterval, | ||
parseQuery, | ||
parseTarget, | ||
parseTimzone, | ||
parseTimezone, | ||
parseToken, | ||
@@ -39,4 +41,27 @@ parseTranslations | ||
const interval = parseInterval(config.interval); | ||
const startTime = parseGridTime(config.start_time, "09:00"); | ||
if (startTime.message) { | ||
log.warn(`config.start_time: ${startTime.message}`, { | ||
docsSlug: "availability-viewer/#config.start_time" | ||
}); | ||
} | ||
const endTime = parseGridTime(config.end_time, "17:00", true); | ||
if (endTime.message) { | ||
log.warn(`config.end_time: ${endTime.message}`, { | ||
docsSlug: "availability-viewer/#config.end_time" | ||
}); | ||
} | ||
if (startTime.time > endTime.time) { | ||
log.error( | ||
`\`config.start_time\` (${startTime.time}) must be earlier than \`config.end_time\` (${endTime.time}).` | ||
); | ||
return false; | ||
} | ||
const rawInterval = parseInterval(config.interval); | ||
const interval = adjustInterval(rawInterval, startTime.offset, log); | ||
if (typeof options.extras !== "undefined") { | ||
@@ -53,3 +78,3 @@ log.warn( | ||
const tzid = parseTimzone(options.tzid, "availability-viewer", log); | ||
const tzid = parseTimezone(options.tzid, "availability-viewer", log); | ||
options = { ...options, ...tzid }; | ||
@@ -147,2 +172,4 @@ | ||
...config, | ||
start_time: startTime.time, | ||
end_time: endTime.time, | ||
interval, | ||
@@ -149,0 +176,0 @@ startDay, |
@@ -228,3 +228,3 @@ import merge from "deepmerge"; | ||
export const parseTimzone = (option, elementSlug = false, log) => { | ||
export const parseTimezone = (option, elementSlug = false, log) => { | ||
const docsHash = elementSlug ? `${elementSlug}/#param-tzid` : ""; | ||
@@ -246,1 +246,56 @@ const isValidTzid = moment.tz.zone(option); | ||
}; | ||
export const parseGridTime = (time, fallback = "09:00", roundUp = false) => { | ||
// If there's no time set, fallback silently. | ||
if (typeof time === "undefined") | ||
return { message: false, time: fallback, offset: 0 }; | ||
// If there is a time, is it in a valid format? | ||
const validTime = moment(time, "HH:mm", true).isValid(); | ||
if (!validTime) { | ||
return { | ||
message: `using default value of ${fallback}`, | ||
time: fallback, | ||
offset: 0 | ||
}; | ||
} | ||
const parts = time.split(":"); | ||
const rawMinutes = parseInt(parts[1], 10); | ||
// If the time falls on an approved interval, it's valid! | ||
if (rawMinutes % 15 === 0) { | ||
return { | ||
message: false, | ||
time: time, | ||
offset: rawMinutes | ||
}; | ||
} | ||
const roundedMinutes = roundUp | ||
? (Math.ceil(rawMinutes / 15) * 15) % 60 | ||
: (Math.floor(rawMinutes / 15) * 15) % 60; | ||
const paddedMinutes = roundedMinutes.toString().padStart(2, "0"); | ||
return { | ||
message: `rounded from ${time} to ${parts[0]}:${paddedMinutes}`, | ||
time: `${parts[0]}:${paddedMinutes}`, | ||
offset: roundedMinutes | ||
}; | ||
}; | ||
export const adjustInterval = (rawInterval, offset, log) => { | ||
if (offset > 0 && rawInterval > offset) { | ||
// Positive offsets will only ever be 15, 30 or 45. | ||
const adjustedInterval = offset === 45 ? 15 : offset; | ||
log.warn( | ||
`config.interval of ${rawInterval} minutes has been shortened to ${adjustedInterval} minutes for optimal tessellation of results in the grid.`, | ||
{ | ||
docsSlug: "availability-viewer/#config.interval" | ||
} | ||
); | ||
return adjustedInterval; | ||
} | ||
return rawInterval; | ||
}; |
@@ -5,3 +5,3 @@ import { | ||
parseTarget, | ||
parseTimzone, | ||
parseTimezone, | ||
parseToken, | ||
@@ -47,3 +47,3 @@ parseTranslations | ||
const tzid = parseTimzone(options.tzid, "slot-picker", log); | ||
const tzid = parseTimezone(options.tzid, "slot-picker", log); | ||
@@ -50,0 +50,0 @@ const domains = parseConnectionDomains( |
@@ -98,6 +98,14 @@ const moment = require("moment-timezone"); | ||
let start = moment.utc(period.start, "YYYY-MM-DDTHH:mm:00Z"); | ||
const startIsHalfHour = | ||
start.minute() === 30 && | ||
start.second() === 0 && | ||
start.millisecond() === 0; | ||
const startIsQuarter = | ||
start.minute() % 15 === 0 && | ||
start.second() === 0 && | ||
start.millisecond() === 0; | ||
if (duration > 30) { | ||
if (duration > 30 && !startIsHalfHour && !startIsQuarter) { | ||
start = hour(start); | ||
} else if (duration > 15) { | ||
} else if (duration > 15 && !startIsQuarter) { | ||
start = half(start); | ||
@@ -104,0 +112,0 @@ } else { |
@@ -8,3 +8,3 @@ import moment from "moment-timezone"; | ||
import { parseSlotPickerOptions } from "../src/js/helpers/init.SlotPicker"; | ||
import { parseQuery } from "../src/js/helpers/init"; | ||
import { parseQuery, parseGridTime } from "../src/js/helpers/init"; | ||
@@ -651,2 +651,271 @@ // The parsers rely on console warning and errors to | ||
}); | ||
it("correctly adds fallback config.start_time and config.end_time", () => { | ||
const input = { | ||
data_center: "DATA_CENTER", | ||
availability_query: { | ||
query_periods: generic_query_periods, | ||
required_duration: "TEST" | ||
}, | ||
target_id: "TARGET", | ||
element_token: "TOKEN" | ||
}; | ||
const result = parseAvailabilityViewerOptions(input); | ||
expect(result.config.start_time).toEqual("09:00"); | ||
expect(result.config.end_time).toEqual("17:00"); | ||
expect(console.warn).toHaveBeenCalledTimes(0); | ||
expect(console.error).toHaveBeenCalledTimes(0); | ||
}); | ||
it("correctly fallbacks when config.start_time is invalid", () => { | ||
const input = { | ||
data_center: "DATA_CENTER", | ||
availability_query: { | ||
query_periods: generic_query_periods, | ||
required_duration: "TEST" | ||
}, | ||
config: { | ||
start_time: "A string that is not a time", | ||
end_time: "13:00" | ||
}, | ||
target_id: "TARGET", | ||
element_token: "TOKEN" | ||
}; | ||
const result = parseAvailabilityViewerOptions(input); | ||
expect(result.config.start_time).toEqual("09:00"); | ||
expect(result.config.end_time).toEqual("13:00"); | ||
expect(console.warn).toHaveBeenCalledTimes(1); | ||
expect(console.warn.mock.calls[0][0]).toContain( | ||
"config.start_time: using default value of 09:00" | ||
); | ||
expect(console.error).toHaveBeenCalledTimes(0); | ||
}); | ||
it("correctly fallbacks when config.end_time is invalid", () => { | ||
const input = { | ||
data_center: "DATA_CENTER", | ||
availability_query: { | ||
query_periods: generic_query_periods, | ||
required_duration: "TEST" | ||
}, | ||
config: { | ||
start_time: "08:00", | ||
end_time: 1234 | ||
}, | ||
target_id: "TARGET", | ||
element_token: "TOKEN" | ||
}; | ||
const result = parseAvailabilityViewerOptions(input); | ||
expect(result.config.start_time).toEqual("08:00"); | ||
expect(result.config.end_time).toEqual("17:00"); | ||
expect(console.warn).toHaveBeenCalledTimes(1); | ||
expect(console.warn.mock.calls[0][0]).toContain( | ||
"config.end_time: using default value of 17:00" | ||
); | ||
expect(console.error).toHaveBeenCalledTimes(0); | ||
}); | ||
it("correctly rounds config.start_time to valid interval", () => { | ||
const input = { | ||
data_center: "DATA_CENTER", | ||
availability_query: { | ||
query_periods: generic_query_periods, | ||
required_duration: "TEST" | ||
}, | ||
config: { | ||
start_time: "08:25" | ||
}, | ||
target_id: "TARGET", | ||
element_token: "TOKEN" | ||
}; | ||
const result = parseAvailabilityViewerOptions(input); | ||
expect(result.config.start_time).toEqual("08:15"); | ||
expect(console.warn).toHaveBeenCalledTimes(1); | ||
expect(console.warn.mock.calls[0][0]).toContain( | ||
"config.start_time: rounded from 08:25 to 08:15" | ||
); | ||
}); | ||
it("correctly rounds config.start_time to valid interval", () => { | ||
const input = { | ||
data_center: "DATA_CENTER", | ||
availability_query: { | ||
query_periods: generic_query_periods, | ||
required_duration: "TEST" | ||
}, | ||
target_id: "TARGET", | ||
element_token: "TOKEN" | ||
}; | ||
const config_1 = { | ||
config: { | ||
end_time: "17:25" | ||
} | ||
}; | ||
const result_1 = parseAvailabilityViewerOptions({ | ||
...input, | ||
...config_1 | ||
}); | ||
expect(result_1.config.end_time).toEqual("17:30"); | ||
expect(console.warn).toHaveBeenCalledTimes(1); | ||
expect(console.warn.mock.calls[0][0]).toContain( | ||
"config.end_time: rounded from 17:25 to 17:30" | ||
); | ||
console.warn.mockClear(); | ||
const config_2 = { | ||
config: { | ||
start_time: "08:38" | ||
} | ||
}; | ||
const result_2 = parseAvailabilityViewerOptions({ | ||
...input, | ||
...config_2 | ||
}); | ||
expect(result_2.config.start_time).toEqual("08:30"); | ||
expect(console.warn).toHaveBeenCalledTimes(1); | ||
expect(console.warn.mock.calls[0][0]).toContain( | ||
"config.start_time: rounded from 08:38 to 08:30" | ||
); | ||
}); | ||
it("adjusts config.interval to match config.start_time when needed", () => { | ||
const input = { | ||
data_center: "DATA_CENTER", | ||
availability_query: { | ||
query_periods: generic_query_periods, | ||
required_duration: "TEST" | ||
}, | ||
target_id: "TARGET", | ||
element_token: "TOKEN" | ||
}; | ||
const config_1 = { | ||
config: { | ||
start_time: "08:30", | ||
interval: 60 | ||
} | ||
}; | ||
const result_1 = parseAvailabilityViewerOptions({ | ||
...input, | ||
...config_1 | ||
}); | ||
expect(result_1.config.interval).toEqual(30); | ||
expect(console.warn).toHaveBeenCalledTimes(1); | ||
expect(console.warn.mock.calls[0][0]).toContain( | ||
"config.interval of 60 minutes has been shortened to 30 minutes for optimal tessellation of results in the grid." | ||
); | ||
console.warn.mockClear(); | ||
const config_2 = { | ||
config: { | ||
start_time: "08:15", | ||
interval: 60 | ||
} | ||
}; | ||
const result_2 = parseAvailabilityViewerOptions({ | ||
...input, | ||
...config_2 | ||
}); | ||
expect(result_2.config.interval).toEqual(15); | ||
expect(console.warn).toHaveBeenCalledTimes(1); | ||
expect(console.warn.mock.calls[0][0]).toContain( | ||
"config.interval of 60 minutes has been shortened to 15 minutes for optimal tessellation of results in the grid." | ||
); | ||
console.warn.mockClear(); | ||
const config_3 = { | ||
config: { | ||
start_time: "08:45", | ||
interval: 60 | ||
} | ||
}; | ||
const result_3 = parseAvailabilityViewerOptions({ | ||
...input, | ||
...config_3 | ||
}); | ||
expect(result_3.config.interval).toEqual(15); | ||
expect(console.warn).toHaveBeenCalledTimes(1); | ||
expect(console.warn.mock.calls[0][0]).toContain( | ||
"config.interval of 60 minutes has been shortened to 15 minutes for optimal tessellation of results in the grid." | ||
); | ||
}); | ||
it("fails if start_time is after end_time", () => { | ||
const input = { | ||
data_center: "DATA_CENTER", | ||
availability_query: { | ||
query_periods: generic_query_periods, | ||
required_duration: "TEST" | ||
}, | ||
target_id: "TARGET", | ||
element_token: "TOKEN", | ||
config: { | ||
start_time: "08:00", | ||
end_time: "06:00", | ||
interval: 60 | ||
} | ||
}; | ||
const result = parseAvailabilityViewerOptions(input); | ||
expect(result).toEqual(false); | ||
expect(console.error).toHaveBeenCalledTimes(1); | ||
expect(console.error.mock.calls[0][0]).toContain( | ||
"must be earlier than" | ||
); | ||
}); | ||
it("correctly parses grid times", () => { | ||
const simpleTime = parseGridTime("08:00"); | ||
expect(simpleTime.time).toBe("08:00"); | ||
expect(simpleTime.offset).toBe(0); | ||
expect(simpleTime.message).toBe(false); | ||
const offsetTime = parseGridTime("08:30"); | ||
expect(offsetTime.time).toBe("08:30"); | ||
expect(offsetTime.offset).toBe(30); | ||
expect(offsetTime.message).toBe(false); | ||
const invalidTime = parseGridTime("INVALID"); | ||
expect(invalidTime.time).toBe("09:00"); | ||
expect(invalidTime.offset).toBe(0); | ||
expect(invalidTime.message).toContain("using default value"); | ||
const roundedDown = parseGridTime("08:42"); | ||
expect(roundedDown.time).toBe("08:30"); | ||
expect(roundedDown.offset).toBe(30); | ||
expect(roundedDown.message).toContain("rounded"); | ||
const roundedUp = parseGridTime("08:42", "", true); | ||
expect(roundedUp.time).toBe("08:45"); | ||
expect(roundedUp.offset).toBe(45); | ||
expect(roundedUp.message).toContain("rounded"); | ||
const noLeadingZeroTime = parseGridTime("8:00"); | ||
expect(noLeadingZeroTime.time).toBe("09:00"); | ||
expect(noLeadingZeroTime.offset).toBe(0); | ||
expect(noLeadingZeroTime.message).toContain("using default value"); | ||
const emptyTime = parseGridTime(); | ||
expect(emptyTime.time).toBe("09:00"); | ||
expect(emptyTime.offset).toBe(0); | ||
expect(emptyTime.message).toBe(false); | ||
const customFallbackTime = parseGridTime("INVALID", "06:00"); | ||
expect(customFallbackTime.time).toBe("06:00"); | ||
expect(customFallbackTime.offset).toBe(0); | ||
expect(customFallbackTime.message).toContain("using default value"); | ||
}); | ||
}); | ||
@@ -653,0 +922,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
2897719
237
27162