@rpcbase/client 0.179.0 → 0.180.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,7 @@ import {flagValues} from "config/flags"
13
13
 
14
14
  import {POSTHOG_KEY} from "env"
15
15
  import { useAuthRouter } from "../auth/useAuthRouter"
16
+ import { NotificationsProvider, NotificationsContainer } from "../notifications"
16
17
 
17
18
 
18
19
  const PostHogWrapper = ({children, ...props}) => {
@@ -56,10 +57,13 @@ const AppProvider = ({children, ...props}) => {
56
57
  return (
57
58
  <PostHogWrapper>
58
59
  <HashStateProvider>
59
- <div className={cx({"d-none": !!authComponent})}>
60
- {children}
61
- </div>
62
- {authComponent}
60
+ <NotificationsProvider>
61
+ <div className={cx({"d-none": !!authComponent})}>
62
+ {children}
63
+ </div>
64
+ {authComponent}
65
+ <NotificationsContainer />
66
+ </NotificationsProvider>
63
67
  </HashStateProvider>
64
68
  </PostHogWrapper>
65
69
  )
@@ -14,6 +14,7 @@ import {AccountListItem} from "../AccountsList/AccountListItem"
14
14
  import {Footer} from "../Footer"
15
15
 
16
16
  import "./sign-out.scss"
17
+ import { useNotifications } from "../../../notifications"
17
18
 
18
19
  // TODO: rts_disconnect
19
20
  // TODO: clear cache + db
@@ -26,6 +27,8 @@ export const SignOut = ({
26
27
  }) => {
27
28
  const {t} = useTranslation("rb.sign_out", {useSuspense: false})
28
29
 
30
+ const {addNotification} = useNotifications()
31
+
29
32
  const [isSignedOut, setIsSignedOut] = useState(false)
30
33
 
31
34
  const [accounts, setAccounts] = useState()
@@ -44,13 +47,20 @@ export const SignOut = ({
44
47
  load()
45
48
  }, [])
46
49
 
47
-
48
50
  const onSignOut = async() => {
49
51
  // TODO: NYI handle sign out from multiple accounts here
50
52
  const res = await post("/api/v1/auth/sign_out")
51
53
  if (res.status === "ok") {
52
54
  signOut()
53
55
  setIsSignedOut(true)
56
+
57
+ const toast = addNotification({
58
+ // id: `create-template-${Date.now()}`,
59
+ loading: false,
60
+ title: "Signed out",
61
+ message: "You have been signed out click here to sign back in",
62
+ })
63
+
54
64
  onSignOutSuccess()
55
65
  } else {
56
66
  throw new Error("unable to sign out")
@@ -0,0 +1,36 @@
1
+ /* @flow */
2
+ import * as React from "react"
3
+ import { useCallback } from "react";
4
+
5
+ import type {Notification as NotificationType} from "types/notification"
6
+
7
+ import "./notification.scss"
8
+
9
+ type Props = {
10
+ notification: NotificationType,
11
+ onClick?: () => void,
12
+ }
13
+
14
+ const Notification = ({ ref, notification, onClick }: Props) => {
15
+ const handleClick = useCallback(() => {
16
+ if (onClick) {
17
+ onClick();
18
+ }
19
+ }, [onClick]);
20
+
21
+ return (
22
+ <a
23
+ className={cx(["dropdown-item", notification.ack && "disabled"])}
24
+ href="#"
25
+ ref={ref}
26
+ onClick={handleClick}
27
+ >
28
+ <span>{notification.title}</span>
29
+ <p className="text-secondary mb-0" style={{whiteSpace: "initial"}}>
30
+ {notification.description}
31
+ </p>
32
+ </a>
33
+ );
34
+ }
35
+
36
+ export default Notification
@@ -0,0 +1 @@
1
+ @import "helpers";
@@ -0,0 +1,93 @@
1
+ /* @flow */
2
+ import {motion} from "framer-motion"
3
+ import {useEffect, useRef, useState} from "react"
4
+
5
+ import ActivityIndicator from "../../ui/ActivityIndicator"
6
+ import LottiePlayer from "../../ui/LottiePlayer"
7
+
8
+ import {SPRING_BOUNCY} from "../../ui/springs"
9
+
10
+ import checkmarkAnimation from "../../ui/animations/checkmark.json"
11
+
12
+ // TODO: we should have a way to unmount loader when animation complete
13
+ const HeaderStatus = ({isLoading, status}) => {
14
+ const playerRef = useRef()
15
+
16
+ const initiallyLoading = useRef(isLoading)
17
+
18
+ const [hasLoader, setHasLoader] = useState(isLoading)
19
+
20
+ const [statusAnimatedVals, setStatusAnimatedVals] = useState({})
21
+ const [loaderAnimatedVals, setLoaderAnimatedVals] = useState({})
22
+
23
+ useEffect(() => {
24
+ // set initial seeker position, TODO: this doesn't work without the animation frame
25
+ requestAnimationFrame(() => {
26
+ playerRef.current?.setSeeker(16, false)
27
+ })
28
+ }, [])
29
+
30
+ const runTransition = () => {
31
+ setStatusAnimatedVals({scale: 1, opacity: 1})
32
+ setLoaderAnimatedVals({scale: 0, opacity: 0})
33
+ requestAnimationFrame(() => {
34
+ playerRef.current?.play()
35
+ })
36
+ }
37
+
38
+ useEffect(() => {
39
+ // TODO: why was it checking on initiallyLoading.current ?
40
+ // if (!isLoading && initiallyLoading.current) {
41
+ if (!isLoading) {
42
+ runTransition()
43
+ }
44
+ }, [isLoading])
45
+
46
+ const onLoaderAnimationComplete = () => {
47
+ setHasLoader(false)
48
+ }
49
+
50
+ return (
51
+ <div style={{position: "relative", background: "red", width: 28}}>
52
+ <motion.div
53
+ className="status-icon"
54
+ style={{position: "absolute", top: -11}}
55
+ initial={{scale: 0.5, opacity: 0}}
56
+ // initial={{scale: 1, opacity: 1}}
57
+ transition={SPRING_BOUNCY}
58
+ animate={statusAnimatedVals}
59
+ >
60
+ <LottiePlayer
61
+ className={cx("", {})}
62
+ ref={playerRef}
63
+ autoplay={false}
64
+ loop={false}
65
+ speed={1}
66
+ keepLastFrame={true}
67
+ src={checkmarkAnimation}
68
+ style={{
69
+ height: `${23}px`,
70
+ width: `${23}px`,
71
+ // opacity: .15,
72
+ }}
73
+ // onEvent={this.onPlayerEvent}
74
+ />
75
+ </motion.div>
76
+
77
+ {hasLoader && (
78
+ <motion.div
79
+ style={{position: "absolute", left: 1, top: -9}}
80
+ initial={{scale: 1, opacity: 1}}
81
+ // initial={{scale: 1, opacity: 0}}
82
+ transition={SPRING_BOUNCY}
83
+ animate={loaderAnimatedVals}
84
+ onAnimationComplete={onLoaderAnimationComplete}
85
+ >
86
+ <ActivityIndicator size={19} />
87
+ </motion.div>
88
+ )}
89
+ </div>
90
+ )
91
+ }
92
+
93
+ export default HeaderStatus
@@ -0,0 +1,65 @@
1
+ /* @flow */
2
+ import assert from "assert"
3
+ import {useRef} from "react"
4
+ import Toast from "react-bootstrap/Toast"
5
+
6
+ import {useNotifications} from "../NotificationsContext"
7
+
8
+ import HeaderStatus from "./HeaderStatus"
9
+
10
+ // TODO: mv to rb server
11
+ // import ack_notification from "rpc!server/notifications/ack_notification"
12
+
13
+ import "./notification-item.scss"
14
+
15
+
16
+ export const NotificationItem = ({ nodeRef, notification }) => {
17
+ const { setNotifications } = useNotifications()
18
+
19
+ const { isLoading = false, status = "success", zIndex = 999 } = notification
20
+
21
+ const onClick = () => {
22
+ console.log("onclicknotif", notification)
23
+ }
24
+
25
+ // remove notification on close
26
+ const onClose = async () => {
27
+ console.log("onClose!!!")
28
+ if (notification.id) {
29
+ console.warn("ACK NOTIFICATION not copied to rb-server yet")
30
+ // const res = await ack_notification({
31
+ // notification_id: notification.id,
32
+ // ack_at_ms: Date.now(),
33
+ // })
34
+ // assert(res.status === "ok")
35
+ }
36
+
37
+ setNotifications((current) => {
38
+ return current.filter((n) => n.id !== notification.id)
39
+ })
40
+ }
41
+
42
+ // no header when no body
43
+ const hasBody = !!notification.body
44
+
45
+ return (
46
+ <Toast
47
+ ref={nodeRef}
48
+ id={notification.id}
49
+ className={cx("notification-item mt-2 notification-toast", {"has-no-body": !hasBody})}
50
+ style={{position: "relative", zIndex}}
51
+ onClose={onClose}
52
+ animation={false}
53
+ >
54
+ <Toast.Header className="ps-2 py-2" closeButton={true}>
55
+ {notification.icon ? <div>{"<IC>"}</div> : <HeaderStatus isLoading={isLoading} status={status} />}
56
+
57
+ <div className="me-auto">{notification.title}</div>
58
+ {notification.type === "workflow" && <small>{notification.formatted_timestamp}</small>}
59
+ {notification.type === "intent" && <small>INTENT</small>}
60
+ </Toast.Header>
61
+
62
+ {hasBody && <Toast.Body onClick={onClick}>{notification.body}</Toast.Body>}
63
+ </Toast>
64
+ )
65
+ }
@@ -0,0 +1,25 @@
1
+ @import "helpers";
2
+
3
+ .notification-item.toast {
4
+ max-width: 260px;
5
+
6
+ .toast-header {
7
+ color: $gray-900;
8
+ font-weight: 600;
9
+ }
10
+
11
+ &.has-no-body {
12
+ background: $white !important;
13
+
14
+ .toast-header {
15
+ background: transparent !important;
16
+ border-bottom: none;
17
+ }
18
+ }
19
+
20
+ svg {
21
+ path {
22
+ fill: $green-500 !important;
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,37 @@
1
+ /* @flow */
2
+ import {createRef} from "react"
3
+ import {CSSTransition, TransitionGroup} from "react-transition-group"
4
+
5
+ import ToastContainer from "react-bootstrap/ToastContainer"
6
+
7
+ // import {useQuery} from "@rpcbase/client/rts"
8
+
9
+ import {useNotifications} from "../NotificationsContext"
10
+ import {NotificationItem} from "../NotificationItem"
11
+
12
+ // import useLLTs from "./useLLTs"
13
+
14
+ import "./notifications-container.scss"
15
+
16
+ export const NotificationsContainer = () => {
17
+ const {notifications} = useNotifications()
18
+
19
+ const renderNotification = (notification, i) => {
20
+ const key = notification.id
21
+ const nodeRef = createRef()
22
+
23
+ return (
24
+ <CSSTransition key={notification.id} nodeRef={nodeRef} timeout={200} classNames="notification-item">
25
+ <NotificationItem key={key} nodeRef={nodeRef} notification={notification} />
26
+ </CSSTransition>
27
+ )
28
+ }
29
+
30
+ return (
31
+ <ToastContainer id="notifications-container" position="bottom-end" className="pb-4 pe-3 ps-4">
32
+ <TransitionGroup className="transition-list">
33
+ {notifications.map(renderNotification)}
34
+ </TransitionGroup>
35
+ </ToastContainer>
36
+ )
37
+ }
@@ -0,0 +1,38 @@
1
+ @import "helpers";
2
+
3
+ $transition-duration: 200ms;
4
+
5
+ #notifications-container {
6
+ position: absolute;
7
+ bottom: 0;
8
+ right: 0;
9
+ max-height: 100vh;
10
+ overflow-y: scroll;
11
+
12
+ .action-notification {
13
+ cursor: pointer;
14
+ }
15
+
16
+ .transition-list {
17
+ transition: max-height $transition-duration ease-in-out;
18
+ }
19
+
20
+ .notification-item-enter {
21
+ opacity: 0;
22
+ }
23
+
24
+ .notification-item-enter-active {
25
+ opacity: 1;
26
+ transition: opacity 400ms;
27
+ }
28
+
29
+ .notification-item-exit {
30
+ opacity: 1;
31
+ }
32
+
33
+ .notification-item-exit-active {
34
+ transform: translateX(390px);
35
+
36
+ transition: all $transition-duration ease-out;
37
+ }
38
+ }
@@ -0,0 +1,28 @@
1
+ /* @flow */
2
+ import assert from "assert"
3
+ import {useEffect, useState} from "react"
4
+
5
+ // TODO: fix ltts import to rb
6
+ // import get_llts from "rpc!server/notifications/llt/get_llts"
7
+
8
+
9
+ const useLLTs = () => {
10
+ const [llts, setLLTs] = useState([])
11
+
12
+ useEffect(() => {
13
+ const load = async() => {
14
+ // const res = await get_llts({
15
+ // env_id: envContext.envId,
16
+ // })
17
+ const res = {status: "ok", llts: []}
18
+ assert(res.status === "ok", "unable to retrieve llts")
19
+ setLLTs(res.llts)
20
+ }
21
+
22
+ load()
23
+ }, [])
24
+
25
+ return llts
26
+ }
27
+
28
+ export default useLLTs
@@ -0,0 +1,71 @@
1
+ /* @flow */
2
+ import {useEffect, createContext, useContext} from "react"
3
+
4
+ import {useNotificationsList} from "./useNotificationsList"
5
+
6
+ // const ICONS_MAP = {
7
+ // warning: "warning",
8
+ // error: "danger",
9
+ // }
10
+
11
+ export const NotificationsContext = createContext()
12
+
13
+ export const NotificationsProvider = ({value, children}) => {
14
+ const [notifications, setNotifications] = useNotificationsList([])
15
+
16
+ const addNotification = ({
17
+ id,
18
+ title,
19
+ body,
20
+ timestamp = Date.now(),
21
+ zIndex = 2000,
22
+ isLoading = false,
23
+ }) => {
24
+ const notification = {
25
+ id: id || `notif-${Date.now().toString()}`,
26
+ title,
27
+ body,
28
+ timestamp,
29
+ zIndex,
30
+ level: "toast",
31
+ isLoading,
32
+ // icon: ICONS_MAP.error,
33
+ }
34
+
35
+ setNotifications((current) => {
36
+ const next = [...current]
37
+ next.push(notification)
38
+ return next
39
+ })
40
+
41
+ const dismiss = () => {
42
+ setNotifications((current) => {
43
+ const next = [...current]
44
+ return next.filter((n) => n.id !== id)
45
+ })
46
+ }
47
+
48
+ const update = (payload) => {
49
+ setNotifications((current) => {
50
+ const next = [...current]
51
+ return next.map((n) => {
52
+ if (n.id === id) {
53
+ // n.isLoading = false
54
+ Object.assign(n, payload)
55
+ }
56
+ return n
57
+ })
58
+ })
59
+ }
60
+
61
+ return {dismiss, update}
62
+ }
63
+
64
+ return (
65
+ <NotificationsContext.Provider value={{notifications, setNotifications, addNotification}}>
66
+ {children}
67
+ </NotificationsContext.Provider>
68
+ )
69
+ }
70
+
71
+ export const useNotifications = () => useContext(NotificationsContext)
@@ -0,0 +1,75 @@
1
+ /* eslint-disable */
2
+ /* @flow */
3
+ import {useState, useEffect} from "react"
4
+
5
+ import {useQuery} from "../../rts"
6
+
7
+ import {ON_NOTIFICATION_RECEIVED_EVENT} from "../config"
8
+
9
+ export const useNotificationsList = (initial = []) => {
10
+ const [allNotifications, setAllNotifications] = useState(initial)
11
+
12
+ const notificationsQuery = useQuery(
13
+ "Notification",
14
+ {
15
+ $or: [{ack_at_ms: null}, {ack_at_ms: {$exists: false}}],
16
+ },
17
+ {
18
+ projection: {
19
+ _owners: 0,
20
+ },
21
+ },
22
+ )
23
+
24
+ useEffect(() => {
25
+ if (!notificationsQuery.data) return
26
+
27
+ // console.log("NOTIFS TMP RETURN", notificationsQuery.data)
28
+ return
29
+
30
+ // console.log("got new notifs data", notificationsQuery.data)
31
+
32
+ // get all current ids
33
+ const allNotificationsIds = allNotifications.filter((n) => !!n.id).map((n) => n.id)
34
+
35
+ const insertNotifications = notificationsQuery.data.filter(
36
+ (n) => !allNotificationsIds.includes(n._id),
37
+ )
38
+
39
+ const notificationsDataIds = notificationsQuery.data.map((n) => n._id)
40
+
41
+ // remove all notifications that are from the notifications collection
42
+ const removeNotificationsIds = allNotifications
43
+ .filter((n) => !!n.id && n.col === "Notification")
44
+ .filter((n) => !notificationsDataIds.includes(n.id))
45
+ .map((n) => n.id)
46
+
47
+ // update our notifications
48
+ if (insertNotifications.length > 0 || removeNotificationsIds.length > 0) {
49
+ setAllNotifications((previous) => {
50
+ // copy and remove old notifs
51
+ const nextNotifs = [...previous].filter((n) => !removeNotificationsIds.includes(n.id))
52
+
53
+ const formattedNotifications = insertNotifications.map((n) => {
54
+ return {
55
+ id: n._id,
56
+ col: "Notification",
57
+ ...n.notification,
58
+ timestamp: n.server_timestamp_ms,
59
+ }
60
+ })
61
+
62
+ nextNotifs.unshift(...formattedNotifications)
63
+ return nextNotifs
64
+ })
65
+ }
66
+
67
+ // play animation if we added anything
68
+ if (insertNotifications.length > 0) {
69
+ const ev = new CustomEvent(ON_NOTIFICATION_RECEIVED_EVENT)
70
+ document.body.dispatchEvent(ev)
71
+ }
72
+ }, [notificationsQuery.data, allNotifications])
73
+
74
+ return [allNotifications, setAllNotifications]
75
+ }
@@ -0,0 +1,52 @@
1
+ /* @flow */
2
+ import Form from "react-bootstrap/Form"
3
+ import TimePicker from "react-time-picker"
4
+
5
+ import {useStoredValue} from "@rpcbase/client"
6
+
7
+
8
+ const TIME_FORMAT = "HH:mm"
9
+
10
+ const timePickerProps = {
11
+ format: TIME_FORMAT,
12
+ maxDetail: "minute",
13
+ renderSecondHand: false,
14
+ }
15
+
16
+ const SettingsForm = () => {
17
+ const [isEnabled, setIsEnabled] = useStoredValue("has_notifications_enabled", "yes")
18
+
19
+ const [fromTime, setFromTime] = useStoredValue("notifications_from_time", "09:00")
20
+ const [toTime, setToTime] = useStoredValue("notifications_to_time", "22:00")
21
+
22
+ const onToggleNotifications = (e) => {
23
+ setIsEnabled(e.target.checked ? "yes" : "no")
24
+ }
25
+
26
+ return (
27
+ <div className="px-2 py-3">
28
+ <div>
29
+ <div className="h6 fw-bold">Enable Notifications</div>
30
+ <Form.Check
31
+ type="switch"
32
+ id="toggle-notifications-switch"
33
+ label={`Notifications are ${isEnabled ? "enabled" : "disabled"}`}
34
+ checked={isEnabled === "yes"}
35
+ onChange={onToggleNotifications}
36
+ />
37
+ </div>
38
+
39
+ <div className="mt-3">
40
+ <div className="h6 fw-bold">Notifications are allowed:</div>
41
+ <div className="">
42
+ <span className="me-2">From:</span>
43
+ <TimePicker onChange={setFromTime} value={fromTime} {...timePickerProps} />
44
+ <span className="me-2">To:</span>
45
+ <TimePicker onChange={setToTime} value={toTime} {...timePickerProps} />
46
+ </div>
47
+ </div>
48
+ </div>
49
+ )
50
+ }
51
+
52
+ export default SettingsForm
@@ -0,0 +1,48 @@
1
+ /* @flow */
2
+ import ActivityIndicator from "@rpcbase/ui/ActivityIndicator"
3
+ import Modal, {withHashStateModal} from "@rpcbase/ui/Modal"
4
+
5
+ // import TemplatesView from "./TemplatesView"
6
+ // import {TemplateContextProvider} from "./TemplateContext"
7
+
8
+ // import DialogAnimatedPreview from "./components/DialogAnimatedPreview"
9
+ // import TogglePreviewButton from "./components/TogglePreviewButton"
10
+
11
+ import SettingsForm from "./SettingsForm"
12
+
13
+ import BellGlyph from "static/icons/notifications/bell-glyph.svg"
14
+
15
+ import "./notifications-settings.scss"
16
+
17
+
18
+ const NotificationsSettingsModal = ({onHide}) => {
19
+ const isLoading = false
20
+
21
+ return (
22
+ <Modal className="channel-templates-modal" show scrollable={false} onHide={onHide}>
23
+ <Modal.Header className="close-top" closeButton>
24
+ <BellGlyph className="me-2 align-self-start mt-1" width={22} fill={"#0d6efd"} />
25
+ <div>
26
+ <div>Notifications</div>
27
+ </div>
28
+ </Modal.Header>
29
+ <Modal.Body className="p-0" style={{maxHeight: "70vh", overflow: "visible"}}>
30
+ {isLoading && (
31
+ <div className="d-flex flex-row align-items-center">
32
+ <ActivityIndicator size={24} />
33
+ <div className="ms-2">Loading text...</div>
34
+ </div>
35
+ )}
36
+
37
+ <SettingsForm />
38
+ </Modal.Body>
39
+ <Modal.Footer className="d-flex justify-content-between">
40
+ <div>
41
+ For more info see <a href="/docs/notifications">/docs/notifications</a>
42
+ </div>
43
+ </Modal.Footer>
44
+ </Modal>
45
+ )
46
+ }
47
+
48
+ export default withHashStateModal(NotificationsSettingsModal, "showNotificationsSettingsModal")
@@ -0,0 +1 @@
1
+ export const ON_NOTIFICATION_RECEIVED_EVENT = "ON_NOTIFICATION_RECEIVED_EVENT"
@@ -0,0 +1,4 @@
1
+ export * from "./NotificationsContext"
2
+ export * from "./NotificationsContainer"
3
+
4
+ export * from "./config"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/client",
3
- "version": "0.179.0",
3
+ "version": "0.180.0",
4
4
  "scripts": {
5
5
  "test": "../../node_modules/.bin/wireit"
6
6
  },
@@ -86,6 +86,7 @@
86
86
  "@ui-kitten/components": "5.3.1",
87
87
  "figma-squircle": "0.3.1",
88
88
  "firebase": "10.12.3",
89
+ "framer-motion": "11.3.22",
89
90
  "i18next": "23.12.2",
90
91
  "i18next-chained-backend": "4.6.2",
91
92
  "i18next-resources-to-backend": "1.2.1",
@@ -29,7 +29,6 @@ const getUseQuery = (register_query) => (
29
29
  const uid = useMemo(() => {
30
30
  // TODO: why is there a options.userId here ??
31
31
  const _uid = Platform.OS === "web" ? getUid() : options.userId
32
- assert(_uid, "missing uid")
33
32
  return _uid
34
33
  }, [])
35
34
 
@@ -81,7 +80,7 @@ const getUseQuery = (register_query) => (
81
80
  if (Platform.OS === "web") {
82
81
  localStorage.setItem(storageKey, LZString.compressToUTF16(JSON.stringify(newData)))
83
82
  } else {
84
- // TODO: rn fast storage async
83
+ // TODO: RN MMKV
85
84
  }
86
85
  }
87
86
 
package/rts/rts.js CHANGED
@@ -197,6 +197,8 @@ export const register_query = (model_name, query, _options, _callback) => {
197
197
  }
198
198
  _queries_store[model_name][query]
199
199
 
200
+ console.log("QUERY OPTIONSSS", options)
201
+
200
202
  // TODO: why both run and register query here ? the run_query should come straight from register ?
201
203
  _socket.emit("run_query", {model_name, query, query_key, options})
202
204
  _socket.emit("register_query", {model_name, query, query_key, options})
@@ -0,0 +1 @@
1
+ {"v":"5.9.6","fr":24,"ip":0,"op":28,"w":64,"h":64,"nm":"ok","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"line","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[32.948,31.305,0],"ix":2,"l":2},"a":{"a":0,"k":[328.975,313.345,0],"ix":1,"l":2},"s":{"a":0,"k":[10.56,10.56,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-12.898,0.665],[-3.898,8.665],[12.898,-8.665]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[32.897,31.335],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[0]},{"t":25,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"tr","p":{"a":0,"k":[328.975,313.345],"ix":2},"a":{"a":0,"k":[32.897,31.335],"ix":1},"s":{"a":0,"k":[1000,1000],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"line-2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-12.898,0.665],[-3.898,8.665],[12.898,-8.665]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[32.897,31.335],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":3,"s":[0]},{"t":8,"s":[100]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"tr","p":{"a":0,"k":[328.975,313.345],"ix":2},"a":{"a":0,"k":[32.897,31.335],"ix":1},"s":{"a":0,"k":[1000,1000],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"line-1","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":29,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"circle","tt":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[32,31.87,0],"ix":2,"l":2},"a":{"a":0,"k":[32,31.87,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":3,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":7,"s":[94.7,94.7,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":11,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":17,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":21,"s":[94.7,94.7,100]},{"t":25,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-14.36],[14.36,0],[0,14.36],[-14.36,0]],"o":[[0,14.36],[-14.36,0],[0,-14.36],[14.36,0]],"v":[[26,0],[0,26],[-26,0],[0,-26]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[32,31.87],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":29,"st":0,"ct":1,"bm":0}],"markers":[]}
package/ui/springs.js ADDED
@@ -0,0 +1,17 @@
1
+ export const SPRING_DEFAULT = {
2
+ type: "spring",
3
+ stiffness: 420,
4
+ damping: 32,
5
+ }
6
+
7
+ export const SPRING_FASTER = {
8
+ type: "spring",
9
+ stiffness: 490,
10
+ damping: 31,
11
+ }
12
+
13
+ export const SPRING_BOUNCY = {
14
+ type: "spring",
15
+ stiffness: 490,
16
+ damping: 16,
17
+ }