adds integrations with bookish-server, the books API and the google books API, adds functionality for library, searching for books, and displaying renters registered in the system
This commit is contained in:
+121
@@ -0,0 +1,121 @@
|
||||
import { getBatch } from './batch'
|
||||
|
||||
// encapsulates the subscription logic for connecting a component to the redux store, as
|
||||
// well as nesting subscriptions of descendant components, so that we can ensure the
|
||||
// ancestor components re-render before descendants
|
||||
|
||||
const nullListeners = { notify() {} }
|
||||
|
||||
function createListenerCollection() {
|
||||
const batch = getBatch()
|
||||
let first = null
|
||||
let last = null
|
||||
|
||||
return {
|
||||
clear() {
|
||||
first = null
|
||||
last = null
|
||||
},
|
||||
|
||||
notify() {
|
||||
batch(() => {
|
||||
let listener = first
|
||||
while (listener) {
|
||||
listener.callback()
|
||||
listener = listener.next
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
get() {
|
||||
let listeners = []
|
||||
let listener = first
|
||||
while (listener) {
|
||||
listeners.push(listener)
|
||||
listener = listener.next
|
||||
}
|
||||
return listeners
|
||||
},
|
||||
|
||||
subscribe(callback) {
|
||||
let isSubscribed = true
|
||||
|
||||
let listener = (last = {
|
||||
callback,
|
||||
next: null,
|
||||
prev: last
|
||||
})
|
||||
|
||||
if (listener.prev) {
|
||||
listener.prev.next = listener
|
||||
} else {
|
||||
first = listener
|
||||
}
|
||||
|
||||
return function unsubscribe() {
|
||||
if (!isSubscribed || first === null) return
|
||||
isSubscribed = false
|
||||
|
||||
if (listener.next) {
|
||||
listener.next.prev = listener.prev
|
||||
} else {
|
||||
last = listener.prev
|
||||
}
|
||||
if (listener.prev) {
|
||||
listener.prev.next = listener.next
|
||||
} else {
|
||||
first = listener.next
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class Subscription {
|
||||
constructor(store, parentSub) {
|
||||
this.store = store
|
||||
this.parentSub = parentSub
|
||||
this.unsubscribe = null
|
||||
this.listeners = nullListeners
|
||||
|
||||
this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
|
||||
}
|
||||
|
||||
addNestedSub(listener) {
|
||||
this.trySubscribe()
|
||||
return this.listeners.subscribe(listener)
|
||||
}
|
||||
|
||||
notifyNestedSubs() {
|
||||
this.listeners.notify()
|
||||
}
|
||||
|
||||
handleChangeWrapper() {
|
||||
if (this.onStateChange) {
|
||||
this.onStateChange()
|
||||
}
|
||||
}
|
||||
|
||||
isSubscribed() {
|
||||
return Boolean(this.unsubscribe)
|
||||
}
|
||||
|
||||
trySubscribe() {
|
||||
if (!this.unsubscribe) {
|
||||
this.unsubscribe = this.parentSub
|
||||
? this.parentSub.addNestedSub(this.handleChangeWrapper)
|
||||
: this.store.subscribe(this.handleChangeWrapper)
|
||||
|
||||
this.listeners = createListenerCollection()
|
||||
}
|
||||
}
|
||||
|
||||
tryUnsubscribe() {
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe()
|
||||
this.unsubscribe = null
|
||||
this.listeners.clear()
|
||||
this.listeners = nullListeners
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Default to a dummy "batch" implementation that just runs the callback
|
||||
function defaultNoopBatch(callback) {
|
||||
callback()
|
||||
}
|
||||
|
||||
let batch = defaultNoopBatch
|
||||
|
||||
// Allow injecting another batching function later
|
||||
export const setBatch = newBatch => (batch = newBatch)
|
||||
|
||||
// Supply a getter just to skip dealing with ESM bindings
|
||||
export const getBatch = () => batch
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @param {any} obj The object to inspect.
|
||||
* @returns {boolean} True if the argument appears to be a plain object.
|
||||
*/
|
||||
export default function isPlainObject(obj) {
|
||||
if (typeof obj !== 'object' || obj === null) return false
|
||||
|
||||
let proto = Object.getPrototypeOf(obj)
|
||||
if (proto === null) return true
|
||||
|
||||
let baseProto = proto
|
||||
while (Object.getPrototypeOf(baseProto) !== null) {
|
||||
baseProto = Object.getPrototypeOf(baseProto)
|
||||
}
|
||||
|
||||
return proto === baseProto
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
/* eslint-disable import/no-unresolved */
|
||||
export { unstable_batchedUpdates } from 'react-dom'
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/* eslint-disable import/no-unresolved */
|
||||
import { unstable_batchedUpdates } from 'react-native'
|
||||
|
||||
export { unstable_batchedUpdates }
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
function is(x, y) {
|
||||
if (x === y) {
|
||||
return x !== 0 || y !== 0 || 1 / x === 1 / y
|
||||
} else {
|
||||
return x !== x && y !== y
|
||||
}
|
||||
}
|
||||
|
||||
export default function shallowEqual(objA, objB) {
|
||||
if (is(objA, objB)) return true
|
||||
|
||||
if (
|
||||
typeof objA !== 'object' ||
|
||||
objA === null ||
|
||||
typeof objB !== 'object' ||
|
||||
objB === null
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
const keysA = Object.keys(objA)
|
||||
const keysB = Object.keys(objB)
|
||||
|
||||
if (keysA.length !== keysB.length) return false
|
||||
|
||||
for (let i = 0; i < keysA.length; i++) {
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
|
||||
!is(objA[keysA[i]], objB[keysA[i]])
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { useEffect, useLayoutEffect } from 'react'
|
||||
|
||||
// React currently throws a warning when using useLayoutEffect on the server.
|
||||
// To get around it, we can conditionally useEffect on the server (no-op) and
|
||||
// useLayoutEffect in the browser. We need useLayoutEffect to ensure the store
|
||||
// subscription callback always has the selector from the latest render commit
|
||||
// available, otherwise a store update may happen between render and the effect,
|
||||
// which may cause missed updates; we also must ensure the store subscription
|
||||
// is created synchronously, otherwise a store update may occur before the
|
||||
// subscription is created and an inconsistent state may be observed
|
||||
|
||||
export const useIsomorphicLayoutEffect =
|
||||
typeof window !== 'undefined' &&
|
||||
typeof window.document !== 'undefined' &&
|
||||
typeof window.document.createElement !== 'undefined'
|
||||
? useLayoutEffect
|
||||
: useEffect
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { useLayoutEffect } from 'react'
|
||||
|
||||
// Under React Native, we know that we always want to use useLayoutEffect
|
||||
|
||||
export const useIsomorphicLayoutEffect = useLayoutEffect
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import isPlainObject from './isPlainObject'
|
||||
import warning from './warning'
|
||||
|
||||
export default function verifyPlainObject(value, displayName, methodName) {
|
||||
if (!isPlainObject(value)) {
|
||||
warning(
|
||||
`${methodName}() in ${displayName} must return a plain object. Instead received ${value}.`
|
||||
)
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Prints a warning in the console if it exists.
|
||||
*
|
||||
* @param {String} message The warning message.
|
||||
* @returns {void}
|
||||
*/
|
||||
export default function warning(message) {
|
||||
/* eslint-disable no-console */
|
||||
if (typeof console !== 'undefined' && typeof console.error === 'function') {
|
||||
console.error(message)
|
||||
}
|
||||
/* eslint-enable no-console */
|
||||
try {
|
||||
// This error was thrown as a convenience so that if you enable
|
||||
// "break on all exceptions" in your console,
|
||||
// it would pause the execution at this line.
|
||||
throw new Error(message)
|
||||
/* eslint-disable no-empty */
|
||||
} catch (e) {}
|
||||
/* eslint-enable no-empty */
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { bindActionCreators } from 'redux'
|
||||
|
||||
export default function wrapActionCreators(actionCreators) {
|
||||
return dispatch => bindActionCreators(actionCreators, dispatch)
|
||||
}
|
||||
Reference in New Issue
Block a user