Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Classes/GraphQL/Middleware/GraphQLMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ private function handleGraphQLError(Throwable $error, callable $formatter): arra
if ($originalException?->getPrevious() instanceof CoerceException) {
$formattedError['extensions']['issues'] = $originalException->getPrevious()->issues;
}
if (method_exists($originalException, 'getRedirectUrl')) {
$formattedError['extensions']['redirectUrl'] = $originalException->getRedirectUrl();
}
return $formattedError;
}

Expand Down
15 changes: 15 additions & 0 deletions Resources/Private/Translations/en/Main.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,21 @@
<trans-unit id="errors.1462196420.message" xml:space="preserve" approved="yes">
<source>Asset could not be deleted, because it is still in use.</source>
</trans-unit>
<trans-unit id="errorOverlay.header" xml:space="preserve" approved="yes">
<source>Login required</source>
</trans-unit>
<trans-unit id="errorOverlay.text" xml:space="preserve" approved="yes">
<source>This media source requires you to be logged in.</source>
</trans-unit>
<trans-unit id="errorOverlay.loginButton" xml:space="preserve" approved="yes">
<source>Open login</source>
</trans-unit>
<trans-unit id="errorOverlay.cancelButton" xml:space="preserve" approved="yes">
<source>Cancel</source>
</trans-unit>
<trans-unit id="errorOverlay.reloadButton" xml:space="preserve" approved="yes">
<source>Reload</source>
</trans-unit>
</body>
</file>
</xliff>
22 changes: 22 additions & 0 deletions packages/core/src/state/errorMessageState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { atom, AtomEffect } from 'recoil';

type SetErrorMessageState = (value: string) => void;

let externalSetMessageState: SetErrorMessageState | null = null;

export const setErrorMessageStateExternal: SetErrorMessageState = (value) => {
externalSetMessageState?.(value);
};

const registerExternalSetter: AtomEffect<string> = ({ setSelf }) => {
externalSetMessageState = setSelf;
return () => {
externalSetMessageState = null;
};
};

export const errorMessageState = atom<string>({
key: 'errorMessage',
default: '',
effects: [registerExternalSetter],
});
22 changes: 22 additions & 0 deletions packages/core/src/state/errorRedirectUrlState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { atom, AtomEffect } from 'recoil';

type SetErrorRedirectUrlState = (value: string) => void;

let externalSetErrorRedirectUrlState: SetErrorRedirectUrlState | null = null;

export const setErrorRiderectUrlStateExternal: SetErrorRedirectUrlState = (value) => {
externalSetErrorRedirectUrlState?.(value);
};

const registerExternalSetter: AtomEffect<string> = ({ setSelf }) => {
externalSetErrorRedirectUrlState = setSelf;
return () => {
externalSetErrorRedirectUrlState = null;
};
};

export const errorRedirectUrlState = atom<string>({
key: 'errorRedirectUrl',
default: '',
effects: [registerExternalSetter],
});
22 changes: 22 additions & 0 deletions packages/core/src/state/errorState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { atom, AtomEffect } from 'recoil';

type SetErrorState = (value: boolean) => void;

let externalSetErrorState: SetErrorState | null = null;

export const setErrorStateExternal: SetErrorState = (value) => {
externalSetErrorState?.(value);
};

const registerExternalSetter: AtomEffect<boolean> = ({ setSelf }) => {
externalSetErrorState = setSelf;
return () => {
externalSetErrorState = null;
};
};

export const errorState = atom<boolean>({
key: 'errorState',
default: false,
effects: [registerExternalSetter],
});
22 changes: 22 additions & 0 deletions packages/core/src/state/errorTitleState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { atom, AtomEffect } from 'recoil';

type SetErrorTitleState = (value: string) => void;

let externalSetTitleState: SetErrorTitleState | null = null;

export const setErrorTitleStateExternal: SetErrorTitleState = (value) => {
externalSetTitleState?.(value);
};

const registerExternalSetter: AtomEffect<string> = ({ setSelf }) => {
externalSetTitleState = setSelf;
return () => {
externalSetTitleState = null;
};
};

export const errorTitleState = atom<string>({
key: 'errorMessage',
default: '',
effects: [registerExternalSetter],
});
4 changes: 4 additions & 0 deletions packages/core/src/state/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
export { availableAssetsState, availableAssetIdentitiesState } from './availableAssetsState';
export { constraintsState } from './constraintsState';
export { currentPageState } from './currentPageState';
export { errorState } from './errorState';
export { errorRedirectUrlState } from './errorRedirectUrlState';
export { errorMessageState } from './errorMessageState';
export { errorTitleState } from './errorTitleState';
export { featureFlagsState } from './featureFlagsState';
export { initialLoadCompleteState } from './initialLoadCompleteState';
export { loadingState } from './loadingState';
Expand Down
13 changes: 13 additions & 0 deletions packages/core/typings/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,16 @@ type MediaType = `${string}/${string}`;

type ApplicationContext = 'browser' | 'details' | 'selection';
type InspectorViewMode = 'asset' | 'assetCollection' | 'tag';

/**
* Extend React's HTMLAttributes to include popover attributes
* TODO remove when updating typescript
*/
declare namespace React {
// eslint-disable-next-line
interface HTMLAttributes<T> {
popovertarget?: string;
popovertargetaction?: 'hide' | 'show' | 'toggle';
popover?: 'auto' | 'manual' | '';
}
}
6 changes: 5 additions & 1 deletion packages/media-module/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import cx from 'classnames';

import { InteractionDialogRenderer, useMediaUi } from '@media-ui/core';
import { useSelectAsset } from '@media-ui/core/src/hooks';
import { searchTermState } from '@media-ui/core/src/state';
import { errorState, searchTermState } from '@media-ui/core/src/state';
import { AssetUsagesModal, assetUsageDetailsModalState } from '@media-ui/feature-asset-usage';
import { ClipboardWatcher } from '@media-ui/feature-clipboard';
import { ConcurrentChangeMonitor } from '@media-ui/feature-concurrent-editing';
Expand All @@ -31,6 +31,7 @@ import ErrorBoundary from './ErrorBoundary';
import theme from '@media-ui/core/src/Theme.module.css';
import classes from './App.module.css';
import './Global.module.css';
import ErrorOverlay from './ErrorOverlay/ErrorOverlay';

const App = () => {
const { selectionMode, isInNodeCreationDialog, containerRef } = useMediaUi();
Expand All @@ -43,6 +44,7 @@ const App = () => {
const searchTerm = useRecoilValue(searchTermState);
const selectAsset = useSelectAsset();
const selectAssetSource = useSetRecoilState(selectedAssetSourceState);
const showErrorWindow = useRecoilValue(errorState);

// TODO: Implement asset source selection via recoil an atom effect in `searchTermState` to avoid this dangerous effect
React.useEffect(() => {
Expand All @@ -65,6 +67,8 @@ const App = () => {
>
<LoadingIndicator />

{showErrorWindow && <ErrorOverlay />}

<div className={classes.gridLeft}>
<ErrorBoundary>
<SideBarLeft />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Popover styles
*/
[popover] {
/* Undo css reset */
margin: auto;

/* Component styles */
color: white;
background: var(--grayDark);
border: 1px solid var(--grayLight);
padding: 0;
border-radius: 0;
outline: none;
}

[popover]::backdrop {
backdrop-filter: blur(3px);
}

.neosHeader {
font-size: calc(var(--generalFontSize) + 2px);
margin: var(--defaultMargin);
line-height: calc(var(--generalFontSize) + 4px);
box-sizing: border-box;
}

.textSection {
margin: var(--defaultMargin);
color: var(--textSubtleLight);
}

.closeButton {
color: white;
font-size: calc(var(--generalFontSize) + 4px);
background: transparent;
width: var(--unit);
height: var(--unit);
position: absolute;
right: 0;
top: 0;
border-left: none;
text-shadow: none;
}

.loginButton {

}
.cancelButton {

}
.reloadButton {

}
.neosFooter {
background: transparent;
margin: var(--defaultMargin);
display: flex;
gap: var(--spacing-Full);
justify-content: flex-end;
}

#workspace-rebase-modal {
width: 50vw;
}
62 changes: 62 additions & 0 deletions packages/media-module/src/components/ErrorOverlay/ErrorOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useEffect, useRef } from 'react';
import { useIntl } from '@media-ui/core';

import classes from './ErrorOverlay.module.css';
import { Icon } from '@neos-project/react-ui-components';
import { useRecoilValue } from 'recoil';
import { errorRedirectUrlState, errorTitleState, errorMessageState } from '@media-ui/core/src/state';

const ErrorOverlay: React.FC = () => {
const { translate } = useIntl();
const popoverRef = useRef<HTMLDivElement>(null);
const errorRedirectUrlValue = useRecoilValue(errorRedirectUrlState);
useEffect(() => {
if (popoverRef.current) {
popoverRef.current.showPopover();

Check failure on line 15 in packages/media-module/src/components/ErrorOverlay/ErrorOverlay.tsx

View workflow job for this annotation

GitHub Actions / lint

Property 'showPopover' does not exist on type 'HTMLDivElement'.
}
}, []);

const handleClose = () => {
if (popoverRef.current) {
popoverRef.current.hidePopover();

Check failure on line 21 in packages/media-module/src/components/ErrorOverlay/ErrorOverlay.tsx

View workflow job for this annotation

GitHub Actions / lint

Property 'hidePopover' does not exist on type 'HTMLDivElement'.
}
};

const handleReload = () => {
window.location.reload();
};

// TODO: Add retry & re-login button in Notification
return (
// eslint-disable-next-line react/no-unknown-property
<div ref={popoverRef} popover="auto" id="error-overlay-popover">
<button type="button" className={`neos-button ${classes.closeButton}`} onClick={handleClose}>
<Icon icon="times" />
</button>
<header className={classes.neosHeader}>{translate('errorOverlay.header', 'Login required')}</header>
<section className={classes.textSection}>
<p>{translate('errorOverlay.text', 'This media source requires you to be logged in.')}</p>
</section>
<footer className={classes.neosFooter}>
<button type="button" className={`neos-button ${classes.cancelButton}`} onClick={handleClose}>
{translate('errorOverlay.cancelButton', 'Cancel')}
</button>
<button type="button" className={`neos-button ${classes.reloadButton}`} onClick={handleReload}>
{translate('errorOverlay.reloadButton', 'Reload')}
</button>
{errorRedirectUrlValue && (
<a
href={errorRedirectUrlValue}
target="_blank"
rel="nofollow, noreferrer"
className={`neos-button ${classes.loginButton}`}
>
{translate('errorOverlay.loginButton', 'Open login')}
</a>
)}
</footer>
</div>
);
};

export default ErrorOverlay;
27 changes: 20 additions & 7 deletions packages/media-module/src/core/CreateErrorHandler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { onError } from '@apollo/client/link/error';
import { setErrorStateExternal } from '@media-ui/core/src/state/errorState';
import { setErrorRiderectUrlStateExternal } from '@media-ui/core/src/state/errorRedirectUrlState';
import { setErrorTitleStateExternal } from '@media-ui/core/src/state/errorTitleState';
import { setErrorMessageStateExternal } from '@media-ui/core/src/state/errorMessageState';

const createErrorHandler = (notify: NeosNotification) => {
const translate = (id, value = null, args = {}, packageKey = 'Flowpack.Media.Ui', source = 'Main') => {
Expand All @@ -8,6 +12,8 @@ const createErrorHandler = (notify: NeosNotification) => {
return onError(({ graphQLErrors, networkError, operation }) => {
const context = operation.getContext();
const isRetrying = context.isRetrying;
let errorTitle = '';
let errorMessage = '';

// Don't show notifications while retrying - only after max retries exhausted
if (isRetrying) {
Expand All @@ -30,21 +36,28 @@ const createErrorHandler = (notify: NeosNotification) => {
errorMessageLabel = data.extensions.debugMessage;
}

console.error('[GraphQL error]:', data);
errorTitle = translate(errorTitleLabel, defaultErrorTitle);
errorMessage = errorMessageLabel.length ? translate(errorMessageLabel) : data.message;

notify.error(
translate(errorTitleLabel, defaultErrorTitle),
errorMessageLabel.length ? translate(errorMessageLabel) : data.message
);
if (data.extensions?.statusCode === 401) {
if (data.extensions?.redirectUrl) {
setErrorRiderectUrlStateExternal(data.extensions.redirectUrl);
}
errorTitle = translate('errorOverlay.header', 'Login required');
errorMessage = translate('errorOverlay.text', 'This media source requires you to be logged in.');
}
});
}

if (networkError) {
console.error('[Network error]:', networkError);
notify.warning('Network error', 'Please check your connection.');
errorTitle = translate('errors.network.title', 'Network error');
errorMessage = translate('errors.network.message', 'Please check your connection.');
}

// TODO: Show error overlay and ask the user on how to continue (retry, reload, re-login)
setErrorTitleStateExternal(errorTitle);
setErrorMessageStateExternal(errorMessage);
setErrorStateExternal(true);
});
};

Expand Down
Loading