akanjs 3.0.0 → 3.0.2-rc.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/CHANGELOG.md +3670 -0
- package/base/base.ts +1 -1
- package/client/cookie.ts +4 -6
- package/client/csrTypes.ts +2 -2
- package/common/Logger.ts +10 -0
- package/common/index.ts +0 -1
- package/common/routeConvention.ts +1 -9
- package/dictionary/base.dictionary.ts +7 -0
- package/dictionary/trans.ts +7 -1
- package/document/documentQuery.ts +6 -1
- package/document/index.ts +1 -0
- package/document/into.ts +15 -7
- package/document/liveRowScope.ts +79 -0
- package/fetch/client/fetchClient.ts +64 -12
- package/fetch/client/httpClient.ts +13 -4
- package/index.ts +22 -0
- package/package.json +1 -1
- package/server/akanOption.ts +20 -1
- package/server/akanServer.ts +5 -0
- package/server/cachePolicy.ts +12 -2
- package/server/crawler.ts +6 -0
- package/server/head.tsx +0 -4
- package/server/mcp/McpAuth.ts +3 -2
- package/server/mcp/McpExecutionContext.ts +15 -14
- package/server/mcp/McpRateLimiter.ts +18 -67
- package/server/proxy/webProxyRunner.ts +2 -0
- package/server/resolver/database.resolver.ts +28 -13
- package/server/resolver/signal.resolver.ts +75 -35
- package/server/robots.ts +1 -23
- package/server/routeTreeBuilder.ts +4 -2
- package/server/rscWorker.tsx +12 -24
- package/server/rscWorkerHost.ts +16 -7
- package/server/ssrFromRscRenderer.tsx +19 -15
- package/server/ssrTypes.ts +9 -0
- package/server/webRouter.ts +9 -2
- package/service/index.ts +1 -0
- package/service/predefinedAdaptor/index.ts +1 -0
- package/service/predefinedAdaptor/rateLimit.adaptor.ts +15 -0
- package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +119 -27
- package/service/predefinedAdaptor/sql/types.ts +14 -12
- package/service/rateLimiter.ts +80 -0
- package/signal/base.signal.ts +7 -6
- package/signal/endpointRateLimit.ts +159 -0
- package/signal/index.ts +1 -0
- package/signal/middleware.ts +6 -5
- package/signal/serializer/fetch.serializer.ts +15 -4
- package/signal/signalContext.ts +148 -67
- package/signal/slice.ts +30 -9
- package/signal/types.ts +8 -0
- package/test/signalTestRuntime.ts +2 -1
- package/types/base/base.d.ts +1 -1
- package/types/client/cookie.d.ts +1 -1
- package/types/client/csrTypes.d.ts +2 -2
- package/types/common/Logger.d.ts +2 -0
- package/types/common/index.d.ts +1 -1
- package/types/common/routeConvention.d.ts +0 -2
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/document/documentQuery.d.ts +5 -1
- package/types/document/index.d.ts +1 -0
- package/types/document/into.d.ts +7 -7
- package/types/document/liveRowScope.d.ts +9 -0
- package/types/fetch/client/fetchClient.d.ts +9 -1
- package/types/index.d.ts +32 -0
- package/types/server/akanOption.d.ts +7 -1
- package/types/server/cachePolicy.d.ts +2 -1
- package/types/server/crawler.d.ts +1 -0
- package/types/server/head.d.ts +0 -3
- package/types/server/mcp/McpExecutionContext.d.ts +4 -1
- package/types/server/mcp/McpRateLimiter.d.ts +2 -6
- package/types/server/rscWorkerHost.d.ts +3 -0
- package/types/server/ssrFromRscRenderer.d.ts +3 -1
- package/types/server/ssrTypes.d.ts +8 -0
- package/types/service/index.d.ts +1 -0
- package/types/service/predefinedAdaptor/index.d.ts +1 -0
- package/types/service/predefinedAdaptor/rateLimit.adaptor.d.ts +10 -0
- package/types/service/predefinedAdaptor/sql/SqlDocumentStore.d.ts +6 -10
- package/types/service/predefinedAdaptor/sql/types.d.ts +13 -17
- package/types/service/rateLimiter.d.ts +24 -0
- package/types/signal/endpointRateLimit.d.ts +37 -0
- package/types/signal/index.d.ts +1 -0
- package/types/signal/signalContext.d.ts +33 -15
- package/types/signal/slice.d.ts +17 -5
- package/types/signal/types.d.ts +8 -0
- package/types/test/signalTestRuntime.d.ts +1 -1
- package/types/ui/recipe/badgeRecipe.d.ts +2 -2
- package/types/ui/recipe/buttonRecipe.d.ts +4 -4
- package/types/ui/recipe/inputRecipe.d.ts +3 -3
- package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +33 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/bplist.d.ts +11 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/dmg.d.ts +59 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/dsStore.d.ts +26 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/macAlias.d.ts +24 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/macossigning.d.ts +0 -1
- package/types/vendor/@akanjs/native/packages/cli/src/lib/project.d.ts +3 -1
- package/types/vendor/@akanjs/native/packages/cli/src/platforms/linux-appimage.d.ts +6 -1
- package/types/vendor/@akanjs/native/packages/desktop/src/deeplinks.d.ts +9 -2
- package/types/webkit/RenderFailure.d.ts +6 -0
- package/types/webkit/useFrameRuntime.d.ts +0 -1
- package/ui/Input.tsx +7 -1
- package/ui/recipe/buttonRecipe.ts +1 -0
- package/ui/recipe/inputRecipe.ts +13 -7
- package/ui/styles.css +1 -0
- package/vendor/@akanjs/native/native/desktop/src/lib.rs +8 -1
- package/vendor/@akanjs/native/native/macos/dmg/background.html +18 -0
- package/vendor/@akanjs/native/native/macos/dmg/background.png +0 -0
- package/vendor/@akanjs/native/native/macos/dmg/background@2x.png +0 -0
- package/vendor/@akanjs/native/packages/cli/src/config.ts +23 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/addons.ts +2 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/bplist.ts +91 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +9 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/dmg.ts +331 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/dsStore.ts +191 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/exec.ts +11 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/macAlias.ts +101 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/macossigning.ts +1 -28
- package/vendor/@akanjs/native/packages/cli/src/lib/project.ts +6 -1
- package/vendor/@akanjs/native/packages/cli/src/platforms/linux-appimage.ts +17 -7
- package/vendor/@akanjs/native/packages/cli/src/platforms/linux.ts +6 -1
- package/vendor/@akanjs/native/packages/cli/src/platforms/macos.ts +5 -2
- package/vendor/@akanjs/native/packages/desktop/src/deeplinks.ts +33 -10
- package/vendor/@akanjs/native/packages/desktop/src/host.ts +1 -1
- package/vendor/use-agentic/CHANGELOG.md +663 -0
- package/webkit/CsrRouteTable.ts +0 -2
- package/webkit/RenderFailure.tsx +32 -0
- package/webkit/RenderLayer.tsx +22 -2
- package/webkit/useCsrValues.ts +21 -11
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { EndpointInfo } from "./endpointInfo.d.ts";
|
|
2
|
+
import type { SignalContext } from "./signalContext.d.ts";
|
|
3
|
+
export interface RateLimitBudget {
|
|
4
|
+
calls: number;
|
|
5
|
+
/** 60_000 when unset. */
|
|
6
|
+
windowMs?: number;
|
|
7
|
+
/** `ip` (default) counts the caller's address; `account` counts `accountKey(account)`, else the address. */
|
|
8
|
+
by?: "ip" | "account";
|
|
9
|
+
}
|
|
10
|
+
export interface RateLimitSetting {
|
|
11
|
+
/** `false` lets every budget stand aside, as for a deployment rate-limited at its edge. */
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
/** The budget of every endpoint that declares none; unset leaves those unlimited. */
|
|
14
|
+
budget?: RateLimitBudget;
|
|
15
|
+
/** By endpoint key, over what the endpoint declares (a lib's included); `false` exempts it. */
|
|
16
|
+
endpoints?: {
|
|
17
|
+
[key: string]: Partial<RateLimitBudget> | false;
|
|
18
|
+
};
|
|
19
|
+
/** Who `by: "account"` counts; nothing returned counts the caller's address instead. */
|
|
20
|
+
accountKey?: (account: unknown) => string | null | undefined;
|
|
21
|
+
}
|
|
22
|
+
type ResolvedBudget = Required<RateLimitBudget>;
|
|
23
|
+
export declare class EndpointRateLimit {
|
|
24
|
+
#private;
|
|
25
|
+
/** Merges over what an earlier call set, so libs configure in mount order and the app's `option.ts` goes last. */
|
|
26
|
+
static configure(setting: RateLimitSetting | false): void;
|
|
27
|
+
/**
|
|
28
|
+
* `off` wins over the code, `on` turns budgets on in a local environment, and `<calls>[/<seconds>]` is the default
|
|
29
|
+
* budget wherever the code names none. Unset, budgets apply everywhere but `local`.
|
|
30
|
+
*/
|
|
31
|
+
static applyEnv(value: string | undefined, environment: string): void;
|
|
32
|
+
static reset(): void;
|
|
33
|
+
static budgetOf(key: string, endpointInfo: EndpointInfo): ResolvedBudget | null;
|
|
34
|
+
/** `ip` runs before the request body is read; `account` after the middlewares resolved the caller. */
|
|
35
|
+
static admit(context: SignalContext, phase: "ip" | "account"): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
package/types/signal/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./base.signal";
|
|
|
7
7
|
export { CrossSiteGuard, type CrossSiteOption } from "./CrossSiteGuard.d.ts";
|
|
8
8
|
export * from "./endpoint.d.ts";
|
|
9
9
|
export * from "./endpointInfo.d.ts";
|
|
10
|
+
export * from "./endpointRateLimit.d.ts";
|
|
10
11
|
export * from "./exception.d.ts";
|
|
11
12
|
export * from "./guard.d.ts";
|
|
12
13
|
export * from "./guards.d.ts";
|
|
@@ -2,7 +2,7 @@ import { type BackendEnv, type PromiseOrObject } from "akanjs/base";
|
|
|
2
2
|
import { type ConstantFieldTypeInput } from "akanjs/constant";
|
|
3
3
|
import type { Adaptor, AdaptorCls, DatabaseService, InjectRegistry, LiveRegistry } from "akanjs/service";
|
|
4
4
|
import type { Internal, InternalInfo, MiddlewareCls } from ".";
|
|
5
|
-
import type { EndpointInfo } from "./endpointInfo.d.ts";
|
|
5
|
+
import type { ArgInfo, EndpointInfo } from "./endpointInfo.d.ts";
|
|
6
6
|
import { SignalTrace, type TraceOrigin } from "./trace.d.ts";
|
|
7
7
|
export type SignalTransportType = "http" | "websocket";
|
|
8
8
|
/** What `Bun.Server.requestIP` reports for the socket a request arrived on. */
|
|
@@ -18,6 +18,20 @@ interface WebSocketRequest {
|
|
|
18
18
|
type RuntimeRecord = Record<string, unknown>;
|
|
19
19
|
/** Request-scoped: the outermost `resolveReturn` starts one and every recursion threads it down. */
|
|
20
20
|
type ResolveCache = Map<ConstantFieldTypeInput, Map<string, Promise<unknown>>>;
|
|
21
|
+
interface SignalContextOption<Ctx, Env extends BackendEnv> {
|
|
22
|
+
endpointInfo: EndpointInfo;
|
|
23
|
+
adaptor: Adaptor;
|
|
24
|
+
registry: InjectRegistry;
|
|
25
|
+
env: Env;
|
|
26
|
+
live: LiveRegistry;
|
|
27
|
+
middleware: Map<string, MiddlewareCls>;
|
|
28
|
+
/** A caller-built context: MCP's arguments arrive as one named object, not in a URL. */
|
|
29
|
+
ctx?: Ctx;
|
|
30
|
+
/** Defaults to the transport. */
|
|
31
|
+
origin?: TraceOrigin;
|
|
32
|
+
/** Unset, the context joins the current trace or opens one that `exec()` closes. */
|
|
33
|
+
trace?: SignalTrace | null;
|
|
34
|
+
}
|
|
21
35
|
export declare class SignalContext<Ctx extends HttpExecutionContext | WebSocketExecutionContext = HttpExecutionContext | WebSocketExecutionContext, Env extends BackendEnv = BackendEnv> {
|
|
22
36
|
#private;
|
|
23
37
|
key: string;
|
|
@@ -27,25 +41,20 @@ export declare class SignalContext<Ctx extends HttpExecutionContext | WebSocketE
|
|
|
27
41
|
adaptor: Adaptor;
|
|
28
42
|
/** `mcp` for a call through the MCP endpoint. Held here, not on the trace: a null trace would let a guard fail open. */
|
|
29
43
|
readonly origin: TraceOrigin;
|
|
30
|
-
args: unknown[];
|
|
31
44
|
internalArgs: unknown[];
|
|
32
45
|
trace: SignalTrace | null;
|
|
33
|
-
constructor(key: string, reqOrWsReq: Bun.BunRequest | WebSocketRequest,
|
|
34
|
-
endpointInfo: EndpointInfo;
|
|
35
|
-
adaptor: Adaptor;
|
|
36
|
-
registry: InjectRegistry;
|
|
37
|
-
env: Env;
|
|
38
|
-
live: LiveRegistry;
|
|
39
|
-
middleware: Map<string, MiddlewareCls>;
|
|
40
|
-
/** A caller-built context: MCP's arguments arrive as one named object, not in a URL. */
|
|
41
|
-
ctx?: Ctx;
|
|
42
|
-
/** Defaults to the transport. */
|
|
43
|
-
origin?: TraceOrigin;
|
|
44
|
-
});
|
|
46
|
+
constructor(key: string, reqOrWsReq: Bun.BunRequest | WebSocketRequest, option: SignalContextOption<Ctx, Env>);
|
|
45
47
|
getAdaptor<T extends Adaptor>(adaptorCls: AdaptorCls<T>): T;
|
|
46
48
|
getService<T>(refName: string): T;
|
|
49
|
+
/** Reads the request but parses no argument: that waits for the account guards in `exec()`. */
|
|
47
50
|
init(): Promise<this>;
|
|
48
|
-
/**
|
|
51
|
+
/** Parsed on first read, so a guard or middleware that reads one still gets it; a parser runs at most once per call. */
|
|
52
|
+
get args(): unknown[];
|
|
53
|
+
set args(args: unknown[]);
|
|
54
|
+
/**
|
|
55
|
+
* Re-checks the guards of a subscribed room outside a request; only global middlewares (account resolution) run. On
|
|
56
|
+
* a fresh context, so a guard that memoizes per context cannot replay the verdict it gave at subscribe.
|
|
57
|
+
*/
|
|
49
58
|
authorize(): Promise<boolean>;
|
|
50
59
|
/**
|
|
51
60
|
* Evaluates only `scope = "account"` guards, to hide listing entries. Never an access gate: a resource guard fails
|
|
@@ -84,6 +93,10 @@ export declare class SignalContext<Ctx extends HttpExecutionContext | WebSocketE
|
|
|
84
93
|
* `::ffff:`; `null`, never a placeholder, when neither is known.
|
|
85
94
|
*/
|
|
86
95
|
getClientIp(): string | null;
|
|
96
|
+
/** A request a proxy rebuilt has no socket behind it for `requestIP`, so it keeps the address its original resolved. */
|
|
97
|
+
static carryClientIp(target: Request, source: Request): void;
|
|
98
|
+
/** `getClientIp` for a request no context wraps, as the MCP router and the page renderer hold one. */
|
|
99
|
+
static clientIpOf(req: Request): string | null;
|
|
87
100
|
/** The caller's source port as the nearest proxy recorded it, else this socket's own. */
|
|
88
101
|
getClientPort(): number | null;
|
|
89
102
|
getRoomId(key: string): string;
|
|
@@ -113,6 +126,9 @@ export declare class HttpExecutionContext<Appended = unknown> {
|
|
|
113
126
|
constructor(req: Bun.BunRequest);
|
|
114
127
|
get url(): URL;
|
|
115
128
|
getArgs(endpointInfo: EndpointInfo): Promise<unknown[]>;
|
|
129
|
+
/** Each argument as the request carries it; `parseArg` turns one into its declared type. */
|
|
130
|
+
readArgs(endpointInfo: EndpointInfo): Promise<unknown[]>;
|
|
131
|
+
parseArg(arg: ArgInfo, raw: unknown): unknown;
|
|
116
132
|
makeResponse(result: unknown, endpointInfo: EndpointInfo): Response;
|
|
117
133
|
}
|
|
118
134
|
export type WebSocketEventType = "open" | "subscribe" | "unsubscribe" | "message" | "close";
|
|
@@ -125,6 +141,8 @@ export declare class WebSocketExecutionContext<Appended = unknown> {
|
|
|
125
141
|
onUnsubscribe: Set<() => PromiseOrObject<void>>;
|
|
126
142
|
constructor(wsReq: WebSocketRequest);
|
|
127
143
|
getArgs(endpointInfo: EndpointInfo): Promise<unknown[]>;
|
|
144
|
+
readArgs(endpointInfo: EndpointInfo): Promise<unknown[]>;
|
|
145
|
+
parseArg(arg: ArgInfo, raw: unknown): unknown;
|
|
128
146
|
makeResponse(result: unknown, endpointInfo: EndpointInfo): Response;
|
|
129
147
|
on: (event: "disconnect" | "unsubscribe", handler: () => PromiseOrObject<void>) => void;
|
|
130
148
|
off: (event: "disconnect" | "unsubscribe", handler: () => PromiseOrObject<void>) => void;
|
package/types/signal/slice.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export type SliceCls<SrvModule extends ServiceModel = ServiceModel, SliceInfoObj
|
|
|
30
30
|
updateGuards: GuardCls[];
|
|
31
31
|
removeGuards: GuardCls[];
|
|
32
32
|
mcp: ResolvedSliceMcp;
|
|
33
|
+
crud: ResolvedSliceCrud;
|
|
33
34
|
};
|
|
34
35
|
/** `root` is absent: it rides on the root slice itself. */
|
|
35
36
|
export interface ResolvedSliceMcp {
|
|
@@ -38,14 +39,25 @@ export interface ResolvedSliceMcp {
|
|
|
38
39
|
update: boolean;
|
|
39
40
|
remove: boolean;
|
|
40
41
|
}
|
|
42
|
+
/** Which generated verbs the server mounts: a verb whose guards resolve to none (or `false`) has no route at all. */
|
|
43
|
+
export interface ResolvedSliceCrud {
|
|
44
|
+
get: boolean;
|
|
45
|
+
create: boolean;
|
|
46
|
+
update: boolean;
|
|
47
|
+
remove: boolean;
|
|
48
|
+
}
|
|
41
49
|
interface RootSliceOption {
|
|
50
|
+
/**
|
|
51
|
+
* `false` (or no guard at all) mounts no generated endpoint for that verb, which is the only way to take one off
|
|
52
|
+
* HTTP: `mcp: { cru: false }` merely keeps it off the agent shelf. `create`/`update`/`remove` fall back to `cru`.
|
|
53
|
+
*/
|
|
42
54
|
guards?: {
|
|
43
55
|
root?: GuardCls | GuardCls[];
|
|
44
|
-
get?: GuardCls | GuardCls[];
|
|
45
|
-
cru?: GuardCls | GuardCls[];
|
|
46
|
-
create?: GuardCls | GuardCls[];
|
|
47
|
-
update?: GuardCls | GuardCls[];
|
|
48
|
-
remove?: GuardCls | GuardCls[];
|
|
56
|
+
get?: GuardCls | GuardCls[] | false;
|
|
57
|
+
cru?: GuardCls | GuardCls[] | false;
|
|
58
|
+
create?: GuardCls | GuardCls[] | false;
|
|
59
|
+
update?: GuardCls | GuardCls[] | false;
|
|
60
|
+
remove?: GuardCls | GuardCls[] | false;
|
|
49
61
|
};
|
|
50
62
|
/**
|
|
51
63
|
* Keyed like `guards`, for the root slice and generated CRUD only; `create`/`update`/`remove` fall back to `cru`
|
package/types/signal/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DefaultPrimitiveName, UnCls } from "akanjs/base";
|
|
2
2
|
import type { ServiceModel } from "akanjs/service";
|
|
3
|
+
import type { RateLimitBudget } from "./endpointRateLimit.d.ts";
|
|
3
4
|
import type { GuardCls } from "./guard.d.ts";
|
|
4
5
|
import type { MiddlewareCls } from "./middleware.d.ts";
|
|
5
6
|
import type { SliceCls } from "./slice.d.ts";
|
|
@@ -82,6 +83,11 @@ export interface SignalOption<Response = any, Nullable extends boolean = false,
|
|
|
82
83
|
* internal argument may carry one (those answer per caller), and the lookup runs after the guards.
|
|
83
84
|
*/
|
|
84
85
|
cache?: number;
|
|
86
|
+
/**
|
|
87
|
+
* Calls per caller per window, refused with 429 `base.error.tooManyRequests`. `by: "ip"` is counted before the body
|
|
88
|
+
* is read, `by: "account"` after the middlewares and before the guards; `false` exempts it from the app's default.
|
|
89
|
+
*/
|
|
90
|
+
rateLimit?: RateLimitBudget | false;
|
|
85
91
|
guards?: GuardCls[];
|
|
86
92
|
middlewares?: MiddlewareCls[];
|
|
87
93
|
prefix?: false | string;
|
|
@@ -178,6 +184,8 @@ export interface SerializedSignal {
|
|
|
178
184
|
removeGuards?: string[];
|
|
179
185
|
/** Generated CRUD verbs kept off the agent shelf, carried here because `FetchClient.getBaseEndpoint` synthesizes them. */
|
|
180
186
|
mcp?: SerializedSignalMcp;
|
|
187
|
+
/** Generated CRUD verbs the server does not mount at all, so no client synthesizes them. */
|
|
188
|
+
crud?: SerializedSignalMcp;
|
|
181
189
|
/** Which generated CRUD verbs a person-only guard protects, by the same verb map; only the `false` keys travel. */
|
|
182
190
|
agents?: SerializedSignalMcp;
|
|
183
191
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/** 뱃지 look — 시맨틱 variant × size, outline 플래그는 색을 유지한 외곽선 스타일. `<Badge>` 가 소비하며, recipes.badge 슬롯으로 교체 가능. */
|
|
2
2
|
export declare const badgeRecipe: (variants?: Omit<({
|
|
3
3
|
size?: "lg" | "md" | "sm" | "xs" | undefined;
|
|
4
|
-
outline?: boolean | undefined;
|
|
5
4
|
variant?: "default" | "error" | "info" | "warning" | "success" | "primary" | "secondary" | "accent" | "neutral" | "outline" | undefined;
|
|
5
|
+
outline?: boolean | undefined;
|
|
6
6
|
} & {
|
|
7
7
|
class?: import("tailwind-variants").ClassValue;
|
|
8
8
|
className?: never;
|
|
9
9
|
}) | ({
|
|
10
10
|
size?: "lg" | "md" | "sm" | "xs" | undefined;
|
|
11
|
-
outline?: boolean | undefined;
|
|
12
11
|
variant?: "default" | "error" | "info" | "warning" | "success" | "primary" | "secondary" | "accent" | "neutral" | "outline" | undefined;
|
|
12
|
+
outline?: boolean | undefined;
|
|
13
13
|
} & {
|
|
14
14
|
class?: never;
|
|
15
15
|
className?: import("tailwind-variants").ClassValue;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/** 버튼 look — 시맨틱 variant × size × shape, outline 플래그는 색을 유지한 외곽선 스타일. `<Button>` 이 소비하며, `_overrides.tsx` 의 recipes.button 슬롯으로 교체 가능. */
|
|
2
2
|
export declare const buttonRecipe: (variants?: Omit<({
|
|
3
|
-
size?: "lg" | "md" | "sm" | "xs" | "icon" | undefined;
|
|
4
|
-
outline?: boolean | undefined;
|
|
3
|
+
size?: "xl" | "lg" | "md" | "sm" | "xs" | "icon" | undefined;
|
|
5
4
|
variant?: "default" | "link" | "info" | "warning" | "success" | "primary" | "secondary" | "accent" | "destructive" | "neutral" | "outline" | "ghost" | undefined;
|
|
5
|
+
outline?: boolean | undefined;
|
|
6
6
|
shape?: "default" | "circle" | "square" | undefined;
|
|
7
7
|
} & {
|
|
8
8
|
class?: import("tailwind-variants").ClassValue;
|
|
9
9
|
className?: never;
|
|
10
10
|
}) | ({
|
|
11
|
-
size?: "lg" | "md" | "sm" | "xs" | "icon" | undefined;
|
|
12
|
-
outline?: boolean | undefined;
|
|
11
|
+
size?: "xl" | "lg" | "md" | "sm" | "xs" | "icon" | undefined;
|
|
13
12
|
variant?: "default" | "link" | "info" | "warning" | "success" | "primary" | "secondary" | "accent" | "destructive" | "neutral" | "outline" | "ghost" | undefined;
|
|
13
|
+
outline?: boolean | undefined;
|
|
14
14
|
shape?: "default" | "circle" | "square" | undefined;
|
|
15
15
|
} & {
|
|
16
16
|
class?: never;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
/** 입력 표면 look — Input/TextArea/DatePicker 가 공유하는 필드 셸. kind 로 한 줄 필드(field)/멀티라인(area), tone 으로 강조/오류 상태를 고른다. */
|
|
1
|
+
/** 입력 표면 look — Input/TextArea/DatePicker 가 공유하는 필드 셸. kind 로 한 줄 필드(field)/멀티라인(area)/네이티브 `<select>`(select), tone 으로 강조/오류 상태를 고른다. */
|
|
2
2
|
export declare const inputRecipe: (variants?: Omit<({
|
|
3
|
-
kind?: "field" | "area" | undefined;
|
|
3
|
+
kind?: "select" | "field" | "area" | undefined;
|
|
4
4
|
size?: "xl" | "lg" | "md" | "sm" | "xs" | undefined;
|
|
5
5
|
tone?: "default" | "error" | "primary" | undefined;
|
|
6
6
|
} & {
|
|
7
7
|
class?: import("tailwind-variants").ClassValue;
|
|
8
8
|
className?: never;
|
|
9
9
|
}) | ({
|
|
10
|
-
kind?: "field" | "area" | undefined;
|
|
10
|
+
kind?: "select" | "field" | "area" | undefined;
|
|
11
11
|
size?: "xl" | "lg" | "md" | "sm" | "xs" | undefined;
|
|
12
12
|
tone?: "default" | "error" | "primary" | undefined;
|
|
13
13
|
} & {
|
|
@@ -300,6 +300,14 @@ export interface AkanNativeConfig {
|
|
|
300
300
|
* macOS builds sign every Mach-O file in it.
|
|
301
301
|
*/
|
|
302
302
|
bin?: string;
|
|
303
|
+
/**
|
|
304
|
+
* macOS: the Finder window the disk image (`--installer`, API `macos.dmg`) opens in: no toolbar, sidebar or status
|
|
305
|
+
* bar, the app's and the Applications link's icons on a background, and the app's icon on the mounted disk. Without
|
|
306
|
+
* it the window is 660×400 pt on akan-native's background, an arrow from the app at (180, 170) to Applications at
|
|
307
|
+
* (480, 170). A position is an icon's center, in points from the window's top left; the default background lines
|
|
308
|
+
* up with the default positions only.
|
|
309
|
+
*/
|
|
310
|
+
dmg?: DmgConfig;
|
|
303
311
|
};
|
|
304
312
|
/**
|
|
305
313
|
* App icon for every platform, generated from one square PNG, ideally 1024×1024 (CLI-8).
|
|
@@ -330,4 +338,29 @@ export interface AkanNativeConfig {
|
|
|
330
338
|
timeout?: number;
|
|
331
339
|
};
|
|
332
340
|
}
|
|
341
|
+
export interface DmgConfig {
|
|
342
|
+
/** A PNG (or TIFF) the size of the window in points, or false for a plain window. */
|
|
343
|
+
background?: string | false;
|
|
344
|
+
/** The same picture at twice the pixels, for a Retina display: the disk image carries both in one TIFF. */
|
|
345
|
+
background2x?: string;
|
|
346
|
+
/** In points. Default the background's size (660×400 without one) at 200, 120 on the screen. */
|
|
347
|
+
window?: {
|
|
348
|
+
x?: number;
|
|
349
|
+
y?: number;
|
|
350
|
+
width?: number;
|
|
351
|
+
height?: number;
|
|
352
|
+
};
|
|
353
|
+
/** Default 128. */
|
|
354
|
+
iconSize?: number;
|
|
355
|
+
/** Default 13. */
|
|
356
|
+
textSize?: number;
|
|
357
|
+
app?: {
|
|
358
|
+
x: number;
|
|
359
|
+
y: number;
|
|
360
|
+
};
|
|
361
|
+
applications?: {
|
|
362
|
+
x: number;
|
|
363
|
+
y: number;
|
|
364
|
+
};
|
|
365
|
+
}
|
|
333
366
|
export declare function defineConfig(config: AkanNativeConfig): AkanNativeConfig;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** A number written as a real even when it is whole: Finder reads `iconSize` and `textSize` as reals. */
|
|
2
|
+
export declare class Real {
|
|
3
|
+
readonly value: number;
|
|
4
|
+
constructor(value: number);
|
|
5
|
+
}
|
|
6
|
+
export type BplistValue = string | number | boolean | Uint8Array | Real | {
|
|
7
|
+
[key: string]: BplistValue;
|
|
8
|
+
};
|
|
9
|
+
export declare function writeBplist(root: {
|
|
10
|
+
[key: string]: BplistValue;
|
|
11
|
+
}): Uint8Array;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { DmgConfig } from "../config.ts";
|
|
2
|
+
import { type DsStoreRecord } from "./dsStore.ts";
|
|
3
|
+
interface Point {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ResolvedDmg {
|
|
8
|
+
/** Absolute; null for a plain window. */
|
|
9
|
+
background: {
|
|
10
|
+
image: string;
|
|
11
|
+
image2x?: string;
|
|
12
|
+
} | null;
|
|
13
|
+
/** The window's size defaults to the background's. */
|
|
14
|
+
window: {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
width?: number;
|
|
18
|
+
height?: number;
|
|
19
|
+
};
|
|
20
|
+
iconSize: number;
|
|
21
|
+
textSize: number;
|
|
22
|
+
app: Point;
|
|
23
|
+
applications: Point;
|
|
24
|
+
}
|
|
25
|
+
export declare const DMG_DEFAULTS: {
|
|
26
|
+
readonly window: {
|
|
27
|
+
readonly x: 200;
|
|
28
|
+
readonly y: 120;
|
|
29
|
+
};
|
|
30
|
+
readonly iconSize: 128;
|
|
31
|
+
readonly textSize: 13;
|
|
32
|
+
readonly app: {
|
|
33
|
+
readonly x: 180;
|
|
34
|
+
readonly y: 170;
|
|
35
|
+
};
|
|
36
|
+
readonly applications: {
|
|
37
|
+
readonly x: 480;
|
|
38
|
+
readonly y: 170;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
export declare function validateDmg(dmg: DmgConfig | undefined, appDir: string, problems: string[]): ResolvedDmg;
|
|
42
|
+
export declare function dmgRecords(layout: ResolvedDmg, { appName, size, backgroundAlias, }: {
|
|
43
|
+
appName: string;
|
|
44
|
+
size: {
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
};
|
|
48
|
+
backgroundAlias?: Uint8Array;
|
|
49
|
+
}): DsStoreRecord[];
|
|
50
|
+
export declare function buildDmg(appPath: string, dmgPath: string, volumeName: string, workDir: string, { layout, volumeIcon }: {
|
|
51
|
+
layout: ResolvedDmg;
|
|
52
|
+
volumeIcon?: string;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* The createDate of the HFS+ volume a read-write image holds: its header ("H+", version 4) sits 1024 bytes into the
|
|
56
|
+
* partition, which `hdiutil create` puts within the image's first mebibyte, its data stored as is.
|
|
57
|
+
*/
|
|
58
|
+
export declare function volumeHeaderCreateDate(image: string): number;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type DsStoreValue = {
|
|
2
|
+
type: "long";
|
|
3
|
+
value: number;
|
|
4
|
+
} | {
|
|
5
|
+
type: "bool";
|
|
6
|
+
value: boolean;
|
|
7
|
+
} | {
|
|
8
|
+
type: "type";
|
|
9
|
+
value: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: "blob";
|
|
12
|
+
value: Uint8Array;
|
|
13
|
+
} | {
|
|
14
|
+
type: "ustr";
|
|
15
|
+
value: string;
|
|
16
|
+
};
|
|
17
|
+
export interface DsStoreRecord {
|
|
18
|
+
/** The file the record is about, "." for the folder itself. */
|
|
19
|
+
name: string;
|
|
20
|
+
/** Four letters: Iloc (icon location), bwsp (window), icvp (icon view), vSrn, icvl … */
|
|
21
|
+
code: string;
|
|
22
|
+
value: DsStoreValue;
|
|
23
|
+
}
|
|
24
|
+
export declare function writeDsStore(records: DsStoreRecord[]): Uint8Array;
|
|
25
|
+
/** Reads back what writeDsStore wrote (one leaf): the tests' check, and a way to look at a .DS_Store. */
|
|
26
|
+
export declare function readDsStore(file: Uint8Array): DsStoreRecord[];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface AliasTarget {
|
|
2
|
+
/** The HFS+ volume name (the disk image's label). */
|
|
3
|
+
volumeName: string;
|
|
4
|
+
/**
|
|
5
|
+
* The volume header's createDate as it stands, in HFS seconds. HFS+ writes it on the local clock of whatever formatted
|
|
6
|
+
* the volume (TN1150), unlike every other date, so it is read off the image rather than computed here.
|
|
7
|
+
*/
|
|
8
|
+
volumeCreated: number;
|
|
9
|
+
/** Where the volume mounts: /Volumes/<name>. */
|
|
10
|
+
volumePath: string;
|
|
11
|
+
kind: "file" | "folder";
|
|
12
|
+
name: string;
|
|
13
|
+
cnid: number;
|
|
14
|
+
created: Date;
|
|
15
|
+
parentName: string;
|
|
16
|
+
parentCnid: number;
|
|
17
|
+
/** The CNIDs of the folders above the target, nearest first, the volume's root left out. */
|
|
18
|
+
cnidPath: number[];
|
|
19
|
+
/** From the volume's root, starting with "/". */
|
|
20
|
+
posixPath: string;
|
|
21
|
+
}
|
|
22
|
+
/** Seconds since 1904-01-01 UTC, the HFS epoch. */
|
|
23
|
+
export declare function hfsTime(date: Date): number;
|
|
24
|
+
export declare function aliasRecord(target: AliasTarget): Uint8Array;
|
|
@@ -83,7 +83,6 @@ export declare function staple(file: string): Promise<void>;
|
|
|
83
83
|
/** The .app as notarytool takes it: a zip that keeps the bundle's symlinks and signature. */
|
|
84
84
|
export declare function zipForNotary(appPath: string, zip: string): Promise<void>;
|
|
85
85
|
/** A compressed disk image of the app beside a link to /Applications, the window people drag it into. */
|
|
86
|
-
export declare function buildDmg(appPath: string, dmgPath: string, volumeName: string, workDir: string): Promise<void>;
|
|
87
86
|
/** What Gatekeeper says of a notarized app or disk image, as a downloaded copy would be judged. */
|
|
88
87
|
export declare function assessGatekeeper(file: string, type: "execute" | "open"): Promise<void>;
|
|
89
88
|
/**
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { AkanNativeConfig } from "../config.ts";
|
|
2
2
|
import { type PermissionSet } from "./acl.ts";
|
|
3
3
|
import { type DesktopServerConfig } from "./desktop-server.ts";
|
|
4
|
+
import { type ResolvedDmg } from "./dmg.ts";
|
|
4
5
|
import { type EnvConfig } from "./env.ts";
|
|
5
6
|
import { CliError } from "./log.ts";
|
|
6
7
|
import { type PermissionsConfig } from "./permissions.ts";
|
|
7
8
|
import type { PlistValue } from "./plist.ts";
|
|
8
9
|
import { type UpdatesConfig } from "./updates.ts";
|
|
9
|
-
export interface ResolvedConfig extends Omit<AkanNativeConfig, "icon" | "splash" | "permissions" | "updates"> {
|
|
10
|
+
export interface ResolvedConfig extends Omit<AkanNativeConfig, "icon" | "splash" | "permissions" | "updates" | "desktop"> {
|
|
10
11
|
app: Required<AkanNativeConfig["app"]>;
|
|
11
12
|
web: Required<Pick<AkanNativeConfig["web"], "dir" | "base">> & {
|
|
12
13
|
build?: string;
|
|
@@ -38,6 +39,7 @@ export interface ResolvedConfig extends Omit<AkanNativeConfig, "icon" | "splash"
|
|
|
38
39
|
server?: DesktopServerConfig;
|
|
39
40
|
/** Absolute. */
|
|
40
41
|
bin?: string;
|
|
42
|
+
dmg: ResolvedDmg;
|
|
41
43
|
};
|
|
42
44
|
updates: UpdatesConfig | null;
|
|
43
45
|
/** Absolute image path. */
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { BuildContext } from "../lib/prepare.ts";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* The launcher entry (freedesktop Desktop Entry): what a menu shows and which link schemes open the app. Named and
|
|
4
|
+
* matched by the app id, the window's program name (native/desktop lib.rs): AppImageLauncher and appimaged rename the
|
|
5
|
+
* file they install, so StartupWMClass is what still ties the window to it.
|
|
6
|
+
*/
|
|
3
7
|
export declare function desktopEntry(input: {
|
|
8
|
+
id: string;
|
|
4
9
|
name: string;
|
|
5
10
|
fileName: string;
|
|
6
11
|
schemes: string[];
|
|
@@ -7,10 +7,16 @@ type Runner = (argv: string[]) => Promise<{
|
|
|
7
7
|
export declare function linkArguments(args: readonly string[], schemes: readonly string[]): string[];
|
|
8
8
|
/** Exec= of a desktop entry: the path quoted, with the characters the spec reserves escaped. */
|
|
9
9
|
export declare function desktopExec(exe: string): string;
|
|
10
|
-
export declare function desktopEntry(name: string, exe: string, schemes: readonly string[]
|
|
10
|
+
export declare function desktopEntry(name: string, exe: string, schemes: readonly string[], { id, icon }?: {
|
|
11
|
+
id?: string;
|
|
12
|
+
icon?: boolean;
|
|
13
|
+
}): string;
|
|
11
14
|
/** mimeapps.list with `x-scheme-handler/<scheme>=<desktop file>` set in [Default Applications]. */
|
|
12
15
|
export declare function withDefaults(list: string, desktopFile: string, schemes: readonly string[]): string;
|
|
13
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Makes this executable the current user's handler of the app's schemes (Windows, Linux). `icon`: a 256 px PNG the
|
|
18
|
+
* Linux entry shows (resources/icon.png).
|
|
19
|
+
*/
|
|
14
20
|
export declare function registerDeepLinks(app: {
|
|
15
21
|
id: string;
|
|
16
22
|
name: string;
|
|
@@ -20,5 +26,6 @@ export declare function registerDeepLinks(app: {
|
|
|
20
26
|
env?: Env;
|
|
21
27
|
home?: string;
|
|
22
28
|
run?: Runner;
|
|
29
|
+
icon?: string;
|
|
23
30
|
}): Promise<void>;
|
|
24
31
|
export {};
|
|
@@ -86,7 +86,6 @@ export declare function resolvePathRoutesWithFrameState(pathRoutes: PathRoute[],
|
|
|
86
86
|
renderRootLayouts: import("akanjs/client").RouteRender[];
|
|
87
87
|
renderLayouts: import("akanjs/client").RouteRender[];
|
|
88
88
|
resolveHead?: import("akanjs/client").ResolveHead;
|
|
89
|
-
isSpecialRoute?: boolean;
|
|
90
89
|
}[];
|
|
91
90
|
export declare function resolveLocationWithFrameState(location: Location | null, resolvedPathRouteMap: Map<string, PathRoute>): Location | null;
|
|
92
91
|
export declare function useFrameRuntimeResync({ updateViewport }: {
|
package/ui/Input.tsx
CHANGED
|
@@ -362,6 +362,12 @@ const DefaultEmail = ({
|
|
|
362
362
|
);
|
|
363
363
|
};
|
|
364
364
|
|
|
365
|
+
const parseTypedNumber = (text: string) => {
|
|
366
|
+
const kept = text.replace(/[^\d.-]/g, "");
|
|
367
|
+
const [whole = "", ...fraction] = kept.replace(/-/g, "").split(".");
|
|
368
|
+
return parseFloat(`${kept.startsWith("-") ? "-" : ""}${whole}${fraction.length ? `.${fraction.join("")}` : ""}`);
|
|
369
|
+
};
|
|
370
|
+
|
|
365
371
|
export type NumberProps = Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "type" | "onChange"> & {
|
|
366
372
|
value: number | null;
|
|
367
373
|
nullable?: boolean;
|
|
@@ -420,7 +426,7 @@ const DefaultNumber = ({
|
|
|
420
426
|
|
|
421
427
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
422
428
|
if (e.key !== "Enter" && e.key !== "Escape") return;
|
|
423
|
-
const numberValue =
|
|
429
|
+
const numberValue = parseTypedNumber(e.currentTarget.value);
|
|
424
430
|
if (Number.isNaN(numberValue)) {
|
|
425
431
|
e.currentTarget.value = "";
|
|
426
432
|
setFormatValue("");
|
package/ui/recipe/inputRecipe.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { recipe, tv } from "./factory";
|
|
2
2
|
|
|
3
|
-
/** 입력 표면 look — Input/TextArea/DatePicker 가 공유하는 필드 셸. kind 로 한 줄 필드(field)/멀티라인(area), tone 으로 강조/오류 상태를 고른다. */
|
|
3
|
+
/** 입력 표면 look — Input/TextArea/DatePicker 가 공유하는 필드 셸. kind 로 한 줄 필드(field)/멀티라인(area)/네이티브 `<select>`(select), tone 으로 강조/오류 상태를 고른다. */
|
|
4
4
|
export const inputRecipe = recipe(
|
|
5
5
|
tv({
|
|
6
6
|
base: "w-full rounded-field border border-input bg-background text-foreground focus:border-primary focus:outline-none",
|
|
7
7
|
variants: {
|
|
8
|
-
kind: {
|
|
8
|
+
kind: {
|
|
9
|
+
field: "px-3",
|
|
10
|
+
area: "p-3",
|
|
11
|
+
|
|
12
|
+
select:
|
|
13
|
+
"appearance-none bg-[image:linear-gradient(to_top_right,transparent_50%,currentColor_50%,currentColor_calc(50%+2px),transparent_calc(50%+2px)),linear-gradient(to_bottom_right,transparent_calc(50%-2px),currentColor_calc(50%-2px),currentColor_50%,transparent_50%)] bg-[length:5px_6px] bg-[position:calc(100%-14px)_50%,calc(100%-9px)_50%] bg-no-repeat pr-7 pl-3",
|
|
14
|
+
},
|
|
9
15
|
size: { xs: "text-xs", sm: "text-sm", md: "text-sm", lg: "text-base", xl: "text-lg" },
|
|
10
16
|
tone: {
|
|
11
17
|
default: "",
|
|
@@ -15,11 +21,11 @@ export const inputRecipe = recipe(
|
|
|
15
21
|
},
|
|
16
22
|
|
|
17
23
|
compoundVariants: [
|
|
18
|
-
{ kind: "field", size: "xs", class: "h-6" },
|
|
19
|
-
{ kind: "field", size: "sm", class: "h-8" },
|
|
20
|
-
{ kind: "field", size: "md", class: "h-10" },
|
|
21
|
-
{ kind: "field", size: "lg", class: "h-12" },
|
|
22
|
-
{ kind: "field", size: "xl", class: "h-14" },
|
|
24
|
+
{ kind: ["field", "select"], size: "xs", class: "h-6" },
|
|
25
|
+
{ kind: ["field", "select"], size: "sm", class: "h-8" },
|
|
26
|
+
{ kind: ["field", "select"], size: "md", class: "h-10" },
|
|
27
|
+
{ kind: ["field", "select"], size: "lg", class: "h-12" },
|
|
28
|
+
{ kind: ["field", "select"], size: "xl", class: "h-14" },
|
|
23
29
|
],
|
|
24
30
|
defaultVariants: { kind: "field", size: "md", tone: "default" },
|
|
25
31
|
}),
|
package/ui/styles.css
CHANGED
|
@@ -759,7 +759,7 @@ struct Config {
|
|
|
759
759
|
data_dir: Option<PathBuf>,
|
|
760
760
|
/// Window icon (Windows, Linux): [u32 width LE][u32 height LE][RGBA], written by the CLI.
|
|
761
761
|
icon: Option<PathBuf>,
|
|
762
|
-
/// app.id (Linux: the
|
|
762
|
+
/// app.id (Linux: the window's program name, so the dock matches it to <app id>.desktop).
|
|
763
763
|
app_id: String,
|
|
764
764
|
/// security.shell.externalSchemes (L0): schemes links may also hand to the OS (navigation.rs).
|
|
765
765
|
external_schemes: Vec<String>,
|
|
@@ -1805,6 +1805,13 @@ pub unsafe extern "C" fn akan_native_run(config_json: *const c_char) -> i32 {
|
|
|
1805
1805
|
log!("pages from {base} (akan-native dev --hmr)");
|
|
1806
1806
|
}
|
|
1807
1807
|
navigation::set_extra_schemes(cfg.external_schemes.clone());
|
|
1808
|
+
// Linux: GTK names the window after the program (X11 WM_CLASS, Wayland app_id), and a dock shows the <name>.desktop
|
|
1809
|
+
// that matches it, so the name is the app id the deep links and the dock plugin write their entry under. Not tao's
|
|
1810
|
+
// with_app_id: a second launch would then hand itself to the first over D-Bus and exit before single-instance runs.
|
|
1811
|
+
#[cfg(target_os = "linux")]
|
|
1812
|
+
if !cfg.app_id.is_empty() {
|
|
1813
|
+
gtk::glib::set_prgname(Some(cfg.app_id.as_str()));
|
|
1814
|
+
}
|
|
1808
1815
|
#[cfg(not(target_os = "windows"))]
|
|
1809
1816
|
#[cfg_attr(not(target_os = "macos"), allow(unused_mut))]
|
|
1810
1817
|
let mut event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();
|