diff --git a/playground/package.json b/playground/package.json index 9ab486756..1083a038b 100644 --- a/playground/package.json +++ b/playground/package.json @@ -79,6 +79,7 @@ "react-router-dom": "^5.1.2", "react-test-renderer": "^16.12.0", "sass-loader": "^7.3.1", + "storage-factory": "^0.1.1", "style-loader": "^0.23.1", "ts-jest": "^25.2.0", "ts-loader": "^6.2.1", diff --git a/playground/src/components/requestCard/header/header.scss b/playground/src/components/requestCard/header/header.scss index 9dd4ddb02..265d5a106 100644 --- a/playground/src/components/requestCard/header/header.scss +++ b/playground/src/components/requestCard/header/header.scss @@ -14,12 +14,37 @@ } } +.bookmarkIcon { + margin-right: 10px; + color: $c3; + transition: all 0.1s ease-out; + + &.bookmarked { + color: $red; + &:hover { + color: $red-light-4; + } + } + &:hover { + color: $red; + } +} + +.hrefIcon { + margin-right: 10px; + color: $c3; + // margin: 5px; + &:hover { + color: $c8; + } +} + .statusCircle { margin-right: 10px; transition: all 0.2s ease-out; - &.blue { - color: $blue; + &.gray { + color: $c3; } &.green { @@ -53,14 +78,5 @@ .callName { display: flex; align-items: center; - - .hrefIcon { - margin-left: 10px; - color: $c3; - margin: 5px; - &:hover { - color: $c8; - } - } } } diff --git a/playground/src/components/requestCard/header/index.tsx b/playground/src/components/requestCard/header/index.tsx index 6a81fd9ff..91ae01219 100644 --- a/playground/src/components/requestCard/header/index.tsx +++ b/playground/src/components/requestCard/header/index.tsx @@ -1,7 +1,8 @@ -import { faChevronDown, faChevronUp, faCircle, faLink } from "@fortawesome/free-solid-svg-icons"; +import { faBookmark, faChevronDown, faChevronUp, faCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import classNames from "classnames"; import { requestModel, RequestStatus } from "helpers/requestModel"; +import { observer } from 'mobx-react-lite'; import * as React from "react"; import s from "./header.scss"; @@ -10,37 +11,38 @@ interface HeaderProps { model: requestModel; closeCard?: () => void; } - -export default function Header(props: HeaderProps) { +export default observer(Header) +function Header(props: HeaderProps) { const { open, closeCard, model } = props; const colors: Record = { - notFetched: s.blue, + notFetched: s.gray, fetching: s.orange, error: s.red, sucess: s.green, }; const accentColorClass = colors[model.status]; + const onClickBookmark = (event: React.MouseEvent) => { + event.stopPropagation(); + model.toogleBookmark(); + } + if (!open) return ( <>
-
{model.name}
- -
-
- {model.status !== "notFetched" ? ( +
- ) : null} + {model.name} +
+
+
+
-
{model.name}
+
+ + {model.name} +
+
+
+
-
); } diff --git a/playground/src/components/section/index.tsx b/playground/src/components/section/index.tsx new file mode 100644 index 000000000..2d6bb1679 --- /dev/null +++ b/playground/src/components/section/index.tsx @@ -0,0 +1,21 @@ + +import * as React from "react"; +import classnames from "classnames"; +import s from "./section.scss"; + +interface TitleProps { + title: string + featured?: boolean; +} + +export function Section(props: React.PropsWithChildren) { + + const { title, featured, children } = props; + + return ( +
+
{title}
+
{children}
+
+ ); +} diff --git a/playground/src/components/section/section.scss b/playground/src/components/section/section.scss new file mode 100644 index 000000000..739be3156 --- /dev/null +++ b/playground/src/components/section/section.scss @@ -0,0 +1,38 @@ +@import "shared-colors"; + +.section { + &.featured { + .title { + color: $red-dark-1; + + &::after { + background: $red-dark-1; + } + } + } + + .title { + color: $c8; + font-weight: bold; + text-transform: capitalize; + + display: flex; + align-items: flex-start; + text-align: left; + + line-height: 25px; + + &::after { + content: ""; + margin: auto; + height: 2px; + background: $lightgray; + flex-grow: 1; + min-width: 25px; + margin-left: 12px; + } + } + .childrenWrapper { + margin: 20px 0px 40px 0px; + } +} diff --git a/playground/src/helpers/localStorage/bookmarkedEndpoints.ts b/playground/src/helpers/localStorage/bookmarkedEndpoints.ts new file mode 100644 index 000000000..63c7879fe --- /dev/null +++ b/playground/src/helpers/localStorage/bookmarkedEndpoints.ts @@ -0,0 +1,27 @@ +import { safeLocalStorage } from "./safeLocalStorage"; + +const BOOKMARK_LOCALSTORAGE_KEY = "bookmarked_endpoints"; + +export function persistEndpointBookmarkStatus(name: string, status: boolean) { + const currentBookmaked = getLocalStorageBookmarks(); + if (status) { + // bookmark + setLocalStorageBookmarks([...currentBookmaked, name]); + } else { + // unbookmark + setLocalStorageBookmarks(currentBookmaked.filter((n) => n !== name)); + } +} + +export function getLocalStorageBookmarks(): string[] { + const content = safeLocalStorage.getItem(BOOKMARK_LOCALSTORAGE_KEY); + if (content) { + return JSON.parse(content); + } else { + return []; + } +} + +export function setLocalStorageBookmarks(names: string[]) { + safeLocalStorage.setItem(BOOKMARK_LOCALSTORAGE_KEY, JSON.stringify(names)); +} diff --git a/playground/src/helpers/localStorage/safeLocalStorage.ts b/playground/src/helpers/localStorage/safeLocalStorage.ts new file mode 100644 index 000000000..beabd2dd7 --- /dev/null +++ b/playground/src/helpers/localStorage/safeLocalStorage.ts @@ -0,0 +1,3 @@ +import { storageFactory } from "storage-factory"; + +export const safeLocalStorage = storageFactory(() => localStorage); diff --git a/playground/src/helpers/requestModel/index.ts b/playground/src/helpers/requestModel/index.ts index b61cdad03..14e9d90e9 100644 --- a/playground/src/helpers/requestModel/index.ts +++ b/playground/src/helpers/requestModel/index.ts @@ -1,5 +1,6 @@ import { observable } from "mobx"; import { AnnotationJson } from "resources/types/ast"; +import { persistEndpointBookmarkStatus } from "helpers/localStorage/bookmarkedEndpoints"; export type RequestStatus = "notFetched" | "sucess" | "fetching" | "error"; @@ -17,6 +18,7 @@ interface ConstructorArgument { baseUrl: string; deviceId: string; annotations: ModelAnotations; + bookmarked: boolean; } export class requestModel { @@ -38,6 +40,14 @@ export class requestModel { @observable public status: RequestStatus; + @observable + public bookmarked: boolean; + + public async toogleBookmark() { + this.bookmarked = !this.bookmarked; + persistEndpointBookmarkStatus(this.name, this.bookmarked); + } + public async call(args: any, callBack?: (status: RequestStatus) => void) { this.args = args; this.loading = true; @@ -97,6 +107,7 @@ export class requestModel { this.deviceId = config.deviceId; this.baseUrl = config.baseUrl; this.annotations = config.annotations; + this.bookmarked = config.bookmarked; this.loading = false; this.response = undefined; this.error = undefined; diff --git a/playground/src/pages/home/index.tsx b/playground/src/pages/home/index.tsx index 6b98095b4..b3d98afa1 100644 --- a/playground/src/pages/home/index.tsx +++ b/playground/src/pages/home/index.tsx @@ -1,10 +1,12 @@ +import * as React from "react"; +import { Section } from 'components/section'; import { RequestCard } from "components/requestCard"; import { SearchInput } from "components/searchInput"; import { observer } from "mobx-react-lite"; -import * as React from "react"; import RootStore from "stores"; import { useDebounce } from "use-debounce"; import s from "./home.scss"; +import { requestModel } from 'helpers/requestModel'; function Home() { const { requestsStore } = React.useContext(RootStore); @@ -12,20 +14,48 @@ function Home() { const [searchStringDebounced] = useDebounce(searchString, 500); const { api } = requestsStore; - const Cards = Object.entries(api) - .filter(([fnName, _]) => - fnName.toLocaleLowerCase().includes(searchStringDebounced.toLocaleLowerCase()), - ) - .map(([fnName, FnModel]) => { - return ; - }); + + const filterBySearch = (value: [string, requestModel], _index: number, _array: [string, requestModel][]): boolean => { + const [fnName] = value; + return fnName.toLocaleLowerCase().includes(searchStringDebounced.toLocaleLowerCase()); + } + + const filterBookmarked = (value: [string, requestModel], _index: number, _array: [string, requestModel][]): boolean => { + const [, model] = value; + return model.bookmarked; + } + + const renderCard = (value: [string, requestModel], _index: number, _array: [string, requestModel][]): JSX.Element => { + const [fnName, model] = value; + return ; + } + + const BookmarkedCards = Object.entries(api) + .filter(filterBySearch) + .filter(filterBookmarked) + .map(renderCard); + + const AllCards = Object.entries(api) + .filter(filterBySearch) + .map(renderCard); + + const hasBookmarks = BookmarkedCards.length > 0; return (
- {Cards} + {hasBookmarks ? ( + <> +
+ {BookmarkedCards} +
+
+ {AllCards} +
+ + ) : AllCards}
); } diff --git a/playground/src/resources/styles/shared-colors.scss b/playground/src/resources/styles/shared-colors.scss index eda7f63f6..7f966d073 100644 --- a/playground/src/resources/styles/shared-colors.scss +++ b/playground/src/resources/styles/shared-colors.scss @@ -15,6 +15,10 @@ $blue-dark-1: darken($blue, 5%); $red: #ec2c81; $red-dark-1: darken($red, 5%); +$red-light-1: lighten($red, 5%); +$red-light-2: lighten($red, 10%); +$red-light-3: lighten($red, 15%); +$red-light-4: lighten($red, 20%); $orange: #ff9800; $orange-dark-1: darken($orange, 5%); diff --git a/playground/src/stores/config.ts b/playground/src/stores/config.ts index 57718df4a..adc806cdd 100644 --- a/playground/src/stores/config.ts +++ b/playground/src/stores/config.ts @@ -1,5 +1,6 @@ import { observable } from "mobx"; import { RootStore } from "."; +import { safeLocalStorage } from "helpers/localStorage/safeLocalStorage"; const endpointUrlFallback = process.env.NODE_ENV === "development" @@ -40,13 +41,13 @@ export class ConfigStore { private syncWithLocalStorage = (override: boolean) => { if (!override) { - this.deviceId = localStorage.getItem("deviceId") || randomBytesHex(16); + this.deviceId = safeLocalStorage.getItem("deviceId") || randomBytesHex(16); this.endpointUrl = - (this.canChangeEndpoint && localStorage.getItem("endpointUrl")) || + (this.canChangeEndpoint && safeLocalStorage.getItem("endpointUrl")) || endpointUrlFallback; } else { - if (this.deviceId) localStorage.setItem("deviceId", this.deviceId); - if (this.endpointUrl) localStorage.setItem("endpointUrl", this.endpointUrl); + if (this.deviceId) safeLocalStorage.setItem("deviceId", this.deviceId); + if (this.endpointUrl) safeLocalStorage.setItem("endpointUrl", this.endpointUrl); } }; } diff --git a/playground/src/stores/requests.ts b/playground/src/stores/requests.ts index 51a9e2e8b..e62332256 100644 --- a/playground/src/stores/requests.ts +++ b/playground/src/stores/requests.ts @@ -1,3 +1,4 @@ +import { getLocalStorageBookmarks } from "helpers/localStorage/bookmarkedEndpoints"; import { ModelAnotations, requestModel } from "helpers/requestModel"; import { observable } from "mobx"; import { ArgsType, AstJson, TypeDescription, TypeTable } from "resources/types/ast"; @@ -152,10 +153,16 @@ export class RequestsStore { return annotations; }; + private createBookmarkedEndpointIndex = (): Record => { + return getLocalStorageBookmarks().reduce((acc, name) => ({ ...acc, [name]: true }), {}); + }; + public createModels = (AST: AstJson) => { + console.log("createModels"); const { endpointUrl, deviceId } = this.rootStore.configStore; const FNs = Object.entries(AST.functionTable); + const bookmarkedEndpointsIndex = this.createBookmarkedEndpointIndex(); this.api = FNs.reduce((acc, [fName, fStruct]) => { const argsMock = this.createMockBasedOnTypes(fStruct.args, AST.typeTable); const annotations = this.getAnotations(AST, fName); @@ -167,6 +174,7 @@ export class RequestsStore { baseUrl: endpointUrl, deviceId: deviceId!, annotations, + bookmarked: Boolean(bookmarkedEndpointsIndex[fName]), }), }; }, {});