Skip to content

Commit

Permalink
Merge pull request #13 from edgeandnode/piotr/copy-url
Browse files Browse the repository at this point in the history
Copy query ID to URL on "Share" click
  • Loading branch information
hasparus committed Nov 13, 2022
2 parents 6ac6d4f + 1d3c80e commit 5616f60
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 7 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@edgeandnode/graphiql-playground",
"version": "1.1.1",
"version": "1.1.2",
"type": "commonjs",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
Expand Down
6 changes: 5 additions & 1 deletion src/SavedQueriesToolbar/SavedQueriesToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Flex, Spacing } from '@edgeandnode/components'

import { ActionsMenu } from './ActionsMenu'
import { SnackbarMessageType, ToastMessage, toToastMessage } from './messages'
import { createQuerySharingURL } from './queryInSearchParams'
import { SavedQueriesActionButtons, SavedQueriesActionButtonsProps } from './SavedQueriesActionButtons'
import { useSavedQueriesContext } from './SavedQueriesContext'
import { SavedQuerySelect } from './SavedQuerySelect'
Expand Down Expand Up @@ -76,8 +77,11 @@ export function SavedQueriesToolbar<TQuery extends SavedQuery>(props: SavedQueri
const handleActionSelected = async (action: QueryAction) => {
switch (action) {
case 'Share': {
const url = window.location.href
if (currentQueryId == null) return

const url = createQuerySharingURL(currentQueryId)
await navigator.clipboard.writeText(url)

setSnackbarMessage('success-share')
return
}
Expand Down
26 changes: 26 additions & 0 deletions src/SavedQueriesToolbar/queryInSearchParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const QUERY_SEARCH_PARAMS_KEY = 'playgroundQuery'

export function createQuerySharingURL(queryId: string | number): string {
const url = new URL(window.location.href)
url.searchParams.set(QUERY_SEARCH_PARAMS_KEY, queryId.toString())
return url.toString()
}

export function pluckQueryIdFromUrl(): string | null {
const url = new URL(window.location.href)
const queryId = url.searchParams.get(QUERY_SEARCH_PARAMS_KEY)

if (queryId) {
// This is in `setTimeout` because React runs `useEffect` twice in dev mode.
setTimeout(() => {
if (window.location.href === url.toString()) {
url.searchParams.delete(QUERY_SEARCH_PARAMS_KEY)
window.history.replaceState({}, '', url.toString())
}
}, 50)

return queryId
}

return null
}
28 changes: 28 additions & 0 deletions src/SavedQueriesToolbar/savedQueriesReducer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ import { savedQueriesReducer as reducer } from './savedQueriesReducer'
const { initialState } = reducer

describe(reducer, () => {
beforeAll(() => setLocationHref('https://playground.test'))

const queries = [
{ id: 101, name: 'Query 1', query: '{ one }' },
{ id: 102, name: 'Query 2', query: '{ two }' },
{ id: 103, name: 'Query 3', query: '{ three }' },
]

it('has initialized set to true after "init"', () => {
expect(initialState.initialized).toBe(false)
const result = reducer(initialState, { type: 'init', payload: queries })
expect(result.initialized).toBe(true)
})

it('has default query selected after "init"', () => {
for (let i = 0; i < queries.length; i++) {
const payload = queries.map((q, j) => ({ ...q, isDefault: i === j }))
Expand Down Expand Up @@ -79,4 +87,24 @@ describe(reducer, () => {
expect(state.currentId).toBe(103)
expect(state.queries).toEqual([queries[2]])
})

it('reads and removes the query id from search params', async () => {
setLocationHref('https://playground.test/?playgroundQuery=123')

const replaceState = jest.fn()
globalThis.window.history = { replaceState } as any

let state = reducer(initialState, { type: 'init', payload: [...queries, { id: 123, query: '', name: '' }] })
expect(state.currentId).toBe(123)

await new Promise((resolve) => setTimeout(resolve, 50))

expect(replaceState).toHaveBeenCalledWith({}, '', 'https://playground.test/')
})
})

function setLocationHref(href: string) {
;(globalThis as any).window = {
location: { href },
} as Window
}
21 changes: 17 additions & 4 deletions src/SavedQueriesToolbar/savedQueriesReducer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { pluckQueryIdFromUrl } from './queryInSearchParams'
import { SavedQuery } from './types'

interface SavedQueriesState<TQuery extends SavedQuery> {
queries: TQuery[]
currentId: TQuery['id'] | null
loading: boolean
initialized: boolean
}
type SavedQueriesAction<TQuery extends SavedQuery> =
| { type: 'init'; payload: TQuery[] }
Expand Down Expand Up @@ -37,16 +38,28 @@ export const savedQueriesReducer = <TQuery extends SavedQuery>(
case 'init': {
const queries = a.payload
const defaultQuery = queries.find((q) => q.isDefault)
const current = defaultQuery || queries[0]
return { ...s, queries: a.payload, currentId: current?.id ?? null }
let current = defaultQuery || queries[0]

// hack: this shouldn't be in a reducer
const queryIdFromSearchParams = pluckQueryIdFromUrl()

if (queryIdFromSearchParams) {
const queryFromSearchParams = queries.find((q) => q.id.toString() === queryIdFromSearchParams)

if (queryFromSearchParams) {
current = queryFromSearchParams
}
}

return { ...s, queries, currentId: current?.id ?? null, initialized: true }
}
default:
const _exhaustive: never = a
return s
}
}

const savedQueriesInitialState: SavedQueriesState<SavedQuery> = { queries: [], currentId: null, loading: true }
const savedQueriesInitialState: SavedQueriesState<SavedQuery> = { queries: [], currentId: null, initialized: false }

savedQueriesReducer.initialState = savedQueriesInitialState

Expand Down
2 changes: 1 addition & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function GraphProtocolGraphiQL<TQuery extends SavedQuery>({
className,
}: GraphProtocolGraphiQLProps<TQuery>) {
const [fetcher] = useState(() => createGraphiQLFetcher(fetcherOptions))
const currentSavedQuery = queries.find((query) => query.id === currentQueryId)
const currentSavedQuery = queries.find((query) => currentQueryId && query.id.toString() === currentQueryId.toString())

const [querySource, setQuerySource] = useState(currentSavedQuery?.query || defaultQuery)

Expand Down

0 comments on commit 5616f60

Please sign in to comment.