mirror of
https://github.com/dsprenkels/backpack.git
synced 2026-05-04 03:00:05 -04:00
Match feature parity with Go api again
This commit is contained in:
167
web/package-lock.json
generated
167
web/package-lock.json
generated
@@ -8,15 +8,17 @@
|
||||
"name": "backpack-vite",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@syncedstore/core": "^0.6.0",
|
||||
"@syncedstore/react": "^0.6.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.2.0",
|
||||
"yjs": "^13.6.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@vitejs/plugin-react": "^2.1.0",
|
||||
"github-fork-ribbon-css": "^0.2.3",
|
||||
"react-router-dom": "^6.4.0",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^3.1.0",
|
||||
@@ -513,6 +515,22 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@reactivedata/react": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@reactivedata/react/-/react-0.2.2.tgz",
|
||||
"integrity": "sha512-fJ8qoHRbicQnVcnwfHa9FO73mbHFfl590xpuGM4Mo1P2ClaDATfl9oUrrySE+tbA//M9cn8De8HTwjoNqt91sw==",
|
||||
"dependencies": {
|
||||
"@reactivedata/reactive": "^0.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/@reactivedata/reactive": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@reactivedata/reactive/-/reactive-0.2.2.tgz",
|
||||
"integrity": "sha512-KnINM/Sng25QAv6sHkJO9q/XyslLegCF5jTsTSVu+AouY3uZDVf4Am99xNCqsfqFZFvnTBBDvCsHNdvTVGvPEA=="
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.0.tgz",
|
||||
@@ -522,6 +540,37 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@syncedstore/core": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@syncedstore/core/-/core-0.6.0.tgz",
|
||||
"integrity": "sha512-6TtjEoYJsceYi8u1oRecXwbbLmjHaU0S7HvVfOaEdDfphZLGm/faVuA2fpazqc28F0yIFGvYzvPEBUJn9vqRNw==",
|
||||
"dependencies": {
|
||||
"@reactivedata/reactive": "^0.2.0",
|
||||
"@syncedstore/yjs-reactive-bindings": "^0.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"yjs": "^13.5.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@syncedstore/react": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@syncedstore/react/-/react-0.6.0.tgz",
|
||||
"integrity": "sha512-pc8ycnBuH2wp7Td5nGzP9Dn2mEW9Yg1qF0yGdjJ5UdgLEwfRkq+8/hdQl+alcoSribv9St/Bi5hJckX8GVsAbQ==",
|
||||
"dependencies": {
|
||||
"@reactivedata/react": "^0.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@syncedstore/core": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@syncedstore/yjs-reactive-bindings": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@syncedstore/yjs-reactive-bindings/-/yjs-reactive-bindings-0.6.0.tgz",
|
||||
"integrity": "sha512-VF78h0J4iOt79YU9d6j5E6bFKu7WXYuiI2ue9ZnA+T4SNVn8viRvg0AHm3NqHzudZZUgYT3dpnbv1/ZmU7yPZQ==",
|
||||
"peerDependencies": {
|
||||
"yjs": "^13.5.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.18.0.tgz",
|
||||
@@ -1360,12 +1409,6 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/github-fork-ribbon-css": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/github-fork-ribbon-css/-/github-fork-ribbon-css-0.2.3.tgz",
|
||||
"integrity": "sha512-cmGBV4sivRwmnteSOkqMjN2cnP5/J1SU5aDCVYsBWHmDokZ/JjwGEkduCxY9IULHdCPpw1WSk5Cy8N1LF6jOEw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
@@ -1408,6 +1451,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/isomorphic.js": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
|
||||
"integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==",
|
||||
"funding": {
|
||||
"type": "GitHub Sponsors ❤",
|
||||
"url": "https://github.com/sponsors/dmonad"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -1437,6 +1489,26 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/lib0": {
|
||||
"version": "0.2.93",
|
||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.93.tgz",
|
||||
"integrity": "sha512-M5IKsiFJYulS+8Eal8f+zAqf5ckm1vffW0fFDxfgxJ+uiVopvDdd3PxJmz0GsVi3YNO7QCFSq0nAsiDmNhLj9Q==",
|
||||
"dependencies": {
|
||||
"isomorphic.js": "^0.2.4"
|
||||
},
|
||||
"bin": {
|
||||
"0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js",
|
||||
"0gentesthtml": "bin/gentesthtml.js",
|
||||
"0serve": "bin/0serve.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "GitHub Sponsors ❤",
|
||||
"url": "https://github.com/sponsors/dmonad"
|
||||
}
|
||||
},
|
||||
"node_modules/local-pkg": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.2.tgz",
|
||||
@@ -1907,6 +1979,22 @@
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/yjs": {
|
||||
"version": "13.6.14",
|
||||
"resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.14.tgz",
|
||||
"integrity": "sha512-D+7KcUr0j+vBCUSKXXEWfA+bG4UQBviAwP3gYBhkstkgwy5+8diOPMx0iqLIOxNo/HxaREUimZRxqHGAHCL2BQ==",
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.86"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "GitHub Sponsors ❤",
|
||||
"url": "https://github.com/sponsors/dmonad"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -2266,12 +2354,48 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"@reactivedata/react": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@reactivedata/react/-/react-0.2.2.tgz",
|
||||
"integrity": "sha512-fJ8qoHRbicQnVcnwfHa9FO73mbHFfl590xpuGM4Mo1P2ClaDATfl9oUrrySE+tbA//M9cn8De8HTwjoNqt91sw==",
|
||||
"requires": {
|
||||
"@reactivedata/reactive": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"@reactivedata/reactive": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@reactivedata/reactive/-/reactive-0.2.2.tgz",
|
||||
"integrity": "sha512-KnINM/Sng25QAv6sHkJO9q/XyslLegCF5jTsTSVu+AouY3uZDVf4Am99xNCqsfqFZFvnTBBDvCsHNdvTVGvPEA=="
|
||||
},
|
||||
"@remix-run/router": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.0.tgz",
|
||||
"integrity": "sha512-SCR1cxRSMNKjaVYptCzBApPDqGwa3FGdjVHc+rOToocNPHQdIYLZBfv/3f+KvYuXDkUGVIW9IAzmPNZDRL1I4A==",
|
||||
"dev": true
|
||||
},
|
||||
"@syncedstore/core": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@syncedstore/core/-/core-0.6.0.tgz",
|
||||
"integrity": "sha512-6TtjEoYJsceYi8u1oRecXwbbLmjHaU0S7HvVfOaEdDfphZLGm/faVuA2fpazqc28F0yIFGvYzvPEBUJn9vqRNw==",
|
||||
"requires": {
|
||||
"@reactivedata/reactive": "^0.2.0",
|
||||
"@syncedstore/yjs-reactive-bindings": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"@syncedstore/react": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@syncedstore/react/-/react-0.6.0.tgz",
|
||||
"integrity": "sha512-pc8ycnBuH2wp7Td5nGzP9Dn2mEW9Yg1qF0yGdjJ5UdgLEwfRkq+8/hdQl+alcoSribv9St/Bi5hJckX8GVsAbQ==",
|
||||
"requires": {
|
||||
"@reactivedata/react": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"@syncedstore/yjs-reactive-bindings": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@syncedstore/yjs-reactive-bindings/-/yjs-reactive-bindings-0.6.0.tgz",
|
||||
"integrity": "sha512-VF78h0J4iOt79YU9d6j5E6bFKu7WXYuiI2ue9ZnA+T4SNVn8viRvg0AHm3NqHzudZZUgYT3dpnbv1/ZmU7yPZQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"@testing-library/dom": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.18.0.tgz",
|
||||
@@ -2798,12 +2922,6 @@
|
||||
"integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==",
|
||||
"dev": true
|
||||
},
|
||||
"github-fork-ribbon-css": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/github-fork-ribbon-css/-/github-fork-ribbon-css-0.2.3.tgz",
|
||||
"integrity": "sha512-cmGBV4sivRwmnteSOkqMjN2cnP5/J1SU5aDCVYsBWHmDokZ/JjwGEkduCxY9IULHdCPpw1WSk5Cy8N1LF6jOEw==",
|
||||
"dev": true
|
||||
},
|
||||
"globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
@@ -2834,6 +2952,11 @@
|
||||
"has": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"isomorphic.js": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
|
||||
"integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw=="
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -2851,6 +2974,14 @@
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"dev": true
|
||||
},
|
||||
"lib0": {
|
||||
"version": "0.2.93",
|
||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.93.tgz",
|
||||
"integrity": "sha512-M5IKsiFJYulS+8Eal8f+zAqf5ckm1vffW0fFDxfgxJ+uiVopvDdd3PxJmz0GsVi3YNO7QCFSq0nAsiDmNhLj9Q==",
|
||||
"requires": {
|
||||
"isomorphic.js": "^0.2.4"
|
||||
}
|
||||
},
|
||||
"local-pkg": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.2.tgz",
|
||||
@@ -3136,6 +3267,14 @@
|
||||
"tinyspy": "^1.0.2",
|
||||
"vite": "^2.9.12 || ^3.0.0-0"
|
||||
}
|
||||
},
|
||||
"yjs": {
|
||||
"version": "13.6.14",
|
||||
"resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.14.tgz",
|
||||
"integrity": "sha512-D+7KcUr0j+vBCUSKXXEWfA+bG4UQBviAwP3gYBhkstkgwy5+8diOPMx0iqLIOxNo/HxaREUimZRxqHGAHCL2BQ==",
|
||||
"requires": {
|
||||
"lib0": "^0.2.86"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@syncedstore/core": "^0.6.0",
|
||||
"@syncedstore/react": "^0.6.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.2.0",
|
||||
"yjs": "^13.6.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/react": "^13.4.0",
|
||||
|
||||
@@ -10,14 +10,14 @@ import { AppStateContext, SetAppStateContext } from './main';
|
||||
function TagList(
|
||||
props: {
|
||||
allTags: string[],
|
||||
selectedTags: Set<string>,
|
||||
selectedTags: {[key: string]: true},
|
||||
onSelectTag: (tag: string, enabled: boolean) => void,
|
||||
}) {
|
||||
let tagElems = props.allTags.map(
|
||||
(tagName) => <Tag
|
||||
key={tagName}
|
||||
name={tagName}
|
||||
selected={props.selectedTags.has(tagName)}
|
||||
selected={props.selectedTags[tagName]}
|
||||
onSelectTag={props.onSelectTag} />
|
||||
)
|
||||
return <ul className="BringListView-tagList">
|
||||
@@ -44,9 +44,9 @@ function Tag(props: {
|
||||
function BringList(props: {
|
||||
bringList: BL,
|
||||
filter: Filter,
|
||||
checkedItems: Set<string>,
|
||||
checkedItems: {[key: string]: true},
|
||||
updateCheckedItems: (name: string, isChecked: boolean) => void,
|
||||
strikedItems: Set<string>,
|
||||
strikedItems: {[key: string]: true},
|
||||
updateStrikedItems: (name: string, isStriked: boolean) => void,
|
||||
}) {
|
||||
let annotate = (cat: BLC): [BLC, ExprIsMatchResult] =>
|
||||
@@ -77,9 +77,9 @@ function BringListCategory(props: {
|
||||
blcIsTrue: string[],
|
||||
blcIsFalse: string[],
|
||||
filter: Filter,
|
||||
checkedItems: Set<string>,
|
||||
checkedItems: {[key: string]: true},
|
||||
updateCheckedItems: (name: string, isChecked: boolean) => void,
|
||||
strikedItems: Set<string>,
|
||||
strikedItems: {[key: string]: true},
|
||||
updateStrikedItems: (name: string, isStriked: boolean) => void,
|
||||
}) {
|
||||
let annotate = (item: Item): [Item, ExprIsMatchResult] =>
|
||||
@@ -103,9 +103,9 @@ function BringListCategory(props: {
|
||||
isTrue={isTrue}
|
||||
isFalse={isFalse}
|
||||
filter={props.filter}
|
||||
isChecked={props.checkedItems.has(item.name)}
|
||||
isChecked={props.checkedItems[item.name]}
|
||||
setIsChecked={(isChecked) => props.updateCheckedItems(item.name, isChecked)}
|
||||
isStriked={props.strikedItems.has(item.name)}
|
||||
isStriked={props.strikedItems[item.name]}
|
||||
setIsStriked={(isStriked) => props.updateStrikedItems(item.name, isStriked)}
|
||||
/>)}
|
||||
</ul>
|
||||
@@ -193,8 +193,8 @@ function BootstrapCross(props: { className?: string, width?: number, height?: nu
|
||||
|
||||
function Settings(props: {
|
||||
bringList: filterspec.BringList,
|
||||
tags: Set<string>,
|
||||
setTags: (tags: Set<string>) => void,
|
||||
tags: {[key: string]: true},
|
||||
setTags: (tags: {[key: string]: true}) => void,
|
||||
nights: number,
|
||||
setNights: (nights: number) => void,
|
||||
doResetAll: () => void,
|
||||
@@ -204,7 +204,7 @@ function Settings(props: {
|
||||
const [confirmResetTimeout, setConfirmResetTimout] = useState<ReturnType<typeof setTimeout> | null>()
|
||||
const tagList = useMemo(() => Array.from(filterspec.collectTagsFromDB(props.bringList)), [props.bringList])
|
||||
|
||||
let noneSelectedElement = props.tags.size === 0 ?
|
||||
let noneSelectedElement = Object.keys(props.tags).length === 0 ?
|
||||
<div className="BringListView-tagListNoneSelected">no tags selected</div> : <></>
|
||||
|
||||
let resetButton;
|
||||
@@ -243,8 +243,15 @@ function Settings(props: {
|
||||
<TagList
|
||||
allTags={tagList}
|
||||
selectedTags={props.tags}
|
||||
onSelectTag={(tag: string, enabled: boolean) =>
|
||||
props.setTags(setAssign(props.tags, tag, enabled))}
|
||||
onSelectTag={(tag: string, enabled: boolean) =>{
|
||||
let tags = { ...props.tags }
|
||||
if (enabled) {
|
||||
tags[tag] = true
|
||||
} else {
|
||||
delete tags[tag]
|
||||
}
|
||||
props.setTags(tags)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="BringListView-nightsContainer BringListView-smallVerticalMargin">
|
||||
@@ -270,11 +277,11 @@ function BringListView() {
|
||||
|
||||
let doResetAll = () => {
|
||||
store.clearAllLocalStorage()
|
||||
SetAppStore?.(store.fromSerializable(store.loadStoreLocal()))
|
||||
SetAppStore?.(store.loadStoreLocal())
|
||||
}
|
||||
|
||||
const bringList = useMemo(() => filterspec.parseDatabase(appStore?.bringListTemplate ?? ""), [appStore?.bringListTemplate])
|
||||
const filter = { tags: appStore?.tags ?? new Set(), nights: appStore?.nights ?? 0 }
|
||||
const filter: Filter = appStore ? { tags: new Set(Object.keys(appStore.tags)), nights: appStore.nights } : { tags: new Set(), nights: 0 }
|
||||
return (
|
||||
<div className="BringListView">
|
||||
<Header
|
||||
@@ -284,7 +291,7 @@ function BringListView() {
|
||||
<Nav />
|
||||
<Settings
|
||||
bringList={bringList}
|
||||
tags={appStore?.tags ?? new Set()}
|
||||
tags={appStore?.tags ?? {}}
|
||||
setTags={(tags) => SetAppStore!({ ...appStore!, tags: tags })}
|
||||
nights={appStore?.nights ?? 0}
|
||||
setNights={(nights) => SetAppStore!({ ...appStore!, nights: nights })}
|
||||
@@ -293,27 +300,29 @@ function BringListView() {
|
||||
<BringList
|
||||
bringList={bringList}
|
||||
filter={filter}
|
||||
checkedItems={appStore?.checkedItems ?? new Set()}
|
||||
checkedItems={appStore?.checkedItems ?? {}}
|
||||
updateCheckedItems={(name: string, isChecked: boolean) => {
|
||||
SetAppStore!({ ...appStore!, checkedItems: setAssign(appStore!.checkedItems, name, isChecked) })
|
||||
let checkedItems = { ...appStore!.checkedItems }
|
||||
if (isChecked) {
|
||||
checkedItems[name] = true
|
||||
} else {
|
||||
delete checkedItems[name]
|
||||
}
|
||||
SetAppStore!({ ...appStore!, checkedItems })
|
||||
}}
|
||||
strikedItems={appStore?.strikedItems ?? new Set()}
|
||||
strikedItems={appStore?.strikedItems ?? {}}
|
||||
updateStrikedItems={(name: string, isStriked: boolean) => {
|
||||
SetAppStore!({ ...appStore!, strikedItems: setAssign(appStore!.strikedItems, name, isStriked) })
|
||||
let strikedItems = { ...appStore!.strikedItems }
|
||||
if (isStriked) {
|
||||
strikedItems[name] = true
|
||||
} else {
|
||||
delete strikedItems[name]
|
||||
}
|
||||
SetAppStore!({ ...appStore!, strikedItems })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function setAssign<T>(_set: Set<T>, key: T, enabled: boolean): Set<T> {
|
||||
let set = new Set(_set)
|
||||
if (enabled) {
|
||||
set.add(key)
|
||||
} else {
|
||||
set.delete(key)
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
export default BringListView;
|
||||
|
||||
@@ -4,64 +4,27 @@ const LOCALSTORAGE_PREFIX = "nl.as8.backpack."
|
||||
const LOCALSTORAGE_STORE = `${LOCALSTORAGE_PREFIX}store`
|
||||
const DEFAULT_STORE = {
|
||||
bringListTemplate: DEFAULT_BRINGLIST_TEMPLATE,
|
||||
tags: new Set<string>(),
|
||||
checkedItems: new Set<string>(),
|
||||
strikedItems: new Set<string>(),
|
||||
tags: {},
|
||||
checkedItems: {},
|
||||
strikedItems: {},
|
||||
nights: 0,
|
||||
header: "",
|
||||
revision: 0,
|
||||
updatedAt: undefined,
|
||||
}
|
||||
|
||||
|
||||
export interface Store {
|
||||
bringListTemplate: string,
|
||||
tags: Set<string>,
|
||||
checkedItems: Set<string>,
|
||||
strikedItems: Set<string>,
|
||||
tags: {[key: string]: true},
|
||||
checkedItems: { [key: string]: true },
|
||||
strikedItems: { [key: string]: true},
|
||||
nights: number,
|
||||
header: string,
|
||||
revision: number,
|
||||
updatedAt?: Date,
|
||||
}
|
||||
|
||||
export interface SerializableStore {
|
||||
bringListTemplate: string,
|
||||
tags: Array<string>,
|
||||
checkedItems: Array<string>,
|
||||
strikedItems: Array<string>,
|
||||
nights: number,
|
||||
header: string,
|
||||
revision: number,
|
||||
updatedAt?: string,
|
||||
}
|
||||
|
||||
export function toSerializable(store: Store): SerializableStore {
|
||||
return {
|
||||
bringListTemplate: store.bringListTemplate,
|
||||
tags: Array.from(store.tags),
|
||||
checkedItems: Array.from(store.checkedItems),
|
||||
strikedItems: Array.from(store.strikedItems),
|
||||
nights: store.nights,
|
||||
header: store.header,
|
||||
revision: store.revision,
|
||||
updatedAt: store.updatedAt?.toISOString(),
|
||||
}
|
||||
}
|
||||
|
||||
export function fromSerializable(store: SerializableStore): Store {
|
||||
return {
|
||||
bringListTemplate: store.bringListTemplate,
|
||||
tags: new Set(store.tags),
|
||||
checkedItems: new Set(store.checkedItems),
|
||||
strikedItems: new Set(store.strikedItems),
|
||||
nights: store.nights,
|
||||
header: store.header,
|
||||
revision: store.revision,
|
||||
updatedAt: new Date(store.updatedAt ?? 0),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function loadStore(): Promise<Store> {
|
||||
let remoteStore
|
||||
let localStore = loadStoreLocal()
|
||||
@@ -70,31 +33,30 @@ export async function loadStore(): Promise<Store> {
|
||||
if (typeof remoteStore === "object" && remoteStore.revision > localStore.revision) {
|
||||
console.info("remote store is newer, overwriting local store")
|
||||
saveStoreLocal(remoteStore)
|
||||
return fromSerializable(remoteStore)
|
||||
return remoteStore
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.warn("error loading store from server, using local store", error)
|
||||
}
|
||||
return fromSerializable(localStore)
|
||||
return localStore
|
||||
}
|
||||
|
||||
export function loadStoreLocal(): SerializableStore {
|
||||
export function loadStoreLocal(): Store {
|
||||
return loadValue(LOCALSTORAGE_STORE, (json?: any) => json ?? DEFAULT_STORE)
|
||||
}
|
||||
|
||||
export function saveStore(store: Store) {
|
||||
let serializableStore = toSerializable(store)
|
||||
serializableStore.revision++
|
||||
saveStoreLocal(serializableStore)
|
||||
saveStoreOnServer(serializableStore)
|
||||
store.revision++
|
||||
saveStoreLocal(store)
|
||||
saveStoreOnServer(store)
|
||||
}
|
||||
|
||||
export function saveStoreLocal(store: SerializableStore) {
|
||||
export function saveStoreLocal(store: Store) {
|
||||
saveValue(LOCALSTORAGE_STORE, store)
|
||||
}
|
||||
|
||||
export async function saveStoreOnServer(store: SerializableStore) {
|
||||
export async function saveStoreOnServer(store: Store) {
|
||||
console.debug("saving store on server", store)
|
||||
let controller = new AbortController()
|
||||
let timeoutID = setTimeout(() => {
|
||||
@@ -116,7 +78,7 @@ export async function saveStoreOnServer(store: SerializableStore) {
|
||||
clearTimeout(timeoutID)
|
||||
}
|
||||
|
||||
export async function loadStoreFromServer(): Promise<SerializableStore> {
|
||||
export async function loadStoreFromServer(): Promise<Store> {
|
||||
let controller = new AbortController()
|
||||
let timeoutID = setTimeout(() => {
|
||||
console.warn("timeout loading store from server")
|
||||
@@ -133,21 +95,13 @@ export async function loadStoreFromServer(): Promise<SerializableStore> {
|
||||
throw response
|
||||
}
|
||||
clearTimeout(timeoutID)
|
||||
return await response.json() as SerializableStore
|
||||
return await response.json() as Store
|
||||
}
|
||||
|
||||
export function clearAllLocalStorage() {
|
||||
localStorage.removeItem(LOCALSTORAGE_STORE)
|
||||
}
|
||||
|
||||
function decodeStringSet(json?: any): Set<string> {
|
||||
return new Set<string>(json)
|
||||
}
|
||||
|
||||
function encodeStringSet(set: Set<string>): Array<string> {
|
||||
return Array.from(set)
|
||||
}
|
||||
|
||||
function loadValue<T>(key: string, constructor: (json?: any) => T): T {
|
||||
let json = localStorage.getItem(key)
|
||||
if (json === null) {
|
||||
|
||||
Reference in New Issue
Block a user