Skip to content

Commit

Permalink
Feat/playground updates (#221)
Browse files Browse the repository at this point in the history
* feat: ๐ŸŽธ added frame to player

* feat: ๐ŸŽธ added validation for states

* feat: ๐ŸŽธ added title edit feature

* feat: ๐ŸŽธ added animation rename feature

* fix: ๐Ÿ› fixed enterkey behaviour

* feat: ๐ŸŽธ added color picker element

* chore: ๐Ÿค– updated ui add new form

* fix: ๐Ÿ› updating src doesn't clear previously loaded states

* chore: ๐Ÿค– added changeset

* fix: ๐Ÿ› recreate instance on src update

* Revert "fix: ๐Ÿ› updating src doesn't clear previously loaded states"

This reverts commit 5acb38c.

* chore: ๐Ÿค– fixed changeset

* refactor: ๐Ÿ’ก change to use abstracted initDotlottiePlayer method

* chore: ๐Ÿค– updated interactivity example lottie
  • Loading branch information
afsalz authored Sep 6, 2023
1 parent eecffd6 commit 3083f09
Show file tree
Hide file tree
Showing 14 changed files with 353 additions and 56 deletions.
5 changes: 5 additions & 0 deletions .changeset/long-cats-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@dotlottie/react-player': patch
---

fix: ๐Ÿ› updating src doesn't clear previously loaded states
Binary file not shown.
2 changes: 1 addition & 1 deletion apps/dotlottie-playground/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import '@dotlottie/react-player/dist/index.css';

const SAMPLE_FILES = [
{ name: 'toggle.lottie', path: `${import.meta.env.BASE_URL}toggle.lottie` },
{ name: 'lf_interactivity_page.lottie', path: `${import.meta.env.BASE_URL}lf_interactivity_page.lottie` },
{ name: 'interactivity_example.lottie', path: `${import.meta.env.BASE_URL}interactivity_example.lottie` },
{ name: 'aniki_hamster.lottie', path: `${import.meta.env.BASE_URL}aniki_hamster.lottie` },
];

Expand Down
58 changes: 58 additions & 0 deletions apps/dotlottie-playground/src/components/editable-title.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Copyright 2023 Design Barn Inc.
*/

import React, { useCallback, useState, useRef, type KeyboardEventHandler } from 'react';
import { BiSolidEdit } from 'react-icons/bi';

import { cn } from '../utils';

interface EditableTitleProps {
onChange: (value: string) => void;
title: string;
}

export const EditableTitle: React.FC<EditableTitleProps> = ({ onChange, title }) => {
const [edit, setEdit] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);

const handleClick = useCallback((): void => {
if (!edit) {
setEdit(true);
}
}, [edit]);

const handleSave = useCallback(() => {
onChange(inputRef.current?.value ? `${inputRef.current.value}.lottie` : title);
setEdit(false);
}, [onchange, setEdit]);

const checkForEnterKey = useCallback<KeyboardEventHandler<HTMLInputElement>>(
(event) => {
if (event.key === 'Enter') {
handleSave();
}
},
[handleSave],
);

return (
<div className="group flex items-center">
{edit && (
<input
ref={inputRef}
autoFocus
className="bg-transparent outline-none"
type="text"
onKeyDown={checkForEnterKey}
onBlur={handleSave}
defaultValue={title.replace(/.lottie$/u, '')}
/>
)}
{!edit && <span>{title || 'unnamed.lottie'}</span>}
<button onClick={handleClick} className={cn('invisible', !edit && 'group-hover:visible')}>
<BiSolidEdit size={20} />
</button>
</div>
);
};
12 changes: 9 additions & 3 deletions apps/dotlottie-playground/src/components/file-tree/add-new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface AddNewProps {
extension: SupportedFileTypes;
onAdd: (value: string) => void;
}
export const AddNew: React.FC<AddNewProps> = ({ extension, onAdd }) => {
export const AddNew: React.FC<AddNewProps> = ({ onAdd }) => {
const ref = useRef<ElementRef<'input'>>(null);

const handleBlur = useCallback<FocusEventHandler<HTMLInputElement>>(
Expand All @@ -38,8 +38,14 @@ export const AddNew: React.FC<AddNewProps> = ({ extension, onAdd }) => {
<span>
<FileIcon type="json" />
</span>
<input ref={ref} type="text" className="bg-gray-white text-black" onKeyUp={handleKeyUp} onBlur={handleBlur} />
<span>{extension}</span>
<input
autoFocus
ref={ref}
type="text"
className="bg-transparent outline-none"
onKeyUp={handleKeyUp}
onBlur={handleBlur}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* Copyright 2023 Design Barn Inc.
*/

import React, {
type HTMLAttributes,
useCallback,
type MouseEventHandler,
useState,
useRef,
type KeyboardEventHandler,
} from 'react';
import { BiSolidEdit } from 'react-icons/bi';
import { RxCross2 } from 'react-icons/rx';

import { processFilename } from '../../utils';

import { FileIcon } from './file-icon';

import { type SupportedFile } from '.';

interface EditableItemProps extends HTMLAttributes<HTMLButtonElement> {
editable?: boolean;
file: SupportedFile;
onClick?: () => void;
onRemove?: (fileName: string) => void;
onRename?: (previousId: string, newId: string) => void;
}

export const EditableItem: React.FC<EditableItemProps> = ({ editable, file, onRemove, onRename, ...props }) => {
const [editMode, setEditMode] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);

const handleRemove = useCallback<MouseEventHandler<HTMLSpanElement>>(
(event) => {
event.stopPropagation();
onRemove?.(file.name);
},
[onRemove, file.name],
);

const enterEditMode = useCallback<MouseEventHandler<HTMLSpanElement>>(
(event) => {
event.stopPropagation();
setEditMode(true);
},
[setEditMode, editMode],
);

const triggerRename = useCallback(() => {
if (inputRef.current && inputRef.current.value !== file.name) {
onRename?.(file.name, processFilename(inputRef.current.value));
}
setEditMode(false);
}, [onRename, setEditMode, inputRef]);

const checkForEnterKey = useCallback<KeyboardEventHandler<HTMLInputElement>>(
(event) => {
if (event.key === 'Enter') {
triggerRename();
}
},
[triggerRename],
);

return (
<button
className="group w-full flex items-center gap-1 px-2 py-1 pl-4 text-sm whitespace-nowrap hover:text-white"
{...props}
>
<span>
<FileIcon type={file.type} />
</span>
{editMode && (
<input
ref={inputRef}
onKeyDown={checkForEnterKey}
onBlur={triggerRename}
autoFocus
className="outline-none bg-transparent flex-1 text-left"
defaultValue={file.name}
/>
)}
{!editMode && <span className="flex-1 text-left">{file.name}</span>}
{!editMode && (
<div className="flex justify-self-end text-gray-400 gap-1">
{editable && (
<span onClick={enterEditMode} className="hover:text-white opacity-0 group-hover:opacity-100">
<BiSolidEdit size={20} />
</span>
)}
<span onClick={handleRemove} title="Remove" className="hover:text-white opacity-0 group-hover:opacity-100">
<RxCross2 size={20} />
</span>
</div>
)}
</button>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { BsFiletypeJson, BsFiletypeCss } from 'react-icons/bs';

import type { SupportedFileTypes } from '.';

export const FileIcon = ({ type }: { type: SupportedFileTypes }): React.ReactNode => {
export const FileIcon = ({ type }: { type: SupportedFileTypes }): JSX.Element => {
if (type === 'lss') {
return <BsFiletypeCss />;
} else {
Expand Down
45 changes: 22 additions & 23 deletions apps/dotlottie-playground/src/components/file-tree/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
*/

import React, { useCallback, useMemo, useState } from 'react';
import { RxCross2 } from 'react-icons/rx';
import { useKey } from 'react-use';

import { useDotLottie } from '../../hooks/use-dotlottie';
import { useAppSelector } from '../../store/hooks';
import { Dropzone } from '../dropzone';

import { AddNew } from './add-new';
import { FileIcon } from './file-icon';
import { EditableItem } from './editable-item';
import { Title } from './title';

const FILE_TYPES = ['json', 'lss'] as const;
Expand Down Expand Up @@ -41,6 +41,8 @@ export const FileTree: React.FC<FileTreeProps> = ({
onUpload,
title,
}) => {
const { renameDotLottieAnimation } = useDotLottie();

const handleClick = useCallback(
(fileName: string) => {
return () => {
Expand All @@ -54,10 +56,7 @@ export const FileTree: React.FC<FileTreeProps> = ({

const handleRemove = useCallback(
(fileName: string) => {
return (event: React.MouseEvent) => {
event.stopPropagation();
onRemove?.(title, fileName);
};
onRemove?.(title, fileName);
},
[onRemove, title],
);
Expand All @@ -84,6 +83,15 @@ export const FileTree: React.FC<FileTreeProps> = ({
[onAddNew, title, fileExtention],
);

const handleRename = useCallback(
(id: string, previousId: string) => {
if (title === 'Animations') {
renameDotLottieAnimation(id, previousId);
}
},
[title, renameDotLottieAnimation],
);

const handleUpload = useCallback(
(file: File) => {
onUpload?.(title, file);
Expand Down Expand Up @@ -122,33 +130,24 @@ export const FileTree: React.FC<FileTreeProps> = ({
)}
<ul className="w-full py-2">
{Array.isArray(files) &&
files.map((file, index) => {
files.map((file) => {
return (
<li
key={index}
key={file.name}
data-value={title}
className={`w-full ${
editorAnimationId === file.name || editorFileName === file.name
? 'bg-gray-700 text-gray-100'
: 'text-gray-400'
}`}
>
<button
<EditableItem
onRemove={handleRemove}
onRename={handleRename}
editable={title === 'Animations'}
file={file}
onClick={handleClick(file.name)}
className="group w-full flex items-center gap-1 px-2 py-1 pl-4 text-sm whitespace-nowrap hover:text-white"
>
<span>
<FileIcon type={file.type} />
</span>
<span className="flex-1 text-left">{file.name}</span>
<span
onClick={handleRemove(file.name)}
title="Remove"
className="justify-self-end text-gray-400 hover:text-white opacity-0 group-hover:opacity-100"
>
<RxCross2 size={20} />
</span>
</button>
/>
</li>
);
})}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Copyright 2023 Design Barn Inc.
*/

import React, { type ChangeEventHandler, useCallback, useRef } from 'react';

interface InputColorPickerProps {
label: string;
onChange?: (value: string) => void;
value?: string;
}

export const InputColorPicker: React.FC<InputColorPickerProps> = ({ label, onChange, value }) => {
const inputRef = useRef<HTMLInputElement>(null);
const handleChange = useCallback<ChangeEventHandler<HTMLInputElement>>(
(event) => {
onChange?.(event.target.value);
},
[onChange],
);

const openPicker = useCallback(() => inputRef.current?.click(), [inputRef]);

return (
<div className="flex flex-col text-gray-400 w-full max-w-xs hover:text-white">
<span className="flex-1 text-lg text-left">{label}</span>
<button
onClick={openPicker}
className={`flex items-center justify-between py-2 px-3 rounded bg-white text-gray-600`}
>
<div className="py-1 px-2 w-full text-left rounded" style={{ backgroundColor: value }}>
<span className="text-white mix-blend-difference">{value || 'transparent'}</span>
</div>
</button>
<input
ref={inputRef}
type="color"
onChange={handleChange}
value={value}
name={label}
className={`invisible h-0 w-0`}
/>
</div>
);
};
18 changes: 16 additions & 2 deletions apps/dotlottie-playground/src/components/player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,22 @@ export const Player: React.FC<PlayerProps> = () => {
const currentPlayerUrl = useAppSelector((state) => state.playground.playerUrl);
const [playerStates, setPlayerStates] = useState<string[]>([]);
const [activeStateId, setActiveStateId] = useState('');
const [currentFrame, setCurrentFrame] = useState(0);

const handlePlayerEvents = useCallback(
(event: PlayerEvents) => {
(event: PlayerEvents, params: unknown) => {
if (event === PlayerEvents.Ready) {
const _states = lottiePlayer.current?.getManifest()?.states;

setPlayerStates(_states || []);
}

if (event === PlayerEvents.Frame) {
const { frame } = params as { frame: number };

setCurrentFrame(Math.floor(frame));
}

const currentState = lottiePlayer.current?.getState();

if (currentState) {
Expand Down Expand Up @@ -63,7 +70,14 @@ export const Player: React.FC<PlayerProps> = () => {
lottieRef={lottiePlayer}
src={currentPlayerUrl}
>
<Controls />
<div className="bg-white">
<Controls />
<div className="px-3 pb-1">
<span className="bg-gray-300 rounded px-2">
# <span>{currentFrame}</span>
</span>
</div>
</div>
</DotLottiePlayer>
<div className="flex flex-wrap gap-2 p-2 text-white">
<div className="text-white">
Expand Down
Loading

0 comments on commit 3083f09

Please sign in to comment.