@yargram/react 1.0.0
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/.storybook/main.ts +21 -0
- package/.storybook/preview.ts +21 -0
- package/dist/components/LogWindow/LogEntryRow.d.ts +8 -0
- package/dist/components/LogWindow/LogEntryRow.d.ts.map +1 -0
- package/dist/components/LogWindow/LogEntryRow.js +14 -0
- package/dist/components/LogWindow/LogWindow.d.ts +41 -0
- package/dist/components/LogWindow/LogWindow.d.ts.map +1 -0
- package/dist/components/LogWindow/LogWindow.js +144 -0
- package/dist/components/LogWindow/LogWindow.stories.d.ts +29 -0
- package/dist/components/LogWindow/LogWindow.stories.d.ts.map +1 -0
- package/dist/components/LogWindow/LogWindow.stories.js +183 -0
- package/dist/components/LogWindow/LogWindow.test.d.ts +2 -0
- package/dist/components/LogWindow/LogWindow.test.d.ts.map +1 -0
- package/dist/components/LogWindow/LogWindow.test.js +61 -0
- package/dist/components/LogWindow/LogWindowEscapeDemo.d.ts +12 -0
- package/dist/components/LogWindow/LogWindowEscapeDemo.d.ts.map +1 -0
- package/dist/components/LogWindow/LogWindowEscapeDemo.js +56 -0
- package/dist/components/LogWindow/NetworkEntryRow.d.ts +8 -0
- package/dist/components/LogWindow/NetworkEntryRow.d.ts.map +1 -0
- package/dist/components/LogWindow/NetworkEntryRow.js +32 -0
- package/dist/components/LogWindow/index.d.ts +7 -0
- package/dist/components/LogWindow/index.d.ts.map +1 -0
- package/dist/components/LogWindow/index.js +4 -0
- package/dist/components/LogWindow/types.d.ts +36 -0
- package/dist/components/LogWindow/types.d.ts.map +1 -0
- package/dist/components/LogWindow/types.js +1 -0
- package/dist/components/LoginWindow/LoginForm.d.ts +11 -0
- package/dist/components/LoginWindow/LoginForm.d.ts.map +1 -0
- package/dist/components/LoginWindow/LoginForm.js +28 -0
- package/dist/components/LoginWindow/LoginForm.test.d.ts +2 -0
- package/dist/components/LoginWindow/LoginForm.test.d.ts.map +1 -0
- package/dist/components/LoginWindow/LoginForm.test.js +34 -0
- package/dist/components/LoginWindow/LoginWindow.d.ts +15 -0
- package/dist/components/LoginWindow/LoginWindow.d.ts.map +1 -0
- package/dist/components/LoginWindow/LoginWindow.js +27 -0
- package/dist/components/LoginWindow/index.d.ts +3 -0
- package/dist/components/LoginWindow/index.d.ts.map +1 -0
- package/dist/components/LoginWindow/index.js +1 -0
- package/dist/contexts/ApiContext.d.ts +35 -0
- package/dist/contexts/ApiContext.d.ts.map +1 -0
- package/dist/contexts/ApiContext.js +82 -0
- package/dist/contexts/ApiContext.test.d.ts +2 -0
- package/dist/contexts/ApiContext.test.d.ts.map +1 -0
- package/dist/contexts/ApiContext.test.js +45 -0
- package/dist/contexts/PrinterContext.d.ts +12 -0
- package/dist/contexts/PrinterContext.d.ts.map +1 -0
- package/dist/contexts/PrinterContext.js +17 -0
- package/dist/contexts/PrinterContext.test.d.ts +2 -0
- package/dist/contexts/PrinterContext.test.d.ts.map +1 -0
- package/dist/contexts/PrinterContext.test.js +19 -0
- package/dist/contexts/YahmanContext.d.ts +69 -0
- package/dist/contexts/YahmanContext.d.ts.map +1 -0
- package/dist/contexts/YahmanContext.js +414 -0
- package/dist/contexts/YahmanContext.stories.d.ts +16 -0
- package/dist/contexts/YahmanContext.stories.d.ts.map +1 -0
- package/dist/contexts/YahmanContext.stories.js +64 -0
- package/dist/contexts/YargramContext.d.ts +69 -0
- package/dist/contexts/YargramContext.d.ts.map +1 -0
- package/dist/contexts/YargramContext.js +414 -0
- package/dist/contexts/YargramContext.stories.d.ts +16 -0
- package/dist/contexts/YargramContext.stories.d.ts.map +1 -0
- package/dist/contexts/YargramContext.stories.js +64 -0
- package/dist/contexts/YargramContext.test.d.ts +2 -0
- package/dist/contexts/YargramContext.test.d.ts.map +1 -0
- package/dist/contexts/YargramContext.test.js +54 -0
- package/dist/hooks/useLogWindowShortcut.d.ts +24 -0
- package/dist/hooks/useLogWindowShortcut.d.ts.map +1 -0
- package/dist/hooks/useLogWindowShortcut.js +61 -0
- package/dist/hooks/useLogWindowShortcut.test.d.ts +2 -0
- package/dist/hooks/useLogWindowShortcut.test.d.ts.map +1 -0
- package/dist/hooks/useLogWindowShortcut.test.js +93 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/test/setup.d.ts +2 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/test/setup.js +1 -0
- package/package.json +49 -0
- package/src/components/LogWindow/LogEntryRow.tsx +38 -0
- package/src/components/LogWindow/LogWindow.css +614 -0
- package/src/components/LogWindow/LogWindow.stories.tsx +206 -0
- package/src/components/LogWindow/LogWindow.test.tsx +68 -0
- package/src/components/LogWindow/LogWindow.tsx +379 -0
- package/src/components/LogWindow/LogWindowEscapeDemo.tsx +100 -0
- package/src/components/LogWindow/NetworkEntryRow.tsx +102 -0
- package/src/components/LogWindow/index.ts +13 -0
- package/src/components/LogWindow/types.ts +40 -0
- package/src/components/LoginWindow/LoginForm.test.tsx +38 -0
- package/src/components/LoginWindow/LoginForm.tsx +78 -0
- package/src/components/LoginWindow/LoginWindow.css +198 -0
- package/src/components/LoginWindow/LoginWindow.tsx +90 -0
- package/src/components/LoginWindow/index.ts +2 -0
- package/src/contexts/ApiContext.test.tsx +68 -0
- package/src/contexts/ApiContext.tsx +155 -0
- package/src/contexts/PrinterContext.test.tsx +37 -0
- package/src/contexts/PrinterContext.tsx +35 -0
- package/src/contexts/YargramContext.stories.tsx +148 -0
- package/src/contexts/YargramContext.test.tsx +105 -0
- package/src/contexts/YargramContext.tsx +676 -0
- package/src/hooks/useLogWindowShortcut.test.ts +111 -0
- package/src/hooks/useLogWindowShortcut.ts +96 -0
- package/src/index.ts +14 -0
- package/src/test/setup.ts +1 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +18 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Globe, Database, ChevronRight, ChevronDown } from 'lucide-react';
|
|
4
|
+
import './LogWindow.css';
|
|
5
|
+
function getStatusColor(status) {
|
|
6
|
+
if (status == null)
|
|
7
|
+
return 'var(--logw-text-muted)';
|
|
8
|
+
if (status >= 200 && status < 300)
|
|
9
|
+
return '#4ec9b0';
|
|
10
|
+
if (status >= 400)
|
|
11
|
+
return '#f14c4c';
|
|
12
|
+
return 'var(--logw-text-muted)';
|
|
13
|
+
}
|
|
14
|
+
export function NetworkEntryRow({ entry }) {
|
|
15
|
+
const [expanded, setExpanded] = useState(false);
|
|
16
|
+
const statusColor = getStatusColor(entry.status);
|
|
17
|
+
const statusText = entry.statusText ?? (entry.status != null ? String(entry.status) : '—');
|
|
18
|
+
const hasDetails = Boolean(entry.request ?? entry.response);
|
|
19
|
+
const handleToggle = () => {
|
|
20
|
+
if (hasDetails)
|
|
21
|
+
setExpanded((e) => !e);
|
|
22
|
+
};
|
|
23
|
+
const header = (_jsxs("div", { className: `logWindowNetworkEntry ${hasDetails ? 'logWindowNetworkEntryClickable' : ''}`, onClick: hasDetails ? handleToggle : undefined, onKeyDown: hasDetails
|
|
24
|
+
? (e) => {
|
|
25
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
handleToggle();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
: undefined, role: hasDetails ? 'button' : undefined, tabIndex: hasDetails ? 0 : undefined, "aria-expanded": hasDetails ? expanded : undefined, children: [_jsx("span", { className: "logWindowNetworkEntryChevron", children: hasDetails ? (expanded ? (_jsx(ChevronDown, { size: 14, "aria-hidden": true })) : (_jsx(ChevronRight, { size: 14, "aria-hidden": true }))) : null }), _jsx("span", { className: `logWindowNetworkEntryIcon ${entry.type === 'graphql' ? 'logWindowNetworkEntryIconGraphql' : ''}`, title: entry.type === 'rest' ? 'REST' : 'GraphQL', children: entry.type === 'rest' ? _jsx(Globe, { size: 12 }) : _jsx(Database, { size: 12 }) }), entry.type === 'rest' ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "logWindowNetworkEntryMethod", children: entry.method }), _jsx("span", { className: "logWindowNetworkEntryUrl", children: entry.url })] })) : (_jsx("span", { className: "logWindowNetworkEntryOperation", children: entry.operationName ?? '(anonymous)' })), _jsx("span", { className: "logWindowNetworkEntryStatus", style: { color: statusColor }, children: statusText })] }));
|
|
31
|
+
return (_jsxs("div", { className: "logWindowNetworkEntryAccordion", children: [header, hasDetails && (entry.request != null || entry.response != null) && (_jsx("div", { className: `logWindowNetworkEntryDetailsWrapper ${expanded ? 'logWindowNetworkEntryDetailsWrapperExpanded' : ''}`, "aria-hidden": !expanded, children: _jsxs("div", { className: "logWindowNetworkEntryDetails", children: [entry.request != null && (_jsxs("div", { className: "logWindowNetworkEntryDetailSection", children: [_jsx("div", { className: "logWindowNetworkEntryDetailLabel", children: "Request" }), _jsx("pre", { className: "logWindowNetworkEntryDetailContent", children: entry.request })] })), entry.response != null && (_jsxs("div", { className: "logWindowNetworkEntryDetailSection", children: [_jsx("div", { className: "logWindowNetworkEntryDetailLabel", children: "Response" }), _jsx("pre", { className: "logWindowNetworkEntryDetailContent", children: entry.response })] }))] }) }))] }));
|
|
32
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { LogWindow } from './LogWindow';
|
|
2
|
+
export { LogWindowEscapeDemo } from './LogWindowEscapeDemo';
|
|
3
|
+
export type { LogWindowProps } from './LogWindow';
|
|
4
|
+
export { LogEntryRow } from './LogEntryRow';
|
|
5
|
+
export { NetworkEntryRow } from './NetworkEntryRow';
|
|
6
|
+
export type { LogEntry, LogLevel, LogWindowTab, NetworkEntry, NetworkEntryRest, NetworkEntryGraphql, } from './types';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/LogWindow/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EACV,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type LogLevel = 'info' | 'warn' | 'error';
|
|
2
|
+
export type LogEntry = {
|
|
3
|
+
id: string;
|
|
4
|
+
level: LogLevel;
|
|
5
|
+
message: string;
|
|
6
|
+
source: string;
|
|
7
|
+
};
|
|
8
|
+
export type LogWindowTab = 'logs' | 'networks';
|
|
9
|
+
/** REST API のネットワークエントリ */
|
|
10
|
+
export type NetworkEntryRest = {
|
|
11
|
+
id: string;
|
|
12
|
+
type: 'rest';
|
|
13
|
+
method: string;
|
|
14
|
+
url: string;
|
|
15
|
+
status?: number;
|
|
16
|
+
statusText?: string;
|
|
17
|
+
/** アコーディオン展開時の Request 表示用 */
|
|
18
|
+
request?: string;
|
|
19
|
+
/** アコーディオン展開時の Response 表示用 */
|
|
20
|
+
response?: string;
|
|
21
|
+
};
|
|
22
|
+
/** GraphQL のネットワークエントリ */
|
|
23
|
+
export type NetworkEntryGraphql = {
|
|
24
|
+
id: string;
|
|
25
|
+
type: 'graphql';
|
|
26
|
+
operationName?: string;
|
|
27
|
+
url: string;
|
|
28
|
+
status?: number;
|
|
29
|
+
statusText?: string;
|
|
30
|
+
/** アコーディオン展開時の Request 表示用 */
|
|
31
|
+
request?: string;
|
|
32
|
+
/** アコーディオン展開時の Response 表示用 */
|
|
33
|
+
response?: string;
|
|
34
|
+
};
|
|
35
|
+
export type NetworkEntry = NetworkEntryRest | NetworkEntryGraphql;
|
|
36
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/LogWindow/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEjD,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,CAAC;AAE/C,2BAA2B;AAC3B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,0BAA0B;AAC1B,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import './LoginWindow.css';
|
|
2
|
+
export type LoginFormProps = {
|
|
3
|
+
onLogin: (password: string) => Promise<void>;
|
|
4
|
+
title?: string;
|
|
5
|
+
submitLabel?: string;
|
|
6
|
+
errorMessage?: string;
|
|
7
|
+
onClearError?: () => void;
|
|
8
|
+
};
|
|
9
|
+
/** ログイン用フォーム(LogWindow 内などに埋め込む用。オーバーレイなし) */
|
|
10
|
+
export declare function LoginForm({ onLogin, title, submitLabel, errorMessage, onClearError, }: LoginFormProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=LoginForm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LoginForm.d.ts","sourceRoot":"","sources":["../../../src/components/LoginWindow/LoginForm.tsx"],"names":[],"mappings":"AAEA,OAAO,mBAAmB,CAAC;AAE3B,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B,CAAC;AAEF,8CAA8C;AAC9C,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,KAAe,EACf,WAAsB,EACtB,YAAY,EACZ,YAAY,GACb,EAAE,cAAc,2CA0DhB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback } from 'react';
|
|
3
|
+
import { Lock, LogIn } from 'lucide-react';
|
|
4
|
+
import './LoginWindow.css';
|
|
5
|
+
/** ログイン用フォーム(LogWindow 内などに埋め込む用。オーバーレイなし) */
|
|
6
|
+
export function LoginForm({ onLogin, title = 'Login', submitLabel = 'Log in', errorMessage, onClearError, }) {
|
|
7
|
+
const [password, setPassword] = useState('');
|
|
8
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
9
|
+
const handleSubmit = useCallback((e) => {
|
|
10
|
+
e.preventDefault();
|
|
11
|
+
onClearError?.();
|
|
12
|
+
if (!password)
|
|
13
|
+
return;
|
|
14
|
+
setIsSubmitting(true);
|
|
15
|
+
onLogin(password)
|
|
16
|
+
.then(() => {
|
|
17
|
+
setPassword('');
|
|
18
|
+
})
|
|
19
|
+
.catch(() => { })
|
|
20
|
+
.finally(() => {
|
|
21
|
+
setIsSubmitting(false);
|
|
22
|
+
});
|
|
23
|
+
}, [onLogin, password, onClearError]);
|
|
24
|
+
return (_jsxs("div", { className: "loginFormWrap", children: [title && (_jsxs("div", { className: "loginFormHeader", children: [_jsx(Lock, { size: 18, className: "loginFormHeaderIcon", "aria-hidden": true }), _jsx("h3", { className: "loginFormTitle", children: title })] })), _jsxs("form", { onSubmit: handleSubmit, className: "loginWindowForm", children: [_jsxs("label", { className: "loginWindowLabel", children: [_jsx(Lock, { size: 16, className: "loginWindowLabelIcon", "aria-hidden": true }), _jsx("span", { children: "Password" }), _jsx("input", { type: "password", className: "loginWindowInput", value: password, onChange: (e) => {
|
|
25
|
+
setPassword(e.target.value);
|
|
26
|
+
onClearError?.();
|
|
27
|
+
}, autoComplete: "current-password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", disabled: isSubmitting })] }), _jsxs("button", { type: "submit", className: "loginWindowSubmit", disabled: isSubmitting || !password, children: [_jsx(LogIn, { size: 16, "aria-hidden": true }), isSubmitting ? '...' : submitLabel] })] })] }));
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LoginForm.test.d.ts","sourceRoot":"","sources":["../../../src/components/LoginWindow/LoginForm.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
import { LoginForm } from './LoginForm';
|
|
6
|
+
describe('LoginForm', () => {
|
|
7
|
+
it('renders password input and submit button', () => {
|
|
8
|
+
const onLogin = vi.fn();
|
|
9
|
+
render(_jsx(LoginForm, { onLogin: onLogin }));
|
|
10
|
+
expect(screen.getByPlaceholderText('••••••••')).toBeInTheDocument();
|
|
11
|
+
expect(screen.getByRole('button', { name: /log in/i })).toBeInTheDocument();
|
|
12
|
+
});
|
|
13
|
+
it('submit button is disabled when password is empty', () => {
|
|
14
|
+
const onLogin = vi.fn();
|
|
15
|
+
render(_jsx(LoginForm, { onLogin: onLogin }));
|
|
16
|
+
expect(screen.getByRole('button', { name: /log in/i })).toBeDisabled();
|
|
17
|
+
});
|
|
18
|
+
it('calls onLogin with password on submit', async () => {
|
|
19
|
+
const user = userEvent.setup();
|
|
20
|
+
const onLogin = vi.fn().mockResolvedValue(undefined);
|
|
21
|
+
render(_jsx(LoginForm, { onLogin: onLogin }));
|
|
22
|
+
await user.type(screen.getByPlaceholderText('••••••••'), 'secret');
|
|
23
|
+
await user.click(screen.getByRole('button', { name: /log in/i }));
|
|
24
|
+
expect(onLogin).toHaveBeenCalledWith('secret');
|
|
25
|
+
});
|
|
26
|
+
it('shows custom title when provided', () => {
|
|
27
|
+
render(_jsx(LoginForm, { onLogin: vi.fn(), title: "Sign in" }));
|
|
28
|
+
expect(screen.getByText('Sign in')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
it('shows custom submit label when provided', () => {
|
|
31
|
+
render(_jsx(LoginForm, { onLogin: vi.fn(), submitLabel: "Submit" }));
|
|
32
|
+
expect(screen.getByRole('button', { name: /submit/i })).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import './LoginWindow.css';
|
|
2
|
+
export type LoginWindowProps = {
|
|
3
|
+
/** ログイン送信。成功で resolve、失敗で reject(メッセージは errorMessage で表示) */
|
|
4
|
+
onLogin: (password: string) => Promise<void>;
|
|
5
|
+
/** ウィンドウタイトル */
|
|
6
|
+
title?: string;
|
|
7
|
+
/** 送信ボタンラベル */
|
|
8
|
+
submitLabel?: string;
|
|
9
|
+
/** エラー表示用(onLogin が reject したとき) */
|
|
10
|
+
errorMessage?: string;
|
|
11
|
+
/** エラーをクリアする(入力変更時などに親から渡す) */
|
|
12
|
+
onClearError?: () => void;
|
|
13
|
+
};
|
|
14
|
+
export declare function LoginWindow({ onLogin, title, submitLabel, errorMessage, onClearError, }: LoginWindowProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=LoginWindow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LoginWindow.d.ts","sourceRoot":"","sources":["../../../src/components/LoginWindow/LoginWindow.tsx"],"names":[],"mappings":"AAEA,OAAO,mBAAmB,CAAC;AAE3B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,6DAA6D;IAC7D,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,gBAAgB;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,KAAe,EACf,WAAsB,EACtB,YAAY,EACZ,YAAY,GACb,EAAE,gBAAgB,2CAkElB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback } from 'react';
|
|
3
|
+
import { Lock, LogIn } from 'lucide-react';
|
|
4
|
+
import './LoginWindow.css';
|
|
5
|
+
export function LoginWindow({ onLogin, title = 'Login', submitLabel = 'Log in', errorMessage, onClearError, }) {
|
|
6
|
+
const [password, setPassword] = useState('');
|
|
7
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
8
|
+
const handleSubmit = useCallback((e) => {
|
|
9
|
+
e.preventDefault();
|
|
10
|
+
onClearError?.();
|
|
11
|
+
if (!password)
|
|
12
|
+
return;
|
|
13
|
+
setIsSubmitting(true);
|
|
14
|
+
onLogin(password)
|
|
15
|
+
.then(() => {
|
|
16
|
+
setPassword('');
|
|
17
|
+
})
|
|
18
|
+
.catch(() => { })
|
|
19
|
+
.finally(() => {
|
|
20
|
+
setIsSubmitting(false);
|
|
21
|
+
});
|
|
22
|
+
}, [onLogin, password, onClearError]);
|
|
23
|
+
return (_jsx("div", { className: "loginWindowOverlay", role: "dialog", "aria-modal": "true", "aria-labelledby": "loginWindowTitle", children: _jsxs("div", { className: "loginWindow", children: [_jsxs("div", { className: "loginWindowHeader", children: [_jsx(Lock, { size: 20, className: "loginWindowHeaderIcon", "aria-hidden": true }), _jsx("h2", { id: "loginWindowTitle", className: "loginWindowTitle", children: title })] }), _jsxs("form", { onSubmit: handleSubmit, className: "loginWindowForm", children: [errorMessage && (_jsx("div", { className: "loginWindowError", role: "alert", children: errorMessage })), _jsxs("label", { className: "loginWindowLabel", children: [_jsx(Lock, { size: 16, className: "loginWindowLabelIcon", "aria-hidden": true }), _jsx("span", { children: "Password" }), _jsx("input", { type: "password", className: "loginWindowInput", value: password, onChange: (e) => {
|
|
24
|
+
setPassword(e.target.value);
|
|
25
|
+
onClearError?.();
|
|
26
|
+
}, autoComplete: "current-password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", disabled: isSubmitting, autoFocus: true })] }), _jsxs("button", { type: "submit", className: "loginWindowSubmit", disabled: isSubmitting || !password, children: [_jsx(LogIn, { size: 16, "aria-hidden": true }), isSubmitting ? '...' : submitLabel] })] })] }) }));
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/LoginWindow/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LoginWindow } from './LoginWindow';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type ApolloClient as ApolloClientType, type NormalizedCacheObject, type QueryOptions, type MutationOptions, type ApolloQueryResult, type FetchResult } from '@apollo/client';
|
|
3
|
+
export type ApiContextValue = RestApiContextValue | GraphqlApiContextValue;
|
|
4
|
+
/** REST: useApi().get / .post / .put / .delete で LogWindow Network に反映 */
|
|
5
|
+
export type RestApiContextValue = {
|
|
6
|
+
provider: 'rest';
|
|
7
|
+
get: (path: string, options?: RequestInit) => Promise<Response>;
|
|
8
|
+
post: (path: string, body?: BodyInit | Record<string, unknown>, options?: RequestInit) => Promise<Response>;
|
|
9
|
+
put: (path: string, body?: BodyInit | Record<string, unknown>, options?: RequestInit) => Promise<Response>;
|
|
10
|
+
delete: (path: string, options?: RequestInit) => Promise<Response>;
|
|
11
|
+
};
|
|
12
|
+
/** GraphQL: useApi().ransack (QUERY) / .handing (MUTATION) で LogWindow Network に反映 */
|
|
13
|
+
export type GraphqlApiContextValue = {
|
|
14
|
+
provider: 'graphql';
|
|
15
|
+
/** QUERY(戻り値は ApolloQueryResult、必要なら as でキャスト) */
|
|
16
|
+
ransack: <TData = unknown, TVariables = unknown>(options: QueryOptions<TVariables, TData>) => Promise<ApolloQueryResult<unknown>>;
|
|
17
|
+
/** MUTATION(戻り値は FetchResult、必要なら as でキャスト) */
|
|
18
|
+
handing: <TData = unknown, TVariables = unknown>(options: MutationOptions<TData, TVariables>) => Promise<FetchResult<unknown>>;
|
|
19
|
+
};
|
|
20
|
+
export declare const ApiContext: React.Context<ApiContextValue | null>;
|
|
21
|
+
export type ApiProviderMode = 'rest' | 'graphql';
|
|
22
|
+
type ApiProviderProps = {
|
|
23
|
+
provider: 'rest';
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
} | {
|
|
27
|
+
provider: 'graphql';
|
|
28
|
+
children: React.ReactNode;
|
|
29
|
+
uri?: string;
|
|
30
|
+
client?: ApolloClientType<NormalizedCacheObject>;
|
|
31
|
+
};
|
|
32
|
+
export declare function ApiProvider(props: ApiProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
export declare function useApi(): ApiContextValue;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=ApiContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApiContext.d.ts","sourceRoot":"","sources":["../../src/contexts/ApiContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6C,MAAM,OAAO,CAAC;AAClE,OAAO,EAIL,KAAK,YAAY,IAAI,gBAAgB,EACrC,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAEjB,MAAM,gBAAgB,CAAC;AAExB,MAAM,MAAM,eAAe,GAAG,mBAAmB,GAAG,sBAAsB,CAAC;AAE3E,0EAA0E;AAC1E,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5G,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3G,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACpE,CAAC;AAEF,sFAAsF;AACtF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,SAAS,CAAC;IACpB,kDAAkD;IAClD,OAAO,EAAE,CAAC,KAAK,GAAG,OAAO,EAAE,UAAU,GAAG,OAAO,EAC7C,OAAO,EAAE,YAAY,CAAC,UAAU,EAAE,KAAK,CAAC,KACrC,OAAO,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC;IACzC,+CAA+C;IAC/C,OAAO,EAAE,CAAC,KAAK,GAAG,OAAO,EAAE,UAAU,GAAG,OAAO,EAC7C,OAAO,EAAE,eAAe,CAAC,KAAK,EAAE,UAAU,CAAC,KACxC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;CACpC,CAAC;AAEF,eAAO,MAAM,UAAU,uCAA8C,CAAC;AAEtE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,CAAC;AAEjD,KAAK,gBAAgB,GACjB;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GACD;IACE,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;CAClD,CAAC;AAiBN,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,2CA4ElD;AAED,wBAAgB,MAAM,IAAI,eAAe,CAMxC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
3
|
+
import { ApolloClient, ApolloProvider, InMemoryCache, } from '@apollo/client';
|
|
4
|
+
export const ApiContext = createContext(null);
|
|
5
|
+
function createApolloClient(uri) {
|
|
6
|
+
return new ApolloClient({
|
|
7
|
+
uri,
|
|
8
|
+
cache: new InMemoryCache(),
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function resolveBody(body) {
|
|
12
|
+
if (body == null)
|
|
13
|
+
return undefined;
|
|
14
|
+
if (typeof body === 'string' || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof FormData || body instanceof URLSearchParams) {
|
|
15
|
+
return body;
|
|
16
|
+
}
|
|
17
|
+
return JSON.stringify(body);
|
|
18
|
+
}
|
|
19
|
+
export function ApiProvider(props) {
|
|
20
|
+
const { children } = props;
|
|
21
|
+
const isGraphql = props.provider === 'graphql';
|
|
22
|
+
const graphqlUri = props.provider === 'graphql' ? props.uri : undefined;
|
|
23
|
+
const graphqlClientProp = props.provider === 'graphql' ? props.client : undefined;
|
|
24
|
+
const graphqlClient = useMemo(() => {
|
|
25
|
+
if (!isGraphql)
|
|
26
|
+
return null;
|
|
27
|
+
if (graphqlClientProp)
|
|
28
|
+
return graphqlClientProp;
|
|
29
|
+
const endpoint = graphqlUri ?? (typeof process !== 'undefined' ? process.env?.GRAPHQL_URI : '');
|
|
30
|
+
if (!endpoint) {
|
|
31
|
+
throw new Error('ApiProvider(provider="graphql") requires either "uri" prop or GRAPHQL_URI environment variable');
|
|
32
|
+
}
|
|
33
|
+
return createApolloClient(endpoint);
|
|
34
|
+
}, [isGraphql, graphqlUri, graphqlClientProp]);
|
|
35
|
+
const baseUrl = props.provider === 'rest' ? props.baseUrl : undefined;
|
|
36
|
+
const endpoint = baseUrl ?? (typeof process !== 'undefined' ? process.env?.ENDPOINT_URL : '') ?? '';
|
|
37
|
+
const restValue = useMemo(() => {
|
|
38
|
+
if (isGraphql)
|
|
39
|
+
return null;
|
|
40
|
+
const url = (path) => `${endpoint}${path}`;
|
|
41
|
+
return {
|
|
42
|
+
provider: 'rest',
|
|
43
|
+
get: (path, options) => fetch(url(path), { ...options, method: 'GET' }),
|
|
44
|
+
post: (path, body, options) => fetch(url(path), {
|
|
45
|
+
...options,
|
|
46
|
+
method: 'POST',
|
|
47
|
+
body: resolveBody(body),
|
|
48
|
+
headers: body != null && typeof body === 'object' && !(body instanceof FormData) && !(body instanceof URLSearchParams)
|
|
49
|
+
? { 'Content-Type': 'application/json', ...options?.headers }
|
|
50
|
+
: options?.headers,
|
|
51
|
+
}),
|
|
52
|
+
put: (path, body, options) => fetch(url(path), {
|
|
53
|
+
...options,
|
|
54
|
+
method: 'PUT',
|
|
55
|
+
body: resolveBody(body),
|
|
56
|
+
headers: body != null && typeof body === 'object' && !(body instanceof FormData) && !(body instanceof URLSearchParams)
|
|
57
|
+
? { 'Content-Type': 'application/json', ...options?.headers }
|
|
58
|
+
: options?.headers,
|
|
59
|
+
}),
|
|
60
|
+
delete: (path, options) => fetch(url(path), { ...options, method: 'DELETE' }),
|
|
61
|
+
};
|
|
62
|
+
}, [isGraphql, endpoint]);
|
|
63
|
+
if (isGraphql && graphqlClient) {
|
|
64
|
+
const graphqlValue = useMemo(() => ({
|
|
65
|
+
provider: 'graphql',
|
|
66
|
+
ransack: (options) => graphqlClient.query(options),
|
|
67
|
+
handing: (options) => graphqlClient.mutate(options),
|
|
68
|
+
}), [graphqlClient]);
|
|
69
|
+
return (_jsx(ApolloProvider, { client: graphqlClient, children: _jsx(ApiContext.Provider, { value: graphqlValue, children: children }) }));
|
|
70
|
+
}
|
|
71
|
+
if (restValue) {
|
|
72
|
+
return _jsx(ApiContext.Provider, { value: restValue, children: children });
|
|
73
|
+
}
|
|
74
|
+
return _jsx(_Fragment, { children: children });
|
|
75
|
+
}
|
|
76
|
+
export function useApi() {
|
|
77
|
+
const ctx = useContext(ApiContext);
|
|
78
|
+
if (!ctx) {
|
|
79
|
+
throw new Error('useApi must be used within ApiProvider or YargramProvider');
|
|
80
|
+
}
|
|
81
|
+
return ctx;
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApiContext.test.d.ts","sourceRoot":"","sources":["../../src/contexts/ApiContext.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
import { ApiProvider, useApi } from './ApiContext';
|
|
7
|
+
function Consumer() {
|
|
8
|
+
const api = useApi();
|
|
9
|
+
const [result, setResult] = React.useState('');
|
|
10
|
+
const callGet = () => {
|
|
11
|
+
api.get('/test').then((r) => setResult(`${r.status}`)).catch(() => setResult('error'));
|
|
12
|
+
};
|
|
13
|
+
return (_jsxs("div", { children: [_jsx("span", { "data-testid": "provider", children: api.provider }), _jsx("button", { type: "button", onClick: callGet, children: "GET" }), _jsx("span", { "data-testid": "result", children: result })] }));
|
|
14
|
+
}
|
|
15
|
+
describe('ApiProvider (REST)', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
|
18
|
+
ok: true,
|
|
19
|
+
status: 200,
|
|
20
|
+
statusText: 'OK',
|
|
21
|
+
clone: () => ({ text: () => Promise.resolve('{}') }),
|
|
22
|
+
}));
|
|
23
|
+
});
|
|
24
|
+
it('provides REST api and useApi returns provider rest', () => {
|
|
25
|
+
render(_jsx(ApiProvider, { provider: "rest", baseUrl: "https://api.example.com", children: _jsx(Consumer, {}) }));
|
|
26
|
+
expect(screen.getByTestId('provider')).toHaveTextContent('rest');
|
|
27
|
+
});
|
|
28
|
+
it('get() calls fetch with correct url', async () => {
|
|
29
|
+
const user = userEvent.setup();
|
|
30
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
31
|
+
ok: true,
|
|
32
|
+
status: 200,
|
|
33
|
+
statusText: 'OK',
|
|
34
|
+
clone: () => ({ text: () => Promise.resolve('[]') }),
|
|
35
|
+
});
|
|
36
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
37
|
+
render(_jsx(ApiProvider, { provider: "rest", baseUrl: "https://api.example.com", children: _jsx(Consumer, {}) }));
|
|
38
|
+
await user.click(screen.getByRole('button', { name: /get/i }));
|
|
39
|
+
expect(fetchMock).toHaveBeenCalledWith('https://api.example.com/test', expect.objectContaining({ method: 'GET' }));
|
|
40
|
+
expect(screen.getByTestId('result')).toHaveTextContent('200');
|
|
41
|
+
});
|
|
42
|
+
it('useApi throws when used outside provider', () => {
|
|
43
|
+
expect(() => render(_jsx(Consumer, {}))).toThrow('useApi must be used within ApiProvider');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createPrinter, type Env } from '@yargram/core';
|
|
3
|
+
export type Printer = ReturnType<typeof createPrinter>;
|
|
4
|
+
export type PrinterProviderProps = {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
env?: Env;
|
|
7
|
+
/** 指定時は createPrinter(env) の代わりにこの printer を利用(YargramProvider で addLogEntry 連携用) */
|
|
8
|
+
printer?: Printer;
|
|
9
|
+
};
|
|
10
|
+
export declare function PrinterProvider({ children, env, printer: printerProp }: PrinterProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare function usePrinter(): Printer;
|
|
12
|
+
//# sourceMappingURL=PrinterContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PrinterContext.d.ts","sourceRoot":"","sources":["../../src/contexts/PrinterContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6C,MAAM,OAAO,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,KAAK,GAAG,EAAE,MAAM,eAAe,CAAC;AAExD,MAAM,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AAQvD,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,qFAAqF;IACrF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,GAAa,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,oBAAoB,2CAQtG;AAED,wBAAgB,UAAU,IAAI,OAAO,CAMpC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
3
|
+
import { createPrinter } from '@yargram/core';
|
|
4
|
+
const PrinterContext = createContext(null);
|
|
5
|
+
export function PrinterProvider({ children, env = 'local', printer: printerProp }) {
|
|
6
|
+
const defaultPrinter = useMemo(() => createPrinter(env), [env]);
|
|
7
|
+
const printer = printerProp ?? defaultPrinter;
|
|
8
|
+
const value = useMemo(() => ({ printer }), [printer]);
|
|
9
|
+
return (_jsx(PrinterContext.Provider, { value: value, children: children }));
|
|
10
|
+
}
|
|
11
|
+
export function usePrinter() {
|
|
12
|
+
const ctx = useContext(PrinterContext);
|
|
13
|
+
if (!ctx) {
|
|
14
|
+
throw new Error('usePrinter must be used within PrinterProvider');
|
|
15
|
+
}
|
|
16
|
+
return ctx.printer;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PrinterContext.test.d.ts","sourceRoot":"","sources":["../../src/contexts/PrinterContext.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { PrinterProvider, usePrinter } from './PrinterContext';
|
|
5
|
+
function Consumer() {
|
|
6
|
+
const printer = usePrinter();
|
|
7
|
+
return (_jsxs("div", { children: [_jsx("button", { type: "button", onClick: () => printer.info('info message'), children: "Info" }), _jsx("button", { type: "button", onClick: () => printer.warn('warn message'), children: "Warn" }), _jsx("button", { type: "button", onClick: () => printer.error('error message'), children: "Error" })] }));
|
|
8
|
+
}
|
|
9
|
+
describe('PrinterProvider', () => {
|
|
10
|
+
it('renders children', () => {
|
|
11
|
+
render(_jsx(PrinterProvider, { children: _jsx(Consumer, {}) }));
|
|
12
|
+
expect(screen.getByRole('button', { name: /info/i })).toBeInTheDocument();
|
|
13
|
+
expect(screen.getByRole('button', { name: /warn/i })).toBeInTheDocument();
|
|
14
|
+
expect(screen.getByRole('button', { name: /error/i })).toBeInTheDocument();
|
|
15
|
+
});
|
|
16
|
+
it('usePrinter throws when used outside provider', () => {
|
|
17
|
+
expect(() => render(_jsx(Consumer, {}))).toThrow('usePrinter must be used within PrinterProvider');
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ApolloClient } from '@apollo/client';
|
|
3
|
+
import type { LogEntry, NetworkEntry } from '../components/LogWindow/types';
|
|
4
|
+
import type { Env } from '@yahman/core';
|
|
5
|
+
import type { NormalizedCacheObject } from '@apollo/client';
|
|
6
|
+
import '../components/LogWindow/LogWindow.css';
|
|
7
|
+
/** Api 設定(ApiProvider の props から children を除いたもの) */
|
|
8
|
+
type YahmanApiConfig = {
|
|
9
|
+
provider: 'rest';
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
} | {
|
|
12
|
+
provider: 'graphql';
|
|
13
|
+
uri?: string;
|
|
14
|
+
client?: ApolloClient<NormalizedCacheObject>;
|
|
15
|
+
};
|
|
16
|
+
/** Printer 設定 */
|
|
17
|
+
type YahmanPrinterConfig = {
|
|
18
|
+
env?: Env;
|
|
19
|
+
};
|
|
20
|
+
/** LogWindow 設定(Escape 5 回で表示) */
|
|
21
|
+
type YahmanLogWindowConfig = Record<string, never>;
|
|
22
|
+
/** 認証設定。本番のみログインを要求する場合は true、カスタム時はオブジェクト */
|
|
23
|
+
type YahmanAuthConfig = true | {
|
|
24
|
+
/** 本番時のみ認証(デフォルト true) */
|
|
25
|
+
productionOnly?: boolean;
|
|
26
|
+
/** Storybook 時のみ本番として扱う(ログイン要求する) */
|
|
27
|
+
storybookSimulateProduction?: boolean;
|
|
28
|
+
/** カスタム認証(指定時は onLogin で検証。未指定時は passwordHash または env でハッシュ比較) */
|
|
29
|
+
onLogin?: (password: string) => Promise<void>;
|
|
30
|
+
/** ログインウィンドウのタイトル */
|
|
31
|
+
loginTitle?: string;
|
|
32
|
+
/**
|
|
33
|
+
* ログイン用パスワードの SHA-256 ハッシュ(hex)。指定時は env より優先。
|
|
34
|
+
* 未指定時は YAHMAN_LOGIN_PASSWORD_HASH / VITE_YAHMAN_LOGIN_PASSWORD_HASH、それも無ければデフォルト(12345678 のハッシュ)。
|
|
35
|
+
*/
|
|
36
|
+
passwordHash?: string;
|
|
37
|
+
/**
|
|
38
|
+
* ログイン後のトークン有効期限(ミリ秒)。未指定時は 7 日。
|
|
39
|
+
*/
|
|
40
|
+
tokenTtlMs?: number;
|
|
41
|
+
};
|
|
42
|
+
type YahmanContextValue = {
|
|
43
|
+
addLogEntry: (entry: Omit<LogEntry, 'id'> | LogEntry) => void;
|
|
44
|
+
addNetworkEntry: (entry: Omit<NetworkEntry, 'id'> | NetworkEntry) => void;
|
|
45
|
+
openLogWindow: () => void;
|
|
46
|
+
closeLogWindow: () => void;
|
|
47
|
+
logEntries: LogEntry[];
|
|
48
|
+
networkEntries: NetworkEntry[];
|
|
49
|
+
isLogWindowOpen: boolean;
|
|
50
|
+
};
|
|
51
|
+
export type YahmanProviderProps = {
|
|
52
|
+
children: React.ReactNode;
|
|
53
|
+
/** Api 設定(REST または GraphQL) */
|
|
54
|
+
api: YahmanApiConfig;
|
|
55
|
+
/** Printer 設定 */
|
|
56
|
+
printer?: YahmanPrinterConfig;
|
|
57
|
+
/** LogWindow を Escape で出せるようにする設定。省略時は LogWindow 機能なし */
|
|
58
|
+
logWindow?: YahmanLogWindowConfig;
|
|
59
|
+
/**
|
|
60
|
+
* 認証を有効にする。true のとき本番(NODE_ENV=production/staging)のみログインウィンドウを表示。
|
|
61
|
+
* オブジェクトで productionOnly / onLogin / loginTitle / passwordHash を指定可能。
|
|
62
|
+
* パスワードハッシュは auth.passwordHash → env(YAHMAN_LOGIN_PASSWORD_HASH 等)→ デフォルト(12345678 の SHA-256)の順で解決される。
|
|
63
|
+
*/
|
|
64
|
+
auth?: YahmanAuthConfig;
|
|
65
|
+
};
|
|
66
|
+
export declare function YahmanProvider({ children, api, printer, logWindow, auth, }: YahmanProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
67
|
+
export declare function useYahman(): YahmanContextValue;
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=YahmanContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"YahmanContext.d.ts","sourceRoot":"","sources":["../../src/contexts/YahmanContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KASN,MAAM,OAAO,CAAC;AAGf,OAAO,EACL,YAAY,EAGb,MAAM,gBAAgB,CAAC;AAOxB,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,EAEV,qBAAqB,EAItB,MAAM,gBAAgB,CAAC;AACxB,OAAO,uCAAuC,CAAC;AAiI/C,qDAAqD;AACrD,KAAK,eAAe,GAChB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACtC;IACE,QAAQ,EAAE,SAAS,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC,qBAAqB,CAAC,CAAC;CAC9C,CAAC;AAEN,iBAAiB;AACjB,KAAK,mBAAmB,GAAG;IACzB,GAAG,CAAC,EAAE,GAAG,CAAC;CACX,CAAC;AAEF,kCAAkC;AAClC,KAAK,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAEnD,8CAA8C;AAC9C,KAAK,gBAAgB,GACjB,IAAI,GACJ;IACE,0BAA0B;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qCAAqC;IACrC,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,kEAAkE;IAClE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,qBAAqB;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEN,KAAK,kBAAkB,GAAG;IACxB,WAAW,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,QAAQ,KAAK,IAAI,CAAC;IAC9D,eAAe,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,GAAG,YAAY,KAAK,IAAI,CAAC;IAC1E,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvB,cAAc,EAAE,YAAY,EAAE,CAAC;IAC/B,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAIF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,+BAA+B;IAC/B,GAAG,EAAE,eAAe,CAAC;IACrB,iBAAiB;IACjB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,yDAAyD;IACzD,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC;;;;OAIG;IACH,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAkEF,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,GAAG,EACH,OAAY,EACZ,SAAS,EACT,IAAI,GACL,EAAE,mBAAmB,2CAgXrB;AAED,wBAAgB,SAAS,IAAI,kBAAkB,CAM9C"}
|