This repository has been archived by the owner on Dec 11, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add object/isSugarObject * Add SugarFormError * Add SugarEventEmitter * Add Empty Sugar * Add Logger * Refresh Export * Add getter and setter * Add isDirty * Follow EsLint
- Loading branch information
Showing
18 changed files
with
1,380 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import type { Sugar, SugarObjectNode, SugarUser, SugarUserReshaper } from '.'; | ||
import { SugarDownstreamEventEmitter } from '../../util/events/downstreamEvent'; | ||
import { SugarUpstreamEventEmitter } from '../../util/events/upstreamEvent'; | ||
import type { SugarObject } from '../../util/object'; | ||
import { isSugarObject } from '../../util/object'; | ||
import { useSugar } from './use'; | ||
|
||
export function createEmptySugar<T>(path: string, template: T): Sugar<T> { | ||
const sugar: Sugar<T> = { | ||
path, | ||
mounted: false, | ||
template, | ||
upstream: new SugarUpstreamEventEmitter(), | ||
downstream: new SugarDownstreamEventEmitter(), | ||
use: | ||
<U extends SugarObject>(options: SugarUserReshaper<T, U>) => useSugar<T, U>(sugar, options), | ||
}; | ||
|
||
if (isSugarObject(template)) { | ||
(sugar as Sugar<SugarObject>).useObject = | ||
(options: SugarUser): SugarObjectNode<SugarObject> => | ||
useSugar<SugarObject, SugarObject>( | ||
sugar as Sugar<SugarObject>, | ||
{ | ||
...options, | ||
reshape: { | ||
transform: x => x, | ||
deform: x => x, | ||
}, | ||
}, | ||
); | ||
} | ||
|
||
return sugar; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import type { Sugar } from '.'; | ||
import { debug } from '../../util/logger'; | ||
|
||
export function setDirty<T>(sugar: Sugar<T>, isDirty: boolean): void { | ||
if (!sugar.mounted) { | ||
debug('WARN', `Sugar is not mounted when tried to set dirty. Path: ${sugar.path}`); | ||
return; | ||
} | ||
if (sugar.isDirty === isDirty) return; | ||
sugar.isDirty = isDirty; | ||
sugar.upstream.fire('updateDirty', { isDirty }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/* eslint-disable @typescript-eslint/no-empty-interface */ | ||
import type { SugarDownstreamEventEmitter } from '../../util/events/downstreamEvent'; | ||
import type { SugarUpstreamEventEmitter } from '../../util/events/upstreamEvent'; | ||
import type { SugarObject } from '../../util/object'; | ||
|
||
export type Sugar<T> = SugarData<T> & ({ | ||
mounted: false, | ||
} | { | ||
mounted: true, | ||
get: () => SugarValue<T>, | ||
set: (value: T) => void, | ||
isDirty: boolean, | ||
}); | ||
|
||
export type SugarData<T> = { | ||
path: string, | ||
template: T, | ||
upstream: SugarUpstreamEventEmitter, | ||
downstream: SugarDownstreamEventEmitter, | ||
} & ( | ||
T extends SugarObject ? | ||
{ | ||
use: <U extends SugarObject>(options: SugarUserReshaper<T, U>) => SugarObjectNode<U>, | ||
useObject: (options: SugarUser) => SugarObjectNode<T> | ||
} : { | ||
use: <U extends SugarObject>(options: SugarUserReshaper<T, U>) => SugarObjectNode<U>, | ||
} | ||
); | ||
// ) & ( | ||
// T extends Array<infer U> ? | ||
// { useArray: (options: SugarUserArray<U>) => SugarArrayNode<U> } : | ||
// Record<string, never> | ||
// ); | ||
|
||
export type SugarValue<T> = { | ||
success: true, | ||
value: T, | ||
} | { | ||
success: false, | ||
value: unknown, | ||
}; | ||
|
||
export interface SugarUser { | ||
|
||
} | ||
|
||
export interface SugarUserReshaper<T, U extends SugarObject> extends SugarUser { | ||
reshape: { | ||
transform: (value: U) => T, | ||
deform: (value: T) => U, | ||
} | ||
} | ||
|
||
export interface SugarObjectNode<U extends SugarObject> { | ||
fields: { [K in keyof U]: Sugar<U[K]> }, | ||
} | ||
|
||
// export interface SugarUserArray<T> { | ||
// | ||
// } | ||
// | ||
// export interface SugarArrayNode<T> { | ||
// | ||
// } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import type { MutableRefObject } from 'react'; | ||
import { useRef } from 'react'; | ||
import type { Sugar, SugarUserReshaper, SugarObjectNode, SugarValue } from '.'; | ||
import { SugarFormError } from '../../util/error'; | ||
import { debug } from '../../util/logger'; | ||
import type { BetterObjectConstructor, SugarObject } from '../../util/object'; | ||
import { createEmptySugar } from './create'; | ||
import { setDirty } from './dirty'; | ||
|
||
declare const Object: BetterObjectConstructor; | ||
|
||
export function useSugar<T, U extends SugarObject>( | ||
sugar: Sugar<T>, | ||
options: SugarUserReshaper<T, U>, | ||
): SugarObjectNode<U> { | ||
|
||
const fieldsRef = useRef<SugarObjectNode<U>['fields']>(); | ||
let fields = fieldsRef.current; | ||
|
||
if (sugar.mounted && fields === undefined) { | ||
debug('WARN', 'Sugar is already mounted, but fields are not initialized. Remounting... Path: ${sugar.path}'); | ||
} | ||
|
||
if (sugar.mounted && fields !== undefined) { | ||
debug('WARN', `Sugar is already mounted. Path: ${sugar.path}`); | ||
} else { | ||
debug('DEBUG', `Mounting sugar. Path: ${sugar.path}`); | ||
const mounted = mountSugar(sugar, options, fieldsRef); | ||
fields = mounted.fields; | ||
fieldsRef.current = fields; | ||
} | ||
|
||
return { | ||
fields, | ||
}; | ||
} | ||
|
||
export function mountSugar<T, U extends SugarObject>( | ||
sugar: Sugar<T>, | ||
options: SugarUserReshaper<T, U>, | ||
fieldsRef: MutableRefObject<SugarObjectNode<U>['fields'] | undefined>, | ||
): SugarObjectNode<U> { | ||
const template = options.reshape.deform(sugar.template); | ||
debug('DEBUG', `Template: ${JSON.stringify(template)}`); | ||
|
||
const fields = wrapSugar(sugar.path, template); | ||
|
||
const getter = (): SugarValue<T> => { | ||
const fields = fieldsRef.current; | ||
if (fields === undefined) throw new SugarFormError('SF0021', `Path: ${sugar.path}}`); | ||
const value = get<U>(fields); | ||
debug('DEBUG', `Getting Value of Sugar: ${JSON.stringify(value)}, Path: ${sugar.path}`); | ||
return !value.success ? value : { | ||
success: true, | ||
value: options.reshape.transform(value.value), | ||
}; | ||
}; | ||
|
||
const setter = (value: T): void => { | ||
debug('DEBUG', `Setting value of sugar. Path: ${sugar.path}`); | ||
const fields = fieldsRef.current; | ||
if (fields === undefined) throw new SugarFormError('SF0021', `Path: ${sugar.path}}`); | ||
set<U>(fields, options.reshape.deform(value)); | ||
}; | ||
|
||
const dirtyControl = ({ isDirty }: { isDirty: boolean }) : void => { | ||
const fields = fieldsRef.current; | ||
if (!sugar.mounted || fields === undefined) throw new SugarFormError('SF0021', `Path: ${sugar.path}}`); | ||
if (isDirty) { | ||
if (sugar.isDirty) return; | ||
setDirty(sugar, true); | ||
} else { | ||
if (Object.values(fields).some(s => s.mounted && s.isDirty)) return; | ||
setDirty(sugar, false); | ||
} | ||
}; | ||
|
||
Object.values(fields).forEach(sugar => sugar.upstream.listen('updateDirty', dirtyControl)); | ||
|
||
const updateSugar = sugar as Sugar<T> & { mounted: true }; | ||
updateSugar.mounted = true; | ||
updateSugar.get = getter; | ||
updateSugar.set = setter; | ||
updateSugar.isDirty = false; | ||
|
||
return { fields }; | ||
} | ||
|
||
|
||
export function wrapSugar<T extends SugarObject>(path: string, template: T): SugarObjectNode<T>['fields'] { | ||
const fields: SugarObjectNode<T>['fields'] = {} as SugarObjectNode<T>['fields']; | ||
|
||
for (const key in template) { | ||
fields[key] = createEmptySugar(`${path}.${key}`, template[key]); | ||
} | ||
|
||
return fields; | ||
} | ||
|
||
export function get<T extends SugarObject>(fields: SugarObjectNode<T>['fields']): SugarValue<T> { | ||
const result = {} as { [P in keyof T]: unknown }; | ||
let success = true; | ||
|
||
for (const key in fields) { | ||
const sugar = fields[key]; | ||
if (!sugar.mounted) { | ||
debug('WARN', `Sugar is not mounted when tried to get. Path: ${sugar.path}`); | ||
result[key] = null; | ||
success = false; | ||
} else { | ||
const value = sugar.get(); | ||
result[key] = value.value; | ||
success &&= value.success; | ||
} | ||
} | ||
|
||
return success ? { | ||
success, | ||
value: result as T, | ||
} : { | ||
success, | ||
value: result, | ||
}; | ||
} | ||
|
||
export function set<T extends SugarObject>(fields: SugarObjectNode<T>['fields'], value: T): void { | ||
for (const key in fields) { | ||
const sugar = fields[key]; | ||
if (!sugar.mounted) { | ||
debug('WARN', `Sugar is not mounted when tried to set. Path: ${sugar.path}`); | ||
} else { | ||
sugar.set(value[key]); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,2 @@ | ||
export function helloSugarForm(): void { | ||
console.log('Hello, Sugar Form!'); | ||
return; | ||
} | ||
export type { Sugar } from './component/sugar'; | ||
export { setSugarFormLogLevel } from './util/logger'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
const SugarFormErrorMap = [ | ||
{ | ||
code: 'SF0001', | ||
name: 'TypeMismatch', | ||
message: 'Incorrect type detected.\nThis may be a bug in SugarForm itself. It would be helpful if you could post the stack trace to GitHub.', | ||
}, | ||
{ | ||
code: 'SF0002', | ||
name: 'DifferentFromTypeDefinition', | ||
message: 'Incorrect type detected.\nIt is believed that the code does not follow TypeScript type annotations.', | ||
}, | ||
{ | ||
code: 'SF0011', | ||
name: 'Not Implemented', | ||
message: 'This feature is not implemented yet.', | ||
}, | ||
{ | ||
code: 'SF0021', | ||
name: 'RequestValueToUnmoutedSugar', | ||
message: 'Sugar was forced unmounted from outside.', | ||
}, | ||
] as const; | ||
|
||
export class SugarFormError extends Error { | ||
constructor(id: (typeof SugarFormErrorMap)[number]['code'], message?: string) { | ||
const error = SugarFormErrorMap.find(e => e.code === id) ?? { | ||
code: 'SF9999', | ||
name: 'SugarFormUnknownError', | ||
message: 'Unknown error occured.', | ||
}; | ||
super(`${error.code} (${error.name})\n${error.message}${message !== undefined ? `\ninfo for debug: ${message}` : ''}`); | ||
this.name = new.target.name; | ||
Error.captureStackTrace(this, this.constructor); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { SugarFormError } from './error'; | ||
|
||
export class SugarEventEmitter<EventTable extends Record<string, Record<string, unknown>>> { | ||
private listeners: Partial< | ||
Record<keyof EventTable, Array<(param: EventTable[keyof EventTable]) => void>> | ||
> = {}; | ||
|
||
|
||
public listen<K extends keyof EventTable>( | ||
eventName: K, callback: (param: EventTable[K]) => void, | ||
): void { | ||
if (!this.listeners[eventName]) { | ||
this.listeners[eventName] = []; | ||
} | ||
const listener: Array<(param: EventTable[K]) => void> | undefined = this.listeners[eventName]; | ||
if (listener === undefined) { | ||
throw new SugarFormError('SF0001', 'SugarEventEmitter#listen'); | ||
} | ||
listener.push(callback); | ||
} | ||
|
||
public fire<K extends keyof EventTable>(eventName: K, param: EventTable[K]): void { | ||
const listener: Array<(param: EventTable[K]) => void> | undefined = this.listeners[eventName]; | ||
if (listener === undefined) return; | ||
listener.forEach(l => l(param)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { SugarEventEmitter } from '../event'; | ||
|
||
export class SugarDownstreamEventEmitter extends SugarEventEmitter<Record<string, never>> {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { SugarEventEmitter } from '../event'; | ||
|
||
export class SugarUpstreamEventEmitter extends SugarEventEmitter<{ | ||
updateDirty: { isDirty: boolean }, | ||
mounted: Record<string, never>, | ||
}> {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* eslint-disable no-console */ | ||
type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'SILENT'; | ||
let SugarFormLogLevel: LogLevel = 'SILENT'; | ||
export function setSugarFormLogLevel(level: LogLevel): void { | ||
SugarFormLogLevel = level; | ||
} | ||
|
||
export function debug(level: LogLevel, message: string): void { | ||
if (SugarFormLogLevel === 'SILENT') return; | ||
if (level === 'WARN') { | ||
console.warn(message); | ||
return; | ||
} | ||
if (SugarFormLogLevel === 'WARN') return; | ||
if (level === 'INFO') { | ||
console.info(message); | ||
return; | ||
} | ||
if (SugarFormLogLevel === 'INFO') return; | ||
if (level === 'DEBUG') { | ||
console.debug(message); | ||
return; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export type SugarObject = Record<string, unknown>; | ||
export function isSugarObject(obj: unknown): obj is SugarObject { | ||
return obj?.constructor.name === 'Object'; | ||
} | ||
|
||
export declare interface BetterObjectConstructor extends ObjectConstructor { | ||
values<T>(obj: T): Array<T[keyof T]>; | ||
} |
Oops, something went wrong.