mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
drag and drop support for tabs (#146)
* back-end implementation. initial ui implementation * rough implementation of drag and drop * wrap with AnimatePresence * persist screen order * cleanup * return all screens when updating indices * remove y axis anitation * remove debugging code * chain filtering and sorting logic * remove unused var * fix issue where tabs shift to left on hover * fix tabElem scroll into view regression * clear scrollIntoViewTimeout when component unmounts * remove borken style prop * completely remove animation related props * scroll into view only when there's a new tab * minor comment fix * resolvePosInt() to resolve index * move tab width const to magiclayout.ts * move back scroll into view code as it was before * clear timout * refactor setTimeout * remov debugging codes * format cmdrunner.go * move clearTimeout * remove wheel event listener
This commit is contained in:
parent
8200a312b9
commit
ea9cb37de1
@ -16,6 +16,7 @@
|
||||
"dayjs": "^1.11.3",
|
||||
"dompurify": "^3.0.2",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"framer-motion": "^10.16.16",
|
||||
"mobx": "6.12",
|
||||
"mobx-react": "^7.5.0",
|
||||
"monaco-editor": "^0.44.0",
|
||||
|
@ -7,11 +7,11 @@ let MagicLayout = {
|
||||
CmdInputHeight: 101, // height of full cmd-input div
|
||||
CmdInputBottom: 12, // .cmd-input
|
||||
|
||||
LineHeaderHeight: 46, // .line-header
|
||||
LinePadding: 24, // .line-header (12px * 2)
|
||||
WindowHeightOffset: 6, // .window-view, height is calc(100%-0.5rem)
|
||||
LineHeaderHeight: 46, // .line-header
|
||||
LinePadding: 24, // .line-header (12px * 2)
|
||||
WindowHeightOffset: 6, // .window-view, height is calc(100%-0.5rem)
|
||||
LinesBottomPadding: 10, // .lines, padding
|
||||
LineMarginTop: 12, // .line, margin
|
||||
LineMarginTop: 12, // .line, margin
|
||||
|
||||
ScreenMaxContentWidthBuffer: 50,
|
||||
ScreenMaxContentHeightBuffer: 0, // calc below
|
||||
@ -21,12 +21,15 @@ let MagicLayout = {
|
||||
// the 3 is for descenders, which get cut off in the terminal without this
|
||||
TermDescendersHeight: 3,
|
||||
TermWidthBuffer: 15,
|
||||
|
||||
TabWidth: 175,
|
||||
};
|
||||
|
||||
let m = MagicLayout;
|
||||
|
||||
// add up all the line overhead + padding. subtract 2 so we don't see the border of neighboring line
|
||||
m.ScreenMaxContentHeightBuffer = m.LineHeaderHeight + m.LinePadding + m.WindowHeightOffset + m.LinesBottomPadding + m.LineMarginTop - 2;
|
||||
m.ScreenMaxContentHeightBuffer =
|
||||
m.LineHeaderHeight + m.LinePadding + m.WindowHeightOffset + m.LinesBottomPadding + m.LineMarginTop - 2;
|
||||
|
||||
(window as any).MagicLayout = MagicLayout;
|
||||
|
||||
|
@ -220,96 +220,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.screen-tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow-x: hidden;
|
||||
align-items: center;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.screen-tab {
|
||||
display: flex;
|
||||
height: 3em;
|
||||
min-width: 14em;
|
||||
max-width: 14em;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
.icon.svg-icon {
|
||||
margin: 0 12px;
|
||||
|
||||
svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.fa-icon {
|
||||
font-size: 16px;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.tab-name {
|
||||
width: 8rem;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-top: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.is-archived {
|
||||
.fa.fa-archive {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-index,
|
||||
.tab-gear {
|
||||
display: none;
|
||||
.icon {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.tab-gear {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .screen-tab .tab-index {
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
&:hover .screen-tab:hover .tab-index {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.new-screen {
|
||||
flex-shrink: 0;
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
cursor: pointer;
|
||||
.icon {
|
||||
height: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
padding: 0.4em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.screen-tabs-container {
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
z-index: 200;
|
||||
@ -325,4 +239,95 @@
|
||||
left: 0px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.screen-tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow-x: hidden;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
overflow-x: overlay;
|
||||
}
|
||||
|
||||
.screen-tab {
|
||||
display: flex;
|
||||
height: 3em;
|
||||
min-width: 14em;
|
||||
max-width: 14em;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
.icon.svg-icon {
|
||||
margin: 0 12px;
|
||||
|
||||
svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.fa-icon {
|
||||
font-size: 16px;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.tab-name {
|
||||
width: 8rem;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-top: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.is-archived {
|
||||
.fa.fa-archive {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-index,
|
||||
.tab-gear {
|
||||
display: none;
|
||||
.icon {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.tab-gear {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .screen-tab .tab-index {
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
&:hover .screen-tab:hover .tab-index {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.new-screen {
|
||||
flex-shrink: 0;
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
height: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
padding: 0.4em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel, GlobalCommandRunner, Session, Screen } from "../../../model/model";
|
||||
@ -17,30 +16,50 @@ import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg"
|
||||
import { ReactComponent as ActionsIcon } from "../../assets/icons/tab/actions.svg";
|
||||
import { ReactComponent as AddIcon } from "../../assets/icons/add.svg";
|
||||
import * as constants from "../../appconst";
|
||||
import { Reorder } from "framer-motion";
|
||||
import { MagicLayout } from "../../magiclayout";
|
||||
|
||||
import "../workspace.less";
|
||||
import "./tabs.less";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
|
||||
@mobxReact.observer
|
||||
class ScreenTabs extends React.Component<{ session: Session }, {}> {
|
||||
class ScreenTabs extends React.Component<{ session: Session }, { showingScreens: Screen[] }> {
|
||||
tabsRef: React.RefObject<any> = React.createRef();
|
||||
tabRefs: { [screenId: string]: React.RefObject<any> } = {};
|
||||
lastActiveScreenId: string = null;
|
||||
scrolling: OV<boolean> = mobx.observable.box(false, { name: "screentabs-scrolling" });
|
||||
|
||||
stopScrolling_debounced: () => void;
|
||||
dragEndTimeout = null;
|
||||
scrollIntoViewTimeout = null;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.stopScrolling_debounced = debounce(1500, this.stopScrolling.bind(this));
|
||||
this.state = {
|
||||
showingScreens: [],
|
||||
};
|
||||
}
|
||||
|
||||
@mobx.computed
|
||||
get activeScreenId(): string {
|
||||
let { session } = this.props;
|
||||
if (session) {
|
||||
return session.activeScreenId.get();
|
||||
}
|
||||
}
|
||||
|
||||
@mobx.computed
|
||||
get screens(): Screen[] {
|
||||
if (this.activeScreenId) {
|
||||
let screens = GlobalModel.getSessionScreens(this.props.session.sessionId);
|
||||
let showingScreens = screens
|
||||
.filter((screen) => !screen.archived.get() || this.activeScreenId === screen.screenId)
|
||||
.sort((a, b) => a.screenIdx.get() - b.screenIdx.get());
|
||||
return showingScreens;
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleNewScreen() {
|
||||
let { session } = this.props;
|
||||
GlobalCommandRunner.createNewScreen();
|
||||
}
|
||||
|
||||
@ -60,38 +79,77 @@ class ScreenTabs extends React.Component<{ session: Session }, {}> {
|
||||
GlobalCommandRunner.switchScreen(screenId);
|
||||
}
|
||||
|
||||
// @boundMethod
|
||||
// handleWheel(event: WheelEvent) {
|
||||
// if (!this.tabsRef.current) return;
|
||||
|
||||
// // Prevent the default vertical scrolling
|
||||
// event.preventDefault();
|
||||
|
||||
// // Scroll horizontally instead
|
||||
// this.tabsRef.current.scrollLeft += event.deltaY;
|
||||
// }
|
||||
|
||||
componentDidMount(): void {
|
||||
this.componentDidUpdate();
|
||||
|
||||
// // Add the wheel event listener to the tabsRef
|
||||
// if (this.tabsRef.current) {
|
||||
// this.tabsRef.current.addEventListener("wheel", this.handleWheel, { passive: false });
|
||||
// }
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.dragEndTimeout) {
|
||||
clearTimeout(this.dragEndTimeout);
|
||||
clearTimeout(this.scrollIntoViewTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(): void {
|
||||
// Scroll the active screen into view
|
||||
let { session } = this.props;
|
||||
let activeScreenId = session.activeScreenId.get();
|
||||
if (activeScreenId != this.lastActiveScreenId && this.tabsRef.current) {
|
||||
let tabElem = this.tabsRef.current.querySelector(
|
||||
sprintf('.screen-tab[data-screenid="%s"]', activeScreenId)
|
||||
);
|
||||
if (tabElem != null) {
|
||||
tabElem.scrollIntoView();
|
||||
if (activeScreenId !== this.lastActiveScreenId) {
|
||||
if (this.scrollIntoViewTimeout) {
|
||||
clearTimeout(this.scrollIntoViewTimeout);
|
||||
}
|
||||
this.scrollIntoViewTimeout = setTimeout(() => {
|
||||
if (this.tabsRef.current) {
|
||||
let tabElem = this.tabsRef.current.querySelector(
|
||||
sprintf('.screen-tab[data-screenid="%s"]', activeScreenId)
|
||||
);
|
||||
if (tabElem) {
|
||||
tabElem.scrollIntoView();
|
||||
}
|
||||
}
|
||||
this.lastActiveScreenId = activeScreenId;
|
||||
}, 100);
|
||||
}
|
||||
this.lastActiveScreenId = activeScreenId;
|
||||
}
|
||||
|
||||
stopScrolling(): void {
|
||||
mobx.action(() => {
|
||||
this.scrolling.set(false);
|
||||
})();
|
||||
// Set the showingScreens state if it's not set or if the number of screens has changed.
|
||||
// Individual screen update are handled automatically by mobx.
|
||||
if (this.screens && this.state.showingScreens.length !== this.screens.length) {
|
||||
this.setState({ showingScreens: this.screens });
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleScroll() {
|
||||
if (!this.scrolling.get()) {
|
||||
mobx.action(() => {
|
||||
this.scrolling.set(true);
|
||||
})();
|
||||
handleDragEnd(screenId) {
|
||||
if (this.dragEndTimeout) {
|
||||
clearTimeout(this.dragEndTimeout);
|
||||
}
|
||||
this.stopScrolling_debounced();
|
||||
|
||||
// Wait for the animation to complete
|
||||
this.dragEndTimeout = setTimeout(() => {
|
||||
const tabElement = this.tabRefs[screenId].current;
|
||||
const finalTabPosition = tabElement.offsetLeft;
|
||||
|
||||
// Calculate the new index based on the final position
|
||||
const newIndex = Math.floor(finalTabPosition / MagicLayout.TabWidth);
|
||||
|
||||
GlobalCommandRunner.screenReorder(screenId, `${newIndex + 1}`);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@ -138,17 +196,28 @@ class ScreenTabs extends React.Component<{ session: Session }, {}> {
|
||||
<i title="shared to web" className="fa-sharp fa-solid fa-share-nodes web-share-icon" />
|
||||
) : null;
|
||||
|
||||
// Create a ref for the tab if it doesn't exist
|
||||
if (!this.tabRefs[screen.screenId]) {
|
||||
this.tabRefs[screen.screenId] = React.createRef();
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={screen.screenId}
|
||||
<Reorder.Item
|
||||
ref={this.tabRefs[screen.screenId]}
|
||||
value={screen}
|
||||
id={screen.name.get()}
|
||||
whileDrag={{
|
||||
backgroundColor: "rgba(13, 13, 13, 0.85)",
|
||||
}}
|
||||
data-screenid={screen.screenId}
|
||||
className={cn(
|
||||
"screen-tab",
|
||||
{ "is-active": activeScreenId == screen.screenId, "is-archived": screen.archived.get() },
|
||||
"color-" + screen.getTabColor()
|
||||
)}
|
||||
onClick={() => this.handleSwitchScreen(screen.screenId)}
|
||||
onPointerDown={() => this.handleSwitchScreen(screen.screenId)}
|
||||
onContextMenu={(event) => this.openScreenSettings(event, screen)}
|
||||
onDragEnd={() => this.handleDragEnd(screen.screenId)}
|
||||
>
|
||||
{this.renderTabIcon(screen)}
|
||||
<div className="tab-name truncate">
|
||||
@ -158,49 +227,39 @@ class ScreenTabs extends React.Component<{ session: Session }, {}> {
|
||||
</div>
|
||||
{tabIndex}
|
||||
{settings}
|
||||
</div>
|
||||
</Reorder.Item>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let { showingScreens } = this.state;
|
||||
let { session } = this.props;
|
||||
if (session == null) {
|
||||
return null;
|
||||
}
|
||||
let screen: Screen | null = null;
|
||||
let index = 0;
|
||||
let showingScreens = [];
|
||||
let activeScreenId = session.activeScreenId.get();
|
||||
let screens = GlobalModel.getSessionScreens(session.sessionId);
|
||||
for (let screen of screens) {
|
||||
if (!screen.archived.get() || activeScreenId == screen.screenId) {
|
||||
showingScreens.push(screen);
|
||||
}
|
||||
}
|
||||
showingScreens.sort((a, b) => {
|
||||
let aidx = a.screenIdx.get();
|
||||
let bidx = b.screenIdx.get();
|
||||
if (aidx < bidx) {
|
||||
return -1;
|
||||
}
|
||||
if (aidx > bidx) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return (
|
||||
<div className="screen-tabs-container">
|
||||
<div
|
||||
className={cn("screen-tabs", { scrolling: this.scrolling.get() })}
|
||||
<Reorder.Group
|
||||
className="screen-tabs"
|
||||
ref={this.tabsRef}
|
||||
onScroll={this.handleScroll}
|
||||
as="ul"
|
||||
axis="x"
|
||||
onReorder={(tabs: Screen[]) => {
|
||||
this.setState({ showingScreens: tabs });
|
||||
}}
|
||||
values={showingScreens}
|
||||
>
|
||||
<For each="screen" index="index" of={showingScreens}>
|
||||
{this.renderTab(screen, activeScreenId, index)}
|
||||
<React.Fragment key={screen.screenId}>
|
||||
{this.renderTab(screen, activeScreenId, index)}
|
||||
</React.Fragment>
|
||||
</For>
|
||||
<div key="new-screen" className="new-screen" onClick={this.handleNewScreen}>
|
||||
<AddIcon className="icon hoverEffect" />
|
||||
</div>
|
||||
</Reorder.Group>
|
||||
<div key="new-screen" className="new-screen" onClick={this.handleNewScreen}>
|
||||
<AddIcon className="icon hoverEffect" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -4196,6 +4196,15 @@ class CommandRunner {
|
||||
GlobalModel.submitCommand("screen", "set", null, kwargs, false);
|
||||
}
|
||||
|
||||
screenReorder(screenId: string, index: string) {
|
||||
let kwargs: Record<string, string> = {
|
||||
nohist: "1",
|
||||
screenId: screenId,
|
||||
index: index,
|
||||
};
|
||||
GlobalModel.submitCommand("screen", "reorder", null, kwargs, false);
|
||||
}
|
||||
|
||||
setTermUsedRows(termContext: RendererContext, height: number) {
|
||||
let kwargs: Record<string, string> = {};
|
||||
kwargs["screen"] = termContext.screenId;
|
||||
|
@ -90,13 +90,14 @@ var SetVarNameMap map[string]string = map[string]string{
|
||||
"anchor": "screen.anchor",
|
||||
"focus": "screen.focus",
|
||||
"line": "screen.line",
|
||||
"index": "screen.index",
|
||||
}
|
||||
|
||||
var SetVarScopes = []SetVarScope{
|
||||
{ScopeName: "global", VarNames: []string{}},
|
||||
{ScopeName: "client", VarNames: []string{"telemetry"}},
|
||||
{ScopeName: "session", VarNames: []string{"name", "pos"}},
|
||||
{ScopeName: "screen", VarNames: []string{"name", "tabcolor", "tabicon", "pos", "pterm", "anchor", "focus", "line"}},
|
||||
{ScopeName: "screen", VarNames: []string{"name", "tabcolor", "tabicon", "pos", "pterm", "anchor", "focus", "line", "index"}},
|
||||
{ScopeName: "line", VarNames: []string{}},
|
||||
// connection = remote, remote = remoteinstance
|
||||
{ScopeName: "connection", VarNames: []string{"alias", "connectmode", "key", "password", "autoinstall", "color"}},
|
||||
@ -167,6 +168,7 @@ func init() {
|
||||
registerCmdFn("screen:showall", ScreenShowAllCommand)
|
||||
registerCmdFn("screen:reset", ScreenResetCommand)
|
||||
registerCmdFn("screen:webshare", ScreenWebShareCommand)
|
||||
registerCmdFn("screen:reorder", ScreenReorderCommand)
|
||||
|
||||
registerCmdAlias("remote", RemoteCommand)
|
||||
registerCmdFn("remote:show", RemoteShowCommand)
|
||||
@ -723,6 +725,45 @@ func ScreenOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func ScreenReorderCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
// Resolve the UI IDs for the session and screen
|
||||
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Extract the screen ID and the new index from the packet
|
||||
screenId := ids.ScreenId
|
||||
newScreenIdxStr := pk.Kwargs["index"]
|
||||
newScreenIdx, err := resolvePosInt(newScreenIdxStr, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid new screen index: %v", err)
|
||||
}
|
||||
|
||||
// Call SetScreenIdx to update the screen's index in the database
|
||||
err = sstore.SetScreenIdx(ctx, ids.SessionId, screenId, newScreenIdx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error updating screen index: %v", err)
|
||||
}
|
||||
|
||||
// Retrieve all session screens
|
||||
screens, err := sstore.GetSessionScreens(ctx, ids.SessionId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving updated screen: %v", err)
|
||||
}
|
||||
|
||||
// Prepare the update packet to send back to the client
|
||||
update := &sstore.ModelUpdate{
|
||||
Screens: screens,
|
||||
Info: &sstore.InfoMsgType{
|
||||
InfoMsg: "screen indices updated successfully",
|
||||
TimeoutMs: 2000,
|
||||
},
|
||||
}
|
||||
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen)
|
||||
if err != nil {
|
||||
|
23
yarn.lock
23
yarn.lock
@ -1699,6 +1699,18 @@
|
||||
minimatch "^3.0.4"
|
||||
plist "^3.0.4"
|
||||
|
||||
"@emotion/is-prop-valid@^0.8.2":
|
||||
version "0.8.8"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
|
||||
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
|
||||
dependencies:
|
||||
"@emotion/memoize" "0.7.4"
|
||||
|
||||
"@emotion/memoize@0.7.4":
|
||||
version "0.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
|
||||
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
|
||||
|
||||
"@gar/promisify@^1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
||||
@ -4225,6 +4237,15 @@ forwarded@0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
||||
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
|
||||
|
||||
framer-motion@^10.16.16:
|
||||
version "10.16.16"
|
||||
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-10.16.16.tgz#a10a03e1190a717109163cfff212a84c8ad11b0c"
|
||||
integrity sha512-je6j91rd7NmUX7L1XHouwJ4v3R+SO4umso2LUcgOct3rHZ0PajZ80ETYZTajzEXEl9DlKyzjyt4AvGQ+lrebOw==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
optionalDependencies:
|
||||
"@emotion/is-prop-valid" "^0.8.2"
|
||||
|
||||
fresh@0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
@ -7891,7 +7912,7 @@ trough@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876"
|
||||
integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==
|
||||
|
||||
tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0:
|
||||
tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
|
||||
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
||||
|
Loading…
Reference in New Issue
Block a user