rxjs/operators#groupBy TypeScript Examples

The following examples show how to use rxjs/operators#groupBy. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: fetch-translations.ts    From react-starter-boilerplate with MIT License 6 votes vote down vote up
fromBabelsheet({
  spreadsheetId: babelsheetConfig.spreadsheetId,
  credentials: require(path.join(projectRoot, babelsheetConfig.credentials)),
}).pipe(
  groupBy(
    ({ language }) => language,
    { element: ({ path, ...entry }) => ({ ...entry, path: path.join(".") }) }
  ),
  mergeMap(languageEntries$ => languageEntries$.pipe(
    writeJSONFile(`./src/i18n/data/${languageEntries$.key}.json`)
  )),
).subscribe(
  ({ filePath, entryCount }) => {
    console.log(`Wrote file: "${filePath}" with ${entryCount} entries`);
  }
);
Example #2
Source File: index.tsx    From jetlinks-ui-antd with MIT License 4 votes vote down vote up
TenantDevice: React.FC<Props> = (props) => {
  const initState: State = {
    searchParam: {
      pageSize: 10, sorts: {
        order: "descend",
        field: "createTime"
      }
    },
    productList: [],
    deviceData: {},
  };

  const [searchParam, setSearchParam] = useState(initState.searchParam);
  const [deviceData, setDeviceData] = useState(initState.deviceData);
  const [spinning, setSpinning] = useState(true);

  const statusMap = new Map();
  statusMap.set('online', <Tag color="#87d068">在线</Tag>);
  statusMap.set('offline', <Tag color="#f50">离线</Tag>);
  statusMap.set('notActive', <Tag color="#1890ff">未激活</Tag>);

  const handleSearch = (params?: any) => {
    setSearchParam(params);
    apis.deviceInstance.list(encodeQueryParam(params))
      .then((response: any) => {
        if (response.status === 200) {
          setDeviceData(response.result);
        }
        setSpinning(false);
      })
      .catch(() => {
      })
  };

  const columns: ColumnProps<DeviceInstance>[] = [
    {
      title: 'ID',
      dataIndex: 'id',
    },
    {
      title: '设备名称',
      dataIndex: 'name',
    },
    {
      title: '产品名称',
      dataIndex: 'productName',
    },
    {
      title: '状态',
      dataIndex: 'state',
      width: '90px',
      render: record => record ? statusMap.get(record.value) : '',
      filters: [
        {
          text: '未激活',
          value: 'notActive',
        },
        {
          text: '离线',
          value: 'offline',
        },
        {
          text: '在线',
          value: 'online',
        },
      ],
      filterMultiple: false,
    },
  ];

  useEffect(() => {
    handleSearch(searchParam);
  }, []);


  const onTableChange = (
    pagination: PaginationConfig,
    filters: any,
    sorter: SorterResult<DeviceInstance>,) => {
    setSpinning(true);
    let { terms } = searchParam;
    if (filters.state) {
      if (terms) {
        terms.state = filters.state[0];
      } else {
        terms = {
          state: filters.state[0],
        };
      }
    }
    handleSearch({
      pageIndex: Number(pagination.current) - 1,
      pageSize: pagination.pageSize,
      terms,
      sorts: sorter,
    });
  };


  const service = new Service('');
  const [data, setData] = useState<any[]>([]);

  const user = JSON.parse(localStorage.getItem('user-detail') || '{}');
  const tenantId = (user.tenants || []).filter((i: any) => i.mainTenant)[0]?.tenantId;
  const tenantAdmin = (user.tenants || []).filter((i: any) => i.mainTenant)[0]?.adminMember;

  const getProduct = (userId: string) =>
    service.assets.productNopaging(encodeQueryParam({
      terms: {
        id$assets: JSON.stringify({
          tenantId: tenantId,
          assetType: 'product',
          memberId: userId,
        }),
      }
    }));

  const getDeviceState = (product: any, userId: string) =>
    service.assets.instanceNopaging(encodeQueryParam({
      terms: {
        productId: product.id,
        // id$assets: JSON.stringify({
        //   tenantId: tenantId,
        //   assetType: 'device',
        //   memberId: userId,
        // }),
      }
    })).pipe(
      groupBy((instance: any) => instance.state.value),
      mergeMap(group$ => group$.pipe(
        count(),
        map(count => {
          let v: any = {};
          v[group$.key] = count;
          return v;
        }),
      )),
      map(state => ({ productName: product.name, online: state.online || 0, offline: state.offline || 0 })),
      defaultIfEmpty({ productName: product.name, 'online': 0, 'offline': 0 }),
    );

  const getAlarmCount = (productId: string, userId: string) => service.alarm.count(encodeQueryParam({
    terms: {
      // deviceId$assets: JSON.stringify({
      //   tenantId: tenantId,
      //   assetType: 'device',
      //   memberId: userId,
      // }),
      productId: productId,
    }
  }));

  useEffect(() => {
    // todo 查询租户
    if (tenantId) {
      service.member.queryNoPaging({})
        .pipe(
          flatMap((i: any) => getProduct(i.userId)
            .pipe(
              flatMap((product: any) =>
                zip(getDeviceState(product, i.userId), getAlarmCount(product.id, i.userId))),
              map(tp2 => ({ userId: i.userId, name: i.name, key: `${i.userId}-${randomString(7)}`, ...tp2[0], alarmCount: tp2[1] })),
              defaultIfEmpty({ userId: i.userId, name: i.name, key: `${i.userId}` })
            )),
          toArray(),
          map(list => list.sort((a, b) => a.userId - b.userId)),
        ).subscribe((result) => {
          setData(result);
        });
    }
  }, [tenantId]);

  const test: string[] = [];

  const columns2 = [
    {
      title: '成员',
      dataIndex: 'name',
      render: (text, row, index) => {
        test.push(text);
        return {
          children: text,
          props: {
            rowSpan: test.filter(i => i === text).length > 1 ? 0 : data.filter(i => i.name === text).length,
          },
        };
      },
    },
    {
      title: '产品',
      dataIndex: 'productName',
      // render: renderContent,
    },
    {
      title: '设备在线',
      // colSpan: 2,
      dataIndex: 'online',
      render: (text: any) => text || 0,
      // render: (value, row, index) => {
      //     const obj = {
      //         children: value,
      //         props: {
      //             // rowSpan: 0,
      //         },
      //     };
      //     if (index === 2) {
      //         obj.props.rowSpan = 2;
      //     }
      //     // These two are merged into above cell
      //     if (index === 3) {
      //         obj.props.rowSpan = 0;
      //     }
      //     if (index === 4) {
      //         obj.props.colSpan = 0;
      //     }
      //     return obj;
      // },
    },
    {
      title: '设备离线',
      // colSpan: 0,
      dataIndex: 'offline',
      render: (text: any) => text || 0,
    },
    {
      dataIndex: 'alarmCount',
      title: '告警记录',
      render: (text: any) => text || 0,
    },
  ];


  return (
    <Spin spinning={spinning}>
      <Card bordered={false}>
        <div className={styles.tableList}>
          <div className={styles.tableListForm}>
            {/* <Search
              search={(params: any) => {
                setSpinning(true);
                params.state = searchParam.terms?.state;
                handleSearch({ terms: params, pageSize: 10, sorts: searchParam.sorts });
              }}
            /> */}
          </div>
          <div className={styles.StandardTable} style={{ marginTop: 10 }}>
            {/* <Table
              size='middle'
              columns={columns}
              dataSource={(deviceData || {}).data}
              rowKey="id"
              onChange={onTableChange}
              pagination={{
                current: deviceData.pageIndex + 1,
                total: deviceData.total,
                pageSize: deviceData.pageSize,
              }}
            /> */}
            <Table
              size="small"
              pagination={false}
              columns={tenantAdmin ? columns2 : columns2.filter(i => i.dataIndex !== 'name')}
              dataSource={data}
              bordered />
          </div>
        </div>
      </Card>
    </Spin>
  );
}
Example #3
Source File: Status.tsx    From jetlinks-ui-antd with MIT License 4 votes vote down vote up
Status: React.FC<Props> = props => {
    const ref = useRef<any[]>();
    const { device } = props;
    const metadata = JSON.parse(device.metadata);
    const [properties$, setProperties$] = useState<any[]>([]);
    const [propertiesList, setPropertiesList] = useState<string[]>([]);
    const events = metadata.events
        .map((item: any) => {
            item.listener = [];
            item.subscribe = (callback: Function) => {
                item.listener.push(callback)
            }
            item.next = (data: any) => {
                item.listener.forEach((element: any) => {
                    element(data);
                });
            }
            return item;
        });
    const [properties, setProperties] = useState<any[]>(metadata.properties
        .map((item: any) => {
            item.listener = [];
            item.subscribe = (callback: Function) => {
                item.listener.push(callback)
            }
            item.next = (data: any) => {
                item.listener.forEach((element: any) => {
                    element(data);
                });
            }
            return item;
        }));

    const [loading, setLoading] = useState<boolean>(false);

    const eventsWs: Observable<any> = getWebsocket(
        `instance-info-event-${device.id}-${device.productId}`,
        `/dashboard/device/${device.productId}/events/realTime`,
        {
            deviceId: device.id,
        },
    ).pipe(
        map(result => result.payload)
    );

    let propertiesMap = {};
    properties.forEach(item => propertiesMap[item.id] = item);

    let eventsMap = {};
    events.forEach((item: any) => eventsMap[item.id] = item);

    const [index, setIndex] = useState<number>(15);

    const service = new Service();

    useEffect(() => {
        const list1: any = []
        metadata.properties.slice(0, 15).map((item: any) => {
            list1.push(item.id)
        })
        setPropertiesList([...list1])
    }, []);

    useEffect(() => {
        if (propertiesList.length > 0) {
            const list = [{
                'dashboard': 'device',
                'object': device.productId,
                'measurement': 'properties',
                'dimension': 'history',
                'params': {
                    'deviceId': device.id,
                    'history': 15,
                    'properties': propertiesList
                },
            }];

            service.propertiesRealTime(list).pipe(
                groupBy((group$: any) => group$.property),
                mergeMap(group => group.pipe(toArray())),
                map(arr => ({
                    list: arr.sort((a, b) => a.timestamp - b.timestamp),
                    property: arr[0].property
                }))
            ).subscribe((data) => {
                const index = properties.findIndex(item => item.id === data.property);
                if (index > -1) {
                    properties[index].list = data.list;
                }
            }, () => {
                message.error('错误处理');
            }, () => {
                setLoading(true);
                setProperties(properties);
            })
          // websocket
          const propertiesWs: Observable<any> = getWebsocket(
            `instance-info-property-${device.id}-${device.productId}-${propertiesList.join('-')}`,
            `/dashboard/device/${device.productId}/properties/realTime`,
            {
              deviceId: device.id,
              properties: propertiesList,
              history: 0,
            },
          ).pipe(
            map(result => result.payload)
          ).subscribe((resp) => {
            const property = resp.value.property;
            const item = propertiesMap[property];
            if (item) {
              item.next(resp);
            }
          });
          properties$.push(propertiesWs)
          setProperties$([...properties$])
        }else if(metadata.properties.length === 0){
            setLoading(true);
        }
    }, [propertiesList]);

    useEffect(() => {

        const events$ = eventsWs.subscribe((resp) => {
            const event = resp.value.event;
            const item = eventsMap[event];
            if (item) {
                item.next(resp);
            }
        });

        return () => {
            events$.unsubscribe()
        };
    }, []);


    const renderProperties = useCallback(
        () => {
            const propertyCard = properties.map((item: any) => (
              <Col {...topColResponsiveProps} key={item.id}>
                <PropertiesCard
                  item={item}
                  key={item.id}
                  device={device}
                />
              </Col>
            ))
            const eventCard = events.map((item: any) => (
                <Col {...topColResponsiveProps} key={item.id}>
                    <EventCard item={item} device={device} key={item.id} />
                </Col>
            ));
            return [...propertyCard, ...eventCard].splice(0, index)

        }, [device, index]);

    window.onscroll = () => {
        var a = document.documentElement.scrollTop;
        var c = document.documentElement.scrollHeight;

        var b = document.body.clientHeight;
        if (a + b >= c - 50) {
            const list: any = [];
            metadata.properties.slice(index, index + 15).map(item => {
                list.push(item.id)
            })
            setPropertiesList([...list]);
            setIndex(index + 15);
        }
    }

    useEffect(() => {
        ref.current = properties$;
    })

    useEffect(() => {
        return () => {
            let list = ref.current;
            list && (list || []).map(item => {
                item && item.unsubscribe()
            })
        }
    }, []);

    return (
        <Spin spinning={!loading}>
            <Row gutter={24} id="device-instance-status">
                <Col {...topColResponsiveProps}>
                    <DeviceState
                        refresh={() => { props.refresh() }}
                        state={device.state}
                        runInfo={device}
                    />
                </Col>
                {loading && renderProperties()}

            </Row>
        </Spin>
    )
}
Example #4
Source File: Status1.tsx    From jetlinks-ui-antd with MIT License 4 votes vote down vote up
Status: React.FC<Props> = props => {
    const { device } = props;
    const metadata = JSON.parse(device.metadata);
    const events = metadata.events
        .map((item: any) => {
            item.listener = [];
            item.subscribe = (callback: Function) => {
                item.listener.push(callback)
            }
            item.next = (data: any) => {
                item.listener.forEach((element: any) => {
                    element(data);
                });
            }
            return item;
        });
    const [properties, setProperties] = useState<any[]>(metadata.properties
        .map((item: any) => {
            item.listener = [];
            item.subscribe = (callback: Function) => {
                item.listener.push(callback)
            }
            item.next = (data: any) => {
                item.listener.forEach((element: any) => {
                    element(data);
                });
            }
            return item;
        }));

    const [loading, setLoading] = useState<boolean>(false);
    const propertiesWs: Observable<any> = getWebsocket(
        `instance-info-property-${device.id}-${device.productId}`,
        `/dashboard/device/${device.productId}/properties/realTime`,
        {
            deviceId: device.id,
            properties: [],
            history: 0,
        },
    ).pipe(
        map(result => result.payload)
    );

    const eventsWs: Observable<any> = getWebsocket(
        `instance-info-event-${device.id}-${device.productId}`,
        `/dashboard/device/${device.productId}/events/realTime`,
        {
            deviceId: device.id,
        },
    ).pipe(
        map(result => result.payload)
    );

    let propertiesMap = {};
    properties.forEach(item => propertiesMap[item.id] = item);
    let eventsMap = {};
    events.forEach((item: any) => eventsMap[item.id] = item);

    const [index, setIndex] = useState<number>(20);
    useEffect(() => {

        const properties$ = propertiesWs.subscribe((resp) => {
            const property = resp.value.property;
            const item = propertiesMap[property];
            if (item) {
                item.next(resp);
            }
        });

        const events$ = eventsWs.subscribe((resp) => {
            const event = resp.value.event;
            const item = eventsMap[event];
            if (item) {
                item.next(resp);
            }
        });

        return () => {
            properties$.unsubscribe();
            events$.unsubscribe()
        };

    }, []);


    const renderProperties = useCallback(
        () => {
            const propertyCard = properties.map((item: any) => (
                <Col {...topColResponsiveProps} key={item.id}>
                    <PropertiesCard
                        item={item}
                        key={item.id}
                        device={device}
                    />
                </Col>
            ))
            const eventCard = events.map((item: any) => (
                <Col {...topColResponsiveProps} key={item.id}>
                    <EventCard item={item} device={device} key={item.id} />
                </Col>
            ));
            return [...propertyCard, ...eventCard].splice(0, index)
        }, [device, index]);

    const service = new Service();

    useEffect(() => {
        const list = [{
            'dashboard': 'device',
            'object': device.productId,
            'measurement': 'properties',
            'dimension': 'history',
            'params': {
                'deviceId': device.id,
                'history': 15,
            },
        }];

        service.propertiesRealTime(list).pipe(
            groupBy((group$: any) => group$.property),
            flatMap(group => group.pipe(toArray())),
            map(arr => ({
                list: arr.sort((a, b) => a.timestamp - b.timestamp),
                property: arr[0].property
            }))
        ).subscribe((data) => {
            const index = properties.findIndex(item => item.id === data.property);
            if (index > -1) {
                properties[index].list = data.list;
            }
        }, () => {
            message.error('错误处理');
        }, () => {
            setLoading(true);
            setProperties(properties);
            console.log(properties)
        })
    }, []);

    window.onscroll = () => {
        var a = document.documentElement.scrollTop;
        var c = document.documentElement.scrollHeight;

        var b = document.body.clientHeight;
        if (a + b >= c - 50) {
            setIndex(index + 10);
        }
    }

    return (
        <Spin spinning={!loading}>
            <Row gutter={24} id="device-instance-status" >
                <Col {...topColResponsiveProps}>
                    <DeviceState
                        refresh={() => { props.refresh() }}
                        state={device.state}
                        runInfo={device}
                    />
                </Col>

                {loading && renderProperties()}
            </Row>
        </Spin>
    )
}
Example #5
Source File: Status.tsx    From jetlinks-ui-antd with MIT License 4 votes vote down vote up
Status: React.FC<Props> = props => {
    const { device } = props;
    const metadata = JSON.parse(device.metadata);
    // const events = metadata.events
    //     .map((item: any) => {
    //         item.listener = [];
    //         item.subscribe = (callback: Function) => {
    //             item.listener.push(callback)
    //         }
    //         item.next = (data: any) => {
    //             item.listener.forEach((element: any) => {
    //                 element(data);
    //             });
    //         }
    //         return item;
    //     });
    const [properties, setProperties] = useState<any[]>(metadata.properties
        .map((item: any) => {
            item.listener = [];
            item.subscribe = (callback: Function) => {
                item.listener.push(callback)
            }
            item.next = (data: any) => {
                item.listener.forEach((element: any) => {
                    element(data);
                });
            }
            return item;
        }));

    const [loading, setLoading] = useState<boolean>(false);
    const propertiesWs: Observable<any> = getWebsocket(
        `instance-info-property-${device.id}-${device.productId}`,
        `/dashboard/device/${device.productId}/properties/realTime`,
        {
            deviceId: device.id,
            history: 0,
        },
    ).pipe(
        map(result => result.payload)
    );

    // const eventsWs: Observable<any> = getWebsocket(
    //     `instance-info-event-${device.id}-${device.productId}`,
    //     `/dashboard/device/${device.productId}/events/realTime`,
    //     {
    //         deviceId: device.id,
    //     },
    // ).pipe(
    //     map(result => result.payload)
    // );

    let propertiesMap = {};
    properties.forEach(item => propertiesMap[item.id] = item);
    // let eventsMap = {};
    // events.forEach((item: any) => eventsMap[item.id] = item);

    const [index, setIndex] = useState<number>(20);
    useEffect(() => {

        const properties$ = propertiesWs.subscribe((resp) => {
            const property = resp.value.property;
            const item = propertiesMap[property];
            if (item) {
                item.next(resp);
            }
        });

        // const events$ = eventsWs.subscribe((resp) => {
        //     const event = resp.value.event;
        //     const item = eventsMap[event];
        //     if (item) {
        //         item.next(resp);
        //     }
        // });

        return () => {
            properties$.unsubscribe();
            // events$.unsubscribe()
        };

    }, []);


    const renderProperties = useCallback(
        () => {
            const propertyCard = properties.map((item: any) => (
                <Col {...topColResponsiveProps} key={item.id}>
                    <PropertiesCard
                        item={item}
                        key={item.id}
                        device={device}
                        deviceId={props.deviceId}
                    />
                </Col>
            ))
            // const eventCard = events.map((item: any) => (
            //     <Col {...topColResponsiveProps} key={item.id}>
            //         <EventCard item={item} device={device} key={item.id} />
            //     </Col>
            // ));
            return [...propertyCard].splice(0, index)
        }, [device, index]);

    const service = new Service();

    useEffect(() => {
        const list = [{
            'dashboard': 'device',
            'object': device.productId,
            'measurement': 'properties',
            'dimension': 'history',
            'params': {
                'deviceId': device.id,
                'history': 15,
            },
        }];

        service.propertiesRealTime(list).pipe(
            groupBy((group$: any) => group$.property),
            flatMap(group => group.pipe(toArray())),
            map(arr => ({
                list: arr.sort((a, b) => a.timestamp - b.timestamp),
                property: arr[0].property
            }))
        ).subscribe((data) => {
            const index = properties.findIndex(item => item.id === data.property);
            if (index > -1) {
                properties[index].list = data.list;
            }
        }, () => {
            message.error('错误处理');
        }, () => {
            setLoading(true);
            setProperties(properties);
        })
    }, []);

    window.onscroll = () => {
        var a = document.documentElement.scrollTop;
        var c = document.documentElement.scrollHeight;

        var b = document.body.clientHeight;
        if (a + b >= c - 50) {
            setIndex(index + 10);
        }
    }

    return (
        <Spin spinning={!loading}>
            <Row gutter={24} id="device-instance-status" >
                <Col {...topColResponsiveProps}>
                    <DeviceState
                        refresh={() => { props.refresh() }}
                        state={device.state}
                        runInfo={device}
                    />
                </Col>

                {loading && renderProperties()}
            </Row>
        </Spin>
    )
}
Example #6
Source File: status.tsx    From jetlinks-ui-antd with MIT License 4 votes vote down vote up
Status: React.FC<Props> = props => {
    const { device, edgeTag } = props;
    const metadata = JSON.parse(device.metadata);
    
    const [properties, setProperties] = useState<any[]>(metadata.properties
        .map((item: any) => {
            item.listener = [];
            item.subscribe = (callback: Function) => {
                item.listener.push(callback)
            }
            item.next = (data: any) => {
                item.listener.forEach((element: any) => {
                    element(data);
                });
            }
            return item;
        }));
    const [loading, setLoading] = useState<boolean>(false);

    const propertiesWs: Observable<any> = getWebsocket(
        `instance-info-property-${device.id}-${device.productId}`,
        `/dashboard/device/${device.productId}/properties/realTime`,
        {
            deviceId: device.id,
            history: 0,
        },
    ).pipe(
        map(result => result.payload)
    );

    const propertiesEdge: Observable<any> = getWebSocket(
        `instance-info-property-${device.id}-${device.productId}`,
        `/edge-gateway-state/device/${device.productId}/properties/realTime`,
        {
            deviceId: device.id,
            history: 0,
        },
    ).pipe(
        map(result => result.payload)
    );

    let propertiesMap = {};
    properties.forEach(item => propertiesMap[item.id] = item);

    const [index, setIndex] = useState<number>(20);

    useEffect(() => {
        let properties$: Subscription | null = null;

        if (edgeTag) {
            properties$ = propertiesEdge.subscribe((resp) => {
                const property = resp.value.property;
                const item = propertiesMap[property];
                if (item) {
                    item.next(resp);
                }
            });
        } else {
            properties$ = propertiesWs.subscribe((resp) => {
                const property = resp.value.property;
                const item = propertiesMap[property];
                if (item) {
                    item.next(resp);
                }
            });
        }
        return () => {
            properties$ && properties$.unsubscribe();
        };
    }, []);


    const renderProperties = useCallback(
        () => {
            const propertyCard = properties.map((item: any) => (
                <Col {...topColResponsiveProps} key={item.id}>
                    <PropertiesCard
                        item={item}
                        key={item.id}
                        device={device}
                    />
                </Col>
            ))
            return [...propertyCard].splice(0, index)
        }, [device, index]);

    const service = new Service();

    useEffect(() => {
        const list = [{
            'dashboard': 'device',
            'object': device.productId,
            'measurement': 'properties',
            'dimension': 'history',
            'params': {
                'deviceId': device.id,
                'history': 15,
            },
        }];

        service.propertiesRealTime(list).pipe(
            groupBy((group$: any) => group$.property),
            flatMap(group => group.pipe(toArray())),
            map(arr => ({
                list: arr.sort((a, b) => a.timestamp - b.timestamp),
                property: arr[0].property
            }))
        ).subscribe((data) => {
            const index = properties.findIndex(item => item.id === data.property);
            if (index > -1) {
                properties[index].list = data.list;
            }
        }, () => {
            message.error('错误处理');
        }, () => {
            setLoading(true);
            setProperties(properties);
        })
    }, []);

    // window.onscroll = () => {
    //     var a = document.documentElement.scrollTop;
    //     var c = document.documentElement.scrollHeight;

    //     var b = document.body.clientHeight;
    //     if (a + b >= c - 50) {
    //         setIndex(index + 10);
    //     }
    // }

    return (
        <Spin spinning={!loading}>
            <Row gutter={24} id="device-edge-status" >
                <Col {...topColResponsiveProps}>
                    <DeviceState
                        refresh={() => { props.refresh() }}
                        state={device.state}
                        runInfo={device}
                    />
                </Col>

                {loading && renderProperties()}
            </Row>
        </Spin>
    )
}