mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-01-30 17:09:35 +08:00
Merge branch 'main' of https://github.com/webzard-io/sunmao-ui into feat/custom-widget
This commit is contained in:
commit
8ad33d6938
@ -51,7 +51,6 @@ export const ColumnSchema = Type.Object({
|
||||
export const TablePropsSchema = Type.Object({
|
||||
data: Type.Array(Type.Any(), {
|
||||
title: 'Data',
|
||||
widget: 'core/v1/Expression',
|
||||
category: Category.Data,
|
||||
weight: 0,
|
||||
}),
|
||||
|
@ -139,25 +139,27 @@ export const Editor: React.FC<Props> = observer(
|
||||
|
||||
const renderMain = () => {
|
||||
const appBox = (
|
||||
<Box
|
||||
id="editor-main"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
width="full"
|
||||
height="full"
|
||||
overflow="auto"
|
||||
p={1}
|
||||
transform={`scale(${scale / 100})`}
|
||||
position="relative"
|
||||
>
|
||||
<EditorMaskWrapper services={services}>
|
||||
{appComponent}
|
||||
<Box id={DIALOG_CONTAINER_ID} />
|
||||
</EditorMaskWrapper>
|
||||
<Flex flexDirection="column" width="full" height="full">
|
||||
<Box
|
||||
id="editor-main"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
width="full"
|
||||
height="full"
|
||||
overflow="auto"
|
||||
padding="20px"
|
||||
transform={`scale(${scale / 100})`}
|
||||
position="relative"
|
||||
>
|
||||
<EditorMaskWrapper services={services}>
|
||||
{appComponent}
|
||||
<Box id={DIALOG_CONTAINER_ID} />
|
||||
</EditorMaskWrapper>
|
||||
</Box>
|
||||
<Box id="warning-area" height="48px" position="relative" flex="0 0 auto">
|
||||
<WarningArea services={services} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
if (codeMode) {
|
||||
|
@ -12,9 +12,15 @@ const outlineMaskTextStyle = css`
|
||||
z-index: 1;
|
||||
right: 0px;
|
||||
padding: 0 4px;
|
||||
font-size: 14px;
|
||||
font-weight: black;
|
||||
height: 20px;
|
||||
right: 0;
|
||||
max-width: 100%;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
transform: translateY(-100%);
|
||||
`;
|
||||
|
||||
const outlineMaskStyle = css`
|
||||
|
@ -74,7 +74,7 @@ export const EditorMaskWrapper: React.FC<Props> = observer(props => {
|
||||
width="full"
|
||||
height="0"
|
||||
flex="1"
|
||||
overflow="auto"
|
||||
overflow="visible"
|
||||
position="relative"
|
||||
// some components stop click event propagation, so here we should capture onClick
|
||||
onClickCapture={onClick}
|
||||
|
@ -23,6 +23,7 @@ import ScrollIntoComponentUtilMethod from '../utilMethods/ScrollIntoComponent';
|
||||
|
||||
import {
|
||||
ImplementedRuntimeComponent,
|
||||
ImplementedRuntimeTraitFactory,
|
||||
ImplementedRuntimeTrait,
|
||||
ImplementedRuntimeModule,
|
||||
UIServices,
|
||||
@ -34,7 +35,7 @@ export type UtilMethodFactory = () => UtilMethod<any>[];
|
||||
|
||||
export type SunmaoLib = {
|
||||
components?: ImplementedRuntimeComponent<string, string, string, string>[];
|
||||
traits?: ImplementedRuntimeTrait[];
|
||||
traits?: ImplementedRuntimeTraitFactory[];
|
||||
modules?: ImplementedRuntimeModule[];
|
||||
utilMethods?: UtilMethodFactory[];
|
||||
};
|
||||
@ -94,7 +95,7 @@ export class Registry {
|
||||
return res;
|
||||
}
|
||||
|
||||
registerTrait(t: ImplementedRuntimeTrait) {
|
||||
registerTrait(t: ImplementedRuntimeTraitFactory) {
|
||||
if (this.traits.get(t.version)?.has(t.metadata.name)) {
|
||||
throw new Error(
|
||||
`Already has trait ${t.version}/${t.metadata.name} in this registry.`
|
||||
@ -103,7 +104,11 @@ export class Registry {
|
||||
if (!this.traits.has(t.version)) {
|
||||
this.traits.set(t.version, new Map());
|
||||
}
|
||||
this.traits.get(t.version)?.set(t.metadata.name, t);
|
||||
const trait = {
|
||||
...t,
|
||||
impl: t.factory(),
|
||||
};
|
||||
this.traits.get(t.version)?.set(t.metadata.name, trait);
|
||||
}
|
||||
|
||||
getTrait(version: string, name: string): ImplementedRuntimeTrait {
|
||||
|
@ -1,75 +1,70 @@
|
||||
import { createTrait } from '@sunmao-ui/core';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { TraitImpl } from '../../types';
|
||||
|
||||
const HasInitializedMap = new Map<string, boolean>();
|
||||
import { TraitImplFactory } from '../../types';
|
||||
|
||||
type KeyValue = { key: string; value: unknown };
|
||||
|
||||
const ArrayStateTrait: TraitImpl<Static<typeof PropsSchema>> = ({
|
||||
key,
|
||||
initialValue,
|
||||
componentId,
|
||||
mergeState,
|
||||
subscribeMethods,
|
||||
services,
|
||||
}) => {
|
||||
const hashId = `#${componentId}@${key}`;
|
||||
const hasInitialized = HasInitializedMap.get(hashId);
|
||||
const ArrayStateTraitFactory: TraitImplFactory<Static<typeof PropsSchema>> = () => {
|
||||
const HasInitializedMap = new Map<string, boolean>();
|
||||
|
||||
if (!hasInitialized) {
|
||||
mergeState({ [key]: initialValue || [] });
|
||||
return ({ key, initialValue, componentId, mergeState, subscribeMethods, services }) => {
|
||||
const hashId = `#${componentId}@${key}`;
|
||||
const hasInitialized = HasInitializedMap.get(hashId);
|
||||
|
||||
const methods = {
|
||||
setArray({ key, value }: KeyValue) {
|
||||
mergeState({ [key]: value });
|
||||
},
|
||||
deleteItemByIndex({ key, index }: { key: string; index: number }) {
|
||||
const _arr = [...services.stateManager.store[componentId][key]];
|
||||
_arr.splice(index, 1);
|
||||
mergeState({ [key]: _arr });
|
||||
},
|
||||
deleteItemById({
|
||||
key,
|
||||
itemIdKey,
|
||||
itemId,
|
||||
}: {
|
||||
key: string;
|
||||
itemIdKey: string;
|
||||
itemId: string;
|
||||
}) {
|
||||
const _arr = [...services.stateManager.store[componentId][key]].filter(item => {
|
||||
return item[itemIdKey] !== itemId;
|
||||
});
|
||||
mergeState({ [key]: _arr });
|
||||
},
|
||||
pushItem({ item, key }: { key: string; item: any }) {
|
||||
const _arr = [...services.stateManager.store[componentId][key], item];
|
||||
mergeState({ [key]: _arr });
|
||||
},
|
||||
modifyItemById({
|
||||
key,
|
||||
itemIdKey,
|
||||
itemId,
|
||||
newItem,
|
||||
}: {
|
||||
key: string;
|
||||
itemIdKey: string;
|
||||
itemId: string;
|
||||
newItem: any;
|
||||
}) {
|
||||
const _arr = [...services.stateManager.store[componentId][key]];
|
||||
const index = _arr.findIndex(v => v[itemIdKey] === itemId);
|
||||
_arr.splice(index, 1, newItem);
|
||||
mergeState({ [key]: _arr });
|
||||
},
|
||||
if (!hasInitialized) {
|
||||
mergeState({ [key]: initialValue || [] });
|
||||
|
||||
const methods = {
|
||||
setArray({ key, value }: KeyValue) {
|
||||
mergeState({ [key]: value });
|
||||
},
|
||||
deleteItemByIndex({ key, index }: { key: string; index: number }) {
|
||||
const _arr = [...services.stateManager.store[componentId][key]];
|
||||
_arr.splice(index, 1);
|
||||
mergeState({ [key]: _arr });
|
||||
},
|
||||
deleteItemById({
|
||||
key,
|
||||
itemIdKey,
|
||||
itemId,
|
||||
}: {
|
||||
key: string;
|
||||
itemIdKey: string;
|
||||
itemId: string;
|
||||
}) {
|
||||
const _arr = [...services.stateManager.store[componentId][key]].filter(item => {
|
||||
return item[itemIdKey] !== itemId;
|
||||
});
|
||||
mergeState({ [key]: _arr });
|
||||
},
|
||||
pushItem({ item, key }: { key: string; item: any }) {
|
||||
const _arr = [...services.stateManager.store[componentId][key], item];
|
||||
mergeState({ [key]: _arr });
|
||||
},
|
||||
modifyItemById({
|
||||
key,
|
||||
itemIdKey,
|
||||
itemId,
|
||||
newItem,
|
||||
}: {
|
||||
key: string;
|
||||
itemIdKey: string;
|
||||
itemId: string;
|
||||
newItem: any;
|
||||
}) {
|
||||
const _arr = [...services.stateManager.store[componentId][key]];
|
||||
const index = _arr.findIndex(v => v[itemIdKey] === itemId);
|
||||
_arr.splice(index, 1, newItem);
|
||||
mergeState({ [key]: _arr });
|
||||
},
|
||||
};
|
||||
subscribeMethods(methods);
|
||||
HasInitializedMap.set(hashId, true);
|
||||
}
|
||||
|
||||
return {
|
||||
props: null,
|
||||
};
|
||||
subscribeMethods(methods);
|
||||
HasInitializedMap.set(hashId, true);
|
||||
}
|
||||
|
||||
return {
|
||||
props: null,
|
||||
};
|
||||
};
|
||||
|
||||
@ -133,5 +128,5 @@ export default {
|
||||
state: {},
|
||||
},
|
||||
}),
|
||||
impl: ArrayStateTrait,
|
||||
factory: ArrayStateTraitFactory,
|
||||
};
|
||||
|
@ -1,71 +1,71 @@
|
||||
import { createTrait } from '@sunmao-ui/core';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { debounce, throttle, delay } from 'lodash-es';
|
||||
import { CallbackMap, TraitImpl } from '../../types';
|
||||
import { CallbackMap, TraitImplFactory } from '../../types';
|
||||
import { EventHandlerSchema } from '../../types/traitPropertiesSchema';
|
||||
|
||||
const PropsSchema = Type.Object({
|
||||
handlers: Type.Array(EventHandlerSchema),
|
||||
});
|
||||
|
||||
const useEventTrait: TraitImpl<Static<typeof PropsSchema>> = ({
|
||||
trait,
|
||||
handlers,
|
||||
services,
|
||||
}) => {
|
||||
const callbackQueueMap: Record<string, Array<() => void>> = {};
|
||||
// setup current handlers
|
||||
for (const i in handlers) {
|
||||
const handler = handlers[i];
|
||||
const cb = () => {
|
||||
const rawHandlers = trait.properties.handlers as Static<typeof EventHandlerSchema>[];
|
||||
// Eval before sending event to assure the handler object is evaled from the latest state.
|
||||
const evaledHandler = services.stateManager.deepEval(rawHandlers[i]);
|
||||
const EventTraitFactory: TraitImplFactory<Static<typeof PropsSchema>> = () => {
|
||||
return ({ trait, handlers, services }) => {
|
||||
const callbackQueueMap: Record<string, Array<() => void>> = {};
|
||||
// setup current handlers
|
||||
for (const i in handlers) {
|
||||
const handler = handlers[i];
|
||||
const cb = () => {
|
||||
const rawHandlers = trait.properties.handlers as Static<
|
||||
typeof EventHandlerSchema
|
||||
>[];
|
||||
// Eval before sending event to assure the handler object is evaled from the latest state.
|
||||
const evaledHandler = services.stateManager.deepEval(rawHandlers[i]);
|
||||
|
||||
if (evaledHandler.disabled && typeof evaledHandler.disabled === 'boolean') {
|
||||
return;
|
||||
if (evaledHandler.disabled && typeof evaledHandler.disabled === 'boolean') {
|
||||
return;
|
||||
}
|
||||
|
||||
services.apiService.send('uiMethod', {
|
||||
componentId: evaledHandler.componentId,
|
||||
name: evaledHandler.method.name,
|
||||
parameters: evaledHandler.method.parameters,
|
||||
});
|
||||
};
|
||||
if (!callbackQueueMap[handler.type]) {
|
||||
callbackQueueMap[handler.type] = [];
|
||||
}
|
||||
|
||||
services.apiService.send('uiMethod', {
|
||||
componentId: evaledHandler.componentId,
|
||||
name: evaledHandler.method.name,
|
||||
parameters: evaledHandler.method.parameters,
|
||||
});
|
||||
};
|
||||
if (!callbackQueueMap[handler.type]) {
|
||||
callbackQueueMap[handler.type] = [];
|
||||
}
|
||||
if (!handler.wait || !handler.wait.time) {
|
||||
callbackQueueMap[handler.type].push(cb);
|
||||
} else {
|
||||
callbackQueueMap[handler.type].push(
|
||||
handler.wait.type === 'debounce'
|
||||
? debounce(cb, handler.wait.time)
|
||||
: handler.wait.type === 'throttle'
|
||||
? throttle(cb, handler.wait.time)
|
||||
: handler.wait.type === 'delay'
|
||||
? () => delay(cb, handler.wait!.time)
|
||||
: cb
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const callbackMap: CallbackMap<string> = {};
|
||||
|
||||
for (const eventName in callbackQueueMap) {
|
||||
callbackMap[eventName] = () => {
|
||||
if (!callbackQueueMap[eventName]) {
|
||||
// maybe log?
|
||||
return;
|
||||
if (!handler.wait || !handler.wait.time) {
|
||||
callbackQueueMap[handler.type].push(cb);
|
||||
} else {
|
||||
callbackQueueMap[handler.type].push(
|
||||
handler.wait.type === 'debounce'
|
||||
? debounce(cb, handler.wait.time)
|
||||
: handler.wait.type === 'throttle'
|
||||
? throttle(cb, handler.wait.time)
|
||||
: handler.wait.type === 'delay'
|
||||
? () => delay(cb, handler.wait!.time)
|
||||
: cb
|
||||
);
|
||||
}
|
||||
callbackQueueMap[eventName].forEach(fn => fn());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
callbackMap,
|
||||
},
|
||||
const callbackMap: CallbackMap<string> = {};
|
||||
|
||||
for (const eventName in callbackQueueMap) {
|
||||
callbackMap[eventName] = () => {
|
||||
if (!callbackQueueMap[eventName]) {
|
||||
// maybe log?
|
||||
return;
|
||||
}
|
||||
callbackQueueMap[eventName].forEach(fn => fn());
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
callbackMap,
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -82,5 +82,5 @@ export default {
|
||||
state: {},
|
||||
},
|
||||
}),
|
||||
impl: useEventTrait,
|
||||
factory: EventTraitFactory,
|
||||
};
|
||||
|
@ -1,142 +1,145 @@
|
||||
import { createTrait } from '@sunmao-ui/core';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { TraitImpl } from '../../types';
|
||||
import { TraitImplFactory } from '../../types';
|
||||
import { FetchTraitPropertiesSchema } from '../../types/traitPropertiesSchema';
|
||||
|
||||
const hasFetchedMap = new Map<string, boolean>();
|
||||
const FetchTraitFactory: TraitImplFactory<Static<typeof FetchTraitPropertiesSchema>> =
|
||||
() => {
|
||||
const hasFetchedMap = new Map<string, boolean>();
|
||||
|
||||
const useFetchTrait: TraitImpl<Static<typeof FetchTraitPropertiesSchema>> = ({
|
||||
trait,
|
||||
url,
|
||||
method,
|
||||
lazy: _lazy,
|
||||
headers: _headers,
|
||||
body,
|
||||
mergeState,
|
||||
services,
|
||||
subscribeMethods,
|
||||
componentId,
|
||||
}) => {
|
||||
const hashId = `#${componentId}@${'fetch'}`;
|
||||
const hasFetched = hasFetchedMap.get(hashId);
|
||||
const lazy = _lazy === undefined ? true : _lazy;
|
||||
|
||||
const fetchData = () => {
|
||||
// TODO: clear when component destory
|
||||
hasFetchedMap.set(hashId, true);
|
||||
// FIXME: listen to the header change
|
||||
const headers = new Headers();
|
||||
if (_headers) {
|
||||
for (const key in _headers) {
|
||||
headers.append(key, _headers[key]);
|
||||
}
|
||||
}
|
||||
|
||||
mergeState({
|
||||
fetch: {
|
||||
loading: true,
|
||||
data: undefined,
|
||||
error: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
// fetch data
|
||||
fetch(url, {
|
||||
return ({
|
||||
trait,
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
body: method === 'get' ? undefined : JSON.stringify(body),
|
||||
}).then(
|
||||
async response => {
|
||||
if (response.ok) {
|
||||
// handle 20x/30x
|
||||
const data = await response.json();
|
||||
mergeState({
|
||||
fetch: {
|
||||
loading: false,
|
||||
data,
|
||||
error: undefined,
|
||||
},
|
||||
});
|
||||
const rawOnComplete = trait.properties.onComplete as Static<
|
||||
typeof FetchTraitPropertiesSchema
|
||||
>['onComplete'];
|
||||
rawOnComplete?.forEach(handler => {
|
||||
const evaledHandler = services.stateManager.deepEval(handler, false);
|
||||
services.apiService.send('uiMethod', {
|
||||
componentId: evaledHandler.componentId,
|
||||
name: evaledHandler.method.name,
|
||||
parameters: evaledHandler.method.parameters,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// TODO: Add FetchError class and remove console info
|
||||
const error = new Error(`HTTP${response.status}: ${response.statusText}`);
|
||||
console.warn(error);
|
||||
mergeState({
|
||||
fetch: {
|
||||
loading: false,
|
||||
data: undefined,
|
||||
error,
|
||||
},
|
||||
});
|
||||
const rawOnError = trait.properties.onError as Static<
|
||||
typeof FetchTraitPropertiesSchema
|
||||
>['onError'];
|
||||
rawOnError?.forEach(handler => {
|
||||
const evaledHandler = services.stateManager.deepEval(handler, false);
|
||||
services.apiService.send('uiMethod', {
|
||||
componentId: evaledHandler.componentId,
|
||||
name: evaledHandler.method.name,
|
||||
parameters: evaledHandler.method.parameters,
|
||||
});
|
||||
});
|
||||
lazy: _lazy,
|
||||
headers: _headers,
|
||||
body,
|
||||
mergeState,
|
||||
services,
|
||||
subscribeMethods,
|
||||
componentId,
|
||||
}) => {
|
||||
const hashId = `#${componentId}@${'fetch'}`;
|
||||
const hasFetched = hasFetchedMap.get(hashId);
|
||||
const lazy = _lazy === undefined ? true : _lazy;
|
||||
|
||||
const fetchData = () => {
|
||||
// TODO: clear when component destory
|
||||
hasFetchedMap.set(hashId, true);
|
||||
// FIXME: listen to the header change
|
||||
const headers = new Headers();
|
||||
if (_headers) {
|
||||
for (const key in _headers) {
|
||||
headers.append(key, _headers[key]);
|
||||
}
|
||||
}
|
||||
},
|
||||
async error => {
|
||||
console.warn(error);
|
||||
|
||||
mergeState({
|
||||
fetch: {
|
||||
loading: false,
|
||||
loading: true,
|
||||
data: undefined,
|
||||
error: error instanceof Error ? error : new Error(error),
|
||||
error: undefined,
|
||||
},
|
||||
});
|
||||
const rawOnError = trait.properties.onError as Static<
|
||||
typeof FetchTraitPropertiesSchema
|
||||
>['onError'];
|
||||
rawOnError?.forEach(handler => {
|
||||
const evaledHandler = services.stateManager.deepEval(handler, false);
|
||||
services.apiService.send('uiMethod', {
|
||||
componentId: evaledHandler.componentId,
|
||||
name: evaledHandler.method.name,
|
||||
parameters: evaledHandler.method.parameters,
|
||||
});
|
||||
});
|
||||
|
||||
// fetch data
|
||||
fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body: method === 'get' ? undefined : JSON.stringify(body),
|
||||
}).then(
|
||||
async response => {
|
||||
if (response.ok) {
|
||||
// handle 20x/30x
|
||||
const data = await response.json();
|
||||
mergeState({
|
||||
fetch: {
|
||||
loading: false,
|
||||
data,
|
||||
error: undefined,
|
||||
},
|
||||
});
|
||||
const rawOnComplete = trait.properties.onComplete as Static<
|
||||
typeof FetchTraitPropertiesSchema
|
||||
>['onComplete'];
|
||||
rawOnComplete?.forEach(handler => {
|
||||
const evaledHandler = services.stateManager.deepEval(handler, false);
|
||||
services.apiService.send('uiMethod', {
|
||||
componentId: evaledHandler.componentId,
|
||||
name: evaledHandler.method.name,
|
||||
parameters: evaledHandler.method.parameters,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// TODO: Add FetchError class and remove console info
|
||||
const error = new Error(`HTTP${response.status}: ${response.statusText}`);
|
||||
console.warn(error);
|
||||
mergeState({
|
||||
fetch: {
|
||||
loading: false,
|
||||
data: undefined,
|
||||
error,
|
||||
},
|
||||
});
|
||||
const rawOnError = trait.properties.onError as Static<
|
||||
typeof FetchTraitPropertiesSchema
|
||||
>['onError'];
|
||||
rawOnError?.forEach(handler => {
|
||||
const evaledHandler = services.stateManager.deepEval(handler, false);
|
||||
services.apiService.send('uiMethod', {
|
||||
componentId: evaledHandler.componentId,
|
||||
name: evaledHandler.method.name,
|
||||
parameters: evaledHandler.method.parameters,
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
async error => {
|
||||
console.warn(error);
|
||||
mergeState({
|
||||
fetch: {
|
||||
loading: false,
|
||||
data: undefined,
|
||||
error: error instanceof Error ? error : new Error(error),
|
||||
},
|
||||
});
|
||||
const rawOnError = trait.properties.onError as Static<
|
||||
typeof FetchTraitPropertiesSchema
|
||||
>['onError'];
|
||||
rawOnError?.forEach(handler => {
|
||||
const evaledHandler = services.stateManager.deepEval(handler, false);
|
||||
services.apiService.send('uiMethod', {
|
||||
componentId: evaledHandler.componentId,
|
||||
name: evaledHandler.method.name,
|
||||
parameters: evaledHandler.method.parameters,
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// non lazy query, listen to the change and query;
|
||||
if (!lazy && url && !hasFetched) {
|
||||
fetchData();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// non lazy query, listen to the change and query;
|
||||
if (!lazy && url && !hasFetched) {
|
||||
fetchData();
|
||||
}
|
||||
|
||||
subscribeMethods({
|
||||
triggerFetch() {
|
||||
fetchData();
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
props: {
|
||||
effects: [
|
||||
() => {
|
||||
hasFetchedMap.set(hashId, false);
|
||||
subscribeMethods({
|
||||
triggerFetch() {
|
||||
fetchData();
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
props: {
|
||||
effects: [
|
||||
() => {
|
||||
hasFetchedMap.set(hashId, false);
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export default {
|
||||
...createTrait({
|
||||
@ -161,5 +164,5 @@ export default {
|
||||
],
|
||||
},
|
||||
}),
|
||||
impl: useFetchTrait,
|
||||
factory: FetchTraitFactory,
|
||||
};
|
||||
|
@ -1,24 +1,23 @@
|
||||
import { createTrait } from '@sunmao-ui/core';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { TraitImpl } from '../../types';
|
||||
import { TraitImplFactory } from '../../types';
|
||||
|
||||
const useHiddenTrait: TraitImpl<Static<typeof PropsSchema>> = ({
|
||||
hidden,
|
||||
visually,
|
||||
}) => {
|
||||
if (visually) {
|
||||
return {
|
||||
props: {
|
||||
customStyle: {
|
||||
content: hidden ? 'display: none' : '',
|
||||
const HiddenTraitFactory: TraitImplFactory<Static<typeof PropsSchema>> = () => {
|
||||
return ({ hidden, visually }) => {
|
||||
if (visually) {
|
||||
return {
|
||||
props: {
|
||||
customStyle: {
|
||||
content: hidden ? 'display: none' : '',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {},
|
||||
unmount: hidden,
|
||||
return {
|
||||
props: {},
|
||||
unmount: hidden,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -40,5 +39,5 @@ export default {
|
||||
methods: [],
|
||||
},
|
||||
}),
|
||||
impl: useHiddenTrait,
|
||||
factory: HiddenTraitFactory,
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { createTrait } from '@sunmao-ui/core';
|
||||
import { isEqual } from 'lodash';
|
||||
import { TraitImpl } from 'src/types';
|
||||
import { TraitImplFactory } from '../../types';
|
||||
|
||||
function getLocalStorageValue(key: string) {
|
||||
try {
|
||||
@ -15,43 +15,40 @@ function getLocalStorageValue(key: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const HasInitializedMap = new Map<string, boolean>();
|
||||
const PrevValueCache: Record<string, unknown> = {};
|
||||
|
||||
const PropsSchema = Type.Object({
|
||||
key: Type.String(),
|
||||
value: Type.Any(),
|
||||
});
|
||||
|
||||
const LocalStorageTraitImpl: TraitImpl<Static<typeof PropsSchema>> = ({
|
||||
key,
|
||||
value,
|
||||
componentId,
|
||||
mergeState,
|
||||
}) => {
|
||||
const hashId = `#${componentId}@${key}`;
|
||||
const hasInitialized = HasInitializedMap.get(hashId);
|
||||
const LocalStorageTraitFactory: TraitImplFactory<Static<typeof PropsSchema>> = () => {
|
||||
const HasInitializedMap = new Map<string, boolean>();
|
||||
const PrevValueCache: Record<string, unknown> = {};
|
||||
|
||||
if (key) {
|
||||
if (!hasInitialized) {
|
||||
PrevValueCache[key] = getLocalStorageValue(hashId);
|
||||
mergeState({
|
||||
[key]: PrevValueCache[key],
|
||||
});
|
||||
HasInitializedMap.set(hashId, true);
|
||||
} else {
|
||||
if (!isEqual(PrevValueCache[key], value)) {
|
||||
PrevValueCache[key] = value;
|
||||
return ({ key, value, componentId, mergeState }) => {
|
||||
const hashId = `#${componentId}@${key}`;
|
||||
const hasInitialized = HasInitializedMap.get(hashId);
|
||||
|
||||
if (key) {
|
||||
if (!hasInitialized) {
|
||||
PrevValueCache[key] = getLocalStorageValue(hashId);
|
||||
mergeState({
|
||||
[key]: value,
|
||||
[key]: PrevValueCache[key],
|
||||
});
|
||||
localStorage.setItem(hashId, JSON.stringify(value));
|
||||
HasInitializedMap.set(hashId, true);
|
||||
} else {
|
||||
if (!isEqual(PrevValueCache[key], value)) {
|
||||
PrevValueCache[key] = value;
|
||||
mergeState({
|
||||
[key]: value,
|
||||
});
|
||||
localStorage.setItem(hashId, JSON.stringify(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: null,
|
||||
return {
|
||||
props: null,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -68,5 +65,5 @@ export default {
|
||||
methods: [],
|
||||
},
|
||||
}),
|
||||
impl: LocalStorageTraitImpl,
|
||||
factory: LocalStorageTraitFactory,
|
||||
};
|
||||
|
@ -23,7 +23,7 @@ export default {
|
||||
methods: [],
|
||||
},
|
||||
}),
|
||||
impl: () => ({
|
||||
factory: () => () => ({
|
||||
props: null,
|
||||
}),
|
||||
};
|
||||
|
@ -1,38 +1,34 @@
|
||||
import { createTrait } from '@sunmao-ui/core';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { TraitImpl } from '../../types';
|
||||
|
||||
const HasInitializedMap = new Map<string, boolean>();
|
||||
import { TraitImplFactory } from '../../types';
|
||||
|
||||
type KeyValue = { key: string; value: unknown };
|
||||
|
||||
const useStateTrait: TraitImpl<Static<typeof PropsSchema>> = ({
|
||||
key,
|
||||
initialValue,
|
||||
componentId,
|
||||
mergeState,
|
||||
subscribeMethods,
|
||||
}) => {
|
||||
const hashId = `#${componentId}@${key}`;
|
||||
const hasInitialized = HasInitializedMap.get(hashId);
|
||||
const StateTraitFactory: TraitImplFactory<Static<typeof PropsSchema>> = () => {
|
||||
const HasInitializedMap = new Map<string, boolean>();
|
||||
|
||||
if (!hasInitialized) {
|
||||
mergeState({ [key]: initialValue });
|
||||
return ({ key, initialValue, componentId, mergeState, subscribeMethods }) => {
|
||||
const hashId = `#${componentId}@${key}`;
|
||||
const hasInitialized = HasInitializedMap.get(hashId);
|
||||
|
||||
const methods = {
|
||||
setValue({ key, value }: KeyValue) {
|
||||
mergeState({ [key]: value });
|
||||
},
|
||||
resetValue({ key }: KeyValue) {
|
||||
mergeState({ [key]: initialValue });
|
||||
},
|
||||
if (!hasInitialized) {
|
||||
mergeState({ [key]: initialValue });
|
||||
|
||||
const methods = {
|
||||
setValue({ key, value }: KeyValue) {
|
||||
mergeState({ [key]: value });
|
||||
},
|
||||
resetValue({ key }: KeyValue) {
|
||||
mergeState({ [key]: initialValue });
|
||||
},
|
||||
};
|
||||
subscribeMethods(methods);
|
||||
HasInitializedMap.set(hashId, true);
|
||||
}
|
||||
|
||||
return {
|
||||
props: null,
|
||||
};
|
||||
subscribeMethods(methods);
|
||||
HasInitializedMap.set(hashId, true);
|
||||
}
|
||||
|
||||
return {
|
||||
props: null,
|
||||
};
|
||||
};
|
||||
|
||||
@ -65,5 +61,5 @@ export default {
|
||||
],
|
||||
},
|
||||
}),
|
||||
impl: useStateTrait,
|
||||
factory: StateTraitFactory,
|
||||
};
|
||||
|
@ -1,16 +1,18 @@
|
||||
import { createTrait } from '@sunmao-ui/core';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { TraitImpl } from '../../types';
|
||||
import { TraitImplFactory } from '../../types';
|
||||
|
||||
const StyleTrait: TraitImpl<Static<typeof PropsSchema>> = ({ styles }) => {
|
||||
const customStyle: Record<string, string> = {};
|
||||
styles.forEach(style => {
|
||||
customStyle[style.styleSlot] = style.style;
|
||||
});
|
||||
return {
|
||||
props: {
|
||||
customStyle,
|
||||
},
|
||||
const StyleTraitFactory: TraitImplFactory<Static<typeof PropsSchema>> = () => {
|
||||
return ({ styles }) => {
|
||||
const customStyle: Record<string, string> = {};
|
||||
styles.forEach(style => {
|
||||
customStyle[style.styleSlot] = style.style;
|
||||
});
|
||||
return {
|
||||
props: {
|
||||
customStyle,
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -36,5 +38,5 @@ export default {
|
||||
state: {},
|
||||
},
|
||||
}),
|
||||
impl: StyleTrait,
|
||||
factory: StyleTraitFactory,
|
||||
};
|
||||
|
@ -1,86 +1,87 @@
|
||||
import { createTrait } from '@sunmao-ui/core';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { TraitImpl } from '../../types';
|
||||
import { TraitImplFactory } from '../../types';
|
||||
import { ValidResultSchema } from '../../types/validResultSchema';
|
||||
|
||||
type ValidationResult = Static<typeof ValidResultSchema>;
|
||||
type ValidationRule = (text: string) => ValidationResult;
|
||||
|
||||
const rules = new Map<string, ValidationRule>();
|
||||
const ValidationTraitFactory: TraitImplFactory<Static<typeof PropsSchema>> = () => {
|
||||
const rules = new Map<string, ValidationRule>();
|
||||
|
||||
export function addValidationRule(name: string, rule: ValidationRule) {
|
||||
rules.set(name, rule);
|
||||
}
|
||||
function addValidationRule(name: string, rule: ValidationRule) {
|
||||
rules.set(name, rule);
|
||||
}
|
||||
|
||||
addValidationRule('email', text => {
|
||||
if (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(text)) {
|
||||
return {
|
||||
addValidationRule('email', text => {
|
||||
if (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(text)) {
|
||||
return {
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
isInvalid: true,
|
||||
errorMsg: '请输入正确的 email',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
addValidationRule('phoneNumber', text => {
|
||||
if (/^1[3456789]\d{9}$/.test(text)) {
|
||||
return {
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
isInvalid: true,
|
||||
errorMsg: '请输入正确的手机号码',
|
||||
};
|
||||
}
|
||||
});
|
||||
const ValidationResultCache: Record<string, ValidationResult> = {};
|
||||
|
||||
return props => {
|
||||
const { value, minLength, maxLength, mergeState, componentId, rule } = props;
|
||||
|
||||
const result: ValidationResult = {
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
isInvalid: true,
|
||||
errorMsg: '请输入正确的 email',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
addValidationRule('phoneNumber', text => {
|
||||
if (/^1[3456789]\d{9}$/.test(text)) {
|
||||
return {
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
isInvalid: true,
|
||||
errorMsg: '请输入正确的手机号码',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const ValidationResultCache: Record<string, ValidationResult> = {};
|
||||
|
||||
const ValidationTraitImpl: TraitImpl<Static<typeof PropsSchema>> = props => {
|
||||
const { value, minLength, maxLength, mergeState, componentId, rule } = props;
|
||||
|
||||
const result: ValidationResult = {
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
};
|
||||
|
||||
if (maxLength !== undefined && value.length > maxLength) {
|
||||
result.isInvalid = true;
|
||||
result.errorMsg = `最长不能超过${maxLength}个字符`;
|
||||
} else if (minLength !== undefined && value.length < minLength) {
|
||||
result.isInvalid = true;
|
||||
result.errorMsg = `不能少于${minLength}个字符`;
|
||||
} else {
|
||||
const rulesArr = rule ? rule.split(',') : [];
|
||||
for (const ruleName of rulesArr) {
|
||||
const validateFunc = rules.get(ruleName);
|
||||
if (validateFunc) {
|
||||
const { isInvalid, errorMsg } = validateFunc(value);
|
||||
if (isInvalid) {
|
||||
result.isInvalid = true;
|
||||
result.errorMsg = errorMsg;
|
||||
break;
|
||||
if (maxLength !== undefined && value.length > maxLength) {
|
||||
result.isInvalid = true;
|
||||
result.errorMsg = `最长不能超过${maxLength}个字符`;
|
||||
} else if (minLength !== undefined && value.length < minLength) {
|
||||
result.isInvalid = true;
|
||||
result.errorMsg = `不能少于${minLength}个字符`;
|
||||
} else {
|
||||
const rulesArr = rule ? rule.split(',') : [];
|
||||
for (const ruleName of rulesArr) {
|
||||
const validateFunc = rules.get(ruleName);
|
||||
if (validateFunc) {
|
||||
const { isInvalid, errorMsg } = validateFunc(value);
|
||||
if (isInvalid) {
|
||||
result.isInvalid = true;
|
||||
result.errorMsg = errorMsg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isEqual(result, ValidationResultCache[componentId])) {
|
||||
ValidationResultCache[componentId] = result;
|
||||
mergeState({
|
||||
validResult: result,
|
||||
});
|
||||
}
|
||||
if (!isEqual(result, ValidationResultCache[componentId])) {
|
||||
ValidationResultCache[componentId] = result;
|
||||
mergeState({
|
||||
validResult: result,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
props: null,
|
||||
return {
|
||||
props: null,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -106,5 +107,5 @@ export default {
|
||||
methods: [],
|
||||
},
|
||||
}),
|
||||
impl: ValidationTraitImpl,
|
||||
factory: ValidationTraitFactory,
|
||||
};
|
||||
|
@ -21,6 +21,12 @@ export type TraitImpl<T = any> = (
|
||||
}
|
||||
) => TraitResult<string, string>;
|
||||
|
||||
export type TraitImplFactory<T = any> = () => TraitImpl<T>;
|
||||
|
||||
export type ImplementedRuntimeTraitFactory = RuntimeTrait & {
|
||||
factory: TraitImplFactory;
|
||||
};
|
||||
|
||||
export type ImplementedRuntimeTrait = RuntimeTrait & {
|
||||
impl: TraitImpl;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user