MOCO Browser Extension (#2)

* spike

* initial draft

* updated styling

* skeleton

* added bubble script to webpack

* added linter settings

* installs

* first implementation

* Update webpack config

- write bundle to `/build`
- add support for SASS
- improve options view as a proof o concept for styling

* Update es-lint rules to mach mocoapp

* Upgrade npm packages

* Mount Bubble only for configured services

* Update react and babel

* Move module resolution config to webpack

* Syncrhonize apiClient with chrome storage

* Load projects and initialize form with last project and task

* Enhance service

* Improve handling of changeset with defaults

* Create activity

* Show error page on missing configuration

* Refactor so that changeset can be used as activity params

* Show form errors

* Fetch and show booked hours for service

* Allow to book hours with colon, error handling, spinner

* WIP: Shadow DOM

* Remove shadow dom

* Render App in iframe

* Refactor App component to load projects and create activity

* Bugsnag integration

* Add title to form and timer hint to hours input field

* Configure positioning of bubble

* Get rid of shared browser instance

* Show Calendar and animate buble

* Update webpack config

* Prevent double animation of bubble

* Fix eslint

* Add margin to iframe body

* Submit form when pressing enter on textarea

* Open select on Enter

* Use local environment for development

* Show upgrade error if version invalid

* Add asana service

*  Add jira and wunderlist services, add better support for query strings

* Match urls with hash

* Show popup in browser action

* Pump version, add version to zip file

* Add youtrack service

* WIP: always show browserAction

* Refactor

* Update design

* Finalize release 1.0.3

* Fix styles

* Add support for Firefox browser

* Extract common webpack config

* Fix eslint

* Close modal with ESC key

* Use TimeInputParser to parse hours input

* Improve webpack config

* Show modal instead of popup when clicking on browser action

* Pre-select last booked activities on service

* Remove badge from booked hours

* Show error and success feedback on options page

* Remove updateBrowserActionForTab

* Animate Bubble on unmount

* Fix select date

* Refactor

* Fix key shortcut

* Show schedule in calendar

* Upload source maps to bugsnag

* Upload sourcemaps to bugsnag

* Define command shortcuts

* Fix race condition where both Bubble and content wanted to mount Popup

The content script is now the only place, where the Popup is mounted

* Replace hash in filename by version

* No new line in textarea and updated shortcuts for chrome

* Change shortcut to Ctrl+Shift+K

* Fix cors issue in new chrome 73

* Style improvements

* Only report errors from own sources

* Prevent sending messages to browser tabs

* Fix scrollbars in iframe

* Add error page for unknown error

* Add stop propagation to Bubble click event

* Update error pages

* Remove timeout in tabHandler.

The messaging error occurs only when the browser extension is reloaded/updated without refreshing the browser tab.

* Refactor messaging

* Show spinner in popup

* Extract message handler to own module

* Update styles and texts of error pages

* Ensure focus is on document when opening popup

* Find projects by identifier and value, do not highlight selected option in select component

* Update docs

* Spread match properties on service; improve remote service configuration for jira and wunderlist

* Add webpack plugin to remove source mapping url

* Bugsnag do not collect user ip

* Upload source maps before removing source mapping url in bundles

* Add support for regex url patterns, update asana config.

* Fix animation

Set default transform property via css

* Improve config for asana

* Change to fad-in/out animation
This commit is contained in:
Manuel Bouza
2019-03-22 15:56:24 +01:00
parent cbf79b960c
commit 28a9a86e27
58 changed files with 11017 additions and 47 deletions

View File

@@ -0,0 +1,127 @@
import ApiClient from "api/Client"
import {
ERROR_UNAUTHORIZED,
ERROR_UPGRADE_REQUIRED,
ERROR_UNKNOWN,
groupedProjectOptions,
weekStartsOn
} from "utils"
import { get, forEach, reject, isNil } from "lodash/fp"
import { startOfWeek, endOfWeek } from "date-fns"
import { createMatcher } from "utils/urlMatcher"
import remoteServices from "remoteServices"
import { queryTabs, isBrowserTab, getSettings } from "utils/browser"
const getStartOfWeek = () => startOfWeek(new Date(), { weekStartsOn })
const getEndOfWeek = () => endOfWeek(new Date(), { weekStartsOn })
const matcher = createMatcher(remoteServices)
export function tabUpdated(tab, { messenger, settings }) {
messenger.connectTab(tab)
const service = matcher(tab.url)
if (service?.match?.id) {
messenger.postMessage(tab, { type: "requestService" })
messenger.once("newService", ({ payload: { service } }) => {
const apiClient = new ApiClient(settings)
apiClient
.bookedHours(service)
.then(({ data }) => {
messenger.postMessage(tab, {
type: "showBubble",
payload: {
bookedHours: parseFloat(data[0]?.hours) || 0,
service
}
})
})
.catch(() => {
messenger.postMessage(tab, {
type: "showBubble",
payload: {
bookedHours: 0,
service
}
})
})
})
} else {
messenger.postMessage(tab, { type: "hideBubble" })
}
}
export function settingsChanged(settings, { messenger }) {
queryTabs({ currentWindow: true })
.then(reject(isBrowserTab))
.then(
forEach(tab => {
messenger.postMessage(tab, { type: "closePopup" })
tabUpdated(tab, { settings, messenger })
})
)
}
export function togglePopup(tab, { messenger }) {
return function({ isOpen, service } = {}) {
if (isNil(isOpen)) {
return
}
if (isOpen) {
messenger.postMessage(tab, { type: "closePopup" })
} else {
openPopup(tab, { service, messenger })
}
}
}
function openPopup(tab, { service, messenger }) {
messenger.postMessage(tab, { type: "openPopup", payload: { loading: true } })
const fromDate = getStartOfWeek()
const toDate = getEndOfWeek()
getSettings()
.then(settings => new ApiClient(settings))
.then(apiClient =>
Promise.all([
apiClient.login(service),
apiClient.projects(),
apiClient.activities(fromDate, toDate),
apiClient.schedules(fromDate, toDate)
])
)
.then(responses => {
const action = {
type: "openPopup",
payload: {
service,
lastProjectId: get("[0].data.last_project_id", responses),
lastTaskId: get("[0].data.last_task_id", responses),
roundTimeEntries: get("[0].data.round_time_entries", responses),
projects: groupedProjectOptions(get("[1].data.projects", responses)),
activities: get("[2].data", responses),
schedules: get("[3].data", responses),
fromDate,
toDate,
loading: false
}
}
messenger.postMessage(tab, action)
})
.catch(error => {
let errorType, errorMessage
if (error.response?.status === 401) {
errorType = ERROR_UNAUTHORIZED
} else if (error.response?.status === 426) {
errorType = ERROR_UPGRADE_REQUIRED
} else {
errorType = ERROR_UNKNOWN
errorMessage = error.message
}
messenger.postMessage(tab, {
type: "openPopup",
payload: { errorType, errorMessage }
})
})
}