Using the new variable concept
parent
c8b6998e35
commit
26ea806686
|
@ -61,7 +61,7 @@ class ConnectController {
|
||||||
private validateNickname: boolean;
|
private validateNickname: boolean;
|
||||||
private validateAddress: boolean;
|
private validateAddress: boolean;
|
||||||
|
|
||||||
constructor(uiVariables: UiVariableProvider<ConnectUiVariables>) {
|
constructor(uiVariables: UiVariableProvider<ConnectUiVariables>) {7
|
||||||
this.uiEvents = new Registry<ConnectUiEvents>();
|
this.uiEvents = new Registry<ConnectUiEvents>();
|
||||||
this.uiEvents.enableDebug("modal-connect");
|
this.uiEvents.enableDebug("modal-connect");
|
||||||
|
|
||||||
|
|
|
@ -28,13 +28,13 @@ export interface ConnectUiVariables {
|
||||||
currentAddress: string,
|
currentAddress: string,
|
||||||
defaultAddress?: string,
|
defaultAddress?: string,
|
||||||
},
|
},
|
||||||
"server_address_valid": boolean,
|
readonly "server_address_valid": boolean,
|
||||||
|
|
||||||
"nickname": {
|
"nickname": {
|
||||||
currentNickname: string | undefined,
|
currentNickname: string | undefined,
|
||||||
defaultNickname?: string,
|
defaultNickname?: string,
|
||||||
},
|
},
|
||||||
"nickname_valid": boolean,
|
readonly "nickname_valid": boolean,
|
||||||
|
|
||||||
"password": {
|
"password": {
|
||||||
password: string,
|
password: string,
|
||||||
|
@ -45,17 +45,16 @@ export interface ConnectUiVariables {
|
||||||
profiles?: ConnectProfileEntry[],
|
profiles?: ConnectProfileEntry[],
|
||||||
selected: string
|
selected: string
|
||||||
},
|
},
|
||||||
"profile_valid": boolean,
|
readonly "profile_valid": boolean,
|
||||||
|
|
||||||
"historyShown": boolean,
|
"historyShown": boolean,
|
||||||
"history": {
|
readonly "history": {
|
||||||
__readonly?,
|
|
||||||
history: ConnectHistoryEntry[],
|
history: ConnectHistoryEntry[],
|
||||||
selected: number | -1,
|
selected: number | -1,
|
||||||
},
|
},
|
||||||
|
|
||||||
"history_entry": ConnectHistoryServerInfo,
|
readonly "history_entry": ConnectHistoryServerInfo,
|
||||||
"history_connections": number
|
readonly "history_connections": number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConnectUiEvents {
|
export interface ConnectUiEvents {
|
||||||
|
|
|
@ -31,7 +31,7 @@ const InputServerAddress = React.memo(() => {
|
||||||
|
|
||||||
const variables = useContext(VariablesContext);
|
const variables = useContext(VariablesContext);
|
||||||
const address = variables.useVariable("server_address");
|
const address = variables.useVariable("server_address");
|
||||||
const addressValid = variables.useVariable("server_address_valid");
|
const addressValid = variables.useReadOnly("server_address_valid", true) || address.localValue !== address.remoteValue;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ControlledFlatInputField
|
<ControlledFlatInputField
|
||||||
|
@ -43,13 +43,10 @@ const InputServerAddress = React.memo(() => {
|
||||||
label={<Translatable>Server address</Translatable>}
|
label={<Translatable>Server address</Translatable>}
|
||||||
labelType={"static"}
|
labelType={"static"}
|
||||||
|
|
||||||
invalid={!!addressValid.localValue ? undefined : <Translatable>Please enter a valid server address</Translatable>}
|
invalid={addressValid ? undefined : <Translatable>Please enter a valid server address</Translatable>}
|
||||||
editable={address.status === "loaded"}
|
editable={address.status === "loaded"}
|
||||||
|
|
||||||
onInput={value => {
|
onInput={value => address.setValue({ currentAddress: value }, true)}
|
||||||
address.setValue({ currentAddress: value }, true);
|
|
||||||
addressValid.setValue(true, true);
|
|
||||||
}}
|
|
||||||
onBlur={() => address.setValue({ currentAddress: address.localValue?.currentAddress })}
|
onBlur={() => address.setValue({ currentAddress: address.localValue?.currentAddress })}
|
||||||
onEnter={() => {
|
onEnter={() => {
|
||||||
/* Setting the address just to ensure */
|
/* Setting the address just to ensure */
|
||||||
|
@ -82,7 +79,7 @@ const InputNickname = () => {
|
||||||
const variables = useContext(VariablesContext);
|
const variables = useContext(VariablesContext);
|
||||||
|
|
||||||
const nickname = variables.useVariable("nickname");
|
const nickname = variables.useVariable("nickname");
|
||||||
const valid = variables.useVariable("nickname_valid");
|
const validState = variables.useReadOnly("nickname_valid", true) || nickname.localValue !== nickname.remoteValue;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ControlledFlatInputField
|
<ControlledFlatInputField
|
||||||
|
@ -91,11 +88,8 @@ const InputNickname = () => {
|
||||||
placeholder={nickname.remoteValue ? nickname.remoteValue.defaultNickname ? nickname.remoteValue.defaultNickname : tr("Please enter a nickname") : tr("loading...")}
|
placeholder={nickname.remoteValue ? nickname.remoteValue.defaultNickname ? nickname.remoteValue.defaultNickname : tr("Please enter a nickname") : tr("loading...")}
|
||||||
label={<Translatable>Nickname</Translatable>}
|
label={<Translatable>Nickname</Translatable>}
|
||||||
labelType={"static"}
|
labelType={"static"}
|
||||||
invalid={!!valid.localValue ? undefined : <Translatable>Nickname too short or too long</Translatable>}
|
invalid={validState ? undefined : <Translatable>Nickname too short or too long</Translatable>}
|
||||||
onInput={value => {
|
onInput={value => nickname.setValue({ currentNickname: value }, true)}
|
||||||
nickname.setValue({ currentNickname: value }, true);
|
|
||||||
valid.setValue(true, true);
|
|
||||||
}}
|
|
||||||
onBlur={() => nickname.setValue({ currentNickname: nickname.localValue?.currentNickname })}
|
onBlur={() => nickname.setValue({ currentNickname: nickname.localValue?.currentNickname })}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -260,7 +254,7 @@ const HistoryTableEntryConnectCount = React.memo((props: { entry: ConnectHistory
|
||||||
const targetType = props.entry.uniqueServerId === kUnknownHistoryServerUniqueId ? "address" : "server-unique-id";
|
const targetType = props.entry.uniqueServerId === kUnknownHistoryServerUniqueId ? "address" : "server-unique-id";
|
||||||
const target = targetType === "address" ? props.entry.targetAddress : props.entry.uniqueServerId;
|
const target = targetType === "address" ? props.entry.targetAddress : props.entry.uniqueServerId;
|
||||||
|
|
||||||
const { value } = useContext(VariablesContext).useVariableReadOnly("history_connections", {
|
const value = useContext(VariablesContext).useReadOnly("history_connections", {
|
||||||
target,
|
target,
|
||||||
targetType
|
targetType
|
||||||
}, -1);
|
}, -1);
|
||||||
|
@ -276,8 +270,8 @@ const HistoryTableEntry = React.memo((props: { entry: ConnectHistoryEntry, selec
|
||||||
const events = useContext(EventContext);
|
const events = useContext(EventContext);
|
||||||
const connectNewTab = useContext(ConnectDefaultNewTabContext);
|
const connectNewTab = useContext(ConnectDefaultNewTabContext);
|
||||||
const variables = useContext(VariablesContext);
|
const variables = useContext(VariablesContext);
|
||||||
const { value: info } = variables.useVariableReadOnly("history_entry", { serverUniqueId: props.entry.uniqueServerId }, undefined);
|
|
||||||
|
|
||||||
|
const info = variables.useReadOnly("history_entry", { serverUniqueId: props.entry.uniqueServerId }, undefined);
|
||||||
const icon = getIconManager().resolveIcon(info ? info.icon.iconId : 0, info?.icon.serverUniqueId, info?.icon.handlerId);
|
const icon = getIconManager().resolveIcon(info ? info.icon.iconId : 0, info?.icon.serverUniqueId, info?.icon.handlerId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -332,7 +326,7 @@ const HistoryTableEntry = React.memo((props: { entry: ConnectHistoryEntry, selec
|
||||||
});
|
});
|
||||||
|
|
||||||
const HistoryTable = () => {
|
const HistoryTable = () => {
|
||||||
const { value: history } = useContext(VariablesContext).useVariableReadOnly("history", undefined, undefined);
|
const history = useContext(VariablesContext).useReadOnly("history", undefined, undefined);
|
||||||
|
|
||||||
let body;
|
let body;
|
||||||
if(history) {
|
if(history) {
|
||||||
|
@ -380,7 +374,7 @@ const HistoryTable = () => {
|
||||||
|
|
||||||
const HistoryContainer = () => {
|
const HistoryContainer = () => {
|
||||||
const variables = useContext(VariablesContext);
|
const variables = useContext(VariablesContext);
|
||||||
const { value: historyShown } = variables.useVariableReadOnly("historyShown", undefined, false);
|
const historyShown = variables.useReadOnly("historyShown", undefined, false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={joinClassList(cssStyle.historyContainer, historyShown && cssStyle.shown)}>
|
<div className={joinClassList(cssStyle.historyContainer, historyShown && cssStyle.shown)}>
|
||||||
|
|
|
@ -10,6 +10,21 @@ import * as _ from "lodash";
|
||||||
export type UiVariable = Transferable | undefined | null | number | string | object;
|
export type UiVariable = Transferable | undefined | null | number | string | object;
|
||||||
export type UiVariableMap = { [key: string]: any }; //UiVariable | Readonly<UiVariable>
|
export type UiVariableMap = { [key: string]: any }; //UiVariable | Readonly<UiVariable>
|
||||||
|
|
||||||
|
type IfEquals<X, Y, A=X, B=never> =
|
||||||
|
(<T>() => T extends X ? 1 : 2) extends
|
||||||
|
(<T>() => T extends Y ? 1 : 2) ? A : B;
|
||||||
|
|
||||||
|
type WritableKeys<T> = {
|
||||||
|
[P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P, never>
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
type ReadonlyKeys<T> = {
|
||||||
|
[P in keyof T]: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, never, P>
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
export type ReadonlyVariables<Variables extends UiVariableMap> = Pick<Variables, ReadonlyKeys<Variables>>
|
||||||
|
export type WriteableVariables<Variables extends UiVariableMap> = Pick<Variables, WritableKeys<Variables>>
|
||||||
|
|
||||||
type UiVariableEditor<Variables extends UiVariableMap, T extends keyof Variables> = Variables[T] extends { __readonly } ?
|
type UiVariableEditor<Variables extends UiVariableMap, T extends keyof Variables> = Variables[T] extends { __readonly } ?
|
||||||
never :
|
never :
|
||||||
(newValue: Variables[T], customData: any) => Variables[T] | void | boolean;
|
(newValue: Variables[T], customData: any) => Variables[T] | void | boolean;
|
||||||
|
@ -47,7 +62,7 @@ export abstract class UiVariableProvider<Variables extends UiVariableMap> {
|
||||||
sendVariable<T extends keyof Variables>(variable: T, customData?: any, forceSend?: boolean) : void | Promise<void> {
|
sendVariable<T extends keyof Variables>(variable: T, customData?: any, forceSend?: boolean) : void | Promise<void> {
|
||||||
const providers = this.variableProvider[variable as any];
|
const providers = this.variableProvider[variable as any];
|
||||||
if(!providers) {
|
if(!providers) {
|
||||||
throw tr("missing provider");
|
throw tra("missing provider for {}", variable);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = providers(customData);
|
const result = providers(customData);
|
||||||
|
@ -65,7 +80,7 @@ export abstract class UiVariableProvider<Variables extends UiVariableMap> {
|
||||||
async getVariable<T extends keyof Variables>(variable: T, customData?: any, ignoreCache?: boolean) : Promise<Variables[T]> {
|
async getVariable<T extends keyof Variables>(variable: T, customData?: any, ignoreCache?: boolean) : Promise<Variables[T]> {
|
||||||
const providers = this.variableProvider[variable as any];
|
const providers = this.variableProvider[variable as any];
|
||||||
if(!providers) {
|
if(!providers) {
|
||||||
throw tr("missing provider");
|
throw tra("missing provider for {}", variable);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = providers(customData);
|
const result = providers(customData);
|
||||||
|
@ -138,38 +153,45 @@ export abstract class UiVariableProvider<Variables extends UiVariableMap> {
|
||||||
protected abstract doSendVariable(variable: string, customData: any, value: any);
|
protected abstract doSendVariable(variable: string, customData: any, value: any);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UiVariableStatus<Variables extends UiVariableMap, T extends keyof Variables> = ({
|
export type UiVariableStatus<Variables extends UiVariableMap, T extends keyof Variables> = {
|
||||||
status: "loading",
|
status: "loading",
|
||||||
|
|
||||||
localValue: undefined,
|
localValue: Variables[T] | undefined,
|
||||||
remoteValue: undefined
|
remoteValue: undefined,
|
||||||
|
|
||||||
|
/* Will do nothing */
|
||||||
|
setValue: (newValue: Variables[T], localOnly?: boolean) => void
|
||||||
} | {
|
} | {
|
||||||
status: "loaded" | "applying",
|
status: "loaded" | "applying",
|
||||||
|
|
||||||
localValue: Omit<Variables[T], "__readonly">,
|
localValue: Variables[T],
|
||||||
remoteValue: Omit<Variables[T], "__readonly">,
|
remoteValue: Variables[T],
|
||||||
}) & (Variables[T] extends { __readonly } ? {} : { setValue: (newValue: Variables[T], localOnly?: boolean) => void });
|
|
||||||
|
|
||||||
export type UiReadOnlyVariableStatus<Variables extends UiVariableMap, T extends keyof Variables, DefaultValue = undefined> = {
|
setValue: (newValue: Variables[T], localOnly?: boolean) => void
|
||||||
status: "loading",
|
};
|
||||||
value: DefaultValue,
|
|
||||||
} | {
|
export type UiReadOnlyVariableStatus<Variables extends UiVariableMap, T extends keyof Variables> = {
|
||||||
status: "loaded" | "applying",
|
status: "loading" | "loaded",
|
||||||
value: Omit<Variables[T], "__readonly">,
|
value: Variables[T],
|
||||||
};
|
};
|
||||||
|
|
||||||
type UiVariableCacheEntry = {
|
type UiVariableCacheEntry = {
|
||||||
|
key: string,
|
||||||
useCount: number,
|
useCount: number,
|
||||||
customData: any | undefined,
|
customData: any | undefined,
|
||||||
currentValue: any | undefined,
|
currentValue: any | undefined,
|
||||||
status: "loading" | "loaded" | "applying",
|
status: "loading" | "loaded" | "applying",
|
||||||
updateListener: ((forceSetLocalVariable: boolean) => void)[]
|
updateListener: ((clearLocalValue: boolean) => void)[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocalVariableValue = {
|
||||||
|
status: "set" | "default",
|
||||||
|
value: any
|
||||||
|
} | {
|
||||||
|
status: "unset"
|
||||||
}
|
}
|
||||||
|
|
||||||
let staticRevisionId = 0;
|
let staticRevisionId = 0;
|
||||||
/**
|
|
||||||
* @returns An array containing variable information `[ value, setValue(newValue, localOnly), remoteValue ]`
|
|
||||||
*/
|
|
||||||
export abstract class UiVariableConsumer<Variables extends UiVariableMap> {
|
export abstract class UiVariableConsumer<Variables extends UiVariableMap> {
|
||||||
private variableCache: {[key: string]: UiVariableCacheEntry[]} = {};
|
private variableCache: {[key: string]: UiVariableCacheEntry[]} = {};
|
||||||
|
|
||||||
|
@ -177,14 +199,15 @@ export abstract class UiVariableConsumer<Variables extends UiVariableMap> {
|
||||||
this.variableCache = {};
|
this.variableCache = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
useVariable<T extends keyof Variables>(
|
private getOrCreateVariable<T extends keyof Variables>(
|
||||||
variable: T,
|
variable: string,
|
||||||
customData?: any
|
customData?: any
|
||||||
) : UiVariableStatus<Variables, T> {
|
) : UiVariableCacheEntry {
|
||||||
let cacheEntry = this.variableCache[variable as string]?.find(variable => _.isEqual(variable.customData, customData));
|
let cacheEntry = this.variableCache[variable]?.find(variable => _.isEqual(variable.customData, customData));
|
||||||
if(!cacheEntry) {
|
if(!cacheEntry) {
|
||||||
this.variableCache[variable as string] = this.variableCache[variable as string] || [];
|
this.variableCache[variable] = this.variableCache[variable] || [];
|
||||||
this.variableCache[variable as string].push(cacheEntry = {
|
this.variableCache[variable].push(cacheEntry = {
|
||||||
|
key: variable,
|
||||||
customData,
|
customData,
|
||||||
currentValue: undefined,
|
currentValue: undefined,
|
||||||
status: "loading",
|
status: "loading",
|
||||||
|
@ -193,28 +216,67 @@ export abstract class UiVariableConsumer<Variables extends UiVariableMap> {
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Might already call notifyRemoteVariable */
|
/* Might already call notifyRemoteVariable */
|
||||||
this.doRequestVariable(variable as string, customData);
|
this.doRequestVariable(variable, customData);
|
||||||
}
|
}
|
||||||
|
return cacheEntry;
|
||||||
|
}
|
||||||
|
|
||||||
const [ localValue, setLocalValue ] = useState(() => {
|
private derefVariable(variable: UiVariableCacheEntry) {
|
||||||
|
if(--variable.useCount === 0) {
|
||||||
|
const cache = this.variableCache[variable.key];
|
||||||
|
if(!cache) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.remove(variable);
|
||||||
|
if(cache.length === 0) {
|
||||||
|
delete this.variableCache[variable.key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useVariable<T extends keyof WriteableVariables<Variables>>(
|
||||||
|
variable: T,
|
||||||
|
customData?: any,
|
||||||
|
defaultValue?: Variables[T]
|
||||||
|
) : UiVariableStatus<Variables, T> {
|
||||||
|
const haveDefaultValue = arguments.length >= 3;
|
||||||
|
const cacheEntry = this.getOrCreateVariable(variable as string, customData);
|
||||||
|
|
||||||
|
const [ localValue, setLocalValue ] = useState<LocalVariableValue>(() => {
|
||||||
/* Variable constructor */
|
/* Variable constructor */
|
||||||
cacheEntry.useCount++;
|
cacheEntry.useCount++;
|
||||||
return cacheEntry.currentValue;
|
|
||||||
|
if(cacheEntry.status === "loading") {
|
||||||
|
return {
|
||||||
|
status: "set",
|
||||||
|
value: cacheEntry.currentValue
|
||||||
|
};
|
||||||
|
} else if(haveDefaultValue) {
|
||||||
|
return {
|
||||||
|
status: "default",
|
||||||
|
value: defaultValue
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
status: "unset"
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [, setRemoteVersion ] = useState(0);
|
const [, setRemoteVersion ] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
/* Initial rendered */
|
/* Initial rendered */
|
||||||
if(cacheEntry.status === "loaded" && !_.isEqual(localValue, cacheEntry.currentValue)) {
|
if(cacheEntry.status === "loaded" && localValue.status !== "set") {
|
||||||
/* Update value to the, now, up 2 date value*/
|
/* Update the local value to the current state */
|
||||||
setLocalValue(cacheEntry.currentValue);
|
setLocalValue(cacheEntry.currentValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
let listener;
|
let listener;
|
||||||
cacheEntry.updateListener.push(listener = forceUpdateLocalVariable => {
|
cacheEntry.updateListener.push(listener = clearLocalValue => {
|
||||||
if(forceUpdateLocalVariable) {
|
if(clearLocalValue) {
|
||||||
setLocalValue(cacheEntry.currentValue);
|
setLocalValue({ status: "unset" });
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We can't just increment the old one by one since this update listener may fires twice before rendering */
|
/* We can't just increment the old one by one since this update listener may fires twice before rendering */
|
||||||
|
@ -223,35 +285,24 @@ export abstract class UiVariableConsumer<Variables extends UiVariableMap> {
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
cacheEntry.updateListener.remove(listener);
|
cacheEntry.updateListener.remove(listener);
|
||||||
|
this.derefVariable(cacheEntry);
|
||||||
/* Variable destructor */
|
|
||||||
if(--cacheEntry.useCount === 0) {
|
|
||||||
const cache = this.variableCache[variable as string];
|
|
||||||
if(!cache) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.remove(cacheEntry);
|
|
||||||
if(cache.length === 0) {
|
|
||||||
delete this.variableCache[variable as string];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
if(cacheEntry.status === "loading") {
|
if(cacheEntry.status === "loading") {
|
||||||
return {
|
return {
|
||||||
status: "loading",
|
status: "loading",
|
||||||
localValue: localValue,
|
localValue: localValue.status === "unset" ? undefined : localValue.value,
|
||||||
remoteValue: undefined,
|
remoteValue: undefined,
|
||||||
setValue: () => {}
|
setValue: () => {}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
status: cacheEntry.status,
|
status: cacheEntry.status,
|
||||||
localValue: localValue,
|
|
||||||
|
localValue: localValue.status === "set" ? localValue.value : cacheEntry.currentValue,
|
||||||
remoteValue: cacheEntry.currentValue,
|
remoteValue: cacheEntry.currentValue,
|
||||||
|
|
||||||
setValue: (newValue, localOnly) => {
|
setValue: (newValue, localOnly) => {
|
||||||
if(!localOnly && !_.isEqual(cacheEntry.currentValue, newValue)) {
|
if(!localOnly && !_.isEqual(cacheEntry.currentValue, newValue)) {
|
||||||
const editingFinished = (succeeded: boolean) => {
|
const editingFinished = (succeeded: boolean) => {
|
||||||
|
@ -260,23 +311,20 @@ export abstract class UiVariableConsumer<Variables extends UiVariableMap> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = newValue;
|
|
||||||
if(!succeeded) {
|
|
||||||
value = cacheEntry.currentValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheEntry.status = "loaded";
|
cacheEntry.status = "loaded";
|
||||||
cacheEntry.currentValue = value;
|
cacheEntry.currentValue = succeeded ? newValue : cacheEntry.currentValue;
|
||||||
cacheEntry.updateListener.forEach(callback => callback(true));
|
cacheEntry.updateListener.forEach(callback => callback(true));
|
||||||
};
|
};
|
||||||
|
|
||||||
cacheEntry.status = "applying";
|
cacheEntry.status = "applying";
|
||||||
const result = this.doEditVariable(variable as string, customData, newValue);
|
const result = this.doEditVariable(variable as string, customData, newValue);
|
||||||
if(result instanceof Promise) {
|
if(result instanceof Promise) {
|
||||||
result.then(() => editingFinished(true)).catch(async error => {
|
result
|
||||||
console.error("Failed to change variable %s: %o", variable, error);
|
.then(() => editingFinished(true))
|
||||||
editingFinished(false);
|
.catch(async error => {
|
||||||
});
|
console.error("Failed to change variable %s: %o", variable, error);
|
||||||
|
editingFinished(false);
|
||||||
|
});
|
||||||
|
|
||||||
/* State has changed, enforce a rerender */
|
/* State has changed, enforce a rerender */
|
||||||
cacheEntry.updateListener.forEach(callback => callback(false));
|
cacheEntry.updateListener.forEach(callback => callback(false));
|
||||||
|
@ -286,31 +334,54 @@ export abstract class UiVariableConsumer<Variables extends UiVariableMap> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!_.isEqual(newValue, localValue)) {
|
if(localValue.status !== "set" || !_.isEqual(newValue, localValue.value)) {
|
||||||
setLocalValue(newValue);
|
setLocalValue({
|
||||||
|
status: "set",
|
||||||
|
value: newValue
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} as any;
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useVariableReadOnly<T extends keyof Variables, DefaultValue>(
|
useReadOnly<T extends keyof Variables>(
|
||||||
variable: T,
|
variable: T,
|
||||||
customData?: any,
|
customData?: any,
|
||||||
defaultValue?: DefaultValue
|
defaultValue?: never
|
||||||
) : UiReadOnlyVariableStatus<Variables, T, DefaultValue> {
|
) : UiReadOnlyVariableStatus<Variables, T>;
|
||||||
/* TODO: Use a simplified logic here */
|
|
||||||
const v = this.useVariable(variable, customData);
|
useReadOnly<T extends keyof Variables>(
|
||||||
if(v.status === "loading") {
|
variable: T,
|
||||||
return {
|
customData: any | undefined,
|
||||||
status: "loading",
|
defaultValue: Variables[T]
|
||||||
value: defaultValue
|
) : Variables[T];
|
||||||
|
|
||||||
|
useReadOnly(variable, customData?, defaultValue?) {
|
||||||
|
const cacheEntry = this.getOrCreateVariable(variable as string, customData);
|
||||||
|
const [, setRemoteVersion ] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
/* Initial rendered */
|
||||||
|
let listener;
|
||||||
|
cacheEntry.updateListener.push(listener = () => {
|
||||||
|
/* We can't just increment the old one by one since this update listener may fires twice before rendering */
|
||||||
|
setRemoteVersion(++staticRevisionId);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cacheEntry.updateListener.remove(listener);
|
||||||
|
this.derefVariable(cacheEntry);
|
||||||
};
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if(arguments.length >= 3) {
|
||||||
|
return cacheEntry.status === "loaded" ? cacheEntry.currentValue : defaultValue;
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
status: v.status,
|
status: cacheEntry.status,
|
||||||
value: v.remoteValue
|
value: cacheEntry.currentValue
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue