From 10342b46b2a12730f4f85b77a3e3dd6964d7c412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Mendes?= Date: Sun, 13 Nov 2022 21:35:35 +0000 Subject: [PATCH] new sample Flight-tracker --- samples/react-flighttracker/README.md | 37 ++++-- ...2-11-10 at 20.49.03.png => sharepoint.png} | Bin ...mage 10-11-2022 at 21.03.jpg => teams.jpg} | Bin .../FlightTracker/FlightTrackerControl.tsx | 1 + .../FlightTracker/IFlightTrackerProps.ts | 3 +- .../FlightTrackerList/FlightTrackerList.tsx | 35 +++-- .../useFlightTrackerListColumns.tsx | 125 ------------------ .../useFlightTrackerStyles.ts | 8 +- .../SelectAirport/SelectAirportPicker.tsx | 47 +++++-- .../src/components/SelectDate/SelectDate.tsx | 9 +- .../SelectInformationType.tsx | 32 ++++- .../src/hooks/useAirlines.ts | 15 +-- .../src/hooks/useLocalStorage.ts | 46 +++---- .../src/models/IGlobalState.ts | 1 + .../src/recoil/atoms/globalState.ts | 1 + .../flightTracker/FlightTrackerWebPart.ts | 97 ++++++-------- 16 files changed, 190 insertions(+), 267 deletions(-) rename samples/react-flighttracker/src/assets/{Screenshot 2022-11-10 at 20.49.03.png => sharepoint.png} (100%) rename samples/react-flighttracker/src/assets/{Image 10-11-2022 at 21.03.jpg => teams.jpg} (100%) delete mode 100644 samples/react-flighttracker/src/components/FlightTrackerList/useFlightTrackerListColumns.tsx diff --git a/samples/react-flighttracker/README.md b/samples/react-flighttracker/README.md index 1501e5703..c939b5506 100644 --- a/samples/react-flighttracker/README.md +++ b/samples/react-flighttracker/README.md @@ -5,10 +5,10 @@ This WebPart allows to track all flights from a selected airport, date and information type. The SPFx use external API (https://aerodatabox.p.rapidapi.com/flights/number/) to get data of flight, please see https://rapidapi.com/aedbx-aedbx/api/aerodatabox/ to get more information. There is some restritions on this API, the number of requests is limited (free version) -![SharePoint View](assets/FQzNLB4XwAIFMRh.jpg "SharePoint View") -![Teams View](assets/FQzO9YjWUAgFlrU.jpg "Teams View") +![SharePoint View](./src/assets/sharepoint.png "SharePoint View") +![Teams View](./src/assets/teams.jpg "Teams View") -## Compatibility +React- ![SPFx 1.15](https://img.shields.io/badge/SPFx-1.15-green.svg) ![Node.js v14 | v12](https://img.shields.io/badge/Node.js-v14%20%7C%20v12-green.svg) @@ -39,25 +39,36 @@ The SPFx use external API (https://aerodatabox.p.rapidapi.com/flights/number/) t Solution|Author(s) --------|--------- -react-fluentui-9 | [Nick Brown](https://github.com/techienickb) ([@techienickb](https://twitter.com/techienickb) / [Jisc](https://jisc.ac.uk)) +react-flighttracker | [João Mendes](https://github.com/joaojmendes) ([@joaojmendes](https://twitter.com/joaojmendes)), StaffBase | ## Version history Version|Date|Comments -------|----|-------- -1.0|April 20, 2022|Initial release +1.0|November 11, 2022|Initial release -- Clone this repository (or [download this solution as a .ZIP file](https://pnp.github.io/download-partial/?url=https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-fluentui-9) then unzip it) -- From your command-line, change your current directory to the directory containing this sample (`react-fluentui-9`, located under `samples`) -- in the command-line run: - - `npm install` - - `gulp serve` +## Minimal Path to Awesome -> This sample can also be opened with [VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview). Visit for further instructions. +- Clone this repository +- Ensure that you are at the solution folder + + - in the command line run: + - `npm install` + - `gulp build --ship` + - `gulp bundle --ship` + - `gulp package-solution --ship` + - Browse to your SharePoint app catalog and load the SPFx package. + - Browse to your SharePoint Admin Center and under advanced you will need to open Api Access and allow the requests for Microsoft Graph. +- If you have the APIs permissions already allowed you can follow the below steps. + + - in the command line run: + *`npm install` + * `gulp serve --nobrowser` + - browse to your hosted workbench [https://YOURTENANT.sharepoint.com/sites/_layouts/15/workbench.aspx](https://YOURTENANT.sharepoint.com/sites/_layouts/15/workbench.aspx) and add the adaptive card extension. ## Features -Very simple demo, the handling of the theme provider is interesting implementing it and handling the custom themes SharePoint can use. + External API , Global State Management with Recoil Library, React Hooks , CCS-In-JS Styling ## References @@ -88,4 +99,4 @@ Finally, if you have an idea for improvement, [make a suggestion](https://github **THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.** - + diff --git a/samples/react-flighttracker/src/assets/Screenshot 2022-11-10 at 20.49.03.png b/samples/react-flighttracker/src/assets/sharepoint.png similarity index 100% rename from samples/react-flighttracker/src/assets/Screenshot 2022-11-10 at 20.49.03.png rename to samples/react-flighttracker/src/assets/sharepoint.png diff --git a/samples/react-flighttracker/src/assets/Image 10-11-2022 at 21.03.jpg b/samples/react-flighttracker/src/assets/teams.jpg similarity index 100% rename from samples/react-flighttracker/src/assets/Image 10-11-2022 at 21.03.jpg rename to samples/react-flighttracker/src/assets/teams.jpg diff --git a/samples/react-flighttracker/src/components/FlightTracker/FlightTrackerControl.tsx b/samples/react-flighttracker/src/components/FlightTracker/FlightTrackerControl.tsx index 6216ca3b5..393396c56 100644 --- a/samples/react-flighttracker/src/components/FlightTracker/FlightTrackerControl.tsx +++ b/samples/react-flighttracker/src/components/FlightTracker/FlightTrackerControl.tsx @@ -25,6 +25,7 @@ export const FlightTrackerControl: React.FunctionComponent context: context, numberItemsPerPage: numberItemsPerPage, webpartContainerWidth: webpartContainerWidth, + }); }, [isDarkTheme, hasTeamsContext, currentTheme, context, setGlobalState, webpartContainerWidth]); diff --git a/samples/react-flighttracker/src/components/FlightTracker/IFlightTrackerProps.ts b/samples/react-flighttracker/src/components/FlightTracker/IFlightTrackerProps.ts index 87c4b2750..7c7304703 100644 --- a/samples/react-flighttracker/src/components/FlightTracker/IFlightTrackerProps.ts +++ b/samples/react-flighttracker/src/components/FlightTracker/IFlightTrackerProps.ts @@ -11,5 +11,6 @@ export interface IFlightTrackerProps { numberItemsPerPage: number; displayMode: DisplayMode; updateProperty: (value: string) => void; - webpartContainerWidth: number; + webpartContainerWidth: number; + } diff --git a/samples/react-flighttracker/src/components/FlightTrackerList/FlightTrackerList.tsx b/samples/react-flighttracker/src/components/FlightTrackerList/FlightTrackerList.tsx index d1e453cba..c63e739a7 100644 --- a/samples/react-flighttracker/src/components/FlightTrackerList/FlightTrackerList.tsx +++ b/samples/react-flighttracker/src/components/FlightTrackerList/FlightTrackerList.tsx @@ -38,7 +38,14 @@ export const FlightTrackerList: React.FunctionComponent const [appState, setGlobalState] = useRecoilState(globalState); const [airlineList, setAirlineList] = useRecoilState(airlineState); const { mapFlightSchedules } = useMappingFlightSchedules(); - const { selectedAirPort, selectedInformationType, selectedDate, numberItemsPerPage, selectedTime } = appState; + const { + selectedAirPort, + selectedInformationType, + selectedDate, + numberItemsPerPage, + selectedTime, + + } = appState; const [isLoadingItems, setIsLoadingItems] = React.useState(true); const [errorMessage, setErrorMessage] = React.useState(""); const [listItems, setListItems] = React.useState([]); @@ -51,6 +58,7 @@ export const FlightTrackerList: React.FunctionComponent const [hasMore, setHasMore] = React.useState(true); const pageIndex = React.useRef(0); const currentInformationType = React.useRef(selectedInformationType); + const [timerId, setTimerId] = React.useState(undefined); const checkTypeInformationToScroll = React.useCallback(() => { if (selectedInformationType !== currentInformationType.current) { @@ -59,6 +67,20 @@ export const FlightTrackerList: React.FunctionComponent } }, [selectedInformationType]); + const onRefresh = React.useCallback(async () => { + pageIndex.current = 0; + const currentDateTime = new Date(); + setGlobalState( + (prevState) => + ({ + ...prevState, + selectedDate: currentDateTime, + selectedTime: currentDateTime, + } as IGlobalState) + ); + setIsRefreshing(true); + }, [appState]); + React.useEffect(() => { if (!isEmpty(airlines)) { setAirlineList(airlines); @@ -139,17 +161,6 @@ export const FlightTrackerList: React.FunctionComponent return (isLoadingFlightSchedules || loadingAirlines || isLoadingItems) && !showMessage; }, [isLoadingFlightSchedules, loadingAirlines, isLoadingItems, errorFlightSchedules]); - const onRefresh = React.useCallback(async () => { - pageIndex.current = 0; - const currentDateTime = new Date(); - setGlobalState((prevState) => ({ - ...prevState, - selectedDate: currentDateTime, - selectedTime: currentDateTime, - } as IGlobalState)); - setIsRefreshing(true); - }, [appState]); - if (!selectedAirPort || !selectedInformationType) { return null; } diff --git a/samples/react-flighttracker/src/components/FlightTrackerList/useFlightTrackerListColumns.tsx b/samples/react-flighttracker/src/components/FlightTrackerList/useFlightTrackerListColumns.tsx deleted file mode 100644 index 99d4948dc..000000000 --- a/samples/react-flighttracker/src/components/FlightTrackerList/useFlightTrackerListColumns.tsx +++ /dev/null @@ -1,125 +0,0 @@ -/* eslint-disable react/self-closing-comp */ -import * as React from 'react'; - -import { IColumn } from 'office-ui-fabric-react/lib/DetailsList'; - -import { IFlightStatus } from '../../models/IFlightStatus'; -import { IFlightTrackerListItem } from '../../models/IFlightTrackerListItem'; -import { FlightStatus } from '../FlightStatus/FlightStatus'; -import { useFlightTrackerStyles } from './useFlightTrackerStyles'; - -export interface IFlightTrackerListColumns { - getListColumns: () => IColumn[]; -} - -export const useFlightTrackerListColumns = ():IFlightTrackerListColumns => { - const { controlStyles } = useFlightTrackerStyles(); - - const getListColumns = React.useCallback((): IColumn[] => { - return [ - { - key: "column1", - name: " flightCompanyImage", - className: controlStyles.fileIconCell, - iconClassName: controlStyles.fileIconHeaderIcon, - ariaLabel: "company image", - isIconOnly: true, - fieldName: "image", - minWidth: 70, - maxWidth: 70, - onRender: (item: IFlightTrackerListItem) => { - return ( - - ); - } - }, - { - key: "column2", - name: "Air line", - fieldName: "flightCompany", - minWidth: 210, - maxWidth: 210, - isRowHeader: true, - isResizable: true, - data: "string", - isPadded: true, - }, - { - key: "column6", - - name: "Flight", - fieldName: "flightNumber", - minWidth: 70, - maxWidth: 90, - isResizable: true, - isCollapsible: true, - data: "number", - onRender: (item: IFlightTrackerListItem) => { - return {item.flightNumber}; - }, - }, - { - key: "column3", - name: "time", - fieldName: "flightTime", - minWidth: 70, - maxWidth: 90, - isResizable: true, - - data: "number", - onRender: (item: IFlightTrackerListItem) => { - return {item.flightTime}; - }, - isPadded: true, - }, - { - key: "column4", - name: "Terminal", - fieldName: "flightTerminal", - minWidth: 70, - maxWidth: 90, - isResizable: true, - isCollapsible: true, - data: "string", - - onRender: (item: IFlightTrackerListItem) => { - return {item.flightTerminal}; - }, - isPadded: true, - }, - { - key: "column5", - name: "Origem", - fieldName: "flightOrigin", - minWidth: 150, - maxWidth: 150, - isResizable: true, - isCollapsible: true, - data: "number", - onRender: (item: IFlightTrackerListItem) => { - return {item.flightOrigin}; - }, - }, - - { - key: "column6", - name: "Status", - fieldName: "flightTimeStatus", - minWidth: 70, - maxWidth: 90, - isResizable: true, - isCollapsible: true, - data: "number", - onRender: (item: IFlightTrackerListItem) => { - const flightInfo:IFlightStatus = { - date: item.flightRealTime, status: item.flightTimeStatusText, - flightId: item.flightNumber - }; - return ; - }, - }, - ]; - }, []); - - return {getListColumns } -}; diff --git a/samples/react-flighttracker/src/components/FlightTrackerList/useFlightTrackerStyles.ts b/samples/react-flighttracker/src/components/FlightTrackerList/useFlightTrackerStyles.ts index 50b4c8842..09174575e 100644 --- a/samples/react-flighttracker/src/components/FlightTrackerList/useFlightTrackerStyles.ts +++ b/samples/react-flighttracker/src/components/FlightTrackerList/useFlightTrackerStyles.ts @@ -15,7 +15,7 @@ import { globalState } from '../../recoil/atoms'; export const useFlightTrackerStyles = () => { const [globalStateApp] = useRecoilState(globalState); - const { currentTheme } = globalStateApp; + const { currentTheme, numberItemsPerPage } = globalStateApp; const listHeaderStyles: ITextStyles = React.useMemo(() => { return { root: { fontWeight: FontWeights.semibold, color: currentTheme?.semanticColors?.bodyText } }; @@ -56,11 +56,11 @@ export const useFlightTrackerStyles = () => { const scollableContainerStyles: Partial = React.useMemo(() => { return { - root: { position: "relative", height: 87 * 6 , + root: { position: "relative", height: (87 * numberItemsPerPage) - 50 , }, contentContainer: { "::-webkit-scrollbar-thumb": { - backgroundColor: currentTheme?.semanticColors.bodyFrameBackground , }, + backgroundColor: currentTheme?.palette.themeLight, }, "::-webkit-scrollbar": { height: 10, width: 7, @@ -69,7 +69,7 @@ export const useFlightTrackerStyles = () => { "scrollbar-color": currentTheme?.semanticColors.bodyFrameBackground, "scrollbar-width": "thin", }, }; - }, [currentTheme]); + }, [currentTheme, numberItemsPerPage]); const stackContainerStyles: IStackStyles= React.useMemo(() => { return { diff --git a/samples/react-flighttracker/src/components/SelectAirport/SelectAirportPicker.tsx b/samples/react-flighttracker/src/components/SelectAirport/SelectAirportPicker.tsx index d9c60e6a7..876da3ae6 100644 --- a/samples/react-flighttracker/src/components/SelectAirport/SelectAirportPicker.tsx +++ b/samples/react-flighttracker/src/components/SelectAirport/SelectAirportPicker.tsx @@ -16,6 +16,7 @@ import { import { useRecoilState } from 'recoil'; import { useAirports } from '../../hooks/useAirports'; +import { useLocalStorage } from '../../hooks/useLocalStorage'; import { IAirport } from '../../models/IAirport'; import { globalState } from '../../recoil/atoms'; import { Airport } from './Airport'; @@ -32,7 +33,9 @@ export const SelectAirportPicker: React.FunctionComponent = () => { const { searchAirportsByText } = useAirports(); const [selectedAirport, setSelectedAirports] = React.useState([]); const { controlStyles, selecteAirportPickerStyles } = useSelectAirportStyles(); - + const { context } = appState; + const SELECTED_AIRPORT_SESSION_STORAGE_KEY = "___selectedAirport___"; + const [getSelectedAirportFromSessionStorage, SetSelectedAirporttoSessionStorage] = useLocalStorage(); const inputProps: IInputProps = React.useMemo(() => { return { placeholder: "Select an airport", @@ -93,14 +96,11 @@ export const SelectAirportPicker: React.FunctionComponent = () => { ); const onRenderSuggestionsItem = React.useCallback( - (props:ITagExtended, itemProps: ISuggestionItemProps) => { + (props: ITagExtended, itemProps: ISuggestionItemProps) => { const { airportData } = props; return (
- +
); }, @@ -118,30 +118,50 @@ export const SelectAirportPicker: React.FunctionComponent = () => { airport={airport} onRemove={(airport) => { setSelectedAirports([]); - setAppState({ ...appState, selectedAirPort: undefined }); + setAppState({ ...appState, selectedAirPort: undefined }); }} /> ); }, - [appState] + [appState,setSelectedAirports,setAppState] ); const pickerCalloutPropsStyles = (props: ICalloutContentStyleProps) => { return { root: { width: divRef?.current?.offsetWidth } }; }; - const onPickerChange = React.useCallback((items: ITag[]) => { - setAppState({ ...appState, selectedAirPort: (items[0] as ITagExtended)?.airportData }); - }, [appState]); + const onPickerChange = React.useCallback( + (items: ITag[]) => { + SetSelectedAirporttoSessionStorage( + `${SELECTED_AIRPORT_SESSION_STORAGE_KEY}${context.instanceId}`, + items[0] as ITagExtended + ); + setAppState({ ...appState, selectedAirPort: (items[0] as ITagExtended)?.airportData }); + }, + [appState] + ); + + React.useEffect(() => { + if (context) { + const selectedAirportInSessionStorage = getSelectedAirportFromSessionStorage( + `${SELECTED_AIRPORT_SESSION_STORAGE_KEY}${context.instanceId}` + ); + if (selectedAirportInSessionStorage) { + setSelectedAirports([selectedAirportInSessionStorage]); + setAppState({ + ...appState, + selectedAirPort: (selectedAirportInSessionStorage as ITagExtended)?.airportData, + }); + } + } + }, [context]); return (
-
{ }} />
-
); }; diff --git a/samples/react-flighttracker/src/components/SelectDate/SelectDate.tsx b/samples/react-flighttracker/src/components/SelectDate/SelectDate.tsx index 77ead6f00..30a3e5ea7 100644 --- a/samples/react-flighttracker/src/components/SelectDate/SelectDate.tsx +++ b/samples/react-flighttracker/src/components/SelectDate/SelectDate.tsx @@ -19,12 +19,16 @@ export interface ISelectDateProps {} export const SelectDate: React.FunctionComponent = ( props: React.PropsWithChildren ) => { + const { selectedDateStyle, textFieldStyles, labelDateStyles, labelTimeStyles } = useSelctedDateStyles(); const [appState, setAppState] = useRecoilState(globalState); - const { selectedDate, selectedTime } = appState; + const { selectedDate, selectedTime } = appState; + const onSelectDate = React.useCallback( (date: Date | null | undefined) => { + + setAppState({ ...appState, selectedDate: date, @@ -33,6 +37,9 @@ export const SelectDate: React.FunctionComponent = ( [appState, setAppState, selectedTime] ); + + + const datePickerRef = React.useRef(null); return ( <> diff --git a/samples/react-flighttracker/src/components/SelectInformationType/SelectInformationType.tsx b/samples/react-flighttracker/src/components/SelectInformationType/SelectInformationType.tsx index 474138383..ea3144564 100644 --- a/samples/react-flighttracker/src/components/SelectInformationType/SelectInformationType.tsx +++ b/samples/react-flighttracker/src/components/SelectInformationType/SelectInformationType.tsx @@ -14,6 +14,7 @@ import { useRecoilState } from 'recoil'; import { EInformationType } from '../../constants/EInformationType'; import { EInformationTypesIcons } from '../../constants/EInformationTypesIcons'; +import { useLocalStorage } from '../../hooks/useLocalStorage'; import { globalState } from '../../recoil/atoms'; import { useSelectInformationStyles } from './useSelectInformationStyles'; @@ -22,8 +23,11 @@ export interface ISelectInformationTypeProps {} export const SelectInformationType: React.FunctionComponent = ( props: React.PropsWithChildren ) => { + const SELECTED_INFORMATION_TYPE_SESSION_STORAGE_KEY = "___selectedInformationType___"; + const [getSelectedInfTypeFromSessionStorage, setSelectedInfTypeToSessionStorage] = useLocalStorage(); const [appState, setAppState] = useRecoilState(globalState); - const { dropdownStyles, controlStyles } = useSelectInformationStyles(); + const { context } = appState; + const { dropdownStyles, controlStyles } = useSelectInformationStyles(); const options: IDropdownOption[] = React.useMemo(() => { return [ { key: "Header", text: "Options", itemType: DropdownMenuItemType.Header }, @@ -79,6 +83,20 @@ export const SelectInformationType: React.FunctionComponent { + if (context) { + const selectedInformationTypeInSessionStorage = getSelectedInfTypeFromSessionStorage( + `${SELECTED_INFORMATION_TYPE_SESSION_STORAGE_KEY}${context.instanceId}` + ); + if (selectedInformationTypeInSessionStorage) { + + setAppState( (prevState) => { + return {...prevState, selectedInformationType: selectedInformationTypeInSessionStorage}; + }); + } + } + }, [context ]); + return ( <> - setAppState({ ...appState, selectedInformationType: option.key as EInformationType }) - } + onChange={(event, option) => { + if (option) { + setAppState( (prevState) => { return {...prevState, selectedInformationType: option.key as EInformationType};}); + setSelectedInfTypeToSessionStorage( + `${SELECTED_INFORMATION_TYPE_SESSION_STORAGE_KEY}${context.instanceId}`, + option.key + ); + } + }} selectedKey={appState.selectedInformationType} /> diff --git a/samples/react-flighttracker/src/hooks/useAirlines.ts b/samples/react-flighttracker/src/hooks/useAirlines.ts index 5fc1109fb..4b78d66d5 100644 --- a/samples/react-flighttracker/src/hooks/useAirlines.ts +++ b/samples/react-flighttracker/src/hooks/useAirlines.ts @@ -9,21 +9,19 @@ import { import { IAirlines } from '../models/IAirlines'; import { useLocalStorage } from './useLocalStorage'; -const airlinesData = require("../mockData/airlines.json"); +const airlinesData = require("../mockData/airlines.json"); /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/no-floating-promises */ export const useAirlines = () => { const [error, setError] = useState(null); const [loading, setLoading] = useState(false); - const [airlines, setAirlines] = useState({} as IAirlines); + const [airlines, setAirlines] = useState({} as IAirlines); - const [airlinesLocalStorage, setAirLinesLocalStorage] = useLocalStorage("__airlines__", []); + const [getAirLinesFromSessionStorage, setAirLinesToSessionStorage] = useLocalStorage(); const fetchAirlines = useCallback(async () => { try { - - setAirLinesLocalStorage(airlinesData); - + setAirLinesToSessionStorage("__airlines__",airlinesData); } catch (error) { if (DEBUG) { console.log("[useAirLines] error", error); @@ -36,12 +34,13 @@ export const useAirlines = () => { useEffect(() => { (async () => { setLoading(true); - if (!airlinesLocalStorage?.rows?.length ) { + const airlinesFromSessionStorage = getAirLinesFromSessionStorage("__airlines__"); + if (!airlinesFromSessionStorage?.rows?.length) { await fetchAirlines(); setAirlines(airlinesData); setError(undefined); } else { - setAirlines(airlinesLocalStorage); + setAirlines(airlinesFromSessionStorage); } setLoading(false); diff --git a/samples/react-flighttracker/src/hooks/useLocalStorage.ts b/samples/react-flighttracker/src/hooks/useLocalStorage.ts index 0d9d65b58..14f3d2c69 100644 --- a/samples/react-flighttracker/src/hooks/useLocalStorage.ts +++ b/samples/react-flighttracker/src/hooks/useLocalStorage.ts @@ -1,36 +1,30 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import * as React from 'react'; +import addSeconds from 'date-fns/addSeconds'; +import isAfter from 'date-fns/isAfter'; + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ interface IStorage { value: unknown; expires?: Date; } -const DEFAULT_EXPIRED_IN_SECONDS = 60 * 60 * 1000; // 1 hour +const DEFAULT_EXPIRED_IN_SECONDS = 60 * 60; // 1 hour -const getStorageValue = (key:string, defaultValue:unknown):any => { - const storage:IStorage = JSON.parse(sessionStorage.getItem(key) || '{}'); - // getting stored value - const { value, expires } = storage || {} as IStorage; - if (expires > new Date() ) { - return value || defaultValue; - } - return undefined ; -} +export const useLocalStorage = (): any => { + const setStorageValue = (key: string, newValue: unknown, expiredInSeconds: number) => { + const expires = addSeconds(new Date(), expiredInSeconds ?? DEFAULT_EXPIRED_IN_SECONDS); + sessionStorage.setItem(key, JSON.stringify({ value: newValue, expires })); + }; + const getStorageValue = (key: string): any => { + const storage: IStorage = JSON.parse(sessionStorage.getItem(key) || "{}"); + // getting stored value + const { value, expires } = storage || ({} as IStorage); + if (isAfter(new Date(expires), new Date())) { + return value; + } + return undefined; + }; -export const useLocalStorage = (key:string, defaultValue:unknown, expiredIn?: number):any => { - const [value, setStorageValue] = React.useState(() => { - return getStorageValue(key, defaultValue); - }); - - React.useEffect(() => { - // save value - const expiredInValue = expiredIn ? new Date(new Date().getTime() + expiredIn * 1000) : DEFAULT_EXPIRED_IN_SECONDS; - - sessionStorage.setItem(key, JSON.stringify({ value, expires:expiredInValue })); - - }, [key, value, expiredIn]); - - return [value, setStorageValue]; + return [getStorageValue, setStorageValue]; }; diff --git a/samples/react-flighttracker/src/models/IGlobalState.ts b/samples/react-flighttracker/src/models/IGlobalState.ts index 8572bf8fd..a91cfed82 100644 --- a/samples/react-flighttracker/src/models/IGlobalState.ts +++ b/samples/react-flighttracker/src/models/IGlobalState.ts @@ -18,4 +18,5 @@ export interface IGlobalState { isScrolling: boolean; hasMore: boolean; webpartContainerWidth: number; + } diff --git a/samples/react-flighttracker/src/recoil/atoms/globalState.ts b/samples/react-flighttracker/src/recoil/atoms/globalState.ts index 750b607cc..2edb66e1a 100644 --- a/samples/react-flighttracker/src/recoil/atoms/globalState.ts +++ b/samples/react-flighttracker/src/recoil/atoms/globalState.ts @@ -19,5 +19,6 @@ export const globalState = atom({ isScrolling: false, hasMore: true, webpartContainerWidth: 0 + }, }); diff --git a/samples/react-flighttracker/src/webparts/flightTracker/FlightTrackerWebPart.ts b/samples/react-flighttracker/src/webparts/flightTracker/FlightTrackerWebPart.ts index 471623bf2..a3c310cfa 100644 --- a/samples/react-flighttracker/src/webparts/flightTracker/FlightTrackerWebPart.ts +++ b/samples/react-flighttracker/src/webparts/flightTracker/FlightTrackerWebPart.ts @@ -14,7 +14,6 @@ import { IPropertyPaneConfiguration, PropertyPaneSlider, PropertyPaneTextField, - PropertyPaneToggle, } from '@microsoft/sp-property-pane'; import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base'; @@ -34,18 +33,14 @@ export interface IFlightTrackerWebPartProps { displayMode: DisplayMode; updateProperty: (value: string) => void; numberItemsPerPage: number; - refreshInterval: number; - enableRefreshInterval: boolean; + } export default class FlightTrackerWebPart extends BaseClientSideWebPart { - private _isDarkTheme: boolean = false; private containerWidth: number = 0; private _currentTheme: IReadonlyTheme | undefined; - - // Apply Teams Context private _applyTheme = (theme: string): void => { this.context.domElement.setAttribute("data-theme", theme); @@ -53,67 +48,63 @@ export default class FlightTrackerWebPart extends BaseClientSideWebPart = React.createElement( - FlightTracker, - { - title: this.properties.title, - isDarkTheme: this._isDarkTheme, - context: this.context, - hasTeamsContext: !!this.context.sdks.microsoftTeams, - currentTheme: this._currentTheme , - displayMode: this.displayMode, - numberItemsPerPage: this.properties.numberItemsPerPage, - updateProperty: (value: string) => { - this.properties.title = value; - }, - webpartContainerWidth: this.containerWidth - } - ); + const element: React.ReactElement = React.createElement(FlightTracker, { + title: this.properties.title, + isDarkTheme: this._isDarkTheme, + context: this.context, + hasTeamsContext: !!this.context.sdks.microsoftTeams, + currentTheme: this._currentTheme, + displayMode: this.displayMode, + numberItemsPerPage: this.properties.numberItemsPerPage, + updateProperty: (value: string) => { + this.properties.title = value; + }, + webpartContainerWidth: this.containerWidth + + }); ReactDom.render(element, this.domElement); } protected onInit(): Promise { - - - if (this.context.sdks.microsoftTeams ) { + if (this.context.sdks.microsoftTeams) { // in teams ? const teamsContext = this.context.sdks.microsoftTeams?.context; this._applyTheme(teamsContext.theme || "default"); - this.context.sdks.microsoftTeams.teamsJs.registerOnThemeChangeHandler( - this._applyTheme - ); + this.context.sdks.microsoftTeams.teamsJs.registerOnThemeChangeHandler(this._applyTheme); } this.containerWidth = this.domElement.clientWidth; return super.onInit(); } - protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void { if (!currentTheme) { return; @@ -127,7 +118,7 @@ export default class FlightTrackerWebPart extends BaseClientSideWebPart