kopia lustrzana https://github.com/Tldraw/Tldraw
[fix] react component runaways, error boundaries (#1625)
This PR fixes a few components that were updating too often. It changes the format of our error boundaries in order to avoid re-rendering them as changed props. ### Change Type - [x] `major` — Breaking changepull/1626/head
rodzic
5cb08711c1
commit
83184aaf43
|
@ -11,7 +11,6 @@ export default function ErrorBoundaryExample() {
|
||||||
shapes={shapes}
|
shapes={shapes}
|
||||||
tools={[]}
|
tools={[]}
|
||||||
components={{
|
components={{
|
||||||
ErrorFallback: null, // disable app-level error boundaries
|
|
||||||
ShapeErrorFallback: ({ error }) => <div>Shape error! {String(error)}</div>, // use a custom error fallback for shapes
|
ShapeErrorFallback: ({ error }) => <div>Shape error! {String(error)}</div>, // use a custom error fallback for shapes
|
||||||
}}
|
}}
|
||||||
onMount={(editor) => {
|
onMount={(editor) => {
|
||||||
|
|
|
@ -770,7 +770,7 @@ export class ErrorBoundary extends React_3.Component<React_3.PropsWithRef<React_
|
||||||
error: Error;
|
error: Error;
|
||||||
};
|
};
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
render(): React_3.ReactNode;
|
render(): boolean | JSX.Element | null | number | React_3.ReactFragment | string | undefined;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
state: TLErrorBoundaryState;
|
state: TLErrorBoundaryState;
|
||||||
}
|
}
|
||||||
|
@ -1743,7 +1743,7 @@ export function openWindow(url: string, target?: string): void;
|
||||||
|
|
||||||
// @internal (undocumented)
|
// @internal (undocumented)
|
||||||
export function OptionalErrorBoundary({ children, fallback, ...props }: Omit<TLErrorBoundaryProps, 'fallback'> & {
|
export function OptionalErrorBoundary({ children, fallback, ...props }: Omit<TLErrorBoundaryProps, 'fallback'> & {
|
||||||
fallback: ((error: unknown) => React_3.ReactNode) | null;
|
fallback: TLErrorFallbackComponent;
|
||||||
}): JSX.Element;
|
}): JSX.Element;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
|
@ -2261,7 +2261,7 @@ export interface TLEditorComponents {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
Cursor: null | TLCursorComponent;
|
Cursor: null | TLCursorComponent;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
ErrorFallback: null | TLErrorFallbackComponent;
|
ErrorFallback: TLErrorFallbackComponent;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
Grid: null | TLGridComponent;
|
Grid: null | TLGridComponent;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -2269,9 +2269,9 @@ export interface TLEditorComponents {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
Scribble: null | TLScribbleComponent;
|
Scribble: null | TLScribbleComponent;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
ShapeErrorFallback: null | TLShapeErrorFallbackComponent;
|
ShapeErrorFallback: TLShapeErrorFallbackComponent;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
ShapeIndicatorErrorFallback: null | TLShapeIndicatorErrorFallback;
|
ShapeIndicatorErrorFallback: TLShapeIndicatorErrorFallback;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
SnapLine: null | TLSnapLineComponent;
|
SnapLine: null | TLSnapLineComponent;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -2306,7 +2306,9 @@ export interface TLErrorBoundaryProps {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
children: React_3.ReactNode;
|
children: React_3.ReactNode;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
fallback: (error: unknown) => React_3.ReactNode;
|
fallback: (props: {
|
||||||
|
error: unknown;
|
||||||
|
}) => any;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
onError?: ((error: unknown) => void) | null;
|
onError?: ((error: unknown) => void) | null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,7 @@ export const TldrawEditor = memo(function TldrawEditor({
|
||||||
return (
|
return (
|
||||||
<div ref={setContainer} draggable={false} className="tl-container tl-theme__light" tabIndex={0}>
|
<div ref={setContainer} draggable={false} className="tl-container tl-theme__light" tabIndex={0}>
|
||||||
<OptionalErrorBoundary
|
<OptionalErrorBoundary
|
||||||
fallback={ErrorFallback ? (error) => <ErrorFallback error={error} /> : null}
|
fallback={ErrorFallback}
|
||||||
onError={(error) => annotateError(error, { tags: { origin: 'react.tldraw-before-app' } })}
|
onError={(error) => annotateError(error, { tags: { origin: 'react.tldraw-before-app' } })}
|
||||||
>
|
>
|
||||||
{container && (
|
{container && (
|
||||||
|
@ -297,7 +297,7 @@ function TldrawEditorWithReadyStore({
|
||||||
// document in the event of an error to reassure them that their work is
|
// document in the event of an error to reassure them that their work is
|
||||||
// not lost.
|
// not lost.
|
||||||
<OptionalErrorBoundary
|
<OptionalErrorBoundary
|
||||||
fallback={ErrorFallback ? (error) => <ErrorFallback error={error} editor={editor} /> : null}
|
fallback={ErrorFallback}
|
||||||
onError={(error) =>
|
onError={(error) =>
|
||||||
editor.annotateError(error, { origin: 'react.tldraw', willCrashApp: true })
|
editor.annotateError(error, { origin: 'react.tldraw', willCrashApp: true })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
/** @public */
|
/** @public */
|
||||||
export type TLShapeErrorFallbackComponent = (props: { error: unknown }) => any | null
|
export type TLShapeErrorFallbackComponent = (props: { error: any }) => any | null
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export const DefaultShapeErrorFallback: TLShapeErrorFallbackComponent = () => {
|
export const DefaultShapeErrorFallback: TLShapeErrorFallbackComponent = ({
|
||||||
return <div className="tl-shape-error-boundary" />
|
error,
|
||||||
|
}: {
|
||||||
|
error: any
|
||||||
|
}) => {
|
||||||
|
return <div className="tl-shape-error-boundary">{error}</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import { TLErrorFallbackComponent } from './DefaultErrorFallback'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export interface TLErrorBoundaryProps {
|
export interface TLErrorBoundaryProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
onError?: ((error: unknown) => void) | null
|
onError?: ((error: unknown) => void) | null
|
||||||
fallback: (error: unknown) => React.ReactNode
|
fallback: (props: { error: unknown }) => any
|
||||||
}
|
}
|
||||||
|
|
||||||
type TLErrorBoundaryState = { error: Error | null }
|
type TLErrorBoundaryState = { error: Error | null }
|
||||||
|
@ -30,7 +31,8 @@ export class ErrorBoundary extends React.Component<
|
||||||
const { error } = this.state
|
const { error } = this.state
|
||||||
|
|
||||||
if (error !== null) {
|
if (error !== null) {
|
||||||
return this.props.fallback(error)
|
const { fallback: Fallback } = this.props
|
||||||
|
return <Fallback error={error} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.props.children
|
return this.props.children
|
||||||
|
@ -43,7 +45,7 @@ export function OptionalErrorBoundary({
|
||||||
fallback,
|
fallback,
|
||||||
...props
|
...props
|
||||||
}: Omit<TLErrorBoundaryProps, 'fallback'> & {
|
}: Omit<TLErrorBoundaryProps, 'fallback'> & {
|
||||||
fallback: ((error: unknown) => React.ReactNode) | null
|
fallback: TLErrorFallbackComponent
|
||||||
}) {
|
}) {
|
||||||
if (fallback === null) {
|
if (fallback === null) {
|
||||||
return <>{children}</>
|
return <>{children}</>
|
||||||
|
|
|
@ -94,6 +94,13 @@ export const Shape = track(function Shape({
|
||||||
|
|
||||||
const shape = editor.getShapeById(id)
|
const shape = editor.getShapeById(id)
|
||||||
|
|
||||||
|
const annotateError = React.useCallback(
|
||||||
|
(error: any) => {
|
||||||
|
editor.annotateError(error, { origin: 'react.shape', willCrashApp: false })
|
||||||
|
},
|
||||||
|
[editor]
|
||||||
|
)
|
||||||
|
|
||||||
if (!shape) return null
|
if (!shape) return null
|
||||||
|
|
||||||
const util = editor.getShapeUtil(shape)
|
const util = editor.getShapeUtil(shape)
|
||||||
|
@ -108,12 +115,7 @@ export const Shape = track(function Shape({
|
||||||
draggable={false}
|
draggable={false}
|
||||||
>
|
>
|
||||||
{!isCulled && (
|
{!isCulled && (
|
||||||
<OptionalErrorBoundary
|
<OptionalErrorBoundary fallback={ShapeErrorFallback} onError={annotateError}>
|
||||||
fallback={ShapeErrorFallback ? (error) => <ShapeErrorFallback error={error} /> : null}
|
|
||||||
onError={(error) =>
|
|
||||||
editor.annotateError(error, { origin: 'react.shape', willCrashApp: false })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<InnerShapeBackground shape={shape} util={util} />
|
<InnerShapeBackground shape={shape} util={util} />
|
||||||
</OptionalErrorBoundary>
|
</OptionalErrorBoundary>
|
||||||
)}
|
)}
|
||||||
|
@ -133,12 +135,7 @@ export const Shape = track(function Shape({
|
||||||
{isCulled && util.canUnmount(shape) ? (
|
{isCulled && util.canUnmount(shape) ? (
|
||||||
<CulledShape shape={shape} />
|
<CulledShape shape={shape} />
|
||||||
) : (
|
) : (
|
||||||
<OptionalErrorBoundary
|
<OptionalErrorBoundary fallback={ShapeErrorFallback} onError={annotateError}>
|
||||||
fallback={ShapeErrorFallback ? (error) => <ShapeErrorFallback error={error} /> : null}
|
|
||||||
onError={(error) =>
|
|
||||||
editor.annotateError(error, { origin: 'react.shape', willCrashApp: false })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<InnerShape shape={shape} util={util} />
|
<InnerShape shape={shape} util={util} />
|
||||||
</OptionalErrorBoundary>
|
</OptionalErrorBoundary>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -31,11 +31,7 @@ export const InnerIndicator = ({ editor, id }: { editor: Editor; id: TLShapeId }
|
||||||
if (!shape.shape) return null
|
if (!shape.shape) return null
|
||||||
return (
|
return (
|
||||||
<OptionalErrorBoundary
|
<OptionalErrorBoundary
|
||||||
fallback={
|
fallback={ShapeIndicatorErrorFallback}
|
||||||
ShapeIndicatorErrorFallback
|
|
||||||
? (error) => <ShapeIndicatorErrorFallback error={error} />
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
onError={(error) =>
|
onError={(error) =>
|
||||||
editor.annotateError(error, { origin: 'react.shapeIndicator', willCrashApp: false })
|
editor.annotateError(error, { origin: 'react.shapeIndicator', willCrashApp: false })
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,9 +39,9 @@ export interface TLEditorComponents {
|
||||||
CollaboratorScribble: TLScribbleComponent | null
|
CollaboratorScribble: TLScribbleComponent | null
|
||||||
SnapLine: TLSnapLineComponent | null
|
SnapLine: TLSnapLineComponent | null
|
||||||
Handle: TLHandleComponent | null
|
Handle: TLHandleComponent | null
|
||||||
ErrorFallback: TLErrorFallbackComponent | null
|
ErrorFallback: TLErrorFallbackComponent
|
||||||
ShapeErrorFallback: TLShapeErrorFallbackComponent | null
|
ShapeErrorFallback: TLShapeErrorFallbackComponent
|
||||||
ShapeIndicatorErrorFallback: TLShapeIndicatorErrorFallbackComponent | null
|
ShapeIndicatorErrorFallback: TLShapeIndicatorErrorFallbackComponent
|
||||||
Spinner: TLSpinnerComponent | null
|
Spinner: TLSpinnerComponent | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ function checkAllShapes(editor: Editor, shapes: string[]) {
|
||||||
describe('<TldrawEditor />', () => {
|
describe('<TldrawEditor />', () => {
|
||||||
it('Renders without crashing', async () => {
|
it('Renders without crashing', async () => {
|
||||||
render(
|
render(
|
||||||
<TldrawEditor autoFocus components={{ ErrorFallback: null }}>
|
<TldrawEditor autoFocus>
|
||||||
<div data-testid="canvas-1" />
|
<div data-testid="canvas-1" />
|
||||||
</TldrawEditor>
|
</TldrawEditor>
|
||||||
)
|
)
|
||||||
|
@ -49,7 +49,6 @@ describe('<TldrawEditor />', () => {
|
||||||
let editor: Editor
|
let editor: Editor
|
||||||
render(
|
render(
|
||||||
<TldrawEditor
|
<TldrawEditor
|
||||||
components={{ ErrorFallback: null }}
|
|
||||||
onMount={(e) => {
|
onMount={(e) => {
|
||||||
editor = e
|
editor = e
|
||||||
}}
|
}}
|
||||||
|
@ -66,7 +65,6 @@ describe('<TldrawEditor />', () => {
|
||||||
let editor: Editor
|
let editor: Editor
|
||||||
render(
|
render(
|
||||||
<TldrawEditor
|
<TldrawEditor
|
||||||
components={{ ErrorFallback: null }}
|
|
||||||
shapes={defaultShapes}
|
shapes={defaultShapes}
|
||||||
onMount={(e) => {
|
onMount={(e) => {
|
||||||
editor = e
|
editor = e
|
||||||
|
@ -100,7 +98,6 @@ describe('<TldrawEditor />', () => {
|
||||||
const store = createTLStore({ shapes: [] })
|
const store = createTLStore({ shapes: [] })
|
||||||
render(
|
render(
|
||||||
<TldrawEditor
|
<TldrawEditor
|
||||||
components={{ ErrorFallback: null }}
|
|
||||||
store={store}
|
store={store}
|
||||||
onMount={(editor) => {
|
onMount={(editor) => {
|
||||||
expect(editor.store).toBe(store)
|
expect(editor.store).toBe(store)
|
||||||
|
@ -119,9 +116,13 @@ describe('<TldrawEditor />', () => {
|
||||||
render(
|
render(
|
||||||
<TldrawEditor
|
<TldrawEditor
|
||||||
shapes={defaultShapes}
|
shapes={defaultShapes}
|
||||||
components={{ ErrorFallback: null }}
|
|
||||||
store={createTLStore({ shapes: [] })}
|
store={createTLStore({ shapes: [] })}
|
||||||
autoFocus
|
autoFocus
|
||||||
|
components={{
|
||||||
|
ErrorFallback: ({ error }) => {
|
||||||
|
throw error
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div data-testid="canvas-1" />
|
<div data-testid="canvas-1" />
|
||||||
</TldrawEditor>
|
</TldrawEditor>
|
||||||
|
@ -133,9 +134,13 @@ describe('<TldrawEditor />', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
render(
|
render(
|
||||||
<TldrawEditor
|
<TldrawEditor
|
||||||
components={{ ErrorFallback: null }}
|
|
||||||
store={createTLStore({ shapes: defaultShapes })}
|
store={createTLStore({ shapes: defaultShapes })}
|
||||||
autoFocus
|
autoFocus
|
||||||
|
components={{
|
||||||
|
ErrorFallback: ({ error }) => {
|
||||||
|
throw error
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div data-testid="canvas-1" />
|
<div data-testid="canvas-1" />
|
||||||
</TldrawEditor>
|
</TldrawEditor>
|
||||||
|
@ -150,12 +155,7 @@ describe('<TldrawEditor />', () => {
|
||||||
const initialStore = createTLStore({ shapes: [] })
|
const initialStore = createTLStore({ shapes: [] })
|
||||||
const onMount = jest.fn()
|
const onMount = jest.fn()
|
||||||
const rendered = render(
|
const rendered = render(
|
||||||
<TldrawEditor
|
<TldrawEditor store={initialStore} onMount={onMount} autoFocus>
|
||||||
components={{ ErrorFallback: null }}
|
|
||||||
store={initialStore}
|
|
||||||
onMount={onMount}
|
|
||||||
autoFocus
|
|
||||||
>
|
|
||||||
<div data-testid="canvas-1" />
|
<div data-testid="canvas-1" />
|
||||||
</TldrawEditor>
|
</TldrawEditor>
|
||||||
)
|
)
|
||||||
|
@ -165,12 +165,7 @@ describe('<TldrawEditor />', () => {
|
||||||
expect(initialEditor.store).toBe(initialStore)
|
expect(initialEditor.store).toBe(initialStore)
|
||||||
// re-render with the same store:
|
// re-render with the same store:
|
||||||
rendered.rerender(
|
rendered.rerender(
|
||||||
<TldrawEditor
|
<TldrawEditor store={initialStore} onMount={onMount} autoFocus>
|
||||||
components={{ ErrorFallback: null }}
|
|
||||||
store={initialStore}
|
|
||||||
onMount={onMount}
|
|
||||||
autoFocus
|
|
||||||
>
|
|
||||||
<div data-testid="canvas-2" />
|
<div data-testid="canvas-2" />
|
||||||
</TldrawEditor>
|
</TldrawEditor>
|
||||||
)
|
)
|
||||||
|
@ -180,12 +175,7 @@ describe('<TldrawEditor />', () => {
|
||||||
// re-render with a new store:
|
// re-render with a new store:
|
||||||
const newStore = createTLStore({ shapes: [] })
|
const newStore = createTLStore({ shapes: [] })
|
||||||
rendered.rerender(
|
rendered.rerender(
|
||||||
<TldrawEditor
|
<TldrawEditor store={newStore} onMount={onMount} autoFocus>
|
||||||
components={{ ErrorFallback: null }}
|
|
||||||
store={newStore}
|
|
||||||
onMount={onMount}
|
|
||||||
autoFocus
|
|
||||||
>
|
|
||||||
<div data-testid="canvas-3" />
|
<div data-testid="canvas-3" />
|
||||||
</TldrawEditor>
|
</TldrawEditor>
|
||||||
)
|
)
|
||||||
|
@ -199,7 +189,6 @@ describe('<TldrawEditor />', () => {
|
||||||
let editor = {} as Editor
|
let editor = {} as Editor
|
||||||
render(
|
render(
|
||||||
<TldrawEditor
|
<TldrawEditor
|
||||||
components={{ ErrorFallback: null }}
|
|
||||||
shapes={defaultShapes}
|
shapes={defaultShapes}
|
||||||
tools={defaultTools}
|
tools={defaultTools}
|
||||||
autoFocus
|
autoFocus
|
||||||
|
@ -322,7 +311,6 @@ describe('Custom shapes', () => {
|
||||||
let editor = {} as Editor
|
let editor = {} as Editor
|
||||||
render(
|
render(
|
||||||
<TldrawEditor
|
<TldrawEditor
|
||||||
components={{ ErrorFallback: null }}
|
|
||||||
shapes={shapes}
|
shapes={shapes}
|
||||||
tools={tools}
|
tools={tools}
|
||||||
autoFocus
|
autoFocus
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { GeoShapeGeoStyle, preventDefault, useEditor } from '@tldraw/editor'
|
import { GeoShapeGeoStyle, preventDefault, useEditor } from '@tldraw/editor'
|
||||||
import { track, useValue } from '@tldraw/state'
|
import { track, useValue } from '@tldraw/state'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import React from 'react'
|
import React, { memo } from 'react'
|
||||||
import { useBreakpoint } from '../../hooks/useBreakpoint'
|
import { useBreakpoint } from '../../hooks/useBreakpoint'
|
||||||
import { useReadonly } from '../../hooks/useReadonly'
|
import { useReadonly } from '../../hooks/useReadonly'
|
||||||
import { TLUiToolbarItem, useToolbarSchema } from '../../hooks/useToolbarSchema'
|
import { TLUiToolbarItem, useToolbarSchema } from '../../hooks/useToolbarSchema'
|
||||||
|
@ -19,7 +19,7 @@ import { kbdStr } from '../primitives/shared'
|
||||||
import { ToggleToolLockedButton } from './ToggleToolLockedButton'
|
import { ToggleToolLockedButton } from './ToggleToolLockedButton'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export const Toolbar = function Toolbar() {
|
export const Toolbar = memo(function Toolbar() {
|
||||||
const editor = useEditor()
|
const editor = useEditor()
|
||||||
const msg = useTranslation()
|
const msg = useTranslation()
|
||||||
const breakpoint = useBreakpoint()
|
const breakpoint = useBreakpoint()
|
||||||
|
@ -217,7 +217,7 @@ export const Toolbar = function Toolbar() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
const OverflowToolsContent = track(function OverflowToolsContent({
|
const OverflowToolsContent = track(function OverflowToolsContent({
|
||||||
toolbarItems,
|
toolbarItems,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Range, Root, Thumb, Track } from '@radix-ui/react-slider'
|
import { Range, Root, Thumb, Track } from '@radix-ui/react-slider'
|
||||||
import { useEditor } from '@tldraw/editor'
|
import { useEditor } from '@tldraw/editor'
|
||||||
import { useCallback } from 'react'
|
import { memo, useCallback } from 'react'
|
||||||
import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
|
import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
|
||||||
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
|
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ export interface SliderProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export function Slider(props: SliderProps) {
|
export const Slider = memo(function Slider(props: SliderProps) {
|
||||||
const { title, steps, value, label, onValueChange } = props
|
const { title, steps, value, label, onValueChange } = props
|
||||||
const editor = useEditor()
|
const editor = useEditor()
|
||||||
const msg = useTranslation()
|
const msg = useTranslation()
|
||||||
|
@ -59,4 +59,4 @@ export function Slider(props: SliderProps) {
|
||||||
</Root>
|
</Root>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
Ładowanie…
Reference in New Issue