Skip to content

Fix esm strict #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,11 @@ module.exports = {
extends: [
'@codingame/eslint-config',
'@codingame/eslint-config-react'
]
],
rules: {
'import/extensions': [
'error',
'always'
]
}
}
6 changes: 0 additions & 6 deletions package-lock.json

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

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
"dependencies": {
"@codingame/monaco-languageclient-wrapper": "^5.0.4",
"@rehooks/local-storage": "^2.4.5",
"activity-detector": "^3.0.0",
"react": ">=18.2.0",
"uuid": "^9.0.1"
},
Expand Down
6 changes: 3 additions & 3 deletions src/LanguageClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { createLanguageClientManager, LanguageClientId, StatusChangeEvent as Wra
import { useLocalStorage, writeStorage } from '@rehooks/local-storage'
import { v4 as uuidv4 } from 'uuid'
import { initializePromise } from '@codingame/monaco-editor-wrapper'
import useIsUserActive from './hooks/useIsUserActive'
import useShouldShutdownLanguageClient from './hooks/useShouldShutdownLanguageClient'
import { useLastVersion } from './hooks/useLastVersion'
import useIsUserActive from './hooks/useIsUserActive.js'
import useShouldShutdownLanguageClient from './hooks/useShouldShutdownLanguageClient.js'
import { useLastVersion } from './hooks/useLastVersion.js'

export interface StatusChangeEvent {
status: WrapperStatusChangeEvent['status'] | 'inactivityShutdown'
Expand Down
162 changes: 159 additions & 3 deletions src/hooks/useIsUserActive.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,172 @@
import { useEffect, useState } from 'react'
import createActivityDetector from 'activity-detector'

enum State {
idle = 'idle',
active = 'active'
}

const DEFAULT_INITIAL_STATE = State.active

const DEFAULT_ACTIVITY_EVENTS = [
'click',
'mousemove',
'keydown',
'DOMMouseScroll',
'mousewheel',
'mousedown',
'touchstart',
'touchmove',
'focus'
]

const DEFAULT_INACTIVITY_EVENTS = ['blur', 'visibilitychange']

const DEFAULT_IGNORED_EVENTS_WHEN_IDLE = ['mousemove']

let hidden: 'hidden' | undefined, visibilityChangeEvent: 'visibilitychange' | undefined
if (typeof document.hidden !== 'undefined') {
hidden = 'hidden'
visibilityChangeEvent = 'visibilitychange'
} else {
const prefixes = ['webkit', 'moz', 'ms']
for (let i = 0; i < prefixes.length; i++) {
const prefix = prefixes[i]
if (typeof (document as unknown as Record<string, unknown>)[`${prefix}Hidden`] !== 'undefined') {
hidden = `${prefix}Hidden` as 'hidden'
visibilityChangeEvent = `${prefix}visibilitychange` as 'visibilitychange'
break
}
}
}

interface ActivityDetectorParams {
/**
* Events which force a transition to 'active'
*/
activityEvents?: string[]
/**
* Events which force a transition to 'idle'
*/
inactivityEvents?: string[]
/**
* Events that are ignored in 'idle' state
*/
ignoredEventsWhenIdle?: string[]
/**
* Inactivity time in ms to transition to 'idle'
*/
timeToIdle?: number
/**
* One of 'active' or 'idle'
*/
initialState?: State
autoInit?: boolean
}
function createActivityDetector ({
activityEvents = DEFAULT_ACTIVITY_EVENTS,
inactivityEvents = DEFAULT_INACTIVITY_EVENTS,
ignoredEventsWhenIdle = DEFAULT_IGNORED_EVENTS_WHEN_IDLE,
timeToIdle = 30000,
initialState = DEFAULT_INITIAL_STATE,
autoInit = true
}: ActivityDetectorParams = {}) {
const listeners: Record<State, (() => void)[]> = { [State.active]: [], [State.idle]: [] }
let state: State
let timer: number

const setState = (newState: State) => {
clearTimeout(timer)
if (newState === State.active) {
timer = window.setTimeout(() => setState(State.idle), timeToIdle)
}
if (state !== newState) {
state = newState
listeners[state].forEach(l => l())
}
}

const handleUserActivityEvent = (event: Event) => {
if (state === State.active || ignoredEventsWhenIdle.indexOf(event.type) < 0) {
setState(State.active)
}
}

const handleUserInactivityEvent = () => {
setState(State.idle)
}

const handleVisibilityChangeEvent = () => {
setState(document[hidden!] ? State.idle : State.active)
}

/**
* Starts the activity detector with the given state.
*/
const init = (firstState = DEFAULT_INITIAL_STATE) => {
setState(firstState === State.active ? State.active : State.idle)
activityEvents.forEach(eventName =>
window.addEventListener(eventName, handleUserActivityEvent))

inactivityEvents.filter(eventName => eventName !== 'visibilitychange')
.forEach(eventName =>
window.addEventListener(eventName, handleUserInactivityEvent))

if (inactivityEvents.indexOf('visibilitychange') >= 0 && visibilityChangeEvent != null) {
document.addEventListener(visibilityChangeEvent, handleVisibilityChangeEvent)
}
}

/**
* Register an event listener for the required event
*/
const on = (eventName: State, listener: () => void) => {
listeners[eventName].push(listener)
const off = () => {
const index = listeners[eventName].indexOf(listener)
if (index >= 0) {
listeners[eventName].splice(index, 1)
}
}
return off
}

/**
* Stops the activity detector and clean the listeners
*/
const stop = () => {
listeners[State.active] = []
listeners[State.idle] = []

clearTimeout(timer)

activityEvents.forEach(eventName =>
window.removeEventListener(eventName, handleUserActivityEvent))

inactivityEvents.forEach(eventName =>
window.removeEventListener(eventName, handleUserInactivityEvent))

if (visibilityChangeEvent != null) {
document.removeEventListener(visibilityChangeEvent, handleVisibilityChangeEvent)
}
}

if (autoInit) {
init(initialState)
}

return { on, stop, init }
}

export default function useIsUserActive (timeToIdle: number): boolean {
const [active, setActive] = useState(true)
useEffect(() => {
const activityDetector = createActivityDetector({
timeToIdle
})
activityDetector.on('idle', () => {
activityDetector.on(State.idle, () => {
setActive(false)
})
activityDetector.on('active', () => {
activityDetector.on(State.active, () => {
setActive(true)
})
return () => {
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CodinGameInfrastructure, Infrastructure, LanguageClientId, loadExtensionConfigurations, registerLanguageClient, WorkspaceFolder } from '@codingame/monaco-languageclient-wrapper'
import LanguageClient, { LanguageClientProps, StatusChangeEvent } from './LanguageClient'
import LanguageClient, { LanguageClientProps, StatusChangeEvent } from './LanguageClient.js'

export default LanguageClient
export {
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"sourceMap": true,
"target": "ES2022"
},
"include": ["src"],
"include": ["src", ".eslintrc.cjs"],
"exclude": [
"dist",
"node_modules"
Expand Down