|
1 | 1 | import { actions, BuiltLogic, connect, kea, listeners, path, reducers, selectors, sharedListeners } from 'kea' |
2 | | -import { actionToUrl, router, urlToAction } from 'kea-router' |
| 2 | +import { actionToUrl, beforeUnload, router, urlToAction } from 'kea-router' |
| 3 | +import { CombinedLocation } from 'kea-router/lib/utils' |
3 | 4 | import { objectsEqual } from 'kea-test-utils' |
4 | 5 | import { AlertType } from 'lib/components/Alerts/types' |
5 | 6 | import { isEmptyObject } from 'lib/utils' |
@@ -31,7 +32,7 @@ import { |
31 | 32 | import { insightDataLogic } from './insightDataLogic' |
32 | 33 | import { insightDataLogicType } from './insightDataLogicType' |
33 | 34 | import type { insightSceneLogicType } from './insightSceneLogicType' |
34 | | -import { parseDraftQueryFromURL } from './utils' |
| 35 | +import { parseDraftQueryFromLocalStorage, parseDraftQueryFromURL } from './utils' |
35 | 36 | import api from 'lib/api' |
36 | 37 | import { checkLatestVersionsOnQuery } from '~/queries/utils' |
37 | 38 |
|
@@ -417,37 +418,88 @@ export const insightSceneLogic = kea<insightSceneLogicType>([ |
417 | 418 | }, |
418 | 419 | })), |
419 | 420 | actionToUrl(({ values }) => { |
| 421 | + // Use the browser redirect to determine state to hook into beforeunload prevention |
420 | 422 | const actionToUrl = ({ |
421 | 423 | insightMode = values.insightMode, |
422 | 424 | insightId = values.insightId, |
423 | 425 | }: { |
424 | 426 | insightMode?: ItemMode |
425 | 427 | insightId?: InsightShortId | 'new' | null |
426 | | - }): [ |
427 | | - string, |
428 | | - string | Record<string, any> | undefined, |
429 | | - string | Record<string, any> | undefined, |
430 | | - { replace?: boolean } |
431 | | - ] => { |
432 | | - const baseUrl = |
433 | | - !insightId || insightId === 'new' |
434 | | - ? urls.insightNew() |
435 | | - : insightMode === ItemMode.View |
436 | | - ? urls.insightView(insightId) |
437 | | - : urls.insightEdit(insightId) |
438 | | - const searchParams = router.values.currentLocation.searchParams |
439 | | - // TODO: also kepe these in the URL? |
440 | | - // const metadataChanged = !!values.insightLogicRef?.logic.values.insightChanged |
441 | | - const queryChanged = !!values.insightDataLogicRef?.logic.values.queryChanged |
442 | | - const query = values.insightDataLogicRef?.logic.values.query |
443 | | - const hashParams = queryChanged ? { q: query } : undefined |
| 428 | + }): string | undefined => { |
| 429 | + if (!insightId || insightId === 'new') { |
| 430 | + return undefined |
| 431 | + } |
444 | 432 |
|
445 | | - return [baseUrl, searchParams, hashParams, { replace: true }] |
| 433 | + const baseUrl = insightMode === ItemMode.View ? urls.insightView(insightId) : urls.insightEdit(insightId) |
| 434 | + const searchParams = window.location.search |
| 435 | + return searchParams ? `${baseUrl}${searchParams}` : baseUrl |
446 | 436 | } |
447 | 437 |
|
448 | 438 | return { |
449 | 439 | setInsightId: actionToUrl, |
450 | 440 | setInsightMode: actionToUrl, |
451 | 441 | } |
452 | 442 | }), |
| 443 | + beforeUnload(({ values }) => ({ |
| 444 | + enabled: (newLocation?: CombinedLocation) => { |
| 445 | + // Don't run this check on other scenes |
| 446 | + if (values.activeScene !== Scene.Insight) { |
| 447 | + return false |
| 448 | + } |
| 449 | + |
| 450 | + if (values.disableNavigationHooks) { |
| 451 | + return false |
| 452 | + } |
| 453 | + |
| 454 | + // If just the hash or project part changes, don't show the prompt |
| 455 | + const currentPathname = router.values.currentLocation.pathname.replace(/\/project\/\d+/, '') |
| 456 | + const newPathname = newLocation?.pathname.replace(/\/project\/\d+/, '') |
| 457 | + if (currentPathname === newPathname) { |
| 458 | + return false |
| 459 | + } |
| 460 | + |
| 461 | + // Don't show the prompt if we're in edit mode (just exploring) |
| 462 | + if (values.insightMode !== ItemMode.Edit) { |
| 463 | + return false |
| 464 | + } |
| 465 | + |
| 466 | + const metadataChanged = !!values.insightLogicRef?.logic.values.insightChanged |
| 467 | + const queryChanged = !!values.insightDataLogicRef?.logic.values.queryChanged |
| 468 | + const draftQueryFromLocalStorage = localStorage.getItem(`draft-query-${values.currentTeamId}`) |
| 469 | + let draftQuery: { query: Node; timestamp: number } | null = null |
| 470 | + if (draftQueryFromLocalStorage) { |
| 471 | + const parsedQuery = parseDraftQueryFromLocalStorage(draftQueryFromLocalStorage) |
| 472 | + if (parsedQuery) { |
| 473 | + draftQuery = parsedQuery |
| 474 | + } else { |
| 475 | + // If the draft query is invalid, remove it |
| 476 | + localStorage.removeItem(`draft-query-${values.currentTeamId}`) |
| 477 | + } |
| 478 | + } |
| 479 | + const query = values.insightDataLogicRef?.logic.values.query |
| 480 | + |
| 481 | + if (draftQuery && query && objectsEqual(draftQuery.query, query)) { |
| 482 | + return false |
| 483 | + } |
| 484 | + |
| 485 | + const isChanged = metadataChanged || queryChanged |
| 486 | + |
| 487 | + if (!isChanged) { |
| 488 | + return false |
| 489 | + } |
| 490 | + |
| 491 | + // Do not show confirmation if newPathname is undefined; this usually means back button in browser |
| 492 | + if (newPathname === undefined) { |
| 493 | + const savedQuery = values.insightDataLogicRef?.logic.values.savedInsight.query |
| 494 | + values.insightDataLogicRef?.logic.actions.setQuery(savedQuery || null) |
| 495 | + return false |
| 496 | + } |
| 497 | + |
| 498 | + return true |
| 499 | + }, |
| 500 | + message: 'Leave insight?\nChanges you made will be discarded.', |
| 501 | + onConfirm: () => { |
| 502 | + values.insightDataLogicRef?.logic.actions.cancelChanges() |
| 503 | + }, |
| 504 | + })), |
453 | 505 | ]) |
0 commit comments