@simplysm/solid 13.0.31 → 13.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -14
- package/dist/hooks/useLogger.js +4 -4
- package/dist/hooks/useLogger.js.map +1 -1
- package/dist/hooks/useSyncConfig.d.ts +1 -1
- package/dist/hooks/useSyncConfig.d.ts.map +1 -1
- package/dist/hooks/useSyncConfig.js +6 -4
- package/dist/hooks/useSyncConfig.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/{hooks/useClipboardValueCopy.d.ts → providers/ClipboardProvider.d.ts} +4 -3
- package/dist/providers/ClipboardProvider.d.ts.map +1 -0
- package/dist/{hooks/useClipboardValueCopy.js → providers/ClipboardProvider.js} +11 -8
- package/dist/providers/ClipboardProvider.js.map +6 -0
- package/dist/providers/ConfigContext.d.ts +15 -47
- package/dist/providers/ConfigContext.d.ts.map +1 -1
- package/dist/providers/ConfigContext.js +18 -1
- package/dist/providers/ConfigContext.js.map +3 -3
- package/dist/providers/ErrorLoggerProvider.d.ts +10 -0
- package/dist/providers/ErrorLoggerProvider.d.ts.map +1 -0
- package/dist/providers/ErrorLoggerProvider.js +23 -0
- package/dist/providers/ErrorLoggerProvider.js.map +6 -0
- package/dist/providers/LoggerContext.d.ts +38 -0
- package/dist/providers/LoggerContext.d.ts.map +1 -0
- package/dist/providers/LoggerContext.js +25 -0
- package/dist/providers/LoggerContext.js.map +6 -0
- package/dist/providers/PwaUpdateProvider.d.ts +12 -0
- package/dist/providers/PwaUpdateProvider.d.ts.map +1 -0
- package/dist/providers/PwaUpdateProvider.js +56 -0
- package/dist/providers/PwaUpdateProvider.js.map +6 -0
- package/dist/providers/ServiceClientContext.d.ts.map +1 -1
- package/dist/providers/ServiceClientContext.js +1 -3
- package/dist/providers/ServiceClientContext.js.map +1 -1
- package/dist/providers/SyncStorageContext.d.ts +42 -0
- package/dist/providers/SyncStorageContext.d.ts.map +1 -0
- package/dist/providers/SyncStorageContext.js +25 -0
- package/dist/providers/SyncStorageContext.js.map +6 -0
- package/dist/providers/ThemeContext.d.ts.map +1 -1
- package/dist/providers/ThemeContext.js +1 -1
- package/dist/providers/ThemeContext.js.map +1 -1
- package/docs/disclosure.md +1 -1
- package/docs/feedback.md +4 -4
- package/docs/form-controls.md +1 -1
- package/docs/hooks.md +8 -42
- package/docs/providers.md +116 -3
- package/docs/styling.md +1 -1
- package/package.json +16 -16
- package/src/hooks/useLogger.ts +4 -4
- package/src/hooks/useSyncConfig.ts +7 -5
- package/src/index.ts +6 -3
- package/src/{hooks/useClipboardValueCopy.ts → providers/ClipboardProvider.tsx} +6 -4
- package/src/providers/ConfigContext.tsx +48 -0
- package/src/providers/ErrorLoggerProvider.tsx +31 -0
- package/src/providers/LoggerContext.tsx +46 -0
- package/src/providers/PwaUpdateProvider.tsx +68 -0
- package/src/providers/ServiceClientContext.ts +1 -3
- package/src/providers/SyncStorageContext.tsx +52 -0
- package/src/providers/ThemeContext.tsx +1 -3
- package/dist/hooks/useClipboardValueCopy.d.ts.map +0 -1
- package/dist/hooks/useClipboardValueCopy.js.map +0 -6
- package/dist/hooks/usePwaUpdate.d.ts +0 -14
- package/dist/hooks/usePwaUpdate.d.ts.map +0 -1
- package/dist/hooks/usePwaUpdate.js +0 -50
- package/dist/hooks/usePwaUpdate.js.map +0 -6
- package/dist/providers/InitializeProvider.d.ts +0 -25
- package/dist/providers/InitializeProvider.d.ts.map +0 -1
- package/dist/providers/InitializeProvider.js +0 -60
- package/dist/providers/InitializeProvider.js.map +0 -6
- package/src/hooks/usePwaUpdate.ts +0 -73
- package/src/providers/ConfigContext.ts +0 -80
- package/src/providers/InitializeProvider.tsx +0 -79
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { onCleanup, onMount } from "solid-js";
|
|
1
|
+
import { onCleanup, onMount, type ParentComponent } from "solid-js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* 폼 컨트롤의 value를 클립보드 복사에 포함시키는
|
|
4
|
+
* 폼 컨트롤의 value를 클립보드 복사에 포함시키는 Provider
|
|
5
5
|
*
|
|
6
6
|
* @remarks
|
|
7
7
|
* 브라우저 기본 동작에서는 드래그 선택 후 복사 시 `<input>`, `<textarea>`, `<select>`의
|
|
@@ -13,7 +13,7 @@ import { onCleanup, onMount } from "solid-js";
|
|
|
13
13
|
* - `<input type="checkbox|radio">` → `.checked` ? "Y" : ""
|
|
14
14
|
* - 테이블 내에서는 셀 간 탭(`\t`), 행 간 개행(`\n`) 구분 (Excel 호환)
|
|
15
15
|
*/
|
|
16
|
-
export
|
|
16
|
+
export const ClipboardProvider: ParentComponent = (props) => {
|
|
17
17
|
onMount(() => {
|
|
18
18
|
const handler = (e: ClipboardEvent) => {
|
|
19
19
|
const sel = window.getSelection();
|
|
@@ -30,7 +30,9 @@ export function useClipboardValueCopy(): void {
|
|
|
30
30
|
document.addEventListener("copy", handler);
|
|
31
31
|
onCleanup(() => document.removeEventListener("copy", handler));
|
|
32
32
|
});
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
return <>{props.children}</>;
|
|
35
|
+
};
|
|
34
36
|
|
|
35
37
|
/**
|
|
36
38
|
* Selection Range 내의 텍스트를 추출한다.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createContext, useContext, type ParentComponent } from "solid-js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 앱 전역 설정
|
|
5
|
+
*/
|
|
6
|
+
export interface AppConfig {
|
|
7
|
+
/**
|
|
8
|
+
* 클라이언트 식별자 (저장소 key prefix로 사용)
|
|
9
|
+
*/
|
|
10
|
+
clientName: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 앱 전역 설정 Context
|
|
15
|
+
*/
|
|
16
|
+
export const ConfigContext = createContext<AppConfig>();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 앱 전역 설정에 접근하는 훅
|
|
20
|
+
*
|
|
21
|
+
* @throws ConfigProvider가 없으면 에러 발생
|
|
22
|
+
*/
|
|
23
|
+
export function useConfig(): AppConfig {
|
|
24
|
+
const context = useContext(ConfigContext);
|
|
25
|
+
if (!context) {
|
|
26
|
+
throw new Error("useConfig는 ConfigProvider 내부에서만 사용할 수 있습니다");
|
|
27
|
+
}
|
|
28
|
+
return context;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 앱 전역 설정 Provider
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* <ConfigProvider clientName="myApp">
|
|
37
|
+
* <App />
|
|
38
|
+
* </ConfigProvider>
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export const ConfigProvider: ParentComponent<{ clientName: string }> = (props) => {
|
|
42
|
+
return (
|
|
43
|
+
// eslint-disable-next-line solid/reactivity -- clientName은 초기 설정값으로 변경되지 않음
|
|
44
|
+
<ConfigContext.Provider value={{ clientName: props.clientName }}>
|
|
45
|
+
{props.children}
|
|
46
|
+
</ConfigContext.Provider>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { onCleanup, type ParentComponent } from "solid-js";
|
|
2
|
+
import { useLogger } from "../hooks/useLogger";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 전역 에러 캡처 Provider
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* window.onerror, unhandledrejection 이벤트를 캡처하여 useLogger를 통해 로깅한다.
|
|
9
|
+
* LoggerProvider가 없으면 consola로 fallback.
|
|
10
|
+
*/
|
|
11
|
+
export const ErrorLoggerProvider: ParentComponent = (props) => {
|
|
12
|
+
const logger = useLogger();
|
|
13
|
+
|
|
14
|
+
const onError = (event: ErrorEvent) => {
|
|
15
|
+
logger.error("Uncaught error:", event.error ?? event.message);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const onUnhandledRejection = (event: PromiseRejectionEvent) => {
|
|
19
|
+
logger.error("Unhandled rejection:", event.reason);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
window.addEventListener("error", onError);
|
|
23
|
+
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
|
24
|
+
|
|
25
|
+
onCleanup(() => {
|
|
26
|
+
window.removeEventListener("error", onError);
|
|
27
|
+
window.removeEventListener("unhandledrejection", onUnhandledRejection);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return <>{props.children}</>;
|
|
31
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createContext, useContext, type ParentComponent } from "solid-js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 로그 어댑터 인터페이스
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* - `useLogger`에서 사용하는 로그 전송 어댑터 (DB, 서버 등)
|
|
8
|
+
* - adapter가 설정되면 consola 대신 adapter만 사용됨
|
|
9
|
+
*/
|
|
10
|
+
export interface LogAdapter {
|
|
11
|
+
write(severity: "error" | "warn" | "info" | "log", ...data: any[]): Promise<void> | void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 로그 어댑터 Context
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* Provider가 없으면 `undefined` (useLogger에서 consola로 fallback)
|
|
19
|
+
*/
|
|
20
|
+
export const LoggerContext = createContext<LogAdapter | undefined>(undefined);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 로그 어댑터 Context에 접근하는 훅
|
|
24
|
+
*
|
|
25
|
+
* @returns LogAdapter 또는 undefined (Provider가 없으면)
|
|
26
|
+
*/
|
|
27
|
+
export function useLogAdapter(): LogAdapter | undefined {
|
|
28
|
+
return useContext(LoggerContext);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 로그 어댑터 Provider
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* <LoggerProvider adapter={myLogAdapter}>
|
|
37
|
+
* <App />
|
|
38
|
+
* </LoggerProvider>
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export const LoggerProvider: ParentComponent<{ adapter: LogAdapter }> = (props) => {
|
|
42
|
+
return (
|
|
43
|
+
// eslint-disable-next-line solid/reactivity -- adapter는 초기 설정값으로 변경되지 않음
|
|
44
|
+
<LoggerContext.Provider value={props.adapter}>{props.children}</LoggerContext.Provider>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { onCleanup, type ParentComponent } from "solid-js";
|
|
2
|
+
import { useNotification } from "../components/feedback/notification/NotificationContext";
|
|
3
|
+
|
|
4
|
+
const UPDATE_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PWA Service Worker 업데이트 감지 Provider
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* 5분마다 SW 업데이트를 폴링하며, 새 버전 감지 시 알림을 표시한다.
|
|
11
|
+
* NotificationProvider 내부에서 사용해야 한다.
|
|
12
|
+
*
|
|
13
|
+
* navigator.serviceWorker가 없거나 등록된 SW가 없으면 graceful no-op.
|
|
14
|
+
*/
|
|
15
|
+
export const PwaUpdateProvider: ParentComponent = (props) => {
|
|
16
|
+
if (typeof navigator !== "undefined" && "serviceWorker" in navigator) {
|
|
17
|
+
const notification = useNotification();
|
|
18
|
+
let intervalId: ReturnType<typeof setInterval> | undefined;
|
|
19
|
+
|
|
20
|
+
void navigator.serviceWorker.getRegistration().then((registration) => {
|
|
21
|
+
if (registration == null) return;
|
|
22
|
+
|
|
23
|
+
intervalId = setInterval(() => {
|
|
24
|
+
void registration.update();
|
|
25
|
+
}, UPDATE_INTERVAL);
|
|
26
|
+
|
|
27
|
+
if (registration.waiting != null) {
|
|
28
|
+
promptUpdate(registration.waiting);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
registration.addEventListener("updatefound", () => {
|
|
32
|
+
const newSW = registration.installing;
|
|
33
|
+
if (newSW == null) return;
|
|
34
|
+
|
|
35
|
+
newSW.addEventListener("statechange", () => {
|
|
36
|
+
if (newSW.state === "installed" && navigator.serviceWorker.controller != null) {
|
|
37
|
+
promptUpdate(newSW);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const onControllerChange = () => {
|
|
44
|
+
window.location.reload();
|
|
45
|
+
};
|
|
46
|
+
navigator.serviceWorker.addEventListener("controllerchange", onControllerChange);
|
|
47
|
+
|
|
48
|
+
onCleanup(() => {
|
|
49
|
+
if (intervalId != null) {
|
|
50
|
+
clearInterval(intervalId);
|
|
51
|
+
}
|
|
52
|
+
navigator.serviceWorker.removeEventListener("controllerchange", onControllerChange);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function promptUpdate(waitingSW: ServiceWorker): void {
|
|
56
|
+
notification.info("앱이 업데이트되었습니다", "새로고침하면 최신 버전을 사용할 수 있습니다", {
|
|
57
|
+
action: {
|
|
58
|
+
label: "새로고침",
|
|
59
|
+
onClick: () => {
|
|
60
|
+
waitingSW.postMessage({ type: "SKIP_WAITING" });
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return <>{props.children}</>;
|
|
68
|
+
};
|
|
@@ -13,9 +13,7 @@ export const ServiceClientContext = createContext<ServiceClientContextValue>();
|
|
|
13
13
|
export function useServiceClient(): ServiceClientContextValue {
|
|
14
14
|
const context = useContext(ServiceClientContext);
|
|
15
15
|
if (!context) {
|
|
16
|
-
throw new Error(
|
|
17
|
-
"useServiceClient는 ServiceClientProvider 내부에서만 사용할 수 있습니다. ServiceClientProvider는 InitializeProvider와 NotificationProvider 아래에 위치해야 합니다",
|
|
18
|
-
);
|
|
16
|
+
throw new Error("useServiceClient는 ServiceClientProvider 내부에서만 사용할 수 있습니다");
|
|
19
17
|
}
|
|
20
18
|
return context;
|
|
21
19
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createContext, useContext, type ParentComponent } from "solid-js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 커스텀 동기화 저장소 어댑터 인터페이스
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* - 동기 저장소: `localStorage`, `sessionStorage` 등 그대로 전달 가능
|
|
8
|
+
* - 비동기 저장소: `getItem`이 `Promise`를 반환하는 구현체 전달
|
|
9
|
+
*/
|
|
10
|
+
export interface StorageAdapter {
|
|
11
|
+
getItem(key: string): string | null | Promise<string | null>;
|
|
12
|
+
setItem(key: string, value: string): void | Promise<unknown>;
|
|
13
|
+
removeItem(key: string): void | Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 동기화 저장소 Context
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* Provider가 없으면 `undefined` (useSyncConfig에서 localStorage로 fallback)
|
|
21
|
+
*/
|
|
22
|
+
export const SyncStorageContext = createContext<StorageAdapter | undefined>(undefined);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 동기화 저장소 Context에 접근하는 훅
|
|
26
|
+
*
|
|
27
|
+
* @returns StorageAdapter 또는 undefined (Provider가 없으면)
|
|
28
|
+
*/
|
|
29
|
+
export function useSyncStorage(): StorageAdapter | undefined {
|
|
30
|
+
return useContext(SyncStorageContext);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 동기화 저장소 Provider
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* <SyncStorageProvider storage={myStorageAdapter}>
|
|
39
|
+
* <ThemeProvider>
|
|
40
|
+
* <App />
|
|
41
|
+
* </ThemeProvider>
|
|
42
|
+
* </SyncStorageProvider>
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export const SyncStorageProvider: ParentComponent<{ storage: StorageAdapter }> = (props) => {
|
|
46
|
+
return (
|
|
47
|
+
// eslint-disable-next-line solid/reactivity -- storage는 초기 설정값으로 변경되지 않음
|
|
48
|
+
<SyncStorageContext.Provider value={props.storage}>
|
|
49
|
+
{props.children}
|
|
50
|
+
</SyncStorageContext.Provider>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
@@ -60,9 +60,7 @@ const ThemeContext = createContext<ThemeContextValue>();
|
|
|
60
60
|
export function useTheme(): ThemeContextValue {
|
|
61
61
|
const context = useContext(ThemeContext);
|
|
62
62
|
if (!context) {
|
|
63
|
-
throw new Error(
|
|
64
|
-
"useTheme는 ThemeProvider 내부에서만 사용할 수 있습니다. ThemeProvider는 InitializeProvider 아래에 위치해야 합니다",
|
|
65
|
-
);
|
|
63
|
+
throw new Error("useTheme는 ThemeProvider 내부에서만 사용할 수 있습니다");
|
|
66
64
|
}
|
|
67
65
|
return context;
|
|
68
66
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useClipboardValueCopy.d.ts","sourceRoot":"","sources":["../../src/hooks/useClipboardValueCopy.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAiB5C"}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/hooks/useClipboardValueCopy.ts"],
|
|
4
|
-
"mappings": "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",
|
|
5
|
-
"names": []
|
|
6
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PWA Service Worker update detection hook.
|
|
3
|
-
*
|
|
4
|
-
* Polls for SW updates every 5 minutes. When a new version is detected,
|
|
5
|
-
* shows a notification with a reload action via the Notification system.
|
|
6
|
-
*
|
|
7
|
-
* No-ops gracefully when:
|
|
8
|
-
* - `navigator.serviceWorker` is unavailable (HTTP, unsupported browser)
|
|
9
|
-
* - No service worker is registered (dev mode, tests)
|
|
10
|
-
*
|
|
11
|
-
* Must be called inside NotificationProvider.
|
|
12
|
-
*/
|
|
13
|
-
export declare function usePwaUpdate(): void;
|
|
14
|
-
//# sourceMappingURL=usePwaUpdate.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"usePwaUpdate.d.ts","sourceRoot":"","sources":["../../src/hooks/usePwaUpdate.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAuDnC"}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { onCleanup } from "solid-js";
|
|
2
|
-
import { useNotification } from "../components/feedback/notification/NotificationContext.js";
|
|
3
|
-
const UPDATE_INTERVAL = 5 * 60 * 1e3;
|
|
4
|
-
function usePwaUpdate() {
|
|
5
|
-
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return;
|
|
6
|
-
const notification = useNotification();
|
|
7
|
-
let intervalId;
|
|
8
|
-
void navigator.serviceWorker.getRegistration().then((registration) => {
|
|
9
|
-
if (registration == null) return;
|
|
10
|
-
intervalId = setInterval(() => {
|
|
11
|
-
void registration.update();
|
|
12
|
-
}, UPDATE_INTERVAL);
|
|
13
|
-
if (registration.waiting != null) {
|
|
14
|
-
promptUpdate(registration.waiting);
|
|
15
|
-
}
|
|
16
|
-
registration.addEventListener("updatefound", () => {
|
|
17
|
-
const newSW = registration.installing;
|
|
18
|
-
if (newSW == null) return;
|
|
19
|
-
newSW.addEventListener("statechange", () => {
|
|
20
|
-
if (newSW.state === "installed" && navigator.serviceWorker.controller != null) {
|
|
21
|
-
promptUpdate(newSW);
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
const onControllerChange = () => {
|
|
27
|
-
window.location.reload();
|
|
28
|
-
};
|
|
29
|
-
navigator.serviceWorker.addEventListener("controllerchange", onControllerChange);
|
|
30
|
-
onCleanup(() => {
|
|
31
|
-
if (intervalId != null) {
|
|
32
|
-
clearInterval(intervalId);
|
|
33
|
-
}
|
|
34
|
-
navigator.serviceWorker.removeEventListener("controllerchange", onControllerChange);
|
|
35
|
-
});
|
|
36
|
-
function promptUpdate(waitingSW) {
|
|
37
|
-
notification.info("\uC571\uC774 \uC5C5\uB370\uC774\uD2B8\uB418\uC5C8\uC2B5\uB2C8\uB2E4", "\uC0C8\uB85C\uACE0\uCE68\uD558\uBA74 \uCD5C\uC2E0 \uBC84\uC804\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4", {
|
|
38
|
-
action: {
|
|
39
|
-
label: "\uC0C8\uB85C\uACE0\uCE68",
|
|
40
|
-
onClick: () => {
|
|
41
|
-
waitingSW.postMessage({ type: "SKIP_WAITING" });
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
export {
|
|
48
|
-
usePwaUpdate
|
|
49
|
-
};
|
|
50
|
-
//# sourceMappingURL=usePwaUpdate.js.map
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/hooks/usePwaUpdate.ts"],
|
|
4
|
-
"mappings": "AAAA,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB;AAEhC,MAAM,kBAAkB,IAAI,KAAK;AAc1B,SAAS,eAAqB;AACnC,MAAI,OAAO,cAAc,eAAe,EAAE,mBAAmB,WAAY;AAEzE,QAAM,eAAe,gBAAgB;AACrC,MAAI;AAEJ,OAAK,UAAU,cAAc,gBAAgB,EAAE,KAAK,CAAC,iBAAiB;AACpE,QAAI,gBAAgB,KAAM;AAG1B,iBAAa,YAAY,MAAM;AAC7B,WAAK,aAAa,OAAO;AAAA,IAC3B,GAAG,eAAe;AAGlB,QAAI,aAAa,WAAW,MAAM;AAChC,mBAAa,aAAa,OAAO;AAAA,IACnC;AAGA,iBAAa,iBAAiB,eAAe,MAAM;AACjD,YAAM,QAAQ,aAAa;AAC3B,UAAI,SAAS,KAAM;AAEnB,YAAM,iBAAiB,eAAe,MAAM;AAC1C,YAAI,MAAM,UAAU,eAAe,UAAU,cAAc,cAAc,MAAM;AAC7E,uBAAa,KAAK;AAAA,QACpB;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH,CAAC;AAGD,QAAM,qBAAqB,MAAM;AAC/B,WAAO,SAAS,OAAO;AAAA,EACzB;AACA,YAAU,cAAc,iBAAiB,oBAAoB,kBAAkB;AAE/E,YAAU,MAAM;AACd,QAAI,cAAc,MAAM;AACtB,oBAAc,UAAU;AAAA,IAC1B;AACA,cAAU,cAAc,oBAAoB,oBAAoB,kBAAkB;AAAA,EACpF,CAAC;AAED,WAAS,aAAa,WAAgC;AACpD,iBAAa,KAAK,uEAAgB,2HAA4B;AAAA,MAC5D,QAAQ;AAAA,QACN,OAAO;AAAA,QACP,SAAS,MAAM;AACb,oBAAU,YAAY,EAAE,MAAM,eAAe,CAAC;AAAA,QAChD;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;",
|
|
5
|
-
"names": []
|
|
6
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { ParentComponent } from "solid-js";
|
|
2
|
-
import { type AppConfig } from "./ConfigContext";
|
|
3
|
-
/**
|
|
4
|
-
* @simplysm/solid 초기화 Provider
|
|
5
|
-
*
|
|
6
|
-
* @remarks
|
|
7
|
-
* 앱 루트에서 한 번 감싸며, 다음을 초기화한다:
|
|
8
|
-
* - 앱 전역 설정 (config) Context 제공
|
|
9
|
-
* - 폼 컨트롤 value 클립보드 복사 지원
|
|
10
|
-
* - 테마 (라이트/다크/시스템)
|
|
11
|
-
* - 알림 시스템 + 배너
|
|
12
|
-
* - 전역 에러 캡처 (window.onerror, unhandledrejection)
|
|
13
|
-
* - 루트 로딩 오버레이
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```tsx
|
|
17
|
-
* <InitializeProvider config={{ clientName: "myApp" }}>
|
|
18
|
-
* <App />
|
|
19
|
-
* </InitializeProvider>
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
export declare const InitializeProvider: ParentComponent<{
|
|
23
|
-
config: AppConfig;
|
|
24
|
-
}>;
|
|
25
|
-
//# sourceMappingURL=InitializeProvider.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"InitializeProvider.d.ts","sourceRoot":"","sources":["../../src/providers/InitializeProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD,OAAO,EAAE,KAAK,SAAS,EAAiB,MAAM,iBAAiB,CAAC;AAuChE;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,kBAAkB,EAAE,eAAe,CAAC;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAkBrE,CAAC"}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { createComponent as _$createComponent } from "solid-js/web";
|
|
2
|
-
import { onCleanup } from "solid-js";
|
|
3
|
-
import { ConfigContext } from "./ConfigContext.js";
|
|
4
|
-
import { useClipboardValueCopy } from "../hooks/useClipboardValueCopy.js";
|
|
5
|
-
import { ThemeProvider } from "./ThemeContext.js";
|
|
6
|
-
import { NotificationProvider } from "../components/feedback/notification/NotificationProvider.js";
|
|
7
|
-
import { NotificationBanner } from "../components/feedback/notification/NotificationBanner.js";
|
|
8
|
-
import { BusyProvider } from "../components/feedback/busy/BusyProvider.js";
|
|
9
|
-
import { usePwaUpdate } from "../hooks/usePwaUpdate.js";
|
|
10
|
-
import { useLogger } from "../hooks/useLogger.js";
|
|
11
|
-
function PwaUpdater() {
|
|
12
|
-
usePwaUpdate();
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
function GlobalErrorLogger() {
|
|
16
|
-
const logger = useLogger();
|
|
17
|
-
const onError = (event) => {
|
|
18
|
-
logger.error("Uncaught error:", event.error ?? event.message);
|
|
19
|
-
};
|
|
20
|
-
const onUnhandledRejection = (event) => {
|
|
21
|
-
logger.error("Unhandled rejection:", event.reason);
|
|
22
|
-
};
|
|
23
|
-
window.addEventListener("error", onError);
|
|
24
|
-
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
|
25
|
-
onCleanup(() => {
|
|
26
|
-
window.removeEventListener("error", onError);
|
|
27
|
-
window.removeEventListener("unhandledrejection", onUnhandledRejection);
|
|
28
|
-
});
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
const InitializeProvider = (props) => {
|
|
32
|
-
useClipboardValueCopy();
|
|
33
|
-
return _$createComponent(ConfigContext.Provider, {
|
|
34
|
-
get value() {
|
|
35
|
-
return props.config;
|
|
36
|
-
},
|
|
37
|
-
get children() {
|
|
38
|
-
return _$createComponent(ThemeProvider, {
|
|
39
|
-
get children() {
|
|
40
|
-
return _$createComponent(NotificationProvider, {
|
|
41
|
-
get children() {
|
|
42
|
-
return [_$createComponent(NotificationBanner, {}), _$createComponent(GlobalErrorLogger, {}), _$createComponent(PwaUpdater, {}), _$createComponent(BusyProvider, {
|
|
43
|
-
get variant() {
|
|
44
|
-
return props.config.busyVariant;
|
|
45
|
-
},
|
|
46
|
-
get children() {
|
|
47
|
-
return props.children;
|
|
48
|
-
}
|
|
49
|
-
})];
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
};
|
|
57
|
-
export {
|
|
58
|
-
InitializeProvider
|
|
59
|
-
};
|
|
60
|
-
//# sourceMappingURL=InitializeProvider.js.map
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/providers/InitializeProvider.tsx"],
|
|
4
|
-
"mappings": ";AACA,SAASA,iBAAiB;AAC1B,SAAyBC,qBAAqB;AAC9C,SAASC,6BAA6B;AACtC,SAASC,qBAAqB;AAC9B,SAASC,4BAA4B;AACrC,SAASC,0BAA0B;AACnC,SAASC,oBAAoB;AAE7B,SAASC,oBAAoB;AAC7B,SAASC,iBAAiB;AAG1B,SAASC,aAAa;AACpBF,eAAa;AACb,SAAO;AACT;AAGA,SAASG,oBAAoB;AAC3B,QAAMC,SAASH,UAAU;AAEzB,QAAMI,UAAWC,WAAsB;AACrCF,WAAOG,MAAM,mBAAmBD,MAAMC,SAASD,MAAME,OAAO;EAC9D;AAEA,QAAMC,uBAAwBH,WAAiC;AAC7DF,WAAOG,MAAM,wBAAwBD,MAAMI,MAAM;EACnD;AAEAC,SAAOC,iBAAiB,SAASP,OAAO;AACxCM,SAAOC,iBAAiB,sBAAsBH,oBAAoB;AAElEhB,YAAU,MAAM;AACdkB,WAAOE,oBAAoB,SAASR,OAAO;AAC3CM,WAAOE,oBAAoB,sBAAsBJ,oBAAoB;EACvE,CAAC;AAED,SAAO;AACT;AAqBO,MAAMK,qBAA8DC,WAAU;AAEnFpB,wBAAsB;AAGtB,SAAAqB,kBACGtB,cAAcuB,UAAQ;IAAA,IAACC,QAAK;AAAA,aAAEH,MAAMI;IAAM;IAAA,IAAAC,WAAA;AAAA,aAAAJ,kBACxCpB,eAAa;QAAA,IAAAwB,WAAA;AAAA,iBAAAJ,kBACXnB,sBAAoB;YAAA,IAAAuB,WAAA;AAAA,qBAAA,CAAAJ,kBAClBlB,oBAAkB,CAAA,CAAA,GAAAkB,kBAClBb,mBAAiB,CAAA,CAAA,GAAAa,kBACjBd,YAAU,CAAA,CAAA,GAAAc,kBACVjB,cAAY;gBAAA,IAACsB,UAAO;AAAA,yBAAEN,MAAMI,OAAOG;gBAAW;gBAAA,IAAAF,WAAA;AAAA,yBAAGL,MAAMK;gBAAQ;cAAA,CAAA,CAAA;YAAA;UAAA,CAAA;QAAA;MAAA,CAAA;IAAA;EAAA,CAAA;AAM1E;",
|
|
5
|
-
"names": ["onCleanup", "ConfigContext", "useClipboardValueCopy", "ThemeProvider", "NotificationProvider", "NotificationBanner", "BusyProvider", "usePwaUpdate", "useLogger", "PwaUpdater", "GlobalErrorLogger", "logger", "onError", "event", "error", "message", "onUnhandledRejection", "reason", "window", "addEventListener", "removeEventListener", "InitializeProvider", "props", "_$createComponent", "Provider", "value", "config", "children", "variant", "busyVariant"]
|
|
6
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { onCleanup } from "solid-js";
|
|
2
|
-
import { useNotification } from "../components/feedback/notification/NotificationContext";
|
|
3
|
-
|
|
4
|
-
const UPDATE_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* PWA Service Worker update detection hook.
|
|
8
|
-
*
|
|
9
|
-
* Polls for SW updates every 5 minutes. When a new version is detected,
|
|
10
|
-
* shows a notification with a reload action via the Notification system.
|
|
11
|
-
*
|
|
12
|
-
* No-ops gracefully when:
|
|
13
|
-
* - `navigator.serviceWorker` is unavailable (HTTP, unsupported browser)
|
|
14
|
-
* - No service worker is registered (dev mode, tests)
|
|
15
|
-
*
|
|
16
|
-
* Must be called inside NotificationProvider.
|
|
17
|
-
*/
|
|
18
|
-
export function usePwaUpdate(): void {
|
|
19
|
-
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return;
|
|
20
|
-
|
|
21
|
-
const notification = useNotification();
|
|
22
|
-
let intervalId: ReturnType<typeof setInterval> | undefined;
|
|
23
|
-
|
|
24
|
-
void navigator.serviceWorker.getRegistration().then((registration) => {
|
|
25
|
-
if (registration == null) return;
|
|
26
|
-
|
|
27
|
-
// Periodic update check
|
|
28
|
-
intervalId = setInterval(() => {
|
|
29
|
-
void registration.update();
|
|
30
|
-
}, UPDATE_INTERVAL);
|
|
31
|
-
|
|
32
|
-
// Already waiting SW
|
|
33
|
-
if (registration.waiting != null) {
|
|
34
|
-
promptUpdate(registration.waiting);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Detect new SW installation
|
|
38
|
-
registration.addEventListener("updatefound", () => {
|
|
39
|
-
const newSW = registration.installing;
|
|
40
|
-
if (newSW == null) return;
|
|
41
|
-
|
|
42
|
-
newSW.addEventListener("statechange", () => {
|
|
43
|
-
if (newSW.state === "installed" && navigator.serviceWorker.controller != null) {
|
|
44
|
-
promptUpdate(newSW);
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// Reload when new SW takes control
|
|
51
|
-
const onControllerChange = () => {
|
|
52
|
-
window.location.reload();
|
|
53
|
-
};
|
|
54
|
-
navigator.serviceWorker.addEventListener("controllerchange", onControllerChange);
|
|
55
|
-
|
|
56
|
-
onCleanup(() => {
|
|
57
|
-
if (intervalId != null) {
|
|
58
|
-
clearInterval(intervalId);
|
|
59
|
-
}
|
|
60
|
-
navigator.serviceWorker.removeEventListener("controllerchange", onControllerChange);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
function promptUpdate(waitingSW: ServiceWorker): void {
|
|
64
|
-
notification.info("앱이 업데이트되었습니다", "새로고침하면 최신 버전을 사용할 수 있습니다", {
|
|
65
|
-
action: {
|
|
66
|
-
label: "새로고침",
|
|
67
|
-
onClick: () => {
|
|
68
|
-
waitingSW.postMessage({ type: "SKIP_WAITING" });
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext } from "solid-js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 커스텀 저장소 어댑터 인터페이스
|
|
5
|
-
*
|
|
6
|
-
* @remarks
|
|
7
|
-
* - 동기 저장소: `localStorage`, `sessionStorage` 등 그대로 전달 가능
|
|
8
|
-
* - 비동기 저장소: `getItem`이 `Promise`를 반환하는 구현체 전달
|
|
9
|
-
*/
|
|
10
|
-
export interface StorageAdapter {
|
|
11
|
-
getItem(key: string): string | null | Promise<string | null>;
|
|
12
|
-
setItem(key: string, value: string): void | Promise<unknown>;
|
|
13
|
-
removeItem(key: string): void | Promise<void>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* 로그 어댑터 인터페이스
|
|
18
|
-
*
|
|
19
|
-
* @remarks
|
|
20
|
-
* - `useLogger`에서 사용하는 로그 전송 어댑터 (DB, 서버 등)
|
|
21
|
-
* - adapter가 설정되면 consola 대신 adapter만 사용됨
|
|
22
|
-
*/
|
|
23
|
-
export interface LogAdapter {
|
|
24
|
-
write(severity: "error" | "warn" | "info" | "log", ...data: any[]): Promise<void> | void;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 앱 전역 설정
|
|
29
|
-
*/
|
|
30
|
-
export interface AppConfig {
|
|
31
|
-
/**
|
|
32
|
-
* 클라이언트 식별자 (저장소 key prefix로 사용)
|
|
33
|
-
*/
|
|
34
|
-
clientName: string;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* 동기화 가능 저장소 (useSyncConfig에서 사용, 없으면 localStorage로 fallback)
|
|
38
|
-
*/
|
|
39
|
-
syncStorage?: StorageAdapter;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* 로그 어댑터 (useLogger에서 consola 외 추가 전송에 사용)
|
|
43
|
-
*/
|
|
44
|
-
logger?: LogAdapter;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 루트 busy 오버레이 변형 (기본값: "spinner")
|
|
48
|
-
*/
|
|
49
|
-
busyVariant?: "spinner" | "bar";
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* 앱 전역 설정 Context
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* ```tsx
|
|
57
|
-
* // 앱 루트에서 Provider 설정
|
|
58
|
-
* <ConfigContext.Provider value={{ clientName: "myApp" }}>
|
|
59
|
-
* <App />
|
|
60
|
-
* </ConfigContext.Provider>
|
|
61
|
-
*
|
|
62
|
-
* // 컴포넌트에서 사용
|
|
63
|
-
* const config = useConfig();
|
|
64
|
-
* console.log(config.clientName); // "myApp"
|
|
65
|
-
* ```
|
|
66
|
-
*/
|
|
67
|
-
export const ConfigContext = createContext<AppConfig>();
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* 앱 전역 설정에 접근하는 훅
|
|
71
|
-
*
|
|
72
|
-
* @throws ConfigContext.Provider가 없으면 에러 발생
|
|
73
|
-
*/
|
|
74
|
-
export function useConfig(): AppConfig {
|
|
75
|
-
const context = useContext(ConfigContext);
|
|
76
|
-
if (!context) {
|
|
77
|
-
throw new Error("useConfig는 ConfigContext.Provider 내부에서만 사용할 수 있습니다");
|
|
78
|
-
}
|
|
79
|
-
return context;
|
|
80
|
-
}
|