Skip to content

Instantly share code, notes, and snippets.

@pardeike
Created May 10, 2020 00:19
Show Gist options
  • Save pardeike/9f95245050b2eb4a2edce1ff6ff7ca6a to your computer and use it in GitHub Desktop.
Save pardeike/9f95245050b2eb4a2edce1ff6ff7ca6a to your computer and use it in GitHub Desktop.
IQ Test
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!--<script type="text/babel" src="https://raw.githubusercontent.com/avkonst/hookstate/master/dist/index.js"></script>-->
<script type="text/babel">
'use strict';
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
/**
* @experimental
*/
var None = Symbol('none');
function createStateLink(initial, transform) {
var stateLink = createState(initial).accessUnmounted();
if (createStateLink[DevToolsID]) {
stateLink.with(createStateLink[DevToolsID]);
}
if (transform) {
return stateLink.wrap(transform);
}
return stateLink;
}
function useStateLink(source, transform) {
var _a = source instanceof StateLinkImpl ?
[source, transform] :
source instanceof WrappedStateLinkImpl ?
[source.state, source.transform] :
[undefined, transform], parentLink = _a[0], tf = _a[1];
if (parentLink) {
if (parentLink.onUpdateUsed === NoActionOnUpdate) {
// Global state mount
// eslint-disable-next-line react-hooks/rules-of-hooks
var _b = React.useState({ state: parentLink.state }), value_1 = _b[0], setValue_1 = _b[1];
var link = useSubscribedStateLink(value_1.state, parentLink.path, function () { return setValue_1({ state: value_1.state }); }, value_1.state, undefined);
return tf ? injectTransform(link, tf) : link;
}
else {
// Scoped state mount
// eslint-disable-next-line react-hooks/rules-of-hooks
var _c = React.useState({}), setValue_2 = _c[1];
var link = useSubscribedStateLink(parentLink.state, parentLink.path, function () { return setValue_2({}); }, parentLink, parentLink.isDowngraded);
return tf ? injectTransform(link, tf) : link;
}
}
else {
// Local state mount
// eslint-disable-next-line react-hooks/rules-of-hooks
var _d = React.useState(function () { return ({ state: createState(source) }); }), value_2 = _d[0], setValue_3 = _d[1];
var link = useSubscribedStateLink(value_2.state, RootPath, function () { return setValue_3({ state: value_2.state }); }, value_2.state, undefined);
React.useEffect(function () { return function () { return value_2.state.destroy(); }; }, []);
if (useStateLink[DevToolsID]) {
link.with(useStateLink[DevToolsID]);
}
return tf ? injectTransform(link, tf) : link;
}
}
function StateFragment(props) {
var scoped = useStateLink(props.state, props.transform);
return props.children(scoped);
}
/**
* It is used in combination with [StateLink.wrap](#wrap).
* It minimises rerendering for states reduced down to a comparable values.
*
* @param transform the original transform function for [StateLink.wrap](#wrap).
* The first argument is a state link to wrap.
* The second argument, if available,
* is the previous result returned by the function.
*
* @param equals a function which compares the next and the previous
* wrapped state values and return true, if there is no change. By default,
* it is shallow triple-equal comparison, i.e. `===`.
*/
function StateMemo(transform, equals) {
return function (link, prev) {
link[StateMemoID] = equals || (function (n, p) { return (n === p); });
return transform(link, prev);
};
}
/**
* A plugin which allows to opt-out from usage of Javascript proxies for
* state usage tracking. It is useful for performance tuning. For example:
*
* ```tsx
* const globalState = createStateLink(someLargeObject as object)
* const MyComponent = () => {
* const state = useStateLink(globalState)
* .with(Downgraded); // the whole state will be used
* // by this component, so no point
* // to track usage of individual properties
* return <>{JSON.stringify(state.value)}</>
* }
* ```
*/
function Downgraded() {
return {
id: DowngradedID,
create: function () { return ({}); }
};
}
/**
* For plugin developers only.
* Reserved plugin ID for developers tools extension.
*
* @hidden
* @ignore
*/
var DevToolsID = Symbol('DevTools');
/**
* Returns access to the development tools for a given state.
* Development tools are delivered as optional plugins.
* You can activate development tools from `@hookstate/devtools`package,
* for example. If no development tools are activated,
* it returns an instance of dummy tools, which do nothing, when called.
*
* @param state A state to relate to the extension.
*
* @returns Interface to interact with the development tools for a given state.
*/
function DevTools(state) {
var plugin = state.with(DevToolsID, function () { return undefined; });
if (plugin) {
return plugin[1];
}
return EmptyDevToolsExtensions;
}
///
/// INTERNAL SYMBOLS (LIBRARY IMPLEMENTATION)
///
var EmptyDevToolsExtensions = {
label: function () { },
log: function () { }
};
var StateLinkInvalidUsageError = /** @class */ (function (_super) {
__extends(StateLinkInvalidUsageError, _super);
function StateLinkInvalidUsageError(op, path, hint) {
return _super.call(this, "StateLink is used incorrectly. Attempted '" + op + "' at '/" + path.join('/') + "'" +
(hint ? ". Hint: " + hint : '')) || this;
}
return StateLinkInvalidUsageError;
}(Error));
function extractSymbol(s) {
var result = s.toString();
var symstr = 'Symbol(';
if (result.startsWith(symstr) && result.endsWith(')')) {
result = result.substring(symstr.length, result.length - 1);
}
return result;
}
var PluginUnknownError = /** @class */ (function (_super) {
__extends(PluginUnknownError, _super);
function PluginUnknownError(s) {
return _super.call(this, "Plugin '" + extractSymbol(s) + "' has not been attached to the StateInf or StateLink. " +
"Hint: you might need to register the required plugin using 'with' method. " +
"See https://github.com/avkonst/hookstate#plugins for more details") || this;
}
return PluginUnknownError;
}(Error));
var DowngradedID = Symbol('Downgraded');
var StateMemoID = Symbol('StateMemo');
var ProxyMarkerID = Symbol('ProxyMarker');
var RootPath = [];
var DestroyedEdition = -1;
var State = /** @class */ (function () {
function State(_value) {
this._value = _value;
this._edition = 0;
this._subscribers = new Set();
this._setSubscribers = new Set();
this._destroySubscribers = new Set();
this._batchStartSubscribers = new Set();
this._batchFinishSubscribers = new Set();
this._plugins = new Map();
this._batches = 0;
if (typeof _value === 'object' &&
Promise.resolve(_value) === _value) {
this._promised = this.createPromised(_value);
this._value = None;
}
else if (_value === None) {
this._promised = this.createPromised(undefined);
}
}
State.prototype.createPromised = function (newValue) {
var _this = this;
var promised = new Promised(newValue ? Promise.resolve(newValue) : undefined, function (r) {
if (_this.promised === promised && _this.edition !== DestroyedEdition) {
_this._promised = undefined;
_this.set(RootPath, r, undefined);
_this.update([RootPath]);
}
}, function () {
if (_this.promised === promised && _this.edition !== DestroyedEdition) {
_this._edition += 1;
_this.update([RootPath]);
}
}, function () {
if (_this._batchesPendingActions &&
_this._value !== None &&
_this.edition !== DestroyedEdition) {
var actions = _this._batchesPendingActions;
_this._batchesPendingActions = undefined;
actions.forEach(function (a) { return a(); });
}
});
return promised;
};
Object.defineProperty(State.prototype, "edition", {
get: function () {
return this._edition;
},
enumerable: true,
configurable: true
});
Object.defineProperty(State.prototype, "promised", {
get: function () {
return this._promised;
},
enumerable: true,
configurable: true
});
State.prototype.get = function (path) {
var result = this._value;
if (result === None) {
return result;
}
path.forEach(function (p) {
result = result[p];
});
return result;
};
State.prototype.set = function (path, value, mergeValue) {
if (this._edition < 0) {
// TODO convert to warning
throw new StateLinkInvalidUsageError("set state for the destroyed state", path, 'make sure all asynchronous operations are cancelled (unsubscribed) when the state is destroyed. ' +
'Global state is explicitly destroyed at \'StateInf.destroy()\'. ' +
'Local state is automatically destroyed when a component is unmounted.');
}
if (path.length === 0) {
// Root value UPDATE case,
var onSetArg = {
path: path,
state: value,
value: value,
previous: this._value,
merged: mergeValue
};
if (value === None) {
this._promised = this.createPromised(undefined);
delete onSetArg.value;
delete onSetArg.state;
}
else if (typeof value === 'object' && Promise.resolve(value) === value) {
this._promised = this.createPromised(value);
value = None;
delete onSetArg.value;
delete onSetArg.state;
}
else if (this._promised && !this._promised.resolver) {
// TODO add hint
throw new StateLinkInvalidUsageError("write promised state", path, '');
}
var prevValue = this._value;
if (prevValue === None) {
delete onSetArg.previous;
}
this._value = value;
this.afterSet(onSetArg);
if (prevValue === None && this._value !== None &&
this.promised && this.promised.resolver) {
this.promised.resolver();
}
return path;
}
if (typeof value === 'object' && Promise.resolve(value) === value) {
throw new StateLinkInvalidUsageError(
// TODO add hint
'set promise for nested property', path, '');
}
var target = this._value;
for (var i = 0; i < path.length - 1; i += 1) {
target = target[path[i]];
}
var p = path[path.length - 1];
if (p in target) {
if (value !== None) {
// Property UPDATE case
var prevValue = target[p];
target[p] = value;
this.afterSet({
path: path,
state: this._value,
value: value,
previous: prevValue,
merged: mergeValue
});
return path;
}
else {
// Property DELETE case
var prevValue = target[p];
if (Array.isArray(target) && typeof p === 'number') {
target.splice(p, 1);
}
else {
delete target[p];
}
this.afterSet({
path: path,
state: this._value,
previous: prevValue,
merged: mergeValue
});
// if an array of object is about to loose existing property
// we consider it is the whole object is changed
// which is identified by upper path
return path.slice(0, -1);
}
}
if (value !== None) {
// Property INSERT case
target[p] = value;
this.afterSet({
path: path,
state: this._value,
value: value,
merged: mergeValue
});
// if an array of object is about to be extended by new property
// we consider it is the whole object is changed
// which is identified by upper path
return path.slice(0, -1);
}
// Non-existing property DELETE case
// no-op
return path;
};
State.prototype.update = function (paths) {
if (this._batches) {
this._batchesPendingPaths = this._batchesPendingPaths || [];
this._batchesPendingPaths = this._batchesPendingPaths.concat(paths);
return;
}
var actions = [];
this._subscribers.forEach(function (s) { return s.onSet(paths, actions); });
actions.forEach(function (a) { return a(); });
};
State.prototype.afterSet = function (params) {
if (this._edition !== DestroyedEdition) {
this._edition += 1;
this._setSubscribers.forEach(function (cb) { return cb(params); });
}
};
State.prototype.startBatch = function (path, options) {
this._batches += 1;
var cbArgument = {
path: path
};
if (options && 'context' in options) {
cbArgument.context = options.context;
}
if (this._value !== None) {
cbArgument.state = this._value;
}
this._batchStartSubscribers.forEach(function (cb) { return cb(cbArgument); });
};
State.prototype.finishBatch = function (path, options) {
var cbArgument = {
path: path
};
if (options && 'context' in options) {
cbArgument.context = options.context;
}
if (this._value !== None) {
cbArgument.state = this._value;
}
this._batchFinishSubscribers.forEach(function (cb) { return cb(cbArgument); });
this._batches -= 1;
if (this._batches === 0) {
if (this._batchesPendingPaths) {
var paths = this._batchesPendingPaths;
this._batchesPendingPaths = undefined;
this.update(paths);
}
}
};
State.prototype.postponeBatch = function (action) {
this._batchesPendingActions = this._batchesPendingActions || [];
this._batchesPendingActions.push(action);
};
State.prototype.getPlugin = function (pluginId) {
return this._plugins.get(pluginId);
};
State.prototype.register = function (plugin) {
var existingInstance = this._plugins.get(plugin.id);
if (existingInstance) {
return;
}
var pluginCallbacks = plugin.create(this.accessUnmounted());
this._plugins.set(plugin.id, pluginCallbacks);
if (pluginCallbacks.onSet) {
this._setSubscribers.add(function (p) { return pluginCallbacks.onSet(p); });
}
if (pluginCallbacks.onDestroy) {
this._destroySubscribers.add(function (p) { return pluginCallbacks.onDestroy(p); });
}
if (pluginCallbacks.onBatchStart) {
this._batchStartSubscribers.add(function (p) { return pluginCallbacks.onBatchStart(p); });
}
if (pluginCallbacks.onBatchFinish) {
this._batchFinishSubscribers.add(function (p) { return pluginCallbacks.onBatchFinish(p); });
}
};
State.prototype.accessUnmounted = function () {
return new StateLinkImpl(this, RootPath, NoActionOnUpdate, this.get(RootPath), this.edition
// TODO downgraded plugin should not be used here as it affects all inherited links (which is temporary fixed in the useStateLink)
// instead optimisations are possible based on checks of onUpdateUsed === NoActionOnUpdate
).with(Downgraded); // it does not matter how it is used, it is not subscribed anyway
};
State.prototype.subscribe = function (l) {
this._subscribers.add(l);
};
State.prototype.unsubscribe = function (l) {
this._subscribers.delete(l);
};
State.prototype.destroy = function () {
var _this = this;
this._destroySubscribers.forEach(function (cb) { return cb(_this._value !== None ? { state: _this._value } : {}); });
this._edition = DestroyedEdition;
};
State.prototype.toJSON = function () {
throw new StateLinkInvalidUsageError('toJSON()', RootPath, 'did you mean to use JSON.stringify(state.get()) instead of JSON.stringify(state)?');
};
return State;
}());
var SynteticID = Symbol('SynteticTypeInferenceMarker');
var ValueCache = Symbol('ValueCache');
var NestedCache = Symbol('NestedCache');
var UnmountedCallback = Symbol('UnmountedCallback');
var NoActionOnUpdate = function () { };
NoActionOnUpdate[UnmountedCallback] = true;
var WrappedStateLinkImpl = /** @class */ (function () {
function WrappedStateLinkImpl(state, transform) {
this.state = state;
this.transform = transform;
// tslint:disable-next-line: variable-name
this.__synteticTypeInferenceMarkerInf = SynteticID;
}
WrappedStateLinkImpl.prototype.access = function () {
return this.transform(this.state, undefined);
};
WrappedStateLinkImpl.prototype.with = function (plugin) {
this.state.with(plugin);
return this;
};
WrappedStateLinkImpl.prototype.wrap = function (transform) {
var _this = this;
return new WrappedStateLinkImpl(this.state, function (s, p) {
return transform(_this.transform(s, undefined), p);
});
};
WrappedStateLinkImpl.prototype.destroy = function () {
this.state.destroy();
};
return WrappedStateLinkImpl;
}());
var Promised = /** @class */ (function () {
function Promised(promise, onResolve, onReject, onPostResolve) {
var _this = this;
this.promise = promise;
if (!promise) {
promise = new Promise(function (resolve) {
_this.resolver = resolve;
});
}
this.promise = promise
.then(function (r) {
_this.fullfilled = true;
if (!_this.resolver) {
onResolve(r);
}
})
.catch(function (err) {
_this.fullfilled = true;
_this.error = err;
onReject();
})
.then(function () { return onPostResolve(); });
}
return Promised;
}());
var StateLinkImpl = /** @class */ (function () {
function StateLinkImpl(state, path, onUpdateUsed, valueSource, valueEdition) {
this.state = state;
this.path = path;
this.onUpdateUsed = onUpdateUsed;
this.valueSource = valueSource;
this.valueEdition = valueEdition;
}
StateLinkImpl.prototype.getUntracked = function (allowPromised) {
if (this.valueEdition !== this.state.edition) {
this.valueSource = this.state.get(this.path);
this.valueEdition = this.state.edition;
if (this.onUpdateUsed[UnmountedCallback]) {
// this link is not mounted to a component
// for example, it might be global link or
// a link which has been discarded after rerender
// but still captured by some callback or an effect
delete this[ValueCache];
delete this[NestedCache];
}
else {
// this link is still mounted to a component
// populate cache again to ensure correct tracking of usage
// when React scans which states to rerender on update
if (ValueCache in this) {
delete this[ValueCache];
this.get(true);
}
if (NestedCache in this) {
delete this[NestedCache];
// tslint:disable-next-line no-unused-expression
this.nested; // trigger call to mark 'nested' as used again
}
}
}
if (this.valueSource === None && !allowPromised) {
if (this.state.promised && this.state.promised.error) {
throw this.state.promised.error;
}
// TODO add hint
throw new StateLinkInvalidUsageError('read promised state', this.path);
}
return this.valueSource;
};
StateLinkImpl.prototype.get = function (allowPromised) {
var currentValue = this.getUntracked(allowPromised);
if (this[ValueCache] === undefined) {
if (this.isDowngraded) {
this[ValueCache] = currentValue;
}
else if (Array.isArray(currentValue)) {
this[ValueCache] = this.valueArrayImpl(currentValue);
}
else if (typeof currentValue === 'object' && currentValue !== null) {
this[ValueCache] = this.valueObjectImpl(currentValue);
}
else {
this[ValueCache] = currentValue;
}
}
return this[ValueCache];
};
Object.defineProperty(StateLinkImpl.prototype, "value", {
get: function () {
return this.get();
},
enumerable: true,
configurable: true
});
Object.defineProperty(StateLinkImpl.prototype, "promised", {
get: function () {
var currentValue = this.get(true); // marks used
if (currentValue === None && this.state.promised && !this.state.promised.fullfilled) {
return true;
}
return false;
},
enumerable: true,
configurable: true
});
Object.defineProperty(StateLinkImpl.prototype, "error", {
get: function () {
var currentValue = this.get(true); // marks used
if (currentValue === None) {
if (this.state.promised && this.state.promised.fullfilled) {
return this.state.promised.error;
}
this.get(); // will throw 'read while promised' exception
}
return undefined;
},
enumerable: true,
configurable: true
});
StateLinkImpl.prototype.setUntracked = function (newValue, mergeValue) {
if (typeof newValue === 'function') {
newValue = newValue(this.getUntracked());
}
if (typeof newValue === 'object' && newValue !== null && newValue[ProxyMarkerID]) {
throw new StateLinkInvalidUsageError("set(state.get() at '/" + newValue[ProxyMarkerID].path.join('/') + "')", this.path, 'did you mean to use state.set(lodash.cloneDeep(value)) instead of state.set(value)?');
}
return this.state.set(this.path, newValue, mergeValue);
};
StateLinkImpl.prototype.set = function (newValue) {
this.state.update([this.setUntracked(newValue)]);
};
StateLinkImpl.prototype.mergeUntracked = function (sourceValue) {
var currentValue = this.getUntracked();
if (typeof sourceValue === 'function') {
sourceValue = sourceValue(currentValue);
}
var updatedPath;
var deletedOrInsertedProps = false;
if (Array.isArray(currentValue)) {
if (Array.isArray(sourceValue)) {
return [this.setUntracked(currentValue.concat(sourceValue), sourceValue)];
}
else {
var deletedIndexes_1 = [];
Object.keys(sourceValue).sort().forEach(function (i) {
var index = Number(i);
var newPropValue = sourceValue[index];
if (newPropValue === None) {
deletedOrInsertedProps = true;
deletedIndexes_1.push(index);
}
else {
deletedOrInsertedProps = deletedOrInsertedProps || !(index in currentValue);
currentValue[index] = newPropValue;
}
});
// indexes are ascending sorted as per above
// so, delete one by one from the end
// this way index positions do not change
deletedIndexes_1.reverse().forEach(function (p) {
currentValue.splice(p, 1);
});
updatedPath = this.setUntracked(currentValue, sourceValue);
}
}
else if (typeof currentValue === 'object' && currentValue !== null) {
Object.keys(sourceValue).forEach(function (key) {
var newPropValue = sourceValue[key];
if (newPropValue === None) {
deletedOrInsertedProps = true;
delete currentValue[key];
}
else {
deletedOrInsertedProps = deletedOrInsertedProps || !(key in currentValue);
currentValue[key] = newPropValue;
}
});
updatedPath = this.setUntracked(currentValue, sourceValue);
}
else if (typeof currentValue === 'string') {
return [this.setUntracked((currentValue + String(sourceValue)))];
}
else {
return [this.setUntracked(sourceValue)];
}
if (updatedPath !== this.path || deletedOrInsertedProps) {
return [updatedPath];
}
return Object.keys(sourceValue).map(function (p) { return updatedPath.slice().concat(p); });
};
StateLinkImpl.prototype.merge = function (sourceValue) {
this.state.update(this.mergeUntracked(sourceValue));
};
StateLinkImpl.prototype.batch = function (action, options) {
var _this = this;
if (this.promised) {
var ifPromised = options && options.ifPromised || 'reject';
if (ifPromised === 'postpone') {
return this.state.postponeBatch(function () { return _this.batch(action, options); });
}
if (ifPromised === 'discard') {
return;
}
if (ifPromised === 'reject') {
this.get(); // this will throw (default behavior)
}
}
try {
this.state.startBatch(this.path, options);
action(this);
}
finally {
this.state.finishBatch(this.path, options);
}
};
StateLinkImpl.prototype.update = function (paths) {
this.state.update(paths);
};
StateLinkImpl.prototype.denull = function () {
var value = this.get();
if (value === null || value === undefined) {
return value;
}
return this;
};
StateLinkImpl.prototype.with = function (plugin, alt) {
if (typeof plugin === 'function') {
var pluginMeta = plugin();
if (pluginMeta.id === DowngradedID) {
this.isDowngraded = true;
return this;
}
this.state.register(pluginMeta);
return this;
}
else {
var instance = this.state.getPlugin(plugin);
if (instance) {
return [this, instance];
}
if (alt) {
return alt();
}
throw new PluginUnknownError(plugin);
}
};
StateLinkImpl.prototype.access = function () {
return this;
};
StateLinkImpl.prototype.wrap = function (transform) {
return new WrappedStateLinkImpl(this, transform);
};
StateLinkImpl.prototype.destroy = function () {
this.state.destroy();
};
StateLinkImpl.prototype.subscribe = function (l) {
if (this.subscribers === undefined) {
this.subscribers = new Set();
}
this.subscribers.add(l);
};
StateLinkImpl.prototype.unsubscribe = function (l) {
this.subscribers.delete(l);
};
StateLinkImpl.prototype.onSet = function (paths, actions) {
this.updateIfUsed(paths, actions);
};
StateLinkImpl.prototype.updateIfUsed = function (paths, actions) {
var _this = this;
var update = function () {
if (_this.isDowngraded &&
(ValueCache in _this || NestedCache in _this)) {
actions.push(_this.onUpdateUsed);
return true;
}
for (var _i = 0, paths_1 = paths; _i < paths_1.length; _i++) {
var path = paths_1[_i];
var firstChildKey = path[_this.path.length];
if (firstChildKey === undefined) {
if (ValueCache in _this || NestedCache in _this) {
actions.push(_this.onUpdateUsed);
return true;
}
}
else {
var firstChildValue = _this.nestedLinksCache && _this.nestedLinksCache[firstChildKey];
if (firstChildValue && firstChildValue.updateIfUsed(paths, actions)) {
return true;
}
}
}
return false;
};
var updated = update();
if (!updated && this.subscribers !== undefined) {
this.subscribers.forEach(function (s) {
s.onSet(paths, actions);
});
}
return updated;
};
Object.defineProperty(StateLinkImpl.prototype, "keys", {
get: function () {
var value = this.get();
if (Array.isArray(value)) {
return Object.keys(value).map(function (i) { return Number(i); }).filter(function (i) { return Number.isInteger(i); });
}
if (typeof value === 'object' && value !== null) {
return Object.keys(value);
}
return undefined;
},
enumerable: true,
configurable: true
});
Object.defineProperty(StateLinkImpl.prototype, "nested", {
get: function () {
var currentValue = this.getUntracked();
if (this[NestedCache] === undefined) {
if (Array.isArray(currentValue)) {
this[NestedCache] = this.nestedArrayImpl(currentValue);
}
else if (typeof currentValue === 'object' && currentValue !== null) {
this[NestedCache] = this.nestedObjectImpl(currentValue);
}
else {
this[NestedCache] = undefined;
}
}
return this[NestedCache];
},
enumerable: true,
configurable: true
});
StateLinkImpl.prototype.nestedArrayImpl = function (currentValue) {
var _this = this;
this.nestedLinksCache = this.nestedLinksCache || {};
var proxyGetterCache = this.nestedLinksCache;
var getter = function (target, key) {
if (key === 'length') {
return target.length;
}
if (key in Array.prototype) {
return Array.prototype[key];
}
if (key === ProxyMarkerID) {
return _this;
}
if (typeof key === 'symbol') {
return undefined;
}
var index = Number(key);
if (!Number.isInteger(index)) {
return undefined;
}
var cachehit = proxyGetterCache[index];
if (cachehit) {
return cachehit;
}
var r = new StateLinkImpl(_this.state, _this.path.slice().concat(index), _this.onUpdateUsed, target[index], _this.valueEdition);
if (_this.isDowngraded) {
r.isDowngraded = true;
}
proxyGetterCache[index] = r;
return r;
};
return this.proxyWrap(currentValue, getter);
};
StateLinkImpl.prototype.valueArrayImpl = function (currentValue) {
var _this = this;
return this.proxyWrap(currentValue, function (target, key) {
if (key === 'length') {
return target.length;
}
if (key in Array.prototype) {
return Array.prototype[key];
}
if (key === ProxyMarkerID) {
return _this;
}
if (typeof key === 'symbol') {
// allow clients to associate hidden cache with state values
return target[key];
}
var index = Number(key);
if (!Number.isInteger(index)) {
return undefined;
}
return (_this.nested)[index].get();
}, function (target, key, value) {
if (typeof key === 'symbol') {
// allow clients to associate hidden cache with state values
target[key] = value;
return true;
}
throw new StateLinkInvalidUsageError('set', _this.path, "did you mean to use 'state.nested[" + key + "].set(value)' instead of 'state[" + key + "] = value'?");
});
};
StateLinkImpl.prototype.nestedObjectImpl = function (currentValue) {
var _this = this;
this.nestedLinksCache = this.nestedLinksCache || {};
var proxyGetterCache = this.nestedLinksCache;
var getter = function (target, key) {
if (key === ProxyMarkerID) {
return _this;
}
if (typeof key === 'symbol') {
return undefined;
}
var cachehit = proxyGetterCache[key];
if (cachehit) {
return cachehit;
}
var r = new StateLinkImpl(_this.state, _this.path.slice().concat(key.toString()), _this.onUpdateUsed, target[key], _this.valueEdition);
if (_this.isDowngraded) {
r.isDowngraded = true;
}
proxyGetterCache[key] = r;
return r;
};
return this.proxyWrap(currentValue, getter);
};
StateLinkImpl.prototype.valueObjectImpl = function (currentValue) {
var _this = this;
return this.proxyWrap(currentValue, function (target, key) {
if (key === ProxyMarkerID) {
return _this;
}
if (typeof key === 'symbol') {
// allow clients to associate hidden cache with state values
return target[key];
}
return (_this.nested)[key].value;
}, function (target, key, value) {
if (typeof key === 'symbol') {
// allow clients to associate hidden cache with state values
target[key] = value;
return true;
}
throw new StateLinkInvalidUsageError('set', _this.path, "did you mean to use 'state.nested." + key + ".set(value)' instead of 'state." + key + " = value'?");
});
};
// tslint:disable-next-line: no-any
StateLinkImpl.prototype.proxyWrap = function (objectToWrap,
// tslint:disable-next-line: no-any
getter,
// tslint:disable-next-line: no-any
setter) {
var _this = this;
var onInvalidUsage = function (op) {
throw new StateLinkInvalidUsageError(op, _this.path);
};
return new Proxy(objectToWrap, {
getPrototypeOf: function (target) {
return Object.getPrototypeOf(target);
},
setPrototypeOf: function (target, v) {
return onInvalidUsage('setPrototypeOf');
},
isExtensible: function (target) {
// should satisfy the invariants:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/
// Reference/Global_Objects/Proxy/handler/isExtensible#Invariants
return Object.isExtensible(target);
},
preventExtensions: function (target) {
return onInvalidUsage('preventExtensions');
},
getOwnPropertyDescriptor: function (target, p) {
var origin = Object.getOwnPropertyDescriptor(target, p);
if (origin && Array.isArray(target) && p in Array.prototype) {
return origin;
}
return origin && {
configurable: true,
enumerable: origin.enumerable,
get: function () { return getter(target, p); },
set: undefined
};
},
has: function (target, p) {
if (typeof p === 'symbol') {
return false;
}
return p in target;
},
get: getter,
set: setter || (function (target, p, value, receiver) {
return onInvalidUsage('set');
}),
deleteProperty: function (target, p) {
return onInvalidUsage('deleteProperty');
},
defineProperty: function (target, p, attributes) {
return onInvalidUsage('defineProperty');
},
enumerate: function (target) {
if (Array.isArray(target)) {
return Object.keys(target).concat('length');
}
return Object.keys(target);
},
ownKeys: function (target) {
if (Array.isArray(target)) {
return Object.keys(target).concat('length');
}
return Object.keys(target);
},
apply: function (target, thisArg, argArray) {
return onInvalidUsage('apply');
},
construct: function (target, argArray, newTarget) {
return onInvalidUsage('construct');
}
});
};
return StateLinkImpl;
}());
function createState(initial) {
var initialValue = initial;
if (typeof initial === 'function') {
initialValue = initial();
}
if (typeof initialValue === 'object' && initialValue[ProxyMarkerID]) {
throw new StateLinkInvalidUsageError("create/useStateLink(state.get() at '/" + initialValue[ProxyMarkerID].path.join('/') + "')", RootPath, 'did you mean to use create/useStateLink(state) OR ' +
'create/useStateLink(lodash.cloneDeep(state.get())) instead of create/useStateLink(state.get())?');
}
return new State(initialValue);
}
function useSubscribedStateLink(state, path, update, subscribeTarget, disabledTracking) {
var link = new StateLinkImpl(state, path, update, state.get(path), state.edition);
if (disabledTracking) {
link.with(Downgraded);
}
React.useEffect(function () {
subscribeTarget.subscribe(link);
return function () {
link.onUpdateUsed[UnmountedCallback] = true;
subscribeTarget.unsubscribe(link);
};
});
return link;
}
function injectTransform(link, transform) {
if (link.onUpdateUsed[UnmountedCallback]) {
// this is unmounted link
return transform(link, undefined);
}
var injectedOnUpdateUsed = undefined;
var originOnUpdateUsed = link.onUpdateUsed;
link.onUpdateUsed = function () {
if (injectedOnUpdateUsed) {
return injectedOnUpdateUsed();
}
return originOnUpdateUsed();
};
var result = transform(link, undefined);
var stateMemoEquals = link[StateMemoID];
if (stateMemoEquals === undefined) {
return result;
}
delete link[StateMemoID];
injectedOnUpdateUsed = function () {
var updatedResult = transform(link, result);
// if result is not changed, it does not affect the rendering result too
// so, we skip triggering rerendering in this case
if (!stateMemoEquals(updatedResult, result)) {
originOnUpdateUsed();
}
};
return result;
}
/**
* @hidden
* @ignore
* @internal
* @deprecated use source directly or source.wrap(transform).access() instead
*/
function useStateLinkUnmounted(source, transform) {
if (source instanceof WrappedStateLinkImpl) {
return source.access();
}
if (transform) {
return transform(source);
}
return source;
}
</script>
</head>
<style>
* {
padding: 0;
margin: 0;
}
.grid {
display: inline-block;
border: 1px solid black;
padding: 2px;
margin: 10px;
position: relative;
}
.cell {
display: inline-block;
width: 60px;
height: 60px;
background-color: lightgreen;
}
.success {
position: absolute;
left: 40%;
top: 40%;
width: 20%;
height: 20%;
background-color: green;
color: white;
border: 2px solid white;
font-size: 2em;
line-height: 2.6em;
font-family: sans-serif;
font-weight: bolder;
text-align: center;
cursor: pointer;
}
.failure {
position: relative;
margin: 10px;
margin-left: auto;
margin-right: auto;
width: 50%;
background-color: gray;
color: white;
border: 2px solid white;
font-size: 2em;
line-height: 2.6em;
font-family: sans-serif;
font-weight: bolder;
text-align: center;
cursor: pointer;
}
.col-1 { background-color: gray }
.col0 { background-color: black; }
.col1 { background-color: lightgreen; }
.col2 { background-color: lightcoral; }
.col3 { background-color: lightskyblue; }
.col4 { background-color: lightgray; }
.col5 { background-color: lightsalmon; }
.col6 { background-color: lightpink; }
.col7 { background-color: lightseagreen; }
.col8 { background-color: peru; }
.col9 { background-color: greenyellow }
.col10 { background-color: khaki }
</style>
<body>
<div id="root"></div>
<script type="text/babel">
const n = 7
const nc = 10
const setGrid = (grid, cell, v) => { grid[cell.y][cell.x] = v }
const dirs = [{x:-1,y:0}, {x:0,y:-1}, {x:1,y:0}, {x:0,y:1}]
const Initialize = () => {
const rows = []
for(let y = 0; y < n; y++) {
const cols = []
for(let x = 0; x < n; x++)
cols.push(0)
rows.push(cols)
}
return rows
}
const Copy = (grid) => {
const newGrid = []
for(let y = 0; y < n; y++)
newGrid[y] = grid[y].slice()
return newGrid
}
const Rnd = (m) => Math.floor(Math.random() * (m ? m : n))
const Inside = (cell) => cell.x >=0 && cell.x < n && cell.y >= 0 && cell.y < n
const RndDir = () => dirs[Rnd(4)]
const FindCell = (grid, cell, negate) => {
let d = Rnd(4)
for(let i = 0; i < 4; i++) {
const c2 = Offset(cell, dirs[(d + i) % 4])
if (Inside(c2) && (!!negate ^ grid[c2.y][c2.x] == 0)) return c2
}
return undefined
}
const NextCell = (grid, cell) => {
for(let i = 0; i < n * n; i++) {
if (grid[cell.y][cell.x] == 0) return cell
cell.x++
if (cell.x == n) { cell.x = 0; cell.y++ }
if (cell.y == n) { cell.y = 0 }
}
return undefined
}
const RndCell = (grid) => NextCell(grid, { x: Rnd(), y: Rnd() })
const Offset = (cell, dir) => ({ x: cell.x + dir.x, y: (cell.y + dir.y) })
const RemoveShape = (grid, c) => {
for(let x = 0; x < n; x++)
for(let y = 0; y < n; y++)
if (grid[y][x] == c) grid[y][x] = 0
}
const AddShapes = (grid) => {
for(let c = 1; c <= nc; c++) {
let cell = RndCell(grid)
if (!cell) break
let s = Rnd(7)
for(let i = 1; i <= s; i++) {
setGrid(grid, cell, c)
const c2 = FindCell(grid, cell)
if(!c2) break
cell = c2
}
}
for(let x = 0; x < n; x++)
for(let y = 0; y < n; y++) {
if (grid[y][x] > 0) continue
const c2 = FindCell(grid, {x, y}, true)
if (!c2) continue
grid[y][x] = grid[c2.y][c2.x]
}
return grid
}
const CanFall = (grid, c) => {
let found = false
for(let x = 0; x < n; x++)
for(let y = 0; y < n; y++)
if (grid[y][x] == c) {
if (y == n - 1) return false
found = true
if (y < n - 1 && grid[y+1][x] != 0 && grid[y+1][x] != c) return false
}
return found
}
const Fall = (grid, c) => {
for(let x = 0; x < n; x++)
for(let y = n - 2; y >= 0; y--)
if (grid[y][x] == c) {
grid[y][x] = 0
if (y < n - 1) grid[y+1][x] = c
}
}
const Equal = (grid1, grid2) => {
if (!grid1 || !grid2) return false
for(let x = 0; x < n; x++)
for(let y = 0; y < n; y++)
if (grid1[y][x] != grid2[y][x]) return false
return true
}
const CascadeRemove = (grid, c) => {
RemoveShape(grid, c)
let retry = true
while(retry) {
retry = false
for(let c = 1; c <= nc; c++)
while (CanFall(grid, c)) {
Fall(grid, c)
retry = true
}
}
return grid
}
const Process = (grid) => {
const colors = []
for(let x = 0; x < n; x++)
for(let y = 0; y < n; y++) {
const c = grid[y][x]
if (colors.indexOf(c) == -1)
colors.push(c)
}
for(let i = 1; i <= 2 + Rnd(4); i++) {
const j = Rnd(colors.length)
let c = colors[j]
colors.splice(j, 1)
CascadeRemove(grid, c)
}
return grid
}
const Grid = (props) => {
const [state, setState] = React.useState({ grid: props.grid })
const Cell = (props) => (<span onClick={() => setState({ grid: CascadeRemove(state.grid, state.grid[props.y][props.x])})} className={`cell col${props.val}`}>&nbsp;</span>)
return <div className="grid">{state.grid.map((g1, y) => <div key={y}>{g1.map((g2, x) => <Cell key={x} x={x} y={y} grid={state.grid} val={g2 > 0 ? (props.hide ? -1 : g2) : 0}/>)}</div>)}
{Equal(props.correct, state.grid) && <div onClick={() => document.location = document.location } className="success">OK</div>}
</div>
}
const Playfield = () => {
const initialGrid = AddShapes(Initialize())
const grid1 = Process(Copy(initialGrid))
const grid2 = Copy(initialGrid)
return <div style={{ width: '100%', textAlign: 'center' }}>
<span>
<Grid hide={true} grid={grid1} />
<Grid grid={grid2} correct={grid1} />
</span>
<div onClick={() => document.location = document.location } className="failure">Give up</div>
</div>
}
ReactDOM.render(
<Playfield/>,
document.getElementById('root')
)
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment