Monorepo containing core modules of Zeichen.
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

107 行
3.5 KiB

  1. import { addTime, TimeDivision } from '../utilities/Date'
  2. import * as Serialization from '../utilities/Serialization'
  3. import LocalStorage from './LocalStorage'
  4. type StorageParams = {
  5. id: string,
  6. url: string,
  7. }
  8. type LoadItems = <T extends Record<string, unknown>>(params: StorageParams) => () => Promise<T[]>
  9. const loadItems: LoadItems = <T extends Record<string, unknown>>(params) => async (): Promise<T[]> => {
  10. const { id, url, } = params
  11. const storage = new LocalStorage(
  12. window.localStorage,
  13. Serialization.serialize,
  14. Serialization.deserialize
  15. )
  16. const localData = storage.getItem(id)
  17. if (localData === null) {
  18. const remoteItems = await window.fetch(url)
  19. // TODO add custom serialization method
  20. const theItems: T[] = await remoteItems.json()
  21. storage.setItem(id, {
  22. // TODO backend should set expiry
  23. expiry: addTime(new Date(), 30)(TimeDivision.DAYS).getTime(),
  24. items: theItems,
  25. })
  26. return theItems
  27. }
  28. const dataExpiry = new Date(Number(localData.expiry))
  29. const now = new Date()
  30. if (now.getTime() > dataExpiry.getTime()) {
  31. storage.removeItem(id)
  32. const loader: () => Promise<T[]> = loadItems(params)
  33. return loader()
  34. }
  35. return localData.items
  36. }
  37. type SaveItem = <T extends Record<string, unknown>>(p: StorageParams) => (item: T) => Promise<T>
  38. const saveItem: SaveItem = <T extends Record<string, unknown>>(params) => async (item) => {
  39. const { id: storageId, url } = params
  40. const storage = new LocalStorage(
  41. window.localStorage,
  42. Serialization.serialize,
  43. Serialization.deserialize
  44. )
  45. const localData = storage.getItem(storageId, {
  46. expiry: addTime(new Date(), 30)(TimeDivision.DAYS).getTime(),
  47. items: [],
  48. })
  49. const { items: localItems } = localData
  50. const { id: itemId, ...theBody } = item
  51. const theItems: T[] = (
  52. localItems.some(i => i.id === itemId)
  53. ? localItems.map(i => i.id === itemId ? item : i)
  54. : [...localItems, item]
  55. )
  56. storage.setItem(storageId, { ...localData, items: theItems })
  57. const response = await window.fetch(`${url}/${itemId}`, {
  58. method: 'put',
  59. body: JSON.stringify(theBody),
  60. headers: {
  61. 'Content-Type': 'application/json',
  62. },
  63. })
  64. const responseBody = await response.json()
  65. if (response.status !== 201 && response.status !== 200) {
  66. throw responseBody
  67. }
  68. return responseBody as T
  69. }
  70. type DeleteItem = <T extends Record<string, unknown>>(p: StorageParams) => (item: T) => Promise<boolean>
  71. const deleteItem: DeleteItem = <T extends Record<string, unknown>>(params) => async (item) => {
  72. const { id: storageId, url } = params
  73. const storage = new LocalStorage(
  74. window.localStorage,
  75. Serialization.serialize,
  76. Serialization.deserialize
  77. )
  78. const localData = storage.getItem(storageId, {
  79. expiry: addTime(new Date(), 30)(TimeDivision.DAYS).getTime(),
  80. items: [],
  81. })
  82. const { items: localItems } = localData
  83. const { id: itemId } = item
  84. const theItems: T[] = localItems.filter(i => i.id !== itemId)
  85. storage.setItem(storageId, { ...localData, items: theItems })
  86. const response = await window.fetch(`${url}/${itemId}`, {
  87. method: 'delete',
  88. })
  89. return response.status === 204
  90. }
  91. export const loadNotes = loadItems({ id: 'notes', url: '/api/notes' })
  92. export const loadFolders = loadItems({ id: 'folders', url: '/api/folders' })
  93. export const saveNote = saveItem({ id: 'notes', url: '/api/notes' })
  94. export const saveFolder = saveItem({ id: 'folders', url: '/api/folders' })
  95. export const deleteNote = deleteItem({ id: 'notes', url: '/api/notes' })