Skip to content
This repository has been archived by the owner on Aug 13, 2024. It is now read-only.

Commit

Permalink
Merge pull request #24 from mariomc/smartSubmit
Browse files Browse the repository at this point in the history
Smart submit
  • Loading branch information
mariomc authored Aug 29, 2021
2 parents 20bd2d5 + 19fb8d4 commit 759a82a
Show file tree
Hide file tree
Showing 13 changed files with 366 additions and 18 deletions.
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
"@types/react-dom": "^17.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"scheduler": "^0.20.2",
"use-context-selector": "^1.3.7",
"use-long-press": "^1.1.1",
"webextension-polyfill": "^0.7.0"
},
Expand Down
4 changes: 2 additions & 2 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ export default {
input: 'src/manifest.json',
output: {
dir: 'dist',
format: 'esm',
format: 'es',
chunkFileNames: path.join('chunks', '[name]-[hash].js'),
},
plugins: [
chromeExtension({ browserPolyfill: true }),
chromeExtension({ browserPolyfill: true, dynamicImportWrapper: false }),
// Adds a Chrome extension reloader during watch mode
simpleReloader(),
resolve(),
Expand Down
43 changes: 41 additions & 2 deletions src/content/components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@
import React from 'react'
import React, { useEffect, useReducer } from 'react'

import { Presets } from './presets'
import { AppContext } from '../state'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function reducer(state: any, action: any) {
if (action.state) {
return action.state
}
return state
}

const initialState = {};

export const WayfarerUltra = (): JSX.Element => {
return <Presets />
const [state, dispatch] = useReducer(reducer, initialState)

useEffect(() => {
const handler = function ({
data,
origin,
}: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
origin: any
}) {
if (location.origin !== origin) {
return
}
dispatch(data)
}
window.addEventListener('message', handler)

return () => {
window.removeEventListener('message', handler)
}
}, [])

return (
<AppContext.Provider value={state}>
<Presets />
</AppContext.Provider>
)
}
18 changes: 11 additions & 7 deletions src/content/components/presets.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'
import React, { memo, useState, useEffect } from 'react'
import Button from '@material-ui/core/Button'
import ButtonGroup from '@material-ui/core/ButtonGroup'
import Dialog from '@material-ui/core/Dialog'
Expand All @@ -15,6 +15,7 @@ import DialogTitle from '@material-ui/core/DialogTitle'
import Icon from '@material-ui/core/Icon'

import { PresetsTable } from './presets-table'
import { SmartSubmit } from './smart-submit'
import type { PresetConfig, PresetScoreKey, FlatPreset } from '../config'
import { applyPreset } from '../utils'

Expand Down Expand Up @@ -115,7 +116,7 @@ const getStoredPresets = (): PresetConfig[] => {
return getLS(LOCAL_STORAGE_KEY) as PresetConfig[]
}

export const Presets = (): JSX.Element => {
const PresetsNoMemo = (): JSX.Element => {
const [changes, setChanges] = useState<FlatPresetMap>({} as FlatPresetMap)
const [presets, setPresets] = useState<PresetConfig[]>(getStoredPresets())
const [open, setOpen] = useState<boolean>(false)
Expand Down Expand Up @@ -176,11 +177,12 @@ export const Presets = (): JSX.Element => {
/>
))}
</ButtonGroup>
<Tooltip title="Add Preset">
<Tooltip title="Edit Presets">
<Fab color="primary" onClick={handleOpen}>
<Icon>add_circle</Icon>
<Icon>settings</Icon>
</Fab>
</Tooltip>
<SmartSubmit />
<Dialog
fullWidth
maxWidth="xl"
Expand All @@ -191,9 +193,9 @@ export const Presets = (): JSX.Element => {
<DialogTitle id="form-dialog-title">Current Presets</DialogTitle>
<DialogContent>
<DialogContentText>
Create new presets by editing the empty lines.
Edit the presets by double-clicking the fields. Remove them by
deleting their title. Commit the changes by saving.
Create new presets by editing the empty lines. Edit the presets by
double-clicking the fields. Remove them by deleting their title.
Commit the changes by saving.
</DialogContentText>
<PresetsTable presets={presets} onChange={handleChange} />
</DialogContent>
Expand All @@ -210,3 +212,5 @@ export const Presets = (): JSX.Element => {
</AppBar>
)
}

export const Presets = memo(PresetsNoMemo)
147 changes: 147 additions & 0 deletions src/content/components/smart-submit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import React, { useEffect, useState, useCallback, useRef } from 'react'
import { useContextSelector } from 'use-context-selector'
import Tooltip from '@material-ui/core/Tooltip'
import Badge from '@material-ui/core/Badge'

import Fab from '@material-ui/core/Fab'
import Icon from '@material-ui/core/Icon'
import Snackbar from '@material-ui/core/Snackbar'
import Alert from '@material-ui/core/Alert'

import { AppContext } from '../state'
import { selectors } from '../config'
import { getWaitingTime } from '../utils'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const expiresSelector = (state: any): number | null => {
return state?.review?.reviewData?.data?.expires
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isValidSelector = (state: any): number | null => {
return state?.review?.reviewResponse?.isValid
}

const timeFormat = {
minute: 'numeric',
second: 'numeric',
}

const timeFromMs = (ms: number) => {
return new Intl.DateTimeFormat('default', timeFormat).format(new Date(ms))
}

const getDelta = (expires: number) => expires - new Date().valueOf()

const style = {
position: 'absolute',
right: 16,
}

const Countdown = ({
initialCount,
invalidValue = '',
}: {
initialCount: number
invalidValue?: string
}): JSX.Element => {
const [counter, setCounter] = useState(initialCount)
const STEP = 1000

useEffect(() => {
const timeout = setTimeout(() => {
const nextStep = counter - STEP
setCounter(Math.max(0, nextStep))
}, STEP)

return () => {
clearTimeout(timeout)
}
})

if (initialCount <= 0) {
return invalidValue
}

return <>{timeFromMs(counter)}</>
}

const Timer = ({ expires }: { expires: number }): JSX.Element => {
return <Countdown initialCount={getDelta(expires)} invalidValue="Expired" />
}

const clickOnFirstEditOption = false // TODO: Change this into the preset. Here to test only

const clickOnSubmit = (callback) => {
const submitButton = document.querySelector(
selectors.smartSubmit.submit,
) as HTMLButtonElement

if (submitButton) {
if (clickOnFirstEditOption) {
const option = document.querySelector(
selectors.smartSubmit.what,
) as HTMLButtonElement
option?.click?.()
}
submitButton?.click?.()
callback(true)
}
}

export const SmartSubmit = (): JSX.Element | null => {
const expires = useContextSelector(AppContext, expiresSelector)
const isValid = useContextSelector(AppContext, isValidSelector)
const timerRef = useRef(null)
const [submitted, setSubmitted] = useState(false)
const [waitingTime, setWaiting] = useState(0)

const handleClick = useCallback(() => {
const waitingMs = getWaitingTime(expires)

setWaiting(waitingMs)

timerRef.current = setTimeout(() => {
clickOnSubmit(setSubmitted)
setWaiting(0)
}, waitingMs)
}, [expires])

useEffect(() => {
timerRef.current = null
setSubmitted(false)
setWaiting(0)
clearTimeout(timerRef.current)
return () => {
clearTimeout(timerRef.current)
timerRef.current = null
}
}, [expires])

if (!expires || submitted) {
return null
}

return (
<>
<Snackbar
open={waitingTime > 0}
sx={{ bottom: 60 }}
autoHideDuration={waitingTime}
>
<Alert severity="info">
Submitting in: <Countdown initialCount={waitingTime} />
</Alert>
</Snackbar>
<Tooltip title="Smart Submit">
<span style={style}>
<Fab color="primary" disabled={!isValid} onClick={handleClick}>
<Badge color="secondary" badgeContent={<Timer expires={expires} />}>
<Icon>send</Icon>
</Badge>
</Fab>
</span>
</Tooltip>
</>
)
}
4 changes: 4 additions & 0 deletions src/content/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export type FlatPreset = {
}

export const selectors = {
smartSubmit: {
submit: 'app-submit-review-split-button .wf-split-button__main',
what: 'app-review-categorization .review-categorization__option'
},
presets: {
selected: '.wf-rate__star--selected',
quality: 'app-should-be-wayspot .wf-rate > li',
Expand Down
69 changes: 69 additions & 0 deletions src/content/hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
type DevToolsConnect = {
send: () => void
subscribe: () => void
unsubscribe: () => void
init: () => void
error: () => void
}

type DevToolsHook = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
connect: (a: any, b: any, c: any, d: any) => DevToolsConnect
send: () => void
}

declare const window: Window &
typeof globalThis & {
__REDUX_DEVTOOLS_EXTENSION__: DevToolsHook
}

const DEBUG = false

function codeToInject() {
// const oldDevTools = window['__REDUX_DEVTOOLS_EXTENSION__']
const sendMessage =
(type: string) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(...args: any[]) => {
if (DEBUG) console.log(type, args)
if (type === 'connect.subscribe') {
return
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [action, state] = args

window.postMessage(
{ sender: 'wfpu', type, action, state },
window.location.origin,
)
}
const devTools: DevToolsHook = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
connect: function (a: any, b: any, c: any, d: any) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sendMessage('connect')(a, b, c, d)
return {
send: sendMessage('connect.send'),
subscribe: sendMessage('connect.subscribe'),
unsubscribe: sendMessage('connect.unsubscribe'),
init: sendMessage('connect.init'),
error: sendMessage('connect.error'),
}
},
send: sendMessage('send'),
}

window.__REDUX_DEVTOOLS_EXTENSION__ = devTools
}

export const embed = (fn: () => void): void => {
const script = document.createElement('script')
const target = document.head || document.documentElement
script.id = 'wfpu'
script.text = `(${fn.toString()})();`
target.insertBefore(script, target.firstChild)
}

export const hook = (): void => {
embed(codeToInject)
}
5 changes: 3 additions & 2 deletions src/content/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react'
import { render } from 'react-dom'
import { hook } from './hook'
import { WayfarerUltra } from './components/index'

const reactRoot = document.createElement('div')

console.log("content script");
hook();

document.body.insertBefore(reactRoot, document.body.firstElementChild)

render(<WayfarerUltra />, reactRoot)
render(<WayfarerUltra />, reactRoot)
3 changes: 3 additions & 0 deletions src/content/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createContext } from 'use-context-selector'

export const AppContext = createContext({})
Loading

0 comments on commit 759a82a

Please sign in to comment.