A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

WIP

+196 -60
+7 -7
package-lock.json
··· 23 23 "subworkers": "^1.0.1", 24 24 "timer.js": "^1.0.4", 25 25 "tocca": "^2.0.9", 26 - "webnative": "^0.35.0" 26 + "webnative": "^0.36.2" 27 27 }, 28 28 "devDependencies": { 29 29 "assert": "^2.0.0", ··· 5046 5046 "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 5047 5047 }, 5048 5048 "node_modules/webnative": { 5049 - "version": "0.35.2", 5050 - "resolved": "https://registry.npmjs.org/webnative/-/webnative-0.35.2.tgz", 5051 - "integrity": "sha512-2TIK8Amt2M0STz3DFpxSgbujzhHjrwqaDgyTvIghzg6qGW9eHS73KwAC4ErjshFNlOPT7w6z1mVoIB9x3AUSJA==", 5049 + "version": "0.36.2", 5050 + "resolved": "https://registry.npmjs.org/webnative/-/webnative-0.36.2.tgz", 5051 + "integrity": "sha512-WrFT/BEEro+QC21gZuegBI3bOR1x0j4AxZTWHa8fC7nhMi1rdc/KuAuPV0yKB7T4xlB8j00N44B+pdfa/h4Rww==", 5052 5052 "dependencies": { 5053 5053 "@ipld/dag-cbor": "^8.0.0", 5054 5054 "@ipld/dag-pb": "^3.0.1", ··· 8773 8773 "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 8774 8774 }, 8775 8775 "webnative": { 8776 - "version": "0.35.2", 8777 - "resolved": "https://registry.npmjs.org/webnative/-/webnative-0.35.2.tgz", 8778 - "integrity": "sha512-2TIK8Amt2M0STz3DFpxSgbujzhHjrwqaDgyTvIghzg6qGW9eHS73KwAC4ErjshFNlOPT7w6z1mVoIB9x3AUSJA==", 8776 + "version": "0.36.2", 8777 + "resolved": "https://registry.npmjs.org/webnative/-/webnative-0.36.2.tgz", 8778 + "integrity": "sha512-WrFT/BEEro+QC21gZuegBI3bOR1x0j4AxZTWHa8fC7nhMi1rdc/KuAuPV0yKB7T4xlB8j00N44B+pdfa/h4Rww==", 8779 8779 "requires": { 8780 8780 "@ipld/dag-cbor": "^8.0.0", 8781 8781 "@ipld/dag-pb": "^3.0.1",
+1 -1
package.json
··· 36 36 "subworkers": "^1.0.1", 37 37 "timer.js": "^1.0.4", 38 38 "tocca": "^2.0.9", 39 - "webnative": "^0.35.0" 39 + "webnative": "^0.36.2" 40 40 } 41 41 }
+25
src/Applications/Brain/User/Hypaethral.elm
··· 29 29 30 30 retrieveFission : HypaethralBit -> Task String (Maybe Json.Decode.Value) 31 31 retrieveFission bit = 32 + let 33 + includePublicData = 34 + bit == Playlists 35 + in 32 36 [ ( "fileName", fileName bit ) 37 + , ( "includePublicData", Json.Encode.bool includePublicData ) 33 38 ] 34 39 |> TaskPort.call 35 40 { function = "fromFission" ··· 95 100 96 101 saveFission : HypaethralBit -> Json.Decode.Value -> Task String () 97 102 saveFission bit data = 103 + let 104 + _ = 105 + Debug.log "saveFission" bit 106 + 107 + savePublicData = 108 + bit == Playlists 109 + in 98 110 [ ( "fileName", fileName bit ) 99 111 , ( "data", data ) 112 + , ( "savePublicData", Json.Encode.bool savePublicData ) 100 113 ] 101 114 |> TaskPort.call 102 115 { function = "toFission" ··· 137 150 138 151 saveLocal : HypaethralBit -> Json.Decode.Value -> Task String () 139 152 saveLocal bit data = 153 + let 154 + _ = 155 + Debug.log "saveLocal" bit 156 + in 140 157 data 141 158 |> Brain.Task.Ports.toCacheWithSuffix 142 159 Alien.SyncLocal 143 160 (hypaethralBitFileName bit) 161 + |> Task.andThen 162 + (\_ -> 163 + let 164 + _ = 165 + Debug.log "saveLocal succeeded" () 166 + in 167 + Task.succeed () 168 + ) 144 169 |> Task.mapError TaskPort.errorToStringCustom 145 170 146 171
+37 -20
src/Applications/Brain/User/State.elm
··· 418 418 419 419 saveHypaethralDataBitsTask : List HypaethralBit -> HypaethralData -> (HypaethralBit -> Json.Value -> Task String ()) -> Task String () 420 420 saveHypaethralDataBitsTask bits userData saveFn = 421 - bits 422 - |> List.map 423 - (\bit -> 424 - let 425 - value = 426 - encodeHypaethralBit bit userData 427 - in 428 - Task.andThen 429 - (\_ -> saveFn bit value) 430 - (Hypaethral.saveLocal bit value) 431 - ) 432 - |> Task.sequence 433 - |> Task.map (always ()) 421 + [ -------- 422 + -- LOCAL 423 + -------- 424 + List.map 425 + (\bit -> 426 + Hypaethral.saveLocal bit (encodeHypaethralBit bit userData) 427 + ) 428 + bits 429 + , --------- 430 + -- REMOTE 431 + --------- 432 + List.map 433 + (\bit -> 434 + saveFn bit (encodeHypaethralBit bit userData) 435 + ) 436 + bits 437 + ] 438 + |> List.concat 439 + |> List.foldl 440 + (\nextTask -> Task.andThen (\_ -> nextTask)) 441 + (Task.succeed ()) 434 442 435 443 436 444 {-| Save different parts of hypaethral data, ··· 452 460 { model | hypaethralUserData = updatedUserData } 453 461 454 462 save saveFn = 455 - saveFn 456 - |> saveHypaethralDataBitsTask bits updatedUserData 463 + Time.now 464 + |> Task.andThen 465 + (\currentTime -> 466 + saveHypaethralDataBitsTask 467 + bits 468 + { updatedUserData | modifiedAt = Just currentTime } 469 + saveFn 470 + ) 457 471 |> Common.attemptTask (always Brain.Bypass) 458 472 |> return updatedModel 459 473 in ··· 470 484 model.currentTime 471 485 (SaveHypaethralDataBits bits) 472 486 (Task.succeed ()) 473 - |> return updatedModel 487 + |> return model 474 488 475 489 else 476 490 save (Hypaethral.saveDropbox accessToken) ··· 507 521 |> Maybe.withDefault [] 508 522 |> EverySet.fromList 509 523 |> EverySet.toList 524 + |> Debug.log "Save bits" 510 525 in 511 526 c 512 527 |> Cmd.map (SaveHypaethralDataSlowly >> UserMsg) ··· 649 664 Alien.SecretKey 650 665 |> Brain.Task.Ports.removeCache 651 666 |> Task.mapError TaskPort.errorToStringCustom 667 + |> Task.andThen (\_ -> Time.now) 652 668 |> Task.andThen 653 - (\_ -> 669 + (\currentTime -> 654 670 case model.userSyncMethod of 655 671 Just method -> 656 672 let 657 673 data = 658 674 model.hypaethralUserData 659 675 in 660 - saveAllHypaethralDataTask { data | modifiedAt = Just model.currentTime } method 676 + saveAllHypaethralDataTask { data | modifiedAt = Just currentTime } method 661 677 662 678 Nothing -> 663 679 Task.succeed () ··· 673 689 passphrase 674 690 |> Brain.Task.Ports.fabricateSecretKey 675 691 |> Task.mapError TaskPort.errorToStringCustom 692 + |> Task.andThen (\_ -> Time.now) 676 693 |> Task.andThen 677 - (\_ -> 694 + (\currentTime -> 678 695 case model.userSyncMethod of 679 696 Just method -> 680 697 let 681 698 data = 682 699 model.hypaethralUserData 683 700 in 684 - saveAllHypaethralDataTask { data | modifiedAt = Just model.currentTime } method 701 + saveAllHypaethralDataTask { data | modifiedAt = Just currentTime } method 685 702 686 703 Nothing -> 687 704 Task.succeed ()
+3
src/Applications/UI.elm
··· 433 433 ShowPlaylistListMenu a b -> 434 434 Playlists.showListMenu a b 435 435 436 + TogglePlaylistVisibility a -> 437 + Playlists.toggleVisibility a 438 + 436 439 ----------------------------------------- 437 440 -- Routing 438 441 -----------------------------------------
+23 -14
src/Applications/UI/Playlists/State.elm
··· 79 79 |> (\s -> s ++ " to the __" ++ properPlaylistName ++ "__ playlist") 80 80 |> Notifications.success 81 81 |> Common.showNotificationWithModel newModel 82 - |> andThen save 82 + |> andThen User.savePlaylists 83 83 84 84 85 85 assistWithAddingTracksToPlaylist : List IdentifiedTrack -> Manager ··· 125 125 , newPlaylistContext = Nothing 126 126 , playlists = playlist :: model.playlists 127 127 } 128 - |> save 128 + |> User.savePlaylists 129 129 |> andThen redirectToPlaylistIndexPage 130 130 131 131 Nothing -> ··· 180 180 else 181 181 Return.singleton 182 182 ) 183 - |> andThen save 183 + |> andThen User.savePlaylists 184 184 185 185 186 186 modify : Manager ··· 219 219 , lastModifiedPlaylist = Just properName 220 220 , playlists = newCollection ++ autoGenerated 221 221 } 222 - |> save 222 + |> User.savePlaylists 223 223 |> andThen redirectToPlaylistIndexPage 224 224 225 225 else ··· 284 284 |> andThen User.savePlaylists 285 285 286 286 287 - save : Manager 288 - save model = 289 - model.playlists 290 - |> List.filterNot .autoGenerated 291 - |> Json.Encode.list Playlists.encode 292 - |> Alien.broadcast Alien.SavePlaylists 293 - |> Ports.toBrain 294 - |> return model 295 - 296 - 297 287 select : Playlist -> Manager 298 288 select playlist model = 299 289 { model | page = Page.Index, selectedPlaylist = Just playlist } ··· 331 321 coordinates 332 322 in 333 323 Return.singleton { model | contextMenu = Just contextMenu } 324 + 325 + 326 + toggleVisibility : Playlist -> Manager 327 + toggleVisibility playlist model = 328 + let 329 + updatedPlaylist = 330 + { playlist | public = not playlist.public } 331 + in 332 + model.playlists 333 + |> List.map 334 + (\p -> 335 + if p.name == playlist.name then 336 + updatedPlaylist 337 + 338 + else 339 + p 340 + ) 341 + |> (\c -> { model | playlists = c }) 342 + |> User.savePlaylists 334 343 335 344 336 345
+2 -1
src/Applications/UI/Playlists/View.elm
··· 79 79 80 80 else 81 81 Icons.public_off 82 - , msg = Nothing 82 + , msg = 83 + Just (\_ -> TogglePlaylistVisibility playlist) 83 84 , title = 84 85 if playlist.public then 85 86 "Make private"
+1
src/Applications/UI/Types.elm
··· 261 261 | SetPlaylistCreationContext String 262 262 | SetPlaylistModificationContext String String 263 263 | ShowPlaylistListMenu Playlist Mouse.Event 264 + | TogglePlaylistVisibility Playlist 264 265 ----------------------------------------- 265 266 -- Routing 266 267 -----------------------------------------
+5 -1
src/Javascript/Brain/common.ts
··· 68 68 69 69 70 70 export function toCache(key, data) { 71 - return db.setInIndex({ key: key, data: data }) 71 + console.log("toCache", key) 72 + return db.setInIndex({ key: key, data: data }).then(result => { 73 + console.log("toCache succeeded", key) 74 + return result 75 + }) 72 76 } 73 77 74 78
+87 -12
src/Javascript/Brain/user.ts
··· 12 12 import * as crypto from "../crypto" 13 13 14 14 import { decryptIfNeeded, encryptIfPossible, SECRET_KEY_LOCATION } from "./common" 15 - import { parseJsonIfNeeded, toCache } from "./common" 15 + import { parseJsonIfNeeded, removeCache, toCache } from "./common" 16 16 17 17 18 18 const ports: Record<string, any> = {} ··· 74 74 let session, wn 75 75 76 76 77 - taskPorts.fromFission = async ({ fileName }) => { 77 + taskPorts.fromFission = async ({ fileName, includePublicData }) => { 78 78 await constructFission() 79 79 80 - const path = wn.path.appData(APP_INFO, wn.path.file(fileName)) 81 - 82 - return await session.fs.exists(path) 83 - ? session.fs.read(path) 80 + // Private data 81 + const privatePath = wn.path.appData(APP_INFO, wn.path.file(fileName)) 82 + const privateData = await session.fs.exists(privatePath) 83 + ? session.fs.read(privatePath) 84 84 .then(bytes => new TextDecoder().decode(bytes)) 85 85 .then(parseJsonIfNeeded) 86 86 : null 87 + 88 + // If public data and working with arrays 89 + if (includePublicData && Array.isArray(privateData)) { 90 + const publicPath = { 91 + file: privatePath.file.map((a: string, idx: number) => { 92 + return idx === 0 93 + ? "public" 94 + : a 95 + }) 96 + } 97 + 98 + const publicData = await session.fs.exists(publicPath) 99 + ? session.fs.read(publicPath) 100 + .then(bytes => new TextDecoder().decode(bytes)) 101 + .then(parseJsonIfNeeded) 102 + : null 103 + 104 + return publicData 105 + ? [ ...privateData, ...publicData ] 106 + : privateData 107 + 108 + // Otherwise 109 + } else { 110 + return privateData 111 + 112 + } 87 113 } 88 114 89 115 90 - taskPorts.toFission = async ({ data, fileName }) => { 116 + taskPorts.toFission = async ({ data, fileName, savePublicData }) => { 117 + console.log("toFission", fileName, data) 91 118 await constructFission() 92 119 93 - const json = JSON.stringify(data) 120 + // Data identifying 121 + const privatePath = wn.path.appData(APP_INFO, wn.path.file(fileName)) 122 + const isDataObject = typeof data === "object" && !!data.data 123 + 124 + if (!isDataObject) { 125 + await session.fs.write( 126 + privatePath, 127 + new TextEncoder().encode(JSON.stringify(data)) 128 + ) 129 + 130 + await session.fs.publish() 131 + 132 + return 133 + } 134 + 135 + // Group data 136 + const [ privateData, publicData ] = Array.isArray(data.data) && savePublicData 137 + ? data.data.reduce( 138 + ([ priv, pub ], item) => { 139 + return item.public 140 + ? [ priv, [ ...pub, item ] ] 141 + : [ [ ...priv, item ], pub ] 142 + }, 143 + [ [], [] ] 144 + ) 145 + : [ data.data, null ] 94 146 147 + // Private data 95 148 await session.fs.write( 96 - wn.path.appData(APP_INFO, wn.path.file(fileName)), 97 - new TextEncoder().encode(json) 149 + privatePath, 150 + new TextEncoder().encode(JSON.stringify({ ...data, data: privateData })) 98 151 ) 99 152 153 + // Public data 154 + if (publicData) { 155 + const publicPath = { 156 + file: privatePath.file.map((a: string, idx: number) => { 157 + return idx === 0 158 + ? "public" 159 + : a 160 + }) 161 + } 162 + 163 + await session.fs.write( 164 + publicPath, 165 + new TextEncoder().encode(JSON.stringify({ ...data, data: publicData })) 166 + ) 167 + } 168 + 169 + // Publish 100 170 await session.fs.publish() 101 171 } 102 172 ··· 114 184 }) 115 185 116 186 session = program.session 117 - session.fs = await program.loadFileSystem(session.username) 187 + 188 + if (!session) { 189 + await removeCache("SYNC_METHOD") 190 + location.reload() 191 + throw new Error("Failed to load Webnative session") 192 + } 118 193 119 - if (!session) throw new Error("Failed to load Webnative session") 194 + session.fs = await program.fileSystem.load(session.username) 120 195 if (!session.fs) throw new Error("Did not load Webnative file system") 121 196 } 122 197
+4 -1
src/Javascript/common.js
··· 11 11 12 12 export const WEBNATIVE_CONFIG = { 13 13 namespace: APP_INFO, 14 - permissions: { app: APP_INFO }, 14 + permissions: { 15 + app: APP_INFO, 16 + fs: { public: [ { directory: [ "Apps", APP_INFO.creator, APP_INFO.name ] } ] } 17 + }, 15 18 debug: true, 16 19 } 17 20
+1 -3
src/Library/User/Layer.elm
··· 227 227 False 228 228 229 229 Fission _ -> 230 - -- NOTE: Temporarily disabled, 231 - -- since we don't actually support public playlists yet. 232 - False 230 + True 233 231 234 232 Ipfs _ -> 235 233 False