@workday/graphiql-plugin-multipart-requests 0.1.0 → 0.1.2

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 CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) Workday, Inc.
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.
1
+ MIT License
2
+
3
+ Copyright (c) Workday, Inc.
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,89 +1,89 @@
1
- # Multipart Requests Plugin
2
-
3
- This plugin allows GraphiQL to include files as part of a request to a GraphQL server
4
- implementing [GraphQL Multipart Request: V3](https://workday.github.io/graphql-multipart-request-spec/graphql-multipart-request-v3.html).
5
-
6
- ![Demo](./demo.gif)
7
-
8
- ## Usage
9
-
10
- ### Using `MultipartFetcher`
11
- ```tsx
12
- import {
13
- MULTIPART_ATTACHMENTS_GRAPHIQL_PLUGIN,
14
- MultipartAttachmentsContext,
15
- MultipartAttachmentsProvider,
16
- } from '@workday/graphiql-plugin-multipart-requests';
17
-
18
- function GraphiQLWrapper() {
19
- const attachments = useContext(MultipartAttachmentsContext);
20
-
21
- const fetcher = useMemo(() => {
22
- // The decoratedFetcher will automatically pass all necessary FormData to the Fetcher
23
- return attachments.decoratedFetcher(
24
- async (graphQLParams: FetcherParams, formData: FormData | null) => {
25
- console.log('Attachments:', formData);
26
-
27
- // Send the request to the server
28
- }
29
- );
30
- }, [attachments]);
31
-
32
- return (
33
- <GraphiQL
34
- fetcher={fetcher}
35
- plugins={[MULTIPART_ATTACHMENTS_GRAPHIQL_PLUGIN]}
36
- />
37
- );
38
- }
39
-
40
- const App = () => (
41
- <MultipartAttachmentsProvider>
42
- <GraphiQLWrapper />
43
- </MultipartAttachmentsProvider>
44
- );
45
- ```
46
-
47
- ### Using `fetch` Override
48
-
49
- ```tsx
50
- import {
51
- MULTIPART_ATTACHMENTS_GRAPHIQL_PLUGIN,
52
- MultipartAttachmentsContext,
53
- MultipartAttachmentsProvider
54
- } from '@workday/graphiql-plugin-multipart-requests';
55
-
56
- function GraphiQLWrapper() {
57
- const attachments = useContext(MultipartAttachmentsContext);
58
-
59
- const fetcher = useMemo(() => {
60
- // attachmentsFetch will automatically include all necessary FormData in the downstream request.
61
- const attachmentsFetch = attachments.decoratedFetch();
62
- async (graphQLParams: Fetcher) => {
63
- const response = await attachmentsFetch('https://example.com/my-graphql', {
64
- method: 'POST',
65
- headers: {
66
- 'Content-Type': 'application/json'
67
- },
68
- body: JSON.stringify(graphQLParams)
69
- }
70
- );
71
-
72
- // Return the response to GraphiQL
73
- };
74
- }, [attachments]);
75
-
76
- return (
77
- <GraphiQL
78
- fetcher={fetcher}
79
- plugins={[MULTIPART_ATTACHMENTS_GRAPHIQL_PLUGIN]}
80
- />
81
- );
82
- }
83
-
84
- const App = () => (
85
- <MultipartAttachmentsProvider>
86
- <GraphiQLWrapper />
87
- </MultipartAttachmentsProvider>
88
- );
89
- ```
1
+ # Multipart Requests Plugin
2
+
3
+ This plugin allows GraphiQL to include files as part of a request to a GraphQL server
4
+ implementing [GraphQL Multipart Request: V3](https://workday.github.io/graphql-multipart-request-spec/graphql-multipart-request-v3.html).
5
+
6
+ ![Demo](./demo.gif)
7
+
8
+ ## Usage
9
+
10
+ ### Using `MultipartFetcher`
11
+ ```tsx
12
+ import {
13
+ MULTIPART_ATTACHMENTS_GRAPHIQL_PLUGIN,
14
+ MultipartAttachmentsContext,
15
+ MultipartAttachmentsProvider,
16
+ } from '@workday/graphiql-plugin-multipart-requests';
17
+
18
+ function GraphiQLWrapper() {
19
+ const attachments = useContext(MultipartAttachmentsContext);
20
+
21
+ const fetcher = useMemo(() => {
22
+ // The decoratedFetcher will automatically pass all necessary FormData to the Fetcher
23
+ return attachments.decoratedFetcher(
24
+ async (graphQLParams: FetcherParams, formData: FormData | null) => {
25
+ console.log('Attachments:', formData);
26
+
27
+ // Send the request to the server
28
+ }
29
+ );
30
+ }, [attachments]);
31
+
32
+ return (
33
+ <GraphiQL
34
+ fetcher={fetcher}
35
+ plugins={[MULTIPART_ATTACHMENTS_GRAPHIQL_PLUGIN]}
36
+ />
37
+ );
38
+ }
39
+
40
+ const App = () => (
41
+ <MultipartAttachmentsProvider>
42
+ <GraphiQLWrapper />
43
+ </MultipartAttachmentsProvider>
44
+ );
45
+ ```
46
+
47
+ ### Using `fetch` Override
48
+
49
+ ```tsx
50
+ import {
51
+ MULTIPART_ATTACHMENTS_GRAPHIQL_PLUGIN,
52
+ MultipartAttachmentsContext,
53
+ MultipartAttachmentsProvider
54
+ } from '@workday/graphiql-plugin-multipart-requests';
55
+
56
+ function GraphiQLWrapper() {
57
+ const attachments = useContext(MultipartAttachmentsContext);
58
+
59
+ const fetcher = useMemo(() => {
60
+ // attachmentsFetch will automatically include all necessary FormData in the downstream request.
61
+ const attachmentsFetch = attachments.decoratedFetch();
62
+ async (graphQLParams: Fetcher) => {
63
+ const response = await attachmentsFetch('https://example.com/my-graphql', {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Content-Type': 'application/json'
67
+ },
68
+ body: JSON.stringify(graphQLParams)
69
+ }
70
+ );
71
+
72
+ // Return the response to GraphiQL
73
+ };
74
+ }, [attachments]);
75
+
76
+ return (
77
+ <GraphiQL
78
+ fetcher={fetcher}
79
+ plugins={[MULTIPART_ATTACHMENTS_GRAPHIQL_PLUGIN]}
80
+ />
81
+ );
82
+ }
83
+
84
+ const App = () => (
85
+ <MultipartAttachmentsProvider>
86
+ <GraphiQLWrapper />
87
+ </MultipartAttachmentsProvider>
88
+ );
89
+ ```
@@ -1 +1 @@
1
- {"version":3,"file":"attachment-icon.svg.js","sourceRoot":"","sources":["../../src/attachment-icon.svg.tsx"],"names":[],"mappings":";;;AAAA,kBAAe,CACb,gCAAK,MAAM,EAAC,KAAK,EAAC,OAAO,EAAC,WAAW,EAAC,IAAI,EAAC,MAAM,EAAC,KAAK,EAAC,4BAA4B,YAClF,iCACE,CAAC,EAAC,iHAAiH,EACnH,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,KAAK,GACjB,GACE,CACP,CAAC","sourcesContent":["export default (\r\n <svg height='1em' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'>\r\n <path\r\n d='M 9 4 L 4.5 8.5 C 3.5 9.5 5 11 6 10 L 11.5 4.5 C 13.5 2.5 11 -0.5 8.5 2 L 2.5 8 C 0 10.5 4 14.5 6.5 12 L 12.5 6'\r\n stroke='currentColor'\r\n strokeWidth='0.8'\r\n />\r\n </svg>\r\n);\r\n"]}
1
+ {"version":3,"file":"attachment-icon.svg.js","sourceRoot":"","sources":["../../src/attachment-icon.svg.tsx"],"names":[],"mappings":";;;AAAA,kBAAe,CACb,gCAAK,MAAM,EAAC,KAAK,EAAC,OAAO,EAAC,WAAW,EAAC,IAAI,EAAC,MAAM,EAAC,KAAK,EAAC,4BAA4B,YAClF,iCACE,CAAC,EAAC,iHAAiH,EACnH,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,KAAK,GACjB,GACE,CACP,CAAC","sourcesContent":["export default (\n <svg height='1em' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path\n d='M 9 4 L 4.5 8.5 C 3.5 9.5 5 11 6 10 L 11.5 4.5 C 13.5 2.5 11 -0.5 8.5 2 L 2.5 8 C 0 10.5 4 14.5 6.5 12 L 12.5 6'\n stroke='currentColor'\n strokeWidth='0.8'\n />\n </svg>\n);\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/context.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\r\nimport { createContext, Reducer, useReducer } from 'react';\r\nimport { AttachmentEntry, MultipartAttachments } from './multipart-attachments.js';\r\n\r\nexport type AttachmentAddAction = {\r\n action: 'add';\r\n name?: string;\r\n};\r\n\r\nexport type AttachmentUpdateAction = {\r\n action: 'update';\r\n id: number;\r\n name?: string;\r\n value?: File;\r\n};\r\n\r\nexport type AttachmentDeleteAction = {\r\n action: 'delete';\r\n id: number;\r\n};\r\n\r\nexport type AttachmentsAction = AttachmentAddAction | AttachmentUpdateAction | AttachmentDeleteAction;\r\n\r\nexport type InternalAttachmentEntry = { id: number } & (\r\n | ({ error?: string } & AttachmentEntry)\r\n | { name: string; value: null; error: string }\r\n);\r\n\r\nexport const NO_FILE_ERROR = 'No File Attached';\r\nexport const DUPLICATE_NAME_ERROR = 'Duplicate Name';\r\n\r\nexport class InternalAttachmentState {\r\n private readonly attachmentId;\r\n public readonly attachments: InternalAttachmentEntry[];\r\n\r\n constructor(attachments: InternalAttachmentEntry[] = [], attachmentId = 0) {\r\n this.attachments = attachments;\r\n this.attachmentId = attachmentId;\r\n }\r\n\r\n public get validAttachments(): AttachmentEntry[] {\r\n return this.attachments.filter(\r\n (\r\n attachment: InternalAttachmentEntry\r\n ): attachment is AttachmentEntry & {\r\n id: number;\r\n } => attachment.value !== null && !attachment.error\r\n );\r\n }\r\n\r\n public map<T>(callback: (value: InternalAttachmentEntry, index: number, array: InternalAttachmentEntry[]) => T): T[] {\r\n return this.attachments.map(callback);\r\n }\r\n\r\n public reduce(action: AttachmentsAction): InternalAttachmentState {\r\n let nextAttachments;\r\n let nextAttachmentId = this.attachmentId;\r\n switch (action.action) {\r\n case 'add': {\r\n nextAttachments = [\r\n ...this.attachments,\r\n {\r\n id: this.attachmentId,\r\n name: action.name ?? `part${this.attachmentId}`,\r\n value: null,\r\n },\r\n ];\r\n nextAttachmentId++;\r\n break;\r\n }\r\n case 'update': {\r\n nextAttachments = this.attachments.map((attachment) => {\r\n if (attachment.id === action.id) {\r\n return {\r\n ...attachment,\r\n name: action.name ?? attachment.name,\r\n value: action.value !== undefined ? action.value : attachment.value,\r\n };\r\n } else {\r\n return attachment;\r\n }\r\n });\r\n break;\r\n }\r\n case 'delete': {\r\n nextAttachments = this.attachments.filter((attachment) => attachment.id !== action.id);\r\n break;\r\n }\r\n default: {\r\n throw new Error(`Unknown action ${action}`);\r\n }\r\n }\r\n\r\n for (const attachment of nextAttachments) {\r\n if (nextAttachments.filter(({ name }) => name === attachment.name).length > 1) {\r\n attachment.error = DUPLICATE_NAME_ERROR;\r\n } else if (attachment.value === null) {\r\n attachment.error = NO_FILE_ERROR;\r\n } else {\r\n delete attachment.error;\r\n }\r\n }\r\n\r\n return new InternalAttachmentState(nextAttachments as InternalAttachmentEntry[], nextAttachmentId);\r\n }\r\n}\r\n\r\nexport const MultipartAttachmentsContext = createContext<MultipartAttachments>(new MultipartAttachments([]));\r\nexport const MultipartAttachmentsInternalContext = createContext<\r\n [InternalAttachmentState, React.Dispatch<AttachmentsAction>]\r\n>([\r\n new InternalAttachmentState(),\r\n () => {\r\n throw new Error('MultipartAttachmentsProvider not found. Make sure to render it higher up in the tree');\r\n },\r\n]);\r\n\r\nexport function MultipartAttachmentsProvider({ children }: { children: React.ReactNode }) {\r\n const [attachments, setAttachmentStates] = useReducer<Reducer<InternalAttachmentState, AttachmentsAction>>(\r\n (attachments, action) => attachments.reduce(action),\r\n new InternalAttachmentState()\r\n );\r\n\r\n return (\r\n <MultipartAttachmentsContext.Provider value={new MultipartAttachments(attachments.validAttachments)}>\r\n <MultipartAttachmentsInternalContext.Provider value={[attachments, setAttachmentStates]}>\r\n {children}\r\n </MultipartAttachmentsInternalContext.Provider>\r\n </MultipartAttachmentsContext.Provider>\r\n );\r\n}\r\n"]}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/context.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\nimport { createContext, Reducer, useReducer } from 'react';\nimport { AttachmentEntry, MultipartAttachments } from './multipart-attachments.js';\n\nexport type AttachmentAddAction = {\n action: 'add';\n name?: string;\n};\n\nexport type AttachmentUpdateAction = {\n action: 'update';\n id: number;\n name?: string;\n value?: File;\n};\n\nexport type AttachmentDeleteAction = {\n action: 'delete';\n id: number;\n};\n\nexport type AttachmentsAction = AttachmentAddAction | AttachmentUpdateAction | AttachmentDeleteAction;\n\nexport type InternalAttachmentEntry = { id: number } & (\n | ({ error?: string } & AttachmentEntry)\n | { name: string; value: null; error: string }\n);\n\nexport const NO_FILE_ERROR = 'No File Attached';\nexport const DUPLICATE_NAME_ERROR = 'Duplicate Name';\n\nexport class InternalAttachmentState {\n private readonly attachmentId;\n public readonly attachments: InternalAttachmentEntry[];\n\n constructor(attachments: InternalAttachmentEntry[] = [], attachmentId = 0) {\n this.attachments = attachments;\n this.attachmentId = attachmentId;\n }\n\n public get validAttachments(): AttachmentEntry[] {\n return this.attachments.filter(\n (\n attachment: InternalAttachmentEntry\n ): attachment is AttachmentEntry & {\n id: number;\n } => attachment.value !== null && !attachment.error\n );\n }\n\n public map<T>(callback: (value: InternalAttachmentEntry, index: number, array: InternalAttachmentEntry[]) => T): T[] {\n return this.attachments.map(callback);\n }\n\n public reduce(action: AttachmentsAction): InternalAttachmentState {\n let nextAttachments;\n let nextAttachmentId = this.attachmentId;\n switch (action.action) {\n case 'add': {\n nextAttachments = [\n ...this.attachments,\n {\n id: this.attachmentId,\n name: action.name ?? `part${this.attachmentId}`,\n value: null,\n },\n ];\n nextAttachmentId++;\n break;\n }\n case 'update': {\n nextAttachments = this.attachments.map((attachment) => {\n if (attachment.id === action.id) {\n return {\n ...attachment,\n name: action.name ?? attachment.name,\n value: action.value !== undefined ? action.value : attachment.value,\n };\n } else {\n return attachment;\n }\n });\n break;\n }\n case 'delete': {\n nextAttachments = this.attachments.filter((attachment) => attachment.id !== action.id);\n break;\n }\n default: {\n throw new Error(`Unknown action ${action}`);\n }\n }\n\n for (const attachment of nextAttachments) {\n if (nextAttachments.filter(({ name }) => name === attachment.name).length > 1) {\n attachment.error = DUPLICATE_NAME_ERROR;\n } else if (attachment.value === null) {\n attachment.error = NO_FILE_ERROR;\n } else {\n delete attachment.error;\n }\n }\n\n return new InternalAttachmentState(nextAttachments as InternalAttachmentEntry[], nextAttachmentId);\n }\n}\n\nexport const MultipartAttachmentsContext = createContext<MultipartAttachments>(new MultipartAttachments([]));\nexport const MultipartAttachmentsInternalContext = createContext<\n [InternalAttachmentState, React.Dispatch<AttachmentsAction>]\n>([\n new InternalAttachmentState(),\n () => {\n throw new Error('MultipartAttachmentsProvider not found. Make sure to render it higher up in the tree');\n },\n]);\n\nexport function MultipartAttachmentsProvider({ children }: { children: React.ReactNode }) {\n const [attachments, setAttachmentStates] = useReducer<Reducer<InternalAttachmentState, AttachmentsAction>>(\n (attachments, action) => attachments.reduce(action),\n new InternalAttachmentState()\n );\n\n return (\n <MultipartAttachmentsContext.Provider value={new MultipartAttachments(attachments.validAttachments)}>\n <MultipartAttachmentsInternalContext.Provider value={[attachments, setAttachmentStates]}>\n {children}\n </MultipartAttachmentsInternalContext.Provider>\n </MultipartAttachmentsContext.Provider>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;AAEA,yCAA+E;AAAtE,mJAAA,OAAO,OAAyC;AACzD,2CAAyF;AAAhF,yHAAA,2BAA2B,OAAA;AAAE,0HAAA,4BAA4B,OAAA","sourcesContent":["export type { MultipartAttachments, MultipartFetcher } from './multipart-attachments.js';\r\n\r\nexport { default as MULTIPART_ATTACHMENTS_GRAPHIQL_PLUGIN } from './plugin.js';\r\nexport { MultipartAttachmentsContext, MultipartAttachmentsProvider } from './context.js';\r\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;AAEA,yCAA+E;AAAtE,mJAAA,OAAO,OAAyC;AACzD,2CAAyF;AAAhF,yHAAA,2BAA2B,OAAA;AAAE,0HAAA,4BAA4B,OAAA","sourcesContent":["export type { MultipartAttachments, MultipartFetcher } from './multipart-attachments.js';\n\nexport { default as MULTIPART_ATTACHMENTS_GRAPHIQL_PLUGIN } from './plugin.js';\nexport { MultipartAttachmentsContext, MultipartAttachmentsProvider } from './context.js';\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"multipart-attachments.js","sourceRoot":"","sources":["../../src/multipart-attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import { Fetcher, FetcherOpts, FetcherParams, FetcherReturnType } from '@graphiql/toolkit';\r\n\r\nconst OPERATIONS_KEY = 'operations';\r\nconst MAP_KEY = 'map';\r\n\r\nexport type AttachmentEntry = {\r\n name: string;\r\n value: File;\r\n};\r\n\r\nexport type MultipartFetcher = (\r\n graphQLParams: FetcherParams,\r\n multipartFormData: FormData | null,\r\n opts?: FetcherOpts\r\n) => FetcherReturnType;\r\n\r\nexport class MultipartAttachments {\r\n private _attachments: AttachmentEntry[];\r\n\r\n constructor(attachments: AttachmentEntry[]) {\r\n this._attachments = attachments;\r\n }\r\n\r\n /**\r\n * Returns an implementation of fetch that will transform the request to a multipart request if any files are found\r\n */\r\n public decoratedFetch(fetchOverride: typeof fetch = fetch): typeof fetch {\r\n if (this._attachments.length > 0) {\r\n return async (input: RequestInfo | URL, init?: RequestInit) => {\r\n const originalRequest = new Request(input, init);\r\n // we're going to overwrite the body but firefox complains if we don't clone it\r\n const originalBody = await originalRequest.clone().text();\r\n\r\n if (JSON.parse(originalBody)?.operationName !== 'IntrospectionQuery') {\r\n const body = this.asFormData(originalBody);\r\n const headers = new Headers(originalRequest.headers);\r\n\r\n // make sure form data sets the correct content type\r\n headers.delete('content-type');\r\n\r\n return await fetchOverride(originalRequest, { body, headers });\r\n } else {\r\n // just pass through for an introspection query\r\n return fetchOverride(originalRequest);\r\n }\r\n };\r\n } else {\r\n return fetchOverride;\r\n }\r\n }\r\n\r\n /**\r\n * Constructs a GraphiQL Fetcher that provides FormData to the passed in MultipartFetcher\r\n */\r\n public decoratedFetcher(fetcher: MultipartFetcher): Fetcher {\r\n return (graphQLParams: FetcherParams, opts?: FetcherOpts) => {\r\n return fetcher(graphQLParams, this.asFormData(graphQLParams), opts);\r\n };\r\n }\r\n\r\n get attachments(): Map<string, File> {\r\n return new Map(this._attachments.map(({ name, value }) => [name, value]));\r\n }\r\n\r\n private asFormData(operations: FetcherParams | string): FormData | null {\r\n const attachments = this.attachments;\r\n\r\n // verify our attachments\r\n for (const invalidKey of [OPERATIONS_KEY, MAP_KEY]) {\r\n if (attachments.has(invalidKey)) {\r\n console.error(`Cannot have an attachment named \"${invalidKey}\"`);\r\n\r\n return null;\r\n }\r\n }\r\n\r\n if (attachments.size > 0) {\r\n const formData = new FormData();\r\n\r\n const encodedOperations = typeof operations === 'string' ? operations : JSON.stringify(operations);\r\n formData.set(OPERATIONS_KEY, encodedOperations);\r\n\r\n attachments.forEach((value, key) => {\r\n formData.append(key, value);\r\n });\r\n\r\n return formData;\r\n } else {\r\n return null;\r\n }\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"multipart-attachments.js","sourceRoot":"","sources":["../../src/multipart-attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import { Fetcher, FetcherOpts, FetcherParams, FetcherReturnType } from '@graphiql/toolkit';\n\nconst OPERATIONS_KEY = 'operations';\nconst MAP_KEY = 'map';\n\nexport type AttachmentEntry = {\n name: string;\n value: File;\n};\n\nexport type MultipartFetcher = (\n graphQLParams: FetcherParams,\n multipartFormData: FormData | null,\n opts?: FetcherOpts\n) => FetcherReturnType;\n\nexport class MultipartAttachments {\n private _attachments: AttachmentEntry[];\n\n constructor(attachments: AttachmentEntry[]) {\n this._attachments = attachments;\n }\n\n /**\n * Returns an implementation of fetch that will transform the request to a multipart request if any files are found\n */\n public decoratedFetch(fetchOverride: typeof fetch = fetch): typeof fetch {\n if (this._attachments.length > 0) {\n return async (input: RequestInfo | URL, init?: RequestInit) => {\n const originalRequest = new Request(input, init);\n // we're going to overwrite the body but firefox complains if we don't clone it\n const originalBody = await originalRequest.clone().text();\n\n if (JSON.parse(originalBody)?.operationName !== 'IntrospectionQuery') {\n const body = this.asFormData(originalBody);\n const headers = new Headers(originalRequest.headers);\n\n // make sure form data sets the correct content type\n headers.delete('content-type');\n\n return await fetchOverride(originalRequest, { body, headers });\n } else {\n // just pass through for an introspection query\n return fetchOverride(originalRequest);\n }\n };\n } else {\n return fetchOverride;\n }\n }\n\n /**\n * Constructs a GraphiQL Fetcher that provides FormData to the passed in MultipartFetcher\n */\n public decoratedFetcher(fetcher: MultipartFetcher): Fetcher {\n return (graphQLParams: FetcherParams, opts?: FetcherOpts) => {\n return fetcher(graphQLParams, this.asFormData(graphQLParams), opts);\n };\n }\n\n get attachments(): Map<string, File> {\n return new Map(this._attachments.map(({ name, value }) => [name, value]));\n }\n\n private asFormData(operations: FetcherParams | string): FormData | null {\n const attachments = this.attachments;\n\n // verify our attachments\n for (const invalidKey of [OPERATIONS_KEY, MAP_KEY]) {\n if (attachments.has(invalidKey)) {\n console.error(`Cannot have an attachment named \"${invalidKey}\"`);\n\n return null;\n }\n }\n\n if (attachments.size > 0) {\n const formData = new FormData();\n\n const encodedOperations = typeof operations === 'string' ? operations : JSON.stringify(operations);\n formData.set(OPERATIONS_KEY, encodedOperations);\n\n attachments.forEach((value, key) => {\n formData.append(key, value);\n });\n\n return formData;\n } else {\n return null;\n }\n }\n}\n"]}
@@ -37,7 +37,7 @@ const GRAPHIQL_PLUGIN = {
37
37
  };
38
38
  function AttachmentContent({ name, value, updateAttachment, error }) {
39
39
  const fileInputRef = (0, react_2.useRef)(null);
40
- return ((0, jsx_runtime_1.jsxs)("div", { className: 'attachment-content', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'attachment-title', children: [(0, jsx_runtime_1.jsx)(PartNameComponent, { name: name, setName: (name) => updateAttachment({ action: 'update', name }) }), (0, jsx_runtime_1.jsx)("button", { onClick: () => updateAttachment({ action: 'delete' }), className: 'attachment-delete', children: (0, jsx_runtime_1.jsx)(react_1.TrashIcon, {}) })] }), error ? (0, jsx_runtime_1.jsx)("div", { className: 'graphiql-doc-explorer-error', children: error }) : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {}), value ? (0, jsx_runtime_1.jsx)(PartHeadersComponent, { name: name, value: value }) : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {}), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("button", { style: { marginTop: '0.5em' }, onClick: () => fileInputRef.current?.click(), children: "Attach File" }), (0, jsx_runtime_1.jsx)("input", { type: 'file', ref: fileInputRef, onChange: (event) => {
40
+ return ((0, jsx_runtime_1.jsxs)("div", { className: 'attachment-content', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'attachment-title', children: [(0, jsx_runtime_1.jsx)(PartNameComponent, { name: name, setName: (name) => updateAttachment({ action: 'update', name }) }), (0, jsx_runtime_1.jsx)("button", { "aria-label": `Delete attachment ${name}`, onClick: () => updateAttachment({ action: 'delete' }), className: 'attachment-delete', children: (0, jsx_runtime_1.jsx)(react_1.TrashIcon, {}) })] }), error ? (0, jsx_runtime_1.jsx)("div", { className: 'graphiql-doc-explorer-error', children: error }) : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {}), value ? (0, jsx_runtime_1.jsx)(PartHeadersComponent, { name: name, value: value }) : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {}), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("button", { style: { marginTop: '0.5em' }, onClick: () => fileInputRef.current?.click(), children: "Attach File" }), (0, jsx_runtime_1.jsx)("input", { type: 'file', ref: fileInputRef, onChange: (event) => {
41
41
  const file = event.target.files?.[0];
42
42
  if (file) {
43
43
  updateAttachment({ action: 'update', value: file });
@@ -58,21 +58,14 @@ function PartNameComponent({ name, setName }) {
58
58
  setName(inputRef.current.value);
59
59
  }
60
60
  }
61
- function onSubmit(event) {
62
- event.preventDefault();
63
- event.stopPropagation();
64
- endEditing();
65
- }
66
61
  if (isEditing) {
67
- return ((0, jsx_runtime_1.jsx)("form", { onSubmit: onSubmit, children: (0, jsx_runtime_1.jsx)("input", { ref: inputRef, type: 'text', defaultValue: name, onBlur: endEditing, onKeyDown: (e) => {
68
- if (e.key === 'Escape') {
69
- onSubmit(e);
70
- }
71
- } }) }));
72
- }
73
- else {
74
- return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsxs)("span", { onClick: () => setIsEditing(true), style: { cursor: 'pointer' }, children: [name, "\u00A0", (0, jsx_runtime_1.jsx)(react_1.PenIcon, {})] }) }));
62
+ return ((0, jsx_runtime_1.jsxs)("label", { className: 'attachment-part-name-edit-label', children: ["Part name:", (0, jsx_runtime_1.jsx)("input", { className: 'attachment-part-name-edit-input', ref: inputRef, type: 'text', "aria-label": 'Part name', defaultValue: name, onBlur: endEditing, onKeyDown: (e) => {
63
+ if (e.key === 'Escape' || e.key === 'Enter') {
64
+ endEditing();
65
+ }
66
+ } })] }));
75
67
  }
68
+ return ((0, jsx_runtime_1.jsxs)("span", { className: 'attachment-part-name-view', children: [(0, jsx_runtime_1.jsxs)("span", { children: ["Part name: ", name] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setIsEditing(true), "aria-label": `Edit part name: ${name}`, title: "Edit part name", children: (0, jsx_runtime_1.jsx)(react_1.PenIcon, {}) })] }));
76
69
  }
77
70
  function PartHeadersComponent({ name, value }) {
78
71
  const baseContentDisposition = `form-data; name="${encodeURIComponent(name)}"`;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin.tsx"],"names":[],"mappings":"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","sourcesContent":["import { GraphiQLPlugin, PenIcon, TrashIcon, useGraphiQL } from '@graphiql/react';\r\nimport * as React from 'react';\r\nimport { CSSProperties, useContext, useEffect, useRef, useState } from 'react';\r\nimport uploadClipSvg from './attachment-icon.svg.js';\r\nimport { AttachmentDeleteAction, AttachmentUpdateAction, MultipartAttachmentsInternalContext } from './context.js';\r\n\r\nimport '../../style.css';\r\n\r\ntype AttachmentContentProps = {\r\n name: string;\r\n value: File | null;\r\n updateAttachment: (action: Omit<AttachmentUpdateAction, 'id'> | Omit<AttachmentDeleteAction, 'id'>) => void;\r\n error?: string;\r\n};\r\n\r\nconst GRAPHIQL_PLUGIN: GraphiQLPlugin = {\r\n title: 'Multipart Attachments',\r\n icon: () => {\r\n const [attachments] = useContext(MultipartAttachmentsInternalContext);\r\n const visiblePlugin = useGraphiQL(state => state.visiblePlugin);\r\n\r\n const style: CSSProperties = {\r\n fontFamily: 'var(--font-family-mono)',\r\n fontSize: 'var(--font-size-inline-code)',\r\n textAlign: 'center',\r\n width: 'auto',\r\n };\r\n if (visiblePlugin === GRAPHIQL_PLUGIN) {\r\n style.fontWeight = 'bold';\r\n }\r\n\r\n return (\r\n <div className='attachments-icon'>\r\n {uploadClipSvg}\r\n {attachments.validAttachments.length > 0 ? (\r\n <span className='attachments-icon-badge'>{attachments.validAttachments.length}</span>\r\n ) : (\r\n <></>\r\n )}\r\n </div>\r\n );\r\n },\r\n content: () => {\r\n const [attachments, attachmentsDispatch] = useContext(MultipartAttachmentsInternalContext);\r\n\r\n return (\r\n <div className={'attachments-container'}>\r\n <h2>Multipart Attachments</h2>\r\n {attachments.map(({ id, name, value, error }) => {\r\n return (\r\n <AttachmentContent\r\n key={id}\r\n name={name}\r\n value={value}\r\n updateAttachment={(action) => attachmentsDispatch({ ...action, id })}\r\n error={error}\r\n />\r\n );\r\n })}\r\n <div style={{ textAlign: 'center' }}>\r\n <button onClick={() => attachmentsDispatch({ action: 'add' })}>Add Part</button>\r\n </div>\r\n </div>\r\n );\r\n },\r\n};\r\n\r\nexport function AttachmentContent({ name, value, updateAttachment, error }: AttachmentContentProps): React.JSX.Element {\r\n const fileInputRef = useRef<HTMLInputElement | null>(null);\r\n\r\n return (\r\n <div className={'attachment-content'}>\r\n <div className={'attachment-title'}>\r\n <PartNameComponent name={name} setName={(name) => updateAttachment({ action: 'update', name })} />\r\n <button onClick={() => updateAttachment({ action: 'delete' })} className={'attachment-delete'}>\r\n <TrashIcon />\r\n </button>\r\n </div>\r\n {error ? <div className='graphiql-doc-explorer-error'>{error}</div> : <></>}\r\n {value ? <PartHeadersComponent name={name} value={value} /> : <></>}\r\n <div>\r\n <button style={{ marginTop: '0.5em' }} onClick={() => fileInputRef.current?.click()}>\r\n Attach File\r\n </button>\r\n <input\r\n type='file'\r\n ref={fileInputRef}\r\n onChange={(event) => {\r\n const file = event.target.files?.[0];\r\n if (file) {\r\n updateAttachment({ action: 'update', value: file });\r\n }\r\n }}\r\n style={{ display: 'none' }}\r\n />\r\n </div>\r\n </div>\r\n );\r\n}\r\n\r\nexport function PartNameComponent({ name, setName }: { name: string; setName: (name: string) => void }) {\r\n const [isEditing, setIsEditing] = useState(false);\r\n const inputRef = useRef<HTMLInputElement | null>(null);\r\n\r\n useEffect(() => {\r\n if (isEditing) {\r\n inputRef.current?.focus();\r\n }\r\n }, [isEditing]);\r\n\r\n function endEditing() {\r\n setIsEditing(false);\r\n if (inputRef.current) {\r\n setName(inputRef.current.value);\r\n }\r\n }\r\n function onSubmit(event: React.BaseSyntheticEvent) {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n endEditing();\r\n }\r\n\r\n if (isEditing) {\r\n return (\r\n <form onSubmit={onSubmit}>\r\n <input\r\n ref={inputRef}\r\n type='text'\r\n defaultValue={name}\r\n onBlur={endEditing}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Escape') {\r\n onSubmit(e);\r\n }\r\n }}\r\n />\r\n </form>\r\n );\r\n } else {\r\n return (\r\n <>\r\n <span onClick={() => setIsEditing(true)} style={{ cursor: 'pointer' }}>\r\n {name}\r\n &nbsp;\r\n <PenIcon />\r\n </span>\r\n </>\r\n );\r\n }\r\n}\r\n\r\nexport function PartHeadersComponent({ name, value }: { name: string; value: File }) {\r\n const baseContentDisposition = `form-data; name=\"${encodeURIComponent(name)}\"`;\r\n let headers;\r\n if (typeof value !== 'string') {\r\n headers = [\r\n ['Content-Disposition', `${baseContentDisposition}; filename=\"${encodeURIComponent(value.name)}\"`],\r\n ['Content-Type', value.type.length > 0 ? value.type : 'application/octet-stream'],\r\n ];\r\n } else {\r\n headers = [['Content-Disposition', baseContentDisposition]];\r\n }\r\n\r\n return (\r\n <div className={'attachment-header'}>\r\n {headers.map(([name, value]) => (\r\n <div key={name}>\r\n <span style={{ color: 'hsl(var(--color-info))' }}>{name}: </span>\r\n <span>{value}</span>\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\nexport default GRAPHIQL_PLUGIN;\r\n"]}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin.tsx"],"names":[],"mappings":"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","sourcesContent":["import { GraphiQLPlugin, PenIcon, TrashIcon, useGraphiQL } from '@graphiql/react';\nimport * as React from 'react';\nimport { CSSProperties, useContext, useEffect, useRef, useState } from 'react';\nimport uploadClipSvg from './attachment-icon.svg.js';\nimport { AttachmentDeleteAction, AttachmentUpdateAction, MultipartAttachmentsInternalContext } from './context.js';\n\nimport '../../style.css';\n\ntype AttachmentContentProps = {\n name: string;\n value: File | null;\n updateAttachment: (action: Omit<AttachmentUpdateAction, 'id'> | Omit<AttachmentDeleteAction, 'id'>) => void;\n error?: string;\n};\n\nconst GRAPHIQL_PLUGIN: GraphiQLPlugin = {\n title: 'Multipart Attachments',\n icon: () => {\n const [attachments] = useContext(MultipartAttachmentsInternalContext);\n const visiblePlugin = useGraphiQL(state => state.visiblePlugin);\n\n const style: CSSProperties = {\n fontFamily: 'var(--font-family-mono)',\n fontSize: 'var(--font-size-inline-code)',\n textAlign: 'center',\n width: 'auto',\n };\n if (visiblePlugin === GRAPHIQL_PLUGIN) {\n style.fontWeight = 'bold';\n }\n\n return (\n <div className='attachments-icon'>\n {uploadClipSvg}\n {attachments.validAttachments.length > 0 ? (\n <span className='attachments-icon-badge'>{attachments.validAttachments.length}</span>\n ) : (\n <></>\n )}\n </div>\n );\n },\n content: () => {\n const [attachments, attachmentsDispatch] = useContext(MultipartAttachmentsInternalContext);\n\n return (\n <div className={'attachments-container'}>\n <h2>Multipart Attachments</h2>\n {attachments.map(({ id, name, value, error }) => {\n return (\n <AttachmentContent\n key={id}\n name={name}\n value={value}\n updateAttachment={(action) => attachmentsDispatch({ ...action, id })}\n error={error}\n />\n );\n })}\n <div style={{ textAlign: 'center' }}>\n <button onClick={() => attachmentsDispatch({ action: 'add' })}>Add Part</button>\n </div>\n </div>\n );\n },\n};\n\nexport function AttachmentContent({ name, value, updateAttachment, error }: AttachmentContentProps): React.JSX.Element {\n const fileInputRef = useRef<HTMLInputElement | null>(null);\n\n return (\n <div className={'attachment-content'}>\n <div className={'attachment-title'}>\n <PartNameComponent name={name} setName={(name) => updateAttachment({ action: 'update', name })} />\n <button aria-label={`Delete attachment ${name}`} onClick={() => updateAttachment({ action: 'delete' })} className={'attachment-delete'}>\n <TrashIcon />\n </button>\n </div>\n {error ? <div className='graphiql-doc-explorer-error'>{error}</div> : <></>}\n {value ? <PartHeadersComponent name={name} value={value} /> : <></>}\n <div>\n <button style={{ marginTop: '0.5em' }} onClick={() => fileInputRef.current?.click()}>\n Attach File\n </button>\n <input\n type='file'\n ref={fileInputRef}\n onChange={(event) => {\n const file = event.target.files?.[0];\n if (file) {\n updateAttachment({ action: 'update', value: file });\n }\n }}\n style={{ display: 'none' }}\n />\n </div>\n </div>\n );\n}\n\nexport function PartNameComponent({ name, setName }: { name: string; setName: (name: string) => void }) {\n const [isEditing, setIsEditing] = useState(false);\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n useEffect(() => {\n if (isEditing) {\n inputRef.current?.focus();\n }\n }, [isEditing]);\n\n function endEditing() {\n setIsEditing(false);\n if (inputRef.current) {\n setName(inputRef.current.value);\n }\n }\n \n if (isEditing) {\n return (\n <label className='attachment-part-name-edit-label'>\n Part name:\n <input\n className='attachment-part-name-edit-input'\n ref={inputRef}\n type='text'\n aria-label='Part name'\n defaultValue={name}\n onBlur={endEditing}\n onKeyDown={(e) => {\n if (e.key === 'Escape' || e.key === 'Enter') {\n endEditing();\n }\n }}\n />\n </label>\n );\n }\n\n return (\n <span className='attachment-part-name-view'>\n <span>Part name: {name}</span>\n <button \n onClick={() => setIsEditing(true)} \n aria-label={`Edit part name: ${name}`}\n title=\"Edit part name\"\n >\n <PenIcon />\n </button>\n </span>\n );\n}\n\nexport function PartHeadersComponent({ name, value }: { name: string; value: File }) {\n const baseContentDisposition = `form-data; name=\"${encodeURIComponent(name)}\"`;\n let headers;\n if (typeof value !== 'string') {\n headers = [\n ['Content-Disposition', `${baseContentDisposition}; filename=\"${encodeURIComponent(value.name)}\"`],\n ['Content-Type', value.type.length > 0 ? value.type : 'application/octet-stream'],\n ];\n } else {\n headers = [['Content-Disposition', baseContentDisposition]];\n }\n\n return (\n <div className={'attachment-header'}>\n {headers.map(([name, value]) => (\n <div key={name}>\n <span style={{ color: 'hsl(var(--color-info))' }}>{name}: </span>\n <span>{value}</span>\n </div>\n ))}\n </div>\n );\n}\n\nexport default GRAPHIQL_PLUGIN;\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"attachment-icon.svg.js","sourceRoot":"","sources":["../../src/attachment-icon.svg.tsx"],"names":[],"mappings":";AAAA,eAAe,CACb,cAAK,MAAM,EAAC,KAAK,EAAC,OAAO,EAAC,WAAW,EAAC,IAAI,EAAC,MAAM,EAAC,KAAK,EAAC,4BAA4B,YAClF,eACE,CAAC,EAAC,iHAAiH,EACnH,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,KAAK,GACjB,GACE,CACP,CAAC","sourcesContent":["export default (\r\n <svg height='1em' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'>\r\n <path\r\n d='M 9 4 L 4.5 8.5 C 3.5 9.5 5 11 6 10 L 11.5 4.5 C 13.5 2.5 11 -0.5 8.5 2 L 2.5 8 C 0 10.5 4 14.5 6.5 12 L 12.5 6'\r\n stroke='currentColor'\r\n strokeWidth='0.8'\r\n />\r\n </svg>\r\n);\r\n"]}
1
+ {"version":3,"file":"attachment-icon.svg.js","sourceRoot":"","sources":["../../src/attachment-icon.svg.tsx"],"names":[],"mappings":";AAAA,eAAe,CACb,cAAK,MAAM,EAAC,KAAK,EAAC,OAAO,EAAC,WAAW,EAAC,IAAI,EAAC,MAAM,EAAC,KAAK,EAAC,4BAA4B,YAClF,eACE,CAAC,EAAC,iHAAiH,EACnH,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,KAAK,GACjB,GACE,CACP,CAAC","sourcesContent":["export default (\n <svg height='1em' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path\n d='M 9 4 L 4.5 8.5 C 3.5 9.5 5 11 6 10 L 11.5 4.5 C 13.5 2.5 11 -0.5 8.5 2 L 2.5 8 C 0 10.5 4 14.5 6.5 12 L 12.5 6'\n stroke='currentColor'\n strokeWidth='0.8'\n />\n </svg>\n);\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/context.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAW,UAAU,EAAE,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAmB,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AA0BnF,MAAM,CAAC,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAChD,MAAM,CAAC,MAAM,oBAAoB,GAAG,gBAAgB,CAAC;AAErD,MAAM,OAAO,uBAAuB;IACjB,YAAY,CAAC;IACd,WAAW,CAA4B;IAEvD,YAAY,cAAyC,EAAE,EAAE,YAAY,GAAG,CAAC;QACvE,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACnC,CAAC;IAED,IAAW,gBAAgB;QACzB,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAC5B,CACE,UAAmC,EAGnC,EAAE,CAAC,UAAU,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CACpD,CAAC;IACJ,CAAC;IAEM,GAAG,CAAI,QAAgG;QAC5G,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAEM,MAAM,CAAC,MAAyB;QACrC,IAAI,eAAe,CAAC;QACpB,IAAI,gBAAgB,GAAG,IAAI,CAAC,YAAY,CAAC;QACzC,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;YACtB,KAAK,KAAK,CAAC,CAAC,CAAC;gBACX,eAAe,GAAG;oBAChB,GAAG,IAAI,CAAC,WAAW;oBACnB;wBACE,EAAE,EAAE,IAAI,CAAC,YAAY;wBACrB,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,YAAY,EAAE;wBAC/C,KAAK,EAAE,IAAI;qBACZ;iBACF,CAAC;gBACF,gBAAgB,EAAE,CAAC;gBACnB,MAAM;YACR,CAAC;YACD,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,eAAe,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE;oBACpD,IAAI,UAAU,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC;wBAChC,OAAO;4BACL,GAAG,UAAU;4BACb,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,UAAU,CAAC,IAAI;4BACpC,KAAK,EAAE,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK;yBACpE,CAAC;oBACJ,CAAC;yBAAM,CAAC;wBACN,OAAO,UAAU,CAAC;oBACpB,CAAC;gBACH,CAAC,CAAC,CAAC;gBACH,MAAM;YACR,CAAC;YACD,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,eAAe,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC;gBACvF,MAAM;YACR,CAAC;YACD,OAAO,CAAC,CAAC,CAAC;gBACR,MAAM,IAAI,KAAK,CAAC,kBAAkB,MAAM,EAAE,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC;QAED,KAAK,MAAM,UAAU,IAAI,eAAe,EAAE,CAAC;YACzC,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,KAAK,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC9E,UAAU,CAAC,KAAK,GAAG,oBAAoB,CAAC;YAC1C,CAAC;iBAAM,IAAI,UAAU,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;gBACrC,UAAU,CAAC,KAAK,GAAG,aAAa,CAAC;YACnC,CAAC;iBAAM,CAAC;gBACN,OAAO,UAAU,CAAC,KAAK,CAAC;YAC1B,CAAC;QACH,CAAC;QAED,OAAO,IAAI,uBAAuB,CAAC,eAA4C,EAAE,gBAAgB,CAAC,CAAC;IACrG,CAAC;CACF;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,aAAa,CAAuB,IAAI,oBAAoB,CAAC,EAAE,CAAC,CAAC,CAAC;AAC7G,MAAM,CAAC,MAAM,mCAAmC,GAAG,aAAa,CAE9D;IACA,IAAI,uBAAuB,EAAE;IAC7B,GAAG,EAAE;QACH,MAAM,IAAI,KAAK,CAAC,sFAAsF,CAAC,CAAC;IAC1G,CAAC;CACF,CAAC,CAAC;AAEH,MAAM,UAAU,4BAA4B,CAAC,EAAE,QAAQ,EAAiC;IACtF,MAAM,CAAC,WAAW,EAAE,mBAAmB,CAAC,GAAG,UAAU,CACnD,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,EACnD,IAAI,uBAAuB,EAAE,CAC9B,CAAC;IAEF,OAAO,CACL,KAAC,2BAA2B,CAAC,QAAQ,IAAC,KAAK,EAAE,IAAI,oBAAoB,CAAC,WAAW,CAAC,gBAAgB,CAAC,YACjG,KAAC,mCAAmC,CAAC,QAAQ,IAAC,KAAK,EAAE,CAAC,WAAW,EAAE,mBAAmB,CAAC,YACpF,QAAQ,GACoC,GACV,CACxC,CAAC;AACJ,CAAC","sourcesContent":["import * as React from 'react';\r\nimport { createContext, Reducer, useReducer } from 'react';\r\nimport { AttachmentEntry, MultipartAttachments } from './multipart-attachments.js';\r\n\r\nexport type AttachmentAddAction = {\r\n action: 'add';\r\n name?: string;\r\n};\r\n\r\nexport type AttachmentUpdateAction = {\r\n action: 'update';\r\n id: number;\r\n name?: string;\r\n value?: File;\r\n};\r\n\r\nexport type AttachmentDeleteAction = {\r\n action: 'delete';\r\n id: number;\r\n};\r\n\r\nexport type AttachmentsAction = AttachmentAddAction | AttachmentUpdateAction | AttachmentDeleteAction;\r\n\r\nexport type InternalAttachmentEntry = { id: number } & (\r\n | ({ error?: string } & AttachmentEntry)\r\n | { name: string; value: null; error: string }\r\n);\r\n\r\nexport const NO_FILE_ERROR = 'No File Attached';\r\nexport const DUPLICATE_NAME_ERROR = 'Duplicate Name';\r\n\r\nexport class InternalAttachmentState {\r\n private readonly attachmentId;\r\n public readonly attachments: InternalAttachmentEntry[];\r\n\r\n constructor(attachments: InternalAttachmentEntry[] = [], attachmentId = 0) {\r\n this.attachments = attachments;\r\n this.attachmentId = attachmentId;\r\n }\r\n\r\n public get validAttachments(): AttachmentEntry[] {\r\n return this.attachments.filter(\r\n (\r\n attachment: InternalAttachmentEntry\r\n ): attachment is AttachmentEntry & {\r\n id: number;\r\n } => attachment.value !== null && !attachment.error\r\n );\r\n }\r\n\r\n public map<T>(callback: (value: InternalAttachmentEntry, index: number, array: InternalAttachmentEntry[]) => T): T[] {\r\n return this.attachments.map(callback);\r\n }\r\n\r\n public reduce(action: AttachmentsAction): InternalAttachmentState {\r\n let nextAttachments;\r\n let nextAttachmentId = this.attachmentId;\r\n switch (action.action) {\r\n case 'add': {\r\n nextAttachments = [\r\n ...this.attachments,\r\n {\r\n id: this.attachmentId,\r\n name: action.name ?? `part${this.attachmentId}`,\r\n value: null,\r\n },\r\n ];\r\n nextAttachmentId++;\r\n break;\r\n }\r\n case 'update': {\r\n nextAttachments = this.attachments.map((attachment) => {\r\n if (attachment.id === action.id) {\r\n return {\r\n ...attachment,\r\n name: action.name ?? attachment.name,\r\n value: action.value !== undefined ? action.value : attachment.value,\r\n };\r\n } else {\r\n return attachment;\r\n }\r\n });\r\n break;\r\n }\r\n case 'delete': {\r\n nextAttachments = this.attachments.filter((attachment) => attachment.id !== action.id);\r\n break;\r\n }\r\n default: {\r\n throw new Error(`Unknown action ${action}`);\r\n }\r\n }\r\n\r\n for (const attachment of nextAttachments) {\r\n if (nextAttachments.filter(({ name }) => name === attachment.name).length > 1) {\r\n attachment.error = DUPLICATE_NAME_ERROR;\r\n } else if (attachment.value === null) {\r\n attachment.error = NO_FILE_ERROR;\r\n } else {\r\n delete attachment.error;\r\n }\r\n }\r\n\r\n return new InternalAttachmentState(nextAttachments as InternalAttachmentEntry[], nextAttachmentId);\r\n }\r\n}\r\n\r\nexport const MultipartAttachmentsContext = createContext<MultipartAttachments>(new MultipartAttachments([]));\r\nexport const MultipartAttachmentsInternalContext = createContext<\r\n [InternalAttachmentState, React.Dispatch<AttachmentsAction>]\r\n>([\r\n new InternalAttachmentState(),\r\n () => {\r\n throw new Error('MultipartAttachmentsProvider not found. Make sure to render it higher up in the tree');\r\n },\r\n]);\r\n\r\nexport function MultipartAttachmentsProvider({ children }: { children: React.ReactNode }) {\r\n const [attachments, setAttachmentStates] = useReducer<Reducer<InternalAttachmentState, AttachmentsAction>>(\r\n (attachments, action) => attachments.reduce(action),\r\n new InternalAttachmentState()\r\n );\r\n\r\n return (\r\n <MultipartAttachmentsContext.Provider value={new MultipartAttachments(attachments.validAttachments)}>\r\n <MultipartAttachmentsInternalContext.Provider value={[attachments, setAttachmentStates]}>\r\n {children}\r\n </MultipartAttachmentsInternalContext.Provider>\r\n </MultipartAttachmentsContext.Provider>\r\n );\r\n}\r\n"]}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/context.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\nimport { createContext, Reducer, useReducer } from 'react';\nimport { AttachmentEntry, MultipartAttachments } from './multipart-attachments.js';\n\nexport type AttachmentAddAction = {\n action: 'add';\n name?: string;\n};\n\nexport type AttachmentUpdateAction = {\n action: 'update';\n id: number;\n name?: string;\n value?: File;\n};\n\nexport type AttachmentDeleteAction = {\n action: 'delete';\n id: number;\n};\n\nexport type AttachmentsAction = AttachmentAddAction | AttachmentUpdateAction | AttachmentDeleteAction;\n\nexport type InternalAttachmentEntry = { id: number } & (\n | ({ error?: string } & AttachmentEntry)\n | { name: string; value: null; error: string }\n);\n\nexport const NO_FILE_ERROR = 'No File Attached';\nexport const DUPLICATE_NAME_ERROR = 'Duplicate Name';\n\nexport class InternalAttachmentState {\n private readonly attachmentId;\n public readonly attachments: InternalAttachmentEntry[];\n\n constructor(attachments: InternalAttachmentEntry[] = [], attachmentId = 0) {\n this.attachments = attachments;\n this.attachmentId = attachmentId;\n }\n\n public get validAttachments(): AttachmentEntry[] {\n return this.attachments.filter(\n (\n attachment: InternalAttachmentEntry\n ): attachment is AttachmentEntry & {\n id: number;\n } => attachment.value !== null && !attachment.error\n );\n }\n\n public map<T>(callback: (value: InternalAttachmentEntry, index: number, array: InternalAttachmentEntry[]) => T): T[] {\n return this.attachments.map(callback);\n }\n\n public reduce(action: AttachmentsAction): InternalAttachmentState {\n let nextAttachments;\n let nextAttachmentId = this.attachmentId;\n switch (action.action) {\n case 'add': {\n nextAttachments = [\n ...this.attachments,\n {\n id: this.attachmentId,\n name: action.name ?? `part${this.attachmentId}`,\n value: null,\n },\n ];\n nextAttachmentId++;\n break;\n }\n case 'update': {\n nextAttachments = this.attachments.map((attachment) => {\n if (attachment.id === action.id) {\n return {\n ...attachment,\n name: action.name ?? attachment.name,\n value: action.value !== undefined ? action.value : attachment.value,\n };\n } else {\n return attachment;\n }\n });\n break;\n }\n case 'delete': {\n nextAttachments = this.attachments.filter((attachment) => attachment.id !== action.id);\n break;\n }\n default: {\n throw new Error(`Unknown action ${action}`);\n }\n }\n\n for (const attachment of nextAttachments) {\n if (nextAttachments.filter(({ name }) => name === attachment.name).length > 1) {\n attachment.error = DUPLICATE_NAME_ERROR;\n } else if (attachment.value === null) {\n attachment.error = NO_FILE_ERROR;\n } else {\n delete attachment.error;\n }\n }\n\n return new InternalAttachmentState(nextAttachments as InternalAttachmentEntry[], nextAttachmentId);\n }\n}\n\nexport const MultipartAttachmentsContext = createContext<MultipartAttachments>(new MultipartAttachments([]));\nexport const MultipartAttachmentsInternalContext = createContext<\n [InternalAttachmentState, React.Dispatch<AttachmentsAction>]\n>([\n new InternalAttachmentState(),\n () => {\n throw new Error('MultipartAttachmentsProvider not found. Make sure to render it higher up in the tree');\n },\n]);\n\nexport function MultipartAttachmentsProvider({ children }: { children: React.ReactNode }) {\n const [attachments, setAttachmentStates] = useReducer<Reducer<InternalAttachmentState, AttachmentsAction>>(\n (attachments, action) => attachments.reduce(action),\n new InternalAttachmentState()\n );\n\n return (\n <MultipartAttachmentsContext.Provider value={new MultipartAttachments(attachments.validAttachments)}>\n <MultipartAttachmentsInternalContext.Provider value={[attachments, setAttachmentStates]}>\n {children}\n </MultipartAttachmentsInternalContext.Provider>\n </MultipartAttachmentsContext.Provider>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,IAAI,qCAAqC,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAC","sourcesContent":["export type { MultipartAttachments, MultipartFetcher } from './multipart-attachments.js';\r\n\r\nexport { default as MULTIPART_ATTACHMENTS_GRAPHIQL_PLUGIN } from './plugin.js';\r\nexport { MultipartAttachmentsContext, MultipartAttachmentsProvider } from './context.js';\r\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,IAAI,qCAAqC,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAC","sourcesContent":["export type { MultipartAttachments, MultipartFetcher } from './multipart-attachments.js';\n\nexport { default as MULTIPART_ATTACHMENTS_GRAPHIQL_PLUGIN } from './plugin.js';\nexport { MultipartAttachmentsContext, MultipartAttachmentsProvider } from './context.js';\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"multipart-attachments.js","sourceRoot":"","sources":["../../src/multipart-attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import { Fetcher, FetcherOpts, FetcherParams, FetcherReturnType } from '@graphiql/toolkit';\r\n\r\nconst OPERATIONS_KEY = 'operations';\r\nconst MAP_KEY = 'map';\r\n\r\nexport type AttachmentEntry = {\r\n name: string;\r\n value: File;\r\n};\r\n\r\nexport type MultipartFetcher = (\r\n graphQLParams: FetcherParams,\r\n multipartFormData: FormData | null,\r\n opts?: FetcherOpts\r\n) => FetcherReturnType;\r\n\r\nexport class MultipartAttachments {\r\n private _attachments: AttachmentEntry[];\r\n\r\n constructor(attachments: AttachmentEntry[]) {\r\n this._attachments = attachments;\r\n }\r\n\r\n /**\r\n * Returns an implementation of fetch that will transform the request to a multipart request if any files are found\r\n */\r\n public decoratedFetch(fetchOverride: typeof fetch = fetch): typeof fetch {\r\n if (this._attachments.length > 0) {\r\n return async (input: RequestInfo | URL, init?: RequestInit) => {\r\n const originalRequest = new Request(input, init);\r\n // we're going to overwrite the body but firefox complains if we don't clone it\r\n const originalBody = await originalRequest.clone().text();\r\n\r\n if (JSON.parse(originalBody)?.operationName !== 'IntrospectionQuery') {\r\n const body = this.asFormData(originalBody);\r\n const headers = new Headers(originalRequest.headers);\r\n\r\n // make sure form data sets the correct content type\r\n headers.delete('content-type');\r\n\r\n return await fetchOverride(originalRequest, { body, headers });\r\n } else {\r\n // just pass through for an introspection query\r\n return fetchOverride(originalRequest);\r\n }\r\n };\r\n } else {\r\n return fetchOverride;\r\n }\r\n }\r\n\r\n /**\r\n * Constructs a GraphiQL Fetcher that provides FormData to the passed in MultipartFetcher\r\n */\r\n public decoratedFetcher(fetcher: MultipartFetcher): Fetcher {\r\n return (graphQLParams: FetcherParams, opts?: FetcherOpts) => {\r\n return fetcher(graphQLParams, this.asFormData(graphQLParams), opts);\r\n };\r\n }\r\n\r\n get attachments(): Map<string, File> {\r\n return new Map(this._attachments.map(({ name, value }) => [name, value]));\r\n }\r\n\r\n private asFormData(operations: FetcherParams | string): FormData | null {\r\n const attachments = this.attachments;\r\n\r\n // verify our attachments\r\n for (const invalidKey of [OPERATIONS_KEY, MAP_KEY]) {\r\n if (attachments.has(invalidKey)) {\r\n console.error(`Cannot have an attachment named \"${invalidKey}\"`);\r\n\r\n return null;\r\n }\r\n }\r\n\r\n if (attachments.size > 0) {\r\n const formData = new FormData();\r\n\r\n const encodedOperations = typeof operations === 'string' ? operations : JSON.stringify(operations);\r\n formData.set(OPERATIONS_KEY, encodedOperations);\r\n\r\n attachments.forEach((value, key) => {\r\n formData.append(key, value);\r\n });\r\n\r\n return formData;\r\n } else {\r\n return null;\r\n }\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"multipart-attachments.js","sourceRoot":"","sources":["../../src/multipart-attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import { Fetcher, FetcherOpts, FetcherParams, FetcherReturnType } from '@graphiql/toolkit';\n\nconst OPERATIONS_KEY = 'operations';\nconst MAP_KEY = 'map';\n\nexport type AttachmentEntry = {\n name: string;\n value: File;\n};\n\nexport type MultipartFetcher = (\n graphQLParams: FetcherParams,\n multipartFormData: FormData | null,\n opts?: FetcherOpts\n) => FetcherReturnType;\n\nexport class MultipartAttachments {\n private _attachments: AttachmentEntry[];\n\n constructor(attachments: AttachmentEntry[]) {\n this._attachments = attachments;\n }\n\n /**\n * Returns an implementation of fetch that will transform the request to a multipart request if any files are found\n */\n public decoratedFetch(fetchOverride: typeof fetch = fetch): typeof fetch {\n if (this._attachments.length > 0) {\n return async (input: RequestInfo | URL, init?: RequestInit) => {\n const originalRequest = new Request(input, init);\n // we're going to overwrite the body but firefox complains if we don't clone it\n const originalBody = await originalRequest.clone().text();\n\n if (JSON.parse(originalBody)?.operationName !== 'IntrospectionQuery') {\n const body = this.asFormData(originalBody);\n const headers = new Headers(originalRequest.headers);\n\n // make sure form data sets the correct content type\n headers.delete('content-type');\n\n return await fetchOverride(originalRequest, { body, headers });\n } else {\n // just pass through for an introspection query\n return fetchOverride(originalRequest);\n }\n };\n } else {\n return fetchOverride;\n }\n }\n\n /**\n * Constructs a GraphiQL Fetcher that provides FormData to the passed in MultipartFetcher\n */\n public decoratedFetcher(fetcher: MultipartFetcher): Fetcher {\n return (graphQLParams: FetcherParams, opts?: FetcherOpts) => {\n return fetcher(graphQLParams, this.asFormData(graphQLParams), opts);\n };\n }\n\n get attachments(): Map<string, File> {\n return new Map(this._attachments.map(({ name, value }) => [name, value]));\n }\n\n private asFormData(operations: FetcherParams | string): FormData | null {\n const attachments = this.attachments;\n\n // verify our attachments\n for (const invalidKey of [OPERATIONS_KEY, MAP_KEY]) {\n if (attachments.has(invalidKey)) {\n console.error(`Cannot have an attachment named \"${invalidKey}\"`);\n\n return null;\n }\n }\n\n if (attachments.size > 0) {\n const formData = new FormData();\n\n const encodedOperations = typeof operations === 'string' ? operations : JSON.stringify(operations);\n formData.set(OPERATIONS_KEY, encodedOperations);\n\n attachments.forEach((value, key) => {\n formData.append(key, value);\n });\n\n return formData;\n } else {\n return null;\n }\n }\n}\n"]}
@@ -29,7 +29,7 @@ const GRAPHIQL_PLUGIN = {
29
29
  };
30
30
  export function AttachmentContent({ name, value, updateAttachment, error }) {
31
31
  const fileInputRef = useRef(null);
32
- return (_jsxs("div", { className: 'attachment-content', children: [_jsxs("div", { className: 'attachment-title', children: [_jsx(PartNameComponent, { name: name, setName: (name) => updateAttachment({ action: 'update', name }) }), _jsx("button", { onClick: () => updateAttachment({ action: 'delete' }), className: 'attachment-delete', children: _jsx(TrashIcon, {}) })] }), error ? _jsx("div", { className: 'graphiql-doc-explorer-error', children: error }) : _jsx(_Fragment, {}), value ? _jsx(PartHeadersComponent, { name: name, value: value }) : _jsx(_Fragment, {}), _jsxs("div", { children: [_jsx("button", { style: { marginTop: '0.5em' }, onClick: () => fileInputRef.current?.click(), children: "Attach File" }), _jsx("input", { type: 'file', ref: fileInputRef, onChange: (event) => {
32
+ return (_jsxs("div", { className: 'attachment-content', children: [_jsxs("div", { className: 'attachment-title', children: [_jsx(PartNameComponent, { name: name, setName: (name) => updateAttachment({ action: 'update', name }) }), _jsx("button", { "aria-label": `Delete attachment ${name}`, onClick: () => updateAttachment({ action: 'delete' }), className: 'attachment-delete', children: _jsx(TrashIcon, {}) })] }), error ? _jsx("div", { className: 'graphiql-doc-explorer-error', children: error }) : _jsx(_Fragment, {}), value ? _jsx(PartHeadersComponent, { name: name, value: value }) : _jsx(_Fragment, {}), _jsxs("div", { children: [_jsx("button", { style: { marginTop: '0.5em' }, onClick: () => fileInputRef.current?.click(), children: "Attach File" }), _jsx("input", { type: 'file', ref: fileInputRef, onChange: (event) => {
33
33
  const file = event.target.files?.[0];
34
34
  if (file) {
35
35
  updateAttachment({ action: 'update', value: file });
@@ -50,21 +50,14 @@ export function PartNameComponent({ name, setName }) {
50
50
  setName(inputRef.current.value);
51
51
  }
52
52
  }
53
- function onSubmit(event) {
54
- event.preventDefault();
55
- event.stopPropagation();
56
- endEditing();
57
- }
58
53
  if (isEditing) {
59
- return (_jsx("form", { onSubmit: onSubmit, children: _jsx("input", { ref: inputRef, type: 'text', defaultValue: name, onBlur: endEditing, onKeyDown: (e) => {
60
- if (e.key === 'Escape') {
61
- onSubmit(e);
62
- }
63
- } }) }));
64
- }
65
- else {
66
- return (_jsx(_Fragment, { children: _jsxs("span", { onClick: () => setIsEditing(true), style: { cursor: 'pointer' }, children: [name, "\u00A0", _jsx(PenIcon, {})] }) }));
54
+ return (_jsxs("label", { className: 'attachment-part-name-edit-label', children: ["Part name:", _jsx("input", { className: 'attachment-part-name-edit-input', ref: inputRef, type: 'text', "aria-label": 'Part name', defaultValue: name, onBlur: endEditing, onKeyDown: (e) => {
55
+ if (e.key === 'Escape' || e.key === 'Enter') {
56
+ endEditing();
57
+ }
58
+ } })] }));
67
59
  }
60
+ return (_jsxs("span", { className: 'attachment-part-name-view', children: [_jsxs("span", { children: ["Part name: ", name] }), _jsx("button", { onClick: () => setIsEditing(true), "aria-label": `Edit part name: ${name}`, title: "Edit part name", children: _jsx(PenIcon, {}) })] }));
68
61
  }
69
62
  export function PartHeadersComponent({ name, value }) {
70
63
  const baseContentDisposition = `form-data; name="${encodeURIComponent(name)}"`;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin.tsx"],"names":[],"mappings":"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","sourcesContent":["import { GraphiQLPlugin, PenIcon, TrashIcon, useGraphiQL } from '@graphiql/react';\r\nimport * as React from 'react';\r\nimport { CSSProperties, useContext, useEffect, useRef, useState } from 'react';\r\nimport uploadClipSvg from './attachment-icon.svg.js';\r\nimport { AttachmentDeleteAction, AttachmentUpdateAction, MultipartAttachmentsInternalContext } from './context.js';\r\n\r\nimport '../../style.css';\r\n\r\ntype AttachmentContentProps = {\r\n name: string;\r\n value: File | null;\r\n updateAttachment: (action: Omit<AttachmentUpdateAction, 'id'> | Omit<AttachmentDeleteAction, 'id'>) => void;\r\n error?: string;\r\n};\r\n\r\nconst GRAPHIQL_PLUGIN: GraphiQLPlugin = {\r\n title: 'Multipart Attachments',\r\n icon: () => {\r\n const [attachments] = useContext(MultipartAttachmentsInternalContext);\r\n const visiblePlugin = useGraphiQL(state => state.visiblePlugin);\r\n\r\n const style: CSSProperties = {\r\n fontFamily: 'var(--font-family-mono)',\r\n fontSize: 'var(--font-size-inline-code)',\r\n textAlign: 'center',\r\n width: 'auto',\r\n };\r\n if (visiblePlugin === GRAPHIQL_PLUGIN) {\r\n style.fontWeight = 'bold';\r\n }\r\n\r\n return (\r\n <div className='attachments-icon'>\r\n {uploadClipSvg}\r\n {attachments.validAttachments.length > 0 ? (\r\n <span className='attachments-icon-badge'>{attachments.validAttachments.length}</span>\r\n ) : (\r\n <></>\r\n )}\r\n </div>\r\n );\r\n },\r\n content: () => {\r\n const [attachments, attachmentsDispatch] = useContext(MultipartAttachmentsInternalContext);\r\n\r\n return (\r\n <div className={'attachments-container'}>\r\n <h2>Multipart Attachments</h2>\r\n {attachments.map(({ id, name, value, error }) => {\r\n return (\r\n <AttachmentContent\r\n key={id}\r\n name={name}\r\n value={value}\r\n updateAttachment={(action) => attachmentsDispatch({ ...action, id })}\r\n error={error}\r\n />\r\n );\r\n })}\r\n <div style={{ textAlign: 'center' }}>\r\n <button onClick={() => attachmentsDispatch({ action: 'add' })}>Add Part</button>\r\n </div>\r\n </div>\r\n );\r\n },\r\n};\r\n\r\nexport function AttachmentContent({ name, value, updateAttachment, error }: AttachmentContentProps): React.JSX.Element {\r\n const fileInputRef = useRef<HTMLInputElement | null>(null);\r\n\r\n return (\r\n <div className={'attachment-content'}>\r\n <div className={'attachment-title'}>\r\n <PartNameComponent name={name} setName={(name) => updateAttachment({ action: 'update', name })} />\r\n <button onClick={() => updateAttachment({ action: 'delete' })} className={'attachment-delete'}>\r\n <TrashIcon />\r\n </button>\r\n </div>\r\n {error ? <div className='graphiql-doc-explorer-error'>{error}</div> : <></>}\r\n {value ? <PartHeadersComponent name={name} value={value} /> : <></>}\r\n <div>\r\n <button style={{ marginTop: '0.5em' }} onClick={() => fileInputRef.current?.click()}>\r\n Attach File\r\n </button>\r\n <input\r\n type='file'\r\n ref={fileInputRef}\r\n onChange={(event) => {\r\n const file = event.target.files?.[0];\r\n if (file) {\r\n updateAttachment({ action: 'update', value: file });\r\n }\r\n }}\r\n style={{ display: 'none' }}\r\n />\r\n </div>\r\n </div>\r\n );\r\n}\r\n\r\nexport function PartNameComponent({ name, setName }: { name: string; setName: (name: string) => void }) {\r\n const [isEditing, setIsEditing] = useState(false);\r\n const inputRef = useRef<HTMLInputElement | null>(null);\r\n\r\n useEffect(() => {\r\n if (isEditing) {\r\n inputRef.current?.focus();\r\n }\r\n }, [isEditing]);\r\n\r\n function endEditing() {\r\n setIsEditing(false);\r\n if (inputRef.current) {\r\n setName(inputRef.current.value);\r\n }\r\n }\r\n function onSubmit(event: React.BaseSyntheticEvent) {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n endEditing();\r\n }\r\n\r\n if (isEditing) {\r\n return (\r\n <form onSubmit={onSubmit}>\r\n <input\r\n ref={inputRef}\r\n type='text'\r\n defaultValue={name}\r\n onBlur={endEditing}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Escape') {\r\n onSubmit(e);\r\n }\r\n }}\r\n />\r\n </form>\r\n );\r\n } else {\r\n return (\r\n <>\r\n <span onClick={() => setIsEditing(true)} style={{ cursor: 'pointer' }}>\r\n {name}\r\n &nbsp;\r\n <PenIcon />\r\n </span>\r\n </>\r\n );\r\n }\r\n}\r\n\r\nexport function PartHeadersComponent({ name, value }: { name: string; value: File }) {\r\n const baseContentDisposition = `form-data; name=\"${encodeURIComponent(name)}\"`;\r\n let headers;\r\n if (typeof value !== 'string') {\r\n headers = [\r\n ['Content-Disposition', `${baseContentDisposition}; filename=\"${encodeURIComponent(value.name)}\"`],\r\n ['Content-Type', value.type.length > 0 ? value.type : 'application/octet-stream'],\r\n ];\r\n } else {\r\n headers = [['Content-Disposition', baseContentDisposition]];\r\n }\r\n\r\n return (\r\n <div className={'attachment-header'}>\r\n {headers.map(([name, value]) => (\r\n <div key={name}>\r\n <span style={{ color: 'hsl(var(--color-info))' }}>{name}: </span>\r\n <span>{value}</span>\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\nexport default GRAPHIQL_PLUGIN;\r\n"]}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin.tsx"],"names":[],"mappings":"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","sourcesContent":["import { GraphiQLPlugin, PenIcon, TrashIcon, useGraphiQL } from '@graphiql/react';\nimport * as React from 'react';\nimport { CSSProperties, useContext, useEffect, useRef, useState } from 'react';\nimport uploadClipSvg from './attachment-icon.svg.js';\nimport { AttachmentDeleteAction, AttachmentUpdateAction, MultipartAttachmentsInternalContext } from './context.js';\n\nimport '../../style.css';\n\ntype AttachmentContentProps = {\n name: string;\n value: File | null;\n updateAttachment: (action: Omit<AttachmentUpdateAction, 'id'> | Omit<AttachmentDeleteAction, 'id'>) => void;\n error?: string;\n};\n\nconst GRAPHIQL_PLUGIN: GraphiQLPlugin = {\n title: 'Multipart Attachments',\n icon: () => {\n const [attachments] = useContext(MultipartAttachmentsInternalContext);\n const visiblePlugin = useGraphiQL(state => state.visiblePlugin);\n\n const style: CSSProperties = {\n fontFamily: 'var(--font-family-mono)',\n fontSize: 'var(--font-size-inline-code)',\n textAlign: 'center',\n width: 'auto',\n };\n if (visiblePlugin === GRAPHIQL_PLUGIN) {\n style.fontWeight = 'bold';\n }\n\n return (\n <div className='attachments-icon'>\n {uploadClipSvg}\n {attachments.validAttachments.length > 0 ? (\n <span className='attachments-icon-badge'>{attachments.validAttachments.length}</span>\n ) : (\n <></>\n )}\n </div>\n );\n },\n content: () => {\n const [attachments, attachmentsDispatch] = useContext(MultipartAttachmentsInternalContext);\n\n return (\n <div className={'attachments-container'}>\n <h2>Multipart Attachments</h2>\n {attachments.map(({ id, name, value, error }) => {\n return (\n <AttachmentContent\n key={id}\n name={name}\n value={value}\n updateAttachment={(action) => attachmentsDispatch({ ...action, id })}\n error={error}\n />\n );\n })}\n <div style={{ textAlign: 'center' }}>\n <button onClick={() => attachmentsDispatch({ action: 'add' })}>Add Part</button>\n </div>\n </div>\n );\n },\n};\n\nexport function AttachmentContent({ name, value, updateAttachment, error }: AttachmentContentProps): React.JSX.Element {\n const fileInputRef = useRef<HTMLInputElement | null>(null);\n\n return (\n <div className={'attachment-content'}>\n <div className={'attachment-title'}>\n <PartNameComponent name={name} setName={(name) => updateAttachment({ action: 'update', name })} />\n <button aria-label={`Delete attachment ${name}`} onClick={() => updateAttachment({ action: 'delete' })} className={'attachment-delete'}>\n <TrashIcon />\n </button>\n </div>\n {error ? <div className='graphiql-doc-explorer-error'>{error}</div> : <></>}\n {value ? <PartHeadersComponent name={name} value={value} /> : <></>}\n <div>\n <button style={{ marginTop: '0.5em' }} onClick={() => fileInputRef.current?.click()}>\n Attach File\n </button>\n <input\n type='file'\n ref={fileInputRef}\n onChange={(event) => {\n const file = event.target.files?.[0];\n if (file) {\n updateAttachment({ action: 'update', value: file });\n }\n }}\n style={{ display: 'none' }}\n />\n </div>\n </div>\n );\n}\n\nexport function PartNameComponent({ name, setName }: { name: string; setName: (name: string) => void }) {\n const [isEditing, setIsEditing] = useState(false);\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n useEffect(() => {\n if (isEditing) {\n inputRef.current?.focus();\n }\n }, [isEditing]);\n\n function endEditing() {\n setIsEditing(false);\n if (inputRef.current) {\n setName(inputRef.current.value);\n }\n }\n \n if (isEditing) {\n return (\n <label className='attachment-part-name-edit-label'>\n Part name:\n <input\n className='attachment-part-name-edit-input'\n ref={inputRef}\n type='text'\n aria-label='Part name'\n defaultValue={name}\n onBlur={endEditing}\n onKeyDown={(e) => {\n if (e.key === 'Escape' || e.key === 'Enter') {\n endEditing();\n }\n }}\n />\n </label>\n );\n }\n\n return (\n <span className='attachment-part-name-view'>\n <span>Part name: {name}</span>\n <button \n onClick={() => setIsEditing(true)} \n aria-label={`Edit part name: ${name}`}\n title=\"Edit part name\"\n >\n <PenIcon />\n </button>\n </span>\n );\n}\n\nexport function PartHeadersComponent({ name, value }: { name: string; value: File }) {\n const baseContentDisposition = `form-data; name=\"${encodeURIComponent(name)}\"`;\n let headers;\n if (typeof value !== 'string') {\n headers = [\n ['Content-Disposition', `${baseContentDisposition}; filename=\"${encodeURIComponent(value.name)}\"`],\n ['Content-Type', value.type.length > 0 ? value.type : 'application/octet-stream'],\n ];\n } else {\n headers = [['Content-Disposition', baseContentDisposition]];\n }\n\n return (\n <div className={'attachment-header'}>\n {headers.map(([name, value]) => (\n <div key={name}>\n <span style={{ color: 'hsl(var(--color-info))' }}>{name}: </span>\n <span>{value}</span>\n </div>\n ))}\n </div>\n );\n}\n\nexport default GRAPHIQL_PLUGIN;\n"]}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "collaborators": [
4
4
  "Dylan Owen <dylan.owen@workday.com>"
5
5
  ],
6
- "version": "0.1.0",
6
+ "version": "0.1.2",
7
7
  "scripts": {
8
8
  "dev": "tsc --project ./tsconfig.dev.json --watch --pretty --incremental --preserveWatchOutput",
9
9
  "test:unit": "jest",
@@ -11,6 +11,11 @@
11
11
  "release": "tshy",
12
12
  "clean": "rimraf dist/"
13
13
  },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/Workday/graphiql-plugins.git",
17
+ "directory": "packages/graphiql-plugin-multipart-requests"
18
+ },
14
19
  "publishConfig": {
15
20
  "registry": "https://registry.npmjs.org/",
16
21
  "access": "public"
@@ -55,5 +60,5 @@
55
60
  }
56
61
  },
57
62
  "module": "./dist/esm/index.js",
58
- "gitHead": "3c8d711c4ce3aa48485e2df047ec8dd4d51f7496"
63
+ "gitHead": "838631ea35e2ae4d518bd9e84a201db982031d55"
59
64
  }
package/style.css CHANGED
@@ -1,88 +1,103 @@
1
- .attachments-container {
2
- display: contents;
3
- }
4
-
5
- .attachments-container h2 {
6
- font-size: var(--font-size-h2);
7
- font-family: var(--font-family);
8
- font-weight: var(--font-weight-medium);
9
- }
10
-
11
- .attachments-container button {
12
- all: unset;
13
- border-radius: var(--border-radius-4);
14
- color: hsla(var(--color-neutral), var(--alpha-secondary));
15
- padding: var(--px-8) var(--px-12);
16
- cursor: pointer;
17
- }
18
-
19
- .attachments-container button:hover {
20
- background-color: hsla(var(--color-neutral), var(--alpha-background-light));
21
- }
22
-
23
- .attachments-container input[type="text"] {
24
- color: hsla(var(--color-neutral));
25
- background-color: hsla(var(--color-neutral), var(--alpha-background-light));
26
- border-radius: var(--border-radius-4);
27
- font-family: var(--font-family-mono);
28
- }
29
-
30
- .attachments-container svg {
31
- height: var(--px-20);
32
- width: var(--px-20);
33
- }
34
-
35
- .attachment-title {
36
- display: flex;
37
- justify-content: space-between;
38
- font-size: var(--font-size-h3);
39
- font-family: var(--font-family-mono);
40
- }
41
-
42
- .attachment-title > :first-child {
43
- flex: 1 1 auto
44
- }
45
-
46
- .attachment-title > :last-child {
47
- flex: 0 1 auto;
48
- }
49
-
50
- button.attachment-delete {
51
- width: 2em;
52
- font-size: 0.8em;
53
- text-align: center;
54
- padding: 0;
55
- }
56
-
57
- .attachment-delete:hover {
58
- color: hsl(var(--color-error));
59
- }
60
-
61
- .attachment-header {
62
- justify-content: start;
63
- font-size: 1em;
64
- font-family: var(--font-family-mono);
65
- }
66
-
67
- .attachments-icon {
68
- display: inline-block;
69
- position: relative;
70
- }
71
-
72
- .attachments-icon svg {
73
- height: var(--px-20);
74
- width: var(--px-20);
75
- }
76
-
77
- .attachments-icon-badge {
78
- background-color: #fa3e3e;
79
- border-radius: 2px;
80
- color: white;
81
-
82
- padding: 1px 3px;
83
- font-size: 10px;
84
-
85
- position: absolute; /* Position the badge within the relatively positioned button */
86
- right: 80%;
87
- bottom: 80%;
88
- }
1
+ .attachments-container {
2
+ display: contents;
3
+ }
4
+
5
+ .attachments-container h2 {
6
+ font-size: var(--font-size-h2);
7
+ font-family: var(--font-family);
8
+ font-weight: var(--font-weight-medium);
9
+ }
10
+
11
+ .attachments-container button {
12
+ all: unset;
13
+ border-radius: var(--border-radius-4);
14
+ color: hsla(var(--color-neutral), var(--alpha-secondary));
15
+ padding: var(--px-8) var(--px-12);
16
+ cursor: pointer;
17
+ }
18
+
19
+ .attachments-container button:hover {
20
+ background-color: hsla(var(--color-neutral), var(--alpha-background-light));
21
+ }
22
+
23
+ .attachments-container input[type="text"] {
24
+ color: hsla(var(--color-neutral));
25
+ background-color: hsla(var(--color-neutral), var(--alpha-background-light));
26
+ border-radius: var(--border-radius-4);
27
+ font-family: var(--font-family-mono);
28
+ }
29
+
30
+ .attachments-container svg {
31
+ height: var(--px-20);
32
+ width: var(--px-20);
33
+ }
34
+
35
+ .attachment-title {
36
+ display: flex;
37
+ justify-content: space-between;
38
+ align-items: center;
39
+ min-height: 2.2em;
40
+ font-size: var(--font-size-h3);
41
+ font-family: var(--font-family-mono);
42
+ }
43
+
44
+ .attachment-title > :first-child {
45
+ flex: 1 1 auto
46
+ }
47
+
48
+ .attachment-title > :last-child {
49
+ flex: 0 1 auto;
50
+ }
51
+
52
+ .attachment-part-name-edit-label,
53
+ .attachment-part-name-view {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ white-space: nowrap;
57
+ }
58
+
59
+ .attachment-part-name-edit-input {
60
+ width: 10em;
61
+ max-width: 100%;
62
+ margin-left: var(--px-10);
63
+ }
64
+
65
+ button.attachment-delete {
66
+ width: 2em;
67
+ font-size: 0.8em;
68
+ text-align: center;
69
+ padding: 0;
70
+ }
71
+
72
+ .attachment-delete:hover {
73
+ color: hsl(var(--color-error));
74
+ }
75
+
76
+ .attachment-header {
77
+ justify-content: start;
78
+ font-size: 1em;
79
+ font-family: var(--font-family-mono);
80
+ }
81
+
82
+ .attachments-icon {
83
+ display: inline-block;
84
+ position: relative;
85
+ }
86
+
87
+ .attachments-icon svg {
88
+ height: var(--px-20);
89
+ width: var(--px-20);
90
+ }
91
+
92
+ .attachments-icon-badge {
93
+ background-color: #fa3e3e;
94
+ border-radius: 2px;
95
+ color: white;
96
+
97
+ padding: 1px 3px;
98
+ font-size: 10px;
99
+
100
+ position: absolute; /* Position the badge within the relatively positioned button */
101
+ right: 80%;
102
+ bottom: 80%;
103
+ }