@tapthrough/expo 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +105 -2
- package/app.plugin.d.ts +37 -0
- package/app.plugin.js +79 -0
- package/dist/Annotator.d.ts +14 -0
- package/dist/Annotator.d.ts.map +1 -0
- package/dist/Annotator.js +160 -0
- package/dist/Annotator.js.map +1 -0
- package/dist/Bubble.d.ts +27 -0
- package/dist/Bubble.d.ts.map +1 -0
- package/dist/Bubble.js +234 -0
- package/dist/Bubble.js.map +1 -0
- package/dist/Feedback.d.ts +14 -0
- package/dist/Feedback.d.ts.map +1 -0
- package/dist/Feedback.js +977 -0
- package/dist/Feedback.js.map +1 -0
- package/dist/Overlay.d.ts +13 -0
- package/dist/Overlay.d.ts.map +1 -0
- package/dist/Overlay.js +44 -0
- package/dist/Overlay.js.map +1 -0
- package/dist/Popover.d.ts +98 -0
- package/dist/Popover.d.ts.map +1 -0
- package/dist/Popover.js +531 -0
- package/dist/Popover.js.map +1 -0
- package/dist/Tapthrough.d.ts +27 -0
- package/dist/Tapthrough.d.ts.map +1 -0
- package/dist/Tapthrough.js +111 -0
- package/dist/Tapthrough.js.map +1 -0
- package/dist/core/annotations.d.ts +63 -0
- package/dist/core/annotations.d.ts.map +1 -0
- package/dist/core/annotations.js +76 -0
- package/dist/core/annotations.js.map +1 -0
- package/dist/core/bubble.d.ts +21 -0
- package/dist/core/bubble.d.ts.map +1 -0
- package/dist/core/bubble.js +25 -0
- package/dist/core/bubble.js.map +1 -0
- package/dist/core/controller.d.ts +46 -0
- package/dist/core/controller.d.ts.map +1 -0
- package/dist/core/controller.js +103 -0
- package/dist/core/controller.js.map +1 -0
- package/dist/core/diagnostics.d.ts +47 -0
- package/dist/core/diagnostics.d.ts.map +1 -0
- package/dist/core/diagnostics.js +101 -0
- package/dist/core/diagnostics.js.map +1 -0
- package/dist/core/feedback.d.ts +302 -0
- package/dist/core/feedback.d.ts.map +1 -0
- package/dist/core/feedback.js +345 -0
- package/dist/core/feedback.js.map +1 -0
- package/dist/core/flags.d.ts +13 -0
- package/dist/core/flags.d.ts.map +1 -0
- package/dist/core/flags.js +25 -0
- package/dist/core/flags.js.map +1 -0
- package/dist/core/ideas.d.ts +166 -0
- package/dist/core/ideas.d.ts.map +1 -0
- package/dist/core/ideas.js +159 -0
- package/dist/core/ideas.js.map +1 -0
- package/dist/core/launch.d.ts +43 -0
- package/dist/core/launch.d.ts.map +1 -0
- package/dist/core/launch.js +81 -0
- package/dist/core/launch.js.map +1 -0
- package/dist/core/reset.d.ts +17 -0
- package/dist/core/reset.d.ts.map +1 -0
- package/dist/core/reset.js +19 -0
- package/dist/core/reset.js.map +1 -0
- package/dist/core/review.d.ts +49 -0
- package/dist/core/review.d.ts.map +1 -0
- package/dist/core/review.js +92 -0
- package/dist/core/review.js.map +1 -0
- package/dist/core/url.d.ts +9 -0
- package/dist/core/url.d.ts.map +1 -0
- package/dist/core/url.js +23 -0
- package/dist/core/url.js.map +1 -0
- package/dist/hooks.d.ts +6 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +13 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/native-intent.d.ts +18 -0
- package/dist/native-intent.d.ts.map +1 -0
- package/dist/native-intent.js +32 -0
- package/dist/native-intent.js.map +1 -0
- package/dist/runtime.d.ts +24 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +17 -0
- package/dist/runtime.js.map +1 -0
- package/dist/screenshot.d.ts +7 -0
- package/dist/screenshot.d.ts.map +1 -0
- package/dist/screenshot.js +48 -0
- package/dist/screenshot.js.map +1 -0
- package/dist/supabase.d.ts +20 -0
- package/dist/supabase.d.ts.map +1 -0
- package/dist/supabase.js +18 -0
- package/dist/supabase.js.map +1 -0
- package/dist/ui/native.d.ts +34 -0
- package/dist/ui/native.d.ts.map +1 -0
- package/dist/ui/native.js +76 -0
- package/dist/ui/native.js.map +1 -0
- package/dist/ui/parts.d.ts +62 -0
- package/dist/ui/parts.d.ts.map +1 -0
- package/dist/ui/parts.js +110 -0
- package/dist/ui/parts.js.map +1 -0
- package/dist/ui/theme.d.ts +90 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +61 -0
- package/dist/ui/theme.js.map +1 -0
- package/package.json +82 -3
- package/src/Annotator.tsx +279 -0
- package/src/Bubble.tsx +331 -0
- package/src/Feedback.tsx +1739 -0
- package/src/Overlay.tsx +61 -0
- package/src/Popover.tsx +1393 -0
- package/src/Tapthrough.tsx +171 -0
- package/src/core/annotations.ts +116 -0
- package/src/core/bubble.ts +45 -0
- package/src/core/controller.ts +119 -0
- package/src/core/diagnostics.ts +128 -0
- package/src/core/feedback.ts +589 -0
- package/src/core/flags.ts +31 -0
- package/src/core/ideas.ts +337 -0
- package/src/core/launch.ts +119 -0
- package/src/core/reset.ts +28 -0
- package/src/core/review.ts +148 -0
- package/src/core/url.ts +21 -0
- package/src/hooks.ts +15 -0
- package/src/index.ts +8 -0
- package/src/native-intent.ts +36 -0
- package/src/runtime.ts +30 -0
- package/src/screenshot.ts +64 -0
- package/src/supabase.ts +27 -0
- package/src/ui/native.ts +116 -0
- package/src/ui/parts.tsx +249 -0
- package/src/ui/theme.ts +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tapthrough contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,106 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tapthrough/expo
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The [Tapthrough](https://github.com/tapthrough/tapthrough) SDK for Expo Router apps. When a reviewer opens
|
|
4
|
+
a pull request's preview link, the app:
|
|
5
|
+
|
|
6
|
+
1. sends the link token to your [seed hook](../seed), which creates a fresh test user in staging
|
|
7
|
+
2. clears local state: query caches, persisted keys, stores
|
|
8
|
+
3. logs in as that user
|
|
9
|
+
4. applies the scenario's feature flags
|
|
10
|
+
5. opens the screen the PR changed
|
|
11
|
+
|
|
12
|
+
When the app opens any other way, the SDK does nothing.
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npx expo install @tapthrough/expo
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
### 1. Config plugin
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// app.config.ts
|
|
24
|
+
export default {
|
|
25
|
+
scheme: 'acmenotes',
|
|
26
|
+
plugins: [
|
|
27
|
+
[
|
|
28
|
+
'@tapthrough/expo',
|
|
29
|
+
{
|
|
30
|
+
enabled: process.env.APP_VARIANT === 'preview',
|
|
31
|
+
seedHookUrl: 'https://<ref>.supabase.co/functions/v1/tapthrough-seed',
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
],
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The SDK ignores launch links unless `enabled` is true, so keep it off for store builds. The plugin
|
|
39
|
+
requires a `scheme`, because links open `<scheme>://tapthrough/launch?t=<token>`.
|
|
40
|
+
|
|
41
|
+
### 2. Wrap the root layout
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
// app/_layout.tsx
|
|
45
|
+
import { resetQueryClient, resetStorage, Tapthrough } from '@tapthrough/expo'
|
|
46
|
+
import { supabaseAuth } from '@tapthrough/expo/supabase'
|
|
47
|
+
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
48
|
+
import { Stack } from 'expo-router'
|
|
49
|
+
|
|
50
|
+
const auth = supabaseAuth(supabase)
|
|
51
|
+
const reset = [resetQueryClient(queryClient), resetStorage(AsyncStorage, ['notes-store'])]
|
|
52
|
+
|
|
53
|
+
export default function RootLayout() {
|
|
54
|
+
return (
|
|
55
|
+
<QueryClientProvider client={queryClient}>
|
|
56
|
+
<Tapthrough auth={auth} reset={reset}>
|
|
57
|
+
<Stack />
|
|
58
|
+
</Tapthrough>
|
|
59
|
+
</QueryClientProvider>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Define `auth` and `reset` outside the component, or memoize them, so they don't change on every render.
|
|
65
|
+
|
|
66
|
+
`reset` runs **before** sign-in, so a listed key can't wipe the new session. List only your app's own
|
|
67
|
+
keys, not the auth client's.
|
|
68
|
+
|
|
69
|
+
### 3. Hand launch links to the SDK
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// app/+native-intent.ts
|
|
73
|
+
export { redirectSystemPath } from '@tapthrough/expo'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Without this file, Expo Router tries to open `/tapthrough/launch` as a screen and shows its "not found"
|
|
77
|
+
page. If you already have a `redirectSystemPath`, call `handleLaunchPath(path)` first and return `'/'`
|
|
78
|
+
(or `null` when the app was already open) when it returns true.
|
|
79
|
+
|
|
80
|
+
### 4. Read flag overrides
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { useFlagOverride } from '@tapthrough/expo'
|
|
84
|
+
|
|
85
|
+
const newEditor = useFlagOverride('newEditor') ?? flags.newEditor
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Outside React, use `getFlagOverride(name)`.
|
|
89
|
+
|
|
90
|
+
## API
|
|
91
|
+
|
|
92
|
+
| Export | Does |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| `<Tapthrough auth reset? seedHookUrl? enabled?>` | Runs the launch sequence and shows a loading screen, or an error card the reviewer can dismiss. `seedHookUrl` and `enabled` override the plugin. |
|
|
95
|
+
| `redirectSystemPath`, `handleLaunchPath(path)` | For `+native-intent`, as above. |
|
|
96
|
+
| `supabaseAuth(client)` from `@tapthrough/expo/supabase` | Logs in with the `{ access_token, refresh_token }` that `createTestUser` returns. |
|
|
97
|
+
| `resetQueryClient(client)`, `resetStorage(storage, keys)`, `resetStores(stores)` | Reset targets for TanStack Query, AsyncStorage or MMKV, and Zustand. A reset target is any `() => void \| Promise<void>`. |
|
|
98
|
+
| `useFlagOverride(name)`, `getFlagOverride(name)`, `getFlagOverrides()` | The scenario's flag values. `undefined` means the scenario leaves that flag alone. |
|
|
99
|
+
| `useLaunchState()` | `idle`, `launching`, `done` (with `path`) or `error` (with `error.code` and `error.message`). |
|
|
100
|
+
|
|
101
|
+
Any other auth provider works through `auth={{ signIn: async (session) => { … } }}`, where `session` is
|
|
102
|
+
whatever your seed hook returns.
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
package/app.plugin.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface TapthroughPluginProps {
|
|
2
|
+
/** Turns the SDK on for this build. Defaults to true; set false for store builds. */
|
|
3
|
+
enabled?: boolean
|
|
4
|
+
/** Your seed hook, e.g. `https://<ref>.supabase.co/functions/v1/tapthrough-seed`. */
|
|
5
|
+
seedHookUrl?: string
|
|
6
|
+
/** Tapthrough's base URL, where in-app reports go. Defaults to `https://tapthrough-cloud.vercel.app`. */
|
|
7
|
+
apiUrl?: string
|
|
8
|
+
/**
|
|
9
|
+
* Staging and TestFlight builds: shows a feedback button outside PR previews,
|
|
10
|
+
* to report issues and share ideas. Never turn it on for store builds.
|
|
11
|
+
*/
|
|
12
|
+
staff?: boolean
|
|
13
|
+
/** The repo's app key from `npx @tapthrough/cli app-key`; required with `staff`. */
|
|
14
|
+
appKey?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type ExpoConfig = { scheme?: string | string[]; extra?: Record<string, unknown> } & Record<
|
|
18
|
+
string,
|
|
19
|
+
unknown
|
|
20
|
+
>
|
|
21
|
+
|
|
22
|
+
declare function withTapthrough<T extends ExpoConfig>(
|
|
23
|
+
config: T,
|
|
24
|
+
props?: TapthroughPluginProps,
|
|
25
|
+
): T & {
|
|
26
|
+
extra: NonNullable<T['extra']> & {
|
|
27
|
+
tapthrough: {
|
|
28
|
+
enabled: boolean
|
|
29
|
+
seedHookUrl: string | undefined
|
|
30
|
+
apiUrl?: string
|
|
31
|
+
staff?: boolean
|
|
32
|
+
appKey?: string
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export = withTapthrough
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* Expo config plugin. Writes the SDK's build-time settings into `extra.tapthrough`.
|
|
4
|
+
*
|
|
5
|
+
* plugins: [['@tapthrough/expo', {
|
|
6
|
+
* // PR previews: launch links, test data, the checklist.
|
|
7
|
+
* enabled: process.env.APP_VARIANT === 'development',
|
|
8
|
+
* seedHookUrl: 'https://<ref>.supabase.co/functions/v1/tapthrough-seed',
|
|
9
|
+
* // Staging and TestFlight builds: report issues and share ideas.
|
|
10
|
+
* staff: process.env.APP_VARIANT === 'staging',
|
|
11
|
+
* appKey: process.env.TAPTHROUGH_APP_KEY,
|
|
12
|
+
* }]]
|
|
13
|
+
*
|
|
14
|
+
* Keep both off for store builds. With `enabled` off the SDK ignores launch
|
|
15
|
+
* links; with `staff` off there's no feedback button outside PR previews.
|
|
16
|
+
*
|
|
17
|
+
* @param {{ expo?: unknown } & Record<string, any>} config
|
|
18
|
+
* @param {{ enabled?: boolean, seedHookUrl?: string, apiUrl?: string, staff?: boolean, appKey?: string }} [props]
|
|
19
|
+
*/
|
|
20
|
+
function withTapthrough(config, props = {}) {
|
|
21
|
+
const enabled = props.enabled ?? true
|
|
22
|
+
const staff = props.staff === true
|
|
23
|
+
const seedHookUrl = props.seedHookUrl
|
|
24
|
+
const apiUrl = props.apiUrl ?? 'https://tapthrough-cloud.vercel.app'
|
|
25
|
+
|
|
26
|
+
if (enabled || staff) {
|
|
27
|
+
if (!isAllowedUrl(apiUrl)) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`[@tapthrough/expo] \`apiUrl\` must be https (http is allowed only for localhost): ${apiUrl}`,
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (enabled) {
|
|
34
|
+
// Optional: without it, links open the right screen but can't add test data.
|
|
35
|
+
if (seedHookUrl && !isAllowedUrl(seedHookUrl)) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`[@tapthrough/expo] \`seedHookUrl\` must be https (http is allowed only for localhost): ${seedHookUrl}`,
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
if (!config.scheme) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
'[@tapthrough/expo] Set `scheme` in your app config; launch links open `<scheme>://tapthrough/launch`.',
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (staff && !props.appKey) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
'[@tapthrough/expo] `staff` needs an `appKey`. Make one with `npx @tapthrough/cli app-key` and pass it from an environment variable.',
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...config,
|
|
54
|
+
extra: {
|
|
55
|
+
...config.extra,
|
|
56
|
+
tapthrough: {
|
|
57
|
+
enabled,
|
|
58
|
+
seedHookUrl: enabled ? seedHookUrl : undefined,
|
|
59
|
+
...(enabled || staff ? { apiUrl } : {}),
|
|
60
|
+
// The key only goes into builds that use it.
|
|
61
|
+
...(staff ? { staff, appKey: props.appKey } : {}),
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @param {string} url */
|
|
68
|
+
function isAllowedUrl(url) {
|
|
69
|
+
try {
|
|
70
|
+
const { protocol, hostname } = new URL(url)
|
|
71
|
+
if (protocol === 'https:') return true
|
|
72
|
+
// 10.0.2.2 is the host machine from the Android emulator.
|
|
73
|
+
return protocol === 'http:' && ['localhost', '127.0.0.1', '10.0.2.2'].includes(hostname)
|
|
74
|
+
} catch {
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = withTapthrough
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Annotation } from './core/annotations.js';
|
|
2
|
+
import type { Screenshot } from './screenshot.js';
|
|
3
|
+
/**
|
|
4
|
+
* Full-screen markup for a report's screenshot: Draw to circle or point at
|
|
5
|
+
* something freehand, Box to outline it, Blur to hide personal data. The
|
|
6
|
+
* server applies the marks before storing the image.
|
|
7
|
+
*/
|
|
8
|
+
export declare function Annotator({ screenshot, initial, onDone, onCancel, }: {
|
|
9
|
+
screenshot: Screenshot;
|
|
10
|
+
initial: Annotation[];
|
|
11
|
+
onDone: (marks: Annotation[]) => void;
|
|
12
|
+
onCancel: () => void;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=Annotator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Annotator.d.ts","sourceRoot":"","sources":["../src/Annotator.tsx"],"names":[],"mappings":"AAWA,OAAO,EACL,KAAK,UAAU,EAOhB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAUjD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EACxB,UAAU,EACV,OAAO,EACP,MAAM,EACN,QAAQ,GACT,EAAE;IACD,UAAU,EAAE,UAAU,CAAA;IACtB,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,CAAA;IACrC,QAAQ,EAAE,MAAM,IAAI,CAAA;CACrB,+BA0JA"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Animated, Image, PanResponder, Pressable, StyleSheet, Text, useWindowDimensions, View, } from 'react-native';
|
|
4
|
+
import { addStrokePoint, boxFromDrag, finishStroke, MAX_ANNOTATIONS, strokeSegments, } from './core/annotations.js';
|
|
5
|
+
import { useBackPress } from './Overlay.js';
|
|
6
|
+
const MODES = [
|
|
7
|
+
{ mode: 'draw', title: 'Draw', hint: 'Draw on the screenshot to circle or point at something.' },
|
|
8
|
+
{ mode: 'highlight', title: 'Box', hint: 'Drag on the screenshot to draw a box.' },
|
|
9
|
+
{ mode: 'blur', title: 'Blur', hint: 'Drag over anything private to blur it.' },
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Full-screen markup for a report's screenshot: Draw to circle or point at
|
|
13
|
+
* something freehand, Box to outline it, Blur to hide personal data. The
|
|
14
|
+
* server applies the marks before storing the image.
|
|
15
|
+
*/
|
|
16
|
+
export function Annotator({ screenshot, initial, onDone, onCancel, }) {
|
|
17
|
+
const window = useWindowDimensions();
|
|
18
|
+
const [boxes, setBoxes] = useState(initial);
|
|
19
|
+
const [kind, setKind] = useState('draw');
|
|
20
|
+
const [draft, setDraft] = useState(null);
|
|
21
|
+
const [aspect, setAspect] = useState(window.width / window.height);
|
|
22
|
+
const uri = `data:${screenshot.contentType};base64,${screenshot.base64}`;
|
|
23
|
+
const shown = useRef(new Animated.Value(0)).current;
|
|
24
|
+
useBackPress(onCancel);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
Animated.timing(shown, { toValue: 1, duration: 180, useNativeDriver: true }).start();
|
|
27
|
+
}, [shown]);
|
|
28
|
+
// The image is shown at the screenshot's aspect ratio, so fractions map 1:1.
|
|
29
|
+
const maxWidth = window.width - 32;
|
|
30
|
+
const maxHeight = window.height - 200;
|
|
31
|
+
const width = Math.min(maxWidth, maxHeight * aspect);
|
|
32
|
+
const height = width / aspect;
|
|
33
|
+
const state = useRef({
|
|
34
|
+
start: { x: 0, y: 0 },
|
|
35
|
+
draft: null,
|
|
36
|
+
points: [],
|
|
37
|
+
kind,
|
|
38
|
+
boxes,
|
|
39
|
+
});
|
|
40
|
+
state.current.kind = kind;
|
|
41
|
+
state.current.boxes = boxes;
|
|
42
|
+
const responder = useMemo(() => PanResponder.create({
|
|
43
|
+
onStartShouldSetPanResponder: () => true,
|
|
44
|
+
onMoveShouldSetPanResponder: () => true,
|
|
45
|
+
onPanResponderGrant: (event) => {
|
|
46
|
+
const start = { x: event.nativeEvent.locationX, y: event.nativeEvent.locationY };
|
|
47
|
+
state.current.start = start;
|
|
48
|
+
state.current.draft = null;
|
|
49
|
+
state.current.points =
|
|
50
|
+
state.current.kind === 'draw' ? addStrokePoint([], start, { width, height }) : [];
|
|
51
|
+
},
|
|
52
|
+
onPanResponderMove: (_event, gesture) => {
|
|
53
|
+
const { start, kind: mode } = state.current;
|
|
54
|
+
const at = { x: start.x + gesture.dx, y: start.y + gesture.dy };
|
|
55
|
+
if (mode === 'draw') {
|
|
56
|
+
const points = addStrokePoint(state.current.points, at, { width, height });
|
|
57
|
+
if (points === state.current.points)
|
|
58
|
+
return;
|
|
59
|
+
state.current.points = points;
|
|
60
|
+
const stroke = { kind: 'draw', points };
|
|
61
|
+
state.current.draft = stroke;
|
|
62
|
+
setDraft(stroke);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const next = boxFromDrag(mode, start, at, { width, height });
|
|
66
|
+
state.current.draft = next;
|
|
67
|
+
setDraft(next);
|
|
68
|
+
},
|
|
69
|
+
onPanResponderRelease: () => {
|
|
70
|
+
const { draft: current } = state.current;
|
|
71
|
+
const finished = current?.kind === 'draw' ? finishStroke(current.points) : current;
|
|
72
|
+
if (finished && state.current.boxes.length < MAX_ANNOTATIONS) {
|
|
73
|
+
setBoxes([...state.current.boxes, finished]);
|
|
74
|
+
}
|
|
75
|
+
state.current.draft = null;
|
|
76
|
+
setDraft(null);
|
|
77
|
+
},
|
|
78
|
+
}), [width, height]);
|
|
79
|
+
return (_jsx(Animated.View, { style: [StyleSheet.absoluteFill, { opacity: shown }], accessibilityViewIsModal: true, children: _jsxs(View, { style: styles.screen, children: [_jsxs(View, { style: styles.toolbar, children: [_jsx(Pressable, { onPress: onCancel, accessibilityRole: "button", hitSlop: 8, children: _jsx(Text, { style: styles.toolbarText, children: "Cancel" }) }), _jsx(View, { style: styles.modes, children: MODES.map(({ mode, title }) => (_jsx(Pressable, { onPress: () => setKind(mode), style: [styles.mode, kind === mode && styles.modeActive], accessibilityRole: "radio", accessibilityState: { selected: kind === mode }, children: _jsx(Text, { style: [styles.modeText, kind === mode && styles.modeTextActive], children: title }) }, mode))) }), _jsx(Pressable, { onPress: () => onDone(boxes), accessibilityRole: "button", hitSlop: 8, children: _jsx(Text, { style: [styles.toolbarText, styles.done], children: "Done" }) })] }), _jsxs(View, { style: { width, height }, ...responder.panHandlers, children: [_jsx(Image, { source: { uri }, style: { width, height }, onLoad: (event) => {
|
|
80
|
+
const { width: w, height: h } = event.nativeEvent.source;
|
|
81
|
+
if (w && h)
|
|
82
|
+
setAspect(w / h);
|
|
83
|
+
}, accessibilityLabel: "Screenshot to mark up" }), [...boxes, ...(draft ? [draft] : [])].map((box, index) => box.kind === 'draw' ? (
|
|
84
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: marks have no identity beyond their order
|
|
85
|
+
_jsx(Stroke, { points: box.points, width: width, height: height }, index)) : (_jsx(View
|
|
86
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: boxes have no identity beyond their order
|
|
87
|
+
, { pointerEvents: "none", style: [
|
|
88
|
+
styles.box,
|
|
89
|
+
box.kind === 'blur' ? styles.blurBox : styles.highlightBox,
|
|
90
|
+
{
|
|
91
|
+
left: box.x * width,
|
|
92
|
+
top: box.y * height,
|
|
93
|
+
width: box.w * width,
|
|
94
|
+
height: box.h * height,
|
|
95
|
+
},
|
|
96
|
+
], children: box.kind === 'blur' ? _jsx(Text, { style: styles.blurLabel, children: "Blurred" }) : null }, index)))] }), _jsxs(View, { style: styles.footer, children: [_jsx(Text, { style: styles.hint, children: boxes.length === 0
|
|
97
|
+
? MODES.find((m) => m.mode === kind)?.hint
|
|
98
|
+
: boxes.length >= MAX_ANNOTATIONS
|
|
99
|
+
? `${MAX_ANNOTATIONS} marks is the most. Undo one to add another.`
|
|
100
|
+
: `${boxes.length} mark${boxes.length === 1 ? '' : 's'}` }), boxes.length > 0 ? (_jsx(Pressable, { onPress: () => setBoxes(boxes.slice(0, -1)), accessibilityRole: "button", hitSlop: 8, children: _jsx(Text, { style: styles.toolbarText, children: "Undo" }) })) : null] })] }) }));
|
|
101
|
+
}
|
|
102
|
+
const STROKE_WIDTH = 4;
|
|
103
|
+
/** A freehand mark, drawn as short rotated bars so it needs no SVG library. */
|
|
104
|
+
function Stroke({ points, width, height, }) {
|
|
105
|
+
return (_jsx(_Fragment, { children: strokeSegments(points, { width, height }).map((segment, i) => (_jsx(View
|
|
106
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: segments are positional
|
|
107
|
+
, { pointerEvents: "none", style: [
|
|
108
|
+
styles.segment,
|
|
109
|
+
{
|
|
110
|
+
left: segment.left,
|
|
111
|
+
top: segment.top - STROKE_WIDTH / 2,
|
|
112
|
+
// A little longer than the gap, so joints don't show.
|
|
113
|
+
width: segment.length + STROKE_WIDTH / 2,
|
|
114
|
+
transform: [{ rotate: `${segment.angle}rad` }],
|
|
115
|
+
},
|
|
116
|
+
] }, i))) }));
|
|
117
|
+
}
|
|
118
|
+
const styles = StyleSheet.create({
|
|
119
|
+
segment: {
|
|
120
|
+
position: 'absolute',
|
|
121
|
+
height: STROKE_WIDTH,
|
|
122
|
+
borderRadius: STROKE_WIDTH / 2,
|
|
123
|
+
backgroundColor: '#e5484d',
|
|
124
|
+
},
|
|
125
|
+
screen: {
|
|
126
|
+
flex: 1,
|
|
127
|
+
alignItems: 'center',
|
|
128
|
+
justifyContent: 'center',
|
|
129
|
+
gap: 16,
|
|
130
|
+
backgroundColor: '#111114',
|
|
131
|
+
paddingVertical: 48,
|
|
132
|
+
},
|
|
133
|
+
toolbar: {
|
|
134
|
+
width: '100%',
|
|
135
|
+
paddingHorizontal: 16,
|
|
136
|
+
flexDirection: 'row',
|
|
137
|
+
alignItems: 'center',
|
|
138
|
+
justifyContent: 'space-between',
|
|
139
|
+
},
|
|
140
|
+
toolbarText: { color: '#ffffff', fontSize: 16 },
|
|
141
|
+
done: { fontWeight: '700', color: '#8ab4f8' },
|
|
142
|
+
modes: { flexDirection: 'row', borderRadius: 999, backgroundColor: '#2a2b31', padding: 3 },
|
|
143
|
+
mode: { paddingVertical: 6, paddingHorizontal: 14, borderRadius: 999 },
|
|
144
|
+
modeActive: { backgroundColor: '#ffffff' },
|
|
145
|
+
modeText: { color: '#d4d4d8', fontSize: 14, fontWeight: '600' },
|
|
146
|
+
modeTextActive: { color: '#111114' },
|
|
147
|
+
box: { position: 'absolute', borderRadius: 6, alignItems: 'center', justifyContent: 'center' },
|
|
148
|
+
highlightBox: { borderWidth: 3, borderColor: '#e5484d' },
|
|
149
|
+
blurBox: { backgroundColor: 'rgba(40, 40, 46, 0.85)' },
|
|
150
|
+
blurLabel: { color: '#ffffff', fontSize: 12, fontWeight: '600' },
|
|
151
|
+
footer: {
|
|
152
|
+
width: '100%',
|
|
153
|
+
paddingHorizontal: 16,
|
|
154
|
+
flexDirection: 'row',
|
|
155
|
+
alignItems: 'center',
|
|
156
|
+
justifyContent: 'space-between',
|
|
157
|
+
},
|
|
158
|
+
hint: { color: '#a1a1aa', fontSize: 14 },
|
|
159
|
+
});
|
|
160
|
+
//# sourceMappingURL=Annotator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Annotator.js","sourceRoot":"","sources":["../src/Annotator.tsx"],"names":[],"mappings":"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"}
|
package/dist/Bubble.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Bounds, type Point } from './core/bubble.js';
|
|
2
|
+
import type { Badge } from './core/feedback.js';
|
|
3
|
+
/** The screen, less conservative insets for the status bar and home indicator. */
|
|
4
|
+
export declare function screenBounds(width: number, height: number): Bounds;
|
|
5
|
+
/**
|
|
6
|
+
* A small, draggable Review button. Drag it anywhere; it snaps to the nearer
|
|
7
|
+
* side. It fades while idle so it stays out of the way of the app under test.
|
|
8
|
+
*/
|
|
9
|
+
export declare function Bubble({ onPress, badge, busy, active, onRest, }: {
|
|
10
|
+
onPress: () => void;
|
|
11
|
+
badge: Badge | null;
|
|
12
|
+
busy: boolean;
|
|
13
|
+
/** The popover is open: show ✕, stay fully visible and hide the badge. */
|
|
14
|
+
active?: boolean;
|
|
15
|
+
/** Where the button settles, so the popover can open beside it. */
|
|
16
|
+
onRest?: (at: Point) => void;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
18
|
+
export declare const COLORS: {
|
|
19
|
+
ink: string;
|
|
20
|
+
tape: string;
|
|
21
|
+
tapeInk: string;
|
|
22
|
+
/** Steps left: the iOS system blue, the SDK's one tint. */
|
|
23
|
+
count: string;
|
|
24
|
+
pass: string;
|
|
25
|
+
fail: string;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=Bubble.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Bubble.d.ts","sourceRoot":"","sources":["../src/Bubble.tsx"],"names":[],"mappings":"AAWA,OAAO,EACL,KAAK,MAAM,EAIX,KAAK,KAAK,EAEX,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAW/C,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAQlE;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,EACrB,OAAO,EACP,KAAK,EACL,IAAI,EACJ,MAAc,EACd,MAAM,GACP,EAAE;IACD,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IACnB,IAAI,EAAE,OAAO,CAAA;IACb,0EAA0E;IAC1E,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,mEAAmE;IACnE,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,KAAK,KAAK,IAAI,CAAA;CAC7B,+BA0JA;AAuDD,eAAO,MAAM,MAAM;;;;IAIjB,2DAA2D;;;;CAI5D,CAAA"}
|