Skip to content

Commit

Permalink
Merge pull request #1003 from ibi-group/fix-timetable-pasting
Browse files Browse the repository at this point in the history
Fix Timetable Pasting
  • Loading branch information
philip-cline authored Dec 5, 2023
2 parents 90eef44 + da5e907 commit 42f00ee
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 36 deletions.
22 changes: 12 additions & 10 deletions lib/editor/components/timetable/EditableCell.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import moment from 'moment'

import * as tripActions from '../../actions/trip'
import {secondsAfterMidnightToHHMM} from '../../../common/util/gtfs'
import { isTimeFormat } from '../../util/timetable'
import { isTimeFormat, parseCellValue } from '../../util/timetable'
import type {TimetableColumn} from '../../../types'
import type {EditorValidationIssue} from '../../util/validation'

Expand Down Expand Up @@ -66,8 +66,6 @@ export default class EditableCell extends Component<Props, State> {
})
}

cellInput = null

/**
* The component may receive data from a save event or
* editing can be changed by a change in the activeCell in the TimetableGrid
Expand Down Expand Up @@ -157,8 +155,6 @@ export default class EditableCell extends Component<Props, State> {
offsetScrollCol(-1)
return
case 38: // up
this.save()
break
case 40: // down
this.save()
break
Expand Down Expand Up @@ -196,6 +192,7 @@ export default class EditableCell extends Component<Props, State> {
}

save () {
this.cancel()
const {column} = this.props
const {data} = this.state
// for non-time rendering
Expand All @@ -206,6 +203,14 @@ export default class EditableCell extends Component<Props, State> {
// Ensure that only a positive integer value can be set.
const value = +data
this._handleSave(value)
} else if (isTimeFormat(column.type)) {
if (typeof data !== 'string') return this.cancel()
const parsedValue = parseCellValue(data, column)
if (parsedValue !== null) {
this._handleSave(parsedValue)
} else {
this.cancel()
}
} else {
if (typeof data !== 'string') return this.cancel()
const duration = moment.duration(data)
Expand Down Expand Up @@ -238,11 +243,8 @@ export default class EditableCell extends Component<Props, State> {
? String.fromCharCode(13)
: undefined
const rows = text.split(rowDelimiter)
const rowsAndColumns = []
// Split each row into columns
for (let i = 0; i < rows.length; i++) {
rowsAndColumns.push(rows[i].split(String.fromCharCode(9)))
}
// Remove carriage return characters (/r) from rows to handle pasted data from Excel
const rowsAndColumns = rows.map(row => row.split(String.fromCharCode(9)).map(cellValue => cellValue.replace(/\r/g, '')))

if (rowsAndColumns.length > 1 || rowsAndColumns[0].length > 1) {
this.cancel()
Expand Down
50 changes: 29 additions & 21 deletions lib/editor/components/timetable/TimetableGrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import objectPath from 'object-path'

import * as tripActions from '../../actions/trip'
import {ENTITY} from '../../constants'
import HeaderCell from './HeaderCell'
import EditableCell from './EditableCell'
import {
getHeaderColumns,
HEADER_GRID_STYLE,
Expand All @@ -23,17 +21,19 @@ import {
MAIN_GRID_WRAPPER_STYLE,
OVERSCAN_COLUMN_COUNT,
OVERSCAN_ROW_COUNT,
parseTime,
parseCellValue,
ROW_HEIGHT,
SCROLL_SIZE,
TOP_LEFT_STYLE,
WRAPPER_STYLE
} from '../../util/timetable'

import type {Pattern, TimetableColumn} from '../../../types'
import type {TimetableState} from '../../../types/reducers'
import type {TripValidationIssues} from '../../selectors/timetable'

import EditableCell from './EditableCell'
import HeaderCell from './HeaderCell'

type Style = {[string]: number | string}

type Props = {
Expand Down Expand Up @@ -371,38 +371,46 @@ export default class TimetableGrid extends Component<Props> {
addNewRow,
columns,
data,
setActiveCell,
hideDepartureTimes,
updateCellValue,
updateScroll
} = this.props
let activeRow = rowIndex
let activeCol = colIndex
// iterate over rows in pasted selection
let errorShown = false
// Iterate over rows in pasted selection
for (var i = 0; i < pastedRows.length; i++) {
activeRow = rowIndex + i
// construct new row if it doesn't exist
if (typeof data[i + rowIndex] === 'undefined') {
// Construct new row if it doesn't exist
if (typeof data[activeRow] === 'undefined') {
addNewRow()
}
// iterate over number of columns in pasted selection
// Iterate over number of columns in pasted selection
for (var j = 0; j < pastedRows[0].length; j++) {
activeCol = colIndex + j
const path = `${rowIndex + i}.${columns[colIndex + j].key}`
const value = parseTime(pastedRows[i][j])
updateCellValue({value, rowIndex: rowIndex + i, key: path})
// if departure times are hidden, paste into adjacent time column
const adjacentPath = `${rowIndex + i}.${columns[colIndex + j + 2].key}`
if (
hideDepartureTimes &&
isTimeFormat(columns[colIndex + j].type) &&
typeof objectPath.get(data, adjacentPath) !== 'undefined'
) {
updateCellValue({value, rowIndex: rowIndex + i, key: adjacentPath})
const col = columns[activeCol]
const path = `${activeRow}.${col.key}`
const originalValue = objectPath.get(data[activeRow], col.key)
const value = parseCellValue(pastedRows[i][j], col, errorShown)
if (value !== null) {
const finalValue = value
updateCellValue({ value: finalValue, rowIndex: activeRow, key: path })
// If departure times are hidden, paste into adjacent time column
const adjacentPath = `${activeRow}.${columns[activeCol + 2].key}`
if (
hideDepartureTimes &&
isTimeFormat(col.type) &&
typeof objectPath.get(data, adjacentPath) !== 'undefined'
) {
updateCellValue({ value: finalValue, rowIndex: activeRow, key: adjacentPath })
}
} else {
// If the value is rejected, keep the original value
updateCellValue({ value: originalValue, rowIndex: activeRow, key: path })
errorShown = true
}
}
}
setActiveCell(`${activeRow}-${activeCol}`)
updateScroll(activeRow, activeCol)
}

Expand Down
15 changes: 10 additions & 5 deletions lib/editor/util/timetable.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,17 @@ export function getHeaderColumns (
return columns.filter(c => c.type !== 'DEPARTURE_TIME')
}

export function parseTime (timeString: string) {
export function parseCellValue (timeString: string, col: TimetableColumn, errorShown?: boolean) {
const date = moment().startOf('day').format('YYYY-MM-DD')
return moment(date + 'T' + timeString, TIMETABLE_FORMATS).diff(
date,
'seconds'
)
const parsedDate = moment(date + 'T' + timeString, TIMETABLE_FORMATS, true)
if (isTimeFormat(col.type)) {
if (!parsedDate.isValid()) {
if (errorShown !== true) alert(`Please enter a valid time format`)
return null
}
return moment(date + 'T' + timeString, TIMETABLE_FORMATS).diff(date, 'seconds')
}
return timeString
}

// Helper function to find and return the active calendar, be it traditional or exception-based.
Expand Down

0 comments on commit 42f00ee

Please sign in to comment.