diff --git a/stack--2022/4-tools/storypad/doc/demo/storypad.html b/stack--2022/4-tools/storypad/doc/demo/storypad.html index 2144ab3c..de1627ed 100644 --- a/stack--2022/4-tools/storypad/doc/demo/storypad.html +++ b/stack--2022/4-tools/storypad/doc/demo/storypad.html @@ -14,7 +14,7 @@ startꓽstorypad( { - demo: storiesⵧdemo, + //demo: storiesⵧdemo, shared: storiesⵧshared, }, { diff --git a/stack--2022/4-tools/storypad/src/flux/dispatcher.ts b/stack--2022/4-tools/storypad/src/flux/dispatcher.ts index 7088bbc7..68bb7411 100644 --- a/stack--2022/4-tools/storypad/src/flux/dispatcher.ts +++ b/stack--2022/4-tools/storypad/src/flux/dispatcher.ts @@ -6,44 +6,37 @@ import { Immutable } from '@offirmo-private/ts-types' import { ImportGlob } from '../types/glob' import { Config } from '../types/config' +import { StoryUId} from './types' import * as InMemState from './state--in-mem' +import * as UrlState from './state--url' ///////////////////////////////////////////////// -let state: InMemState.State = null as any +let stateⵧin_mem: InMemState.State = InMemState.create() async function init(stories_glob: Immutable, config?: Immutable): Promise { - state = InMemState.create() + console.group('Flux init...') + stateⵧin_mem = InMemState.setꓽconfig(stateⵧin_mem, config) + stateⵧin_mem = await InMemState.registerꓽstoriesⵧfrom_glob(stateⵧin_mem, stories_glob) - state = InMemState.setꓽconfig(state, config) + UrlState.init() - state = await InMemState.registerꓽstoriesⵧfrom_glob(state, stories_glob) + // other states don't need an init - //state = enrich_state_from_local_storage(state) - //state = enrich_state_from_query_parameters(state) - //state = enrich_state_from_env(state) - - console.log('final state =', state) + console.log('final stateⵧin_mem =', stateⵧin_mem) + console.groupEnd() } // explicit request on user's click -function requestꓽstory(uid: InMemState.StoryUId) { - state = InMemState.activateꓽstory(state, uid) - // TODO propagate to url state! - - //throw new Error('NIMP propagate on activateꓽstory() !') - /*try { - localStorage.setItem(LS_KEYS.current_story_uid, (new URL(href)).searchParams.get(MAIN_IFRAME_QUERYPARAMS.story_uid)) - } - catch { - // ignore - }*/ +function requestꓽstory(uid: StoryUId) { + stateⵧin_mem = InMemState.requestꓽstory(stateⵧin_mem, uid) + UrlState.requestꓽstory(uid) } // DO NOT USE, only for the flux selectors function _getꓽstateⵧin_mem(): Immutable { - assert(state, `init() must be called first!`) - return state + assert(stateⵧin_mem, `init() must be called first!`) + return stateⵧin_mem } ///////////////////////////////////////////////// diff --git a/stack--2022/4-tools/storypad/src/flux/selectors.ts b/stack--2022/4-tools/storypad/src/flux/selectors.ts index 2c479c62..cc64f338 100644 --- a/stack--2022/4-tools/storypad/src/flux/selectors.ts +++ b/stack--2022/4-tools/storypad/src/flux/selectors.ts @@ -7,17 +7,17 @@ import { Immutable } from '@offirmo-private/ts-types' import { Config } from '../types/config' import { _getꓽstateⵧin_mem } from './dispatcher' -import { State, FolderUId, StoryEntry, StoryUId, RenderMode } from './types' +import { FolderUId, StoryEntry, RenderMode, StoryTree } from './types' import * as InMemStateSelectors from './state--in-mem/selectors' import * as EnvStateSelectors from './state--env/selectors' import * as UrlStateSelectors from './state--url/selectors' -import { RenderParams } from '../types/csf/common' +import { RenderParams } from '../types/csf' ///////////////////////////////////////////////// // for rendering the stories tree -function getꓽtree_root(): Immutable { +function getꓽtree_root(): Immutable { return _getꓽstateⵧin_mem().tree } @@ -49,8 +49,7 @@ function getꓽrender_mode(): RenderMode { return 'full' } -// for rendering the story -// must only return "undef" if NO stories. Else should pick the 1st one. +// must only return "undef" if NO stories function getꓽstoryⵧcurrent(): Immutable | undefined { const state = _getꓽstateⵧin_mem() @@ -61,15 +60,10 @@ function getꓽstoryⵧcurrent(): Immutable | undefined { return candidate } - return InMemStateSelectors.getꓽstoryⵧcurrent(state) + return InMemStateSelectors.getꓽstoryⵧsuggested(state) } -function getꓽstoryⵧexplicitely_requested‿uid(): StoryUId | undefined { - const state = _getꓽstateⵧin_mem() - return state.last_explicitly_activated_story‿uid -} - const getꓽmain_frame_url = UrlStateSelectors.getꓽmain_frame_url const getꓽstory_frame_url = UrlStateSelectors.getꓽstory_frame_url @@ -90,7 +84,6 @@ export { getꓽrender_mode, getꓽstoryⵧcurrent, - getꓽstoryⵧexplicitely_requested‿uid, getꓽRenderParamsⵧglobal, getꓽmain_frame_url, diff --git a/stack--2022/4-tools/storypad/src/flux/state--env/selectors.ts b/stack--2022/4-tools/storypad/src/flux/state--env/selectors.ts index 57f95791..36e1a4c3 100644 --- a/stack--2022/4-tools/storypad/src/flux/state--env/selectors.ts +++ b/stack--2022/4-tools/storypad/src/flux/state--env/selectors.ts @@ -3,6 +3,11 @@ ///////////////////////////////////////////////// +console.log('installing popstate listener') +addEventListener("popstate", (event) => { + console.log('Seen popstate!', event) +}); + function isꓽiframe():boolean { return window.location !== window.parent.location } diff --git a/stack--2022/4-tools/storypad/src/flux/state--in-mem/reducers--init--globs.ts b/stack--2022/4-tools/storypad/src/flux/state--in-mem/reducers--init--globs.ts index 5f62324b..cc999945 100644 --- a/stack--2022/4-tools/storypad/src/flux/state--in-mem/reducers--init--globs.ts +++ b/stack--2022/4-tools/storypad/src/flux/state--in-mem/reducers--init--globs.ts @@ -6,15 +6,13 @@ import { ImportModule, isꓽImportModule, } from '../../types/glob' -import { - StoryEntry, isꓽStoryEntry, - State, -} from './types' +import { StoryEntry, isꓽStoryEntry } from '../types' +import { State } from './types' import { registerꓽstory } from './reducers' ///////////////////////////////////////////////// -const SEP = '/' // Ⳇ +const SEP = '/' ///////////////////////////////////////////////// diff --git a/stack--2022/4-tools/storypad/src/flux/state--in-mem/reducers.ts b/stack--2022/4-tools/storypad/src/flux/state--in-mem/reducers.ts index 89affc35..2ced9035 100644 --- a/stack--2022/4-tools/storypad/src/flux/state--in-mem/reducers.ts +++ b/stack--2022/4-tools/storypad/src/flux/state--in-mem/reducers.ts @@ -1,17 +1,10 @@ import assert from 'tiny-invariant' import { Immutable, RelativePath } from '@offirmo-private/ts-types' -import { FileSystemNode, createꓽfilesystem, insertꓽfile } from '@offirmo-private/data-structures' +import { createꓽfilesystem, insertꓽfile } from '@offirmo-private/data-structures' -import { - Config, -} from '../../types/config' - -import { - StoryUId, - StoryEntry, - State, - StoryFolder, -} from './types' +import { Config } from '../../types/config' +import { StoryUId, StoryEntry, StoryFolder } from '../types' +import { State } from './types' //////////////////////////////////////////////////////////////////////////////////// // init @@ -21,9 +14,6 @@ function create(): State { root_title: 'Stories', decorators: [], }, - //stories_by_uid: {}, - //folders_by_uid: {}, - last_explicitly_activated_story‿uid: undefined, first_encountered_story‿uid: undefined, tree: createꓽfilesystem(), } @@ -49,23 +39,17 @@ function registerꓽstory(state: State, story: StoryEntry, path: RelativePath): return { ...state, first_encountered_story‿uid: state.first_encountered_story‿uid || uid, - /*stories_by_uid: { - ...state.stories_by_uid, - [uid]: { - ...story, - uid, - }, - },*/ } } ///////////////////////////////////////////////// -function activateꓽstory(state: State, uid: StoryUId): State { +// TODO clarify +function requestꓽstory(state: State, uid: StoryUId): State { //assert(getꓽstoryⵧby_uid(state, uid), `story should exist! "${uid}"`) state = { ...state, - last_explicitly_activated_story‿uid: uid, + //last_explicitly_activated_story‿uid: uid, } state = folderⵧexpand(state, uid) @@ -78,27 +62,6 @@ function activateꓽstory(state: State, uid: StoryUId): State { // id can be story or folder, don't mind function folderⵧexpand(state: State, uid: StoryUId): State { console.warn('TODO folderⵧexpand') - /* - const path = id.split(SEP_FOR_IDS) - - // in-place mutation SORRY TODO fix? - let folder: StoryFolder = state.folders_by_uid[ROOT_ID]! as any - do { - console.log('expanding', { path: structuredClone(path), folder }) - - const segment = path.shift()! - folder = folder.children[segment]! as StoryFolder - assert(folder, 'next segment is present') - if (isꓽStoryEntry(folder)) { - assert(path.length === 0, 'last segment is story') - } - else { - assert(Object.hasOwn(folder, 'isꓽexpandedⵧinitially')) - folder.isꓽexpandedⵧinitially = true - } - } while(path.length) - - return state*/ return state } @@ -108,7 +71,6 @@ export { create, setꓽconfig, registerꓽstory, - //registerꓽfolder, - activateꓽstory, + requestꓽstory, } diff --git a/stack--2022/4-tools/storypad/src/flux/state--in-mem/selectors.ts b/stack--2022/4-tools/storypad/src/flux/state--in-mem/selectors.ts index c07b6b47..a1589231 100644 --- a/stack--2022/4-tools/storypad/src/flux/state--in-mem/selectors.ts +++ b/stack--2022/4-tools/storypad/src/flux/state--in-mem/selectors.ts @@ -5,12 +5,8 @@ import assert from 'tiny-invariant' import { Immutable } from '@offirmo-private/ts-types' import { getꓽnodeⵧby_pathⵧensure_file } from '@offirmo-private/data-structures' -import { - State, - StoryUId, - StoryEntry, - StoryFolder, -} from './types' +import { StoryUId, StoryEntry, StoryFolder } from '../types' +import { State } from './types' ///////////////////////////////////////////////// @@ -19,12 +15,11 @@ function getꓽstoryⵧby_uid(state: Immutable, uid: StoryUId): Immutable return node.payload } -//function getꓽstoryⵧcurrent‿uid(state: Immutable): StoryUId | '[NO-KNOWN-STORIES]' { -function getꓽstoryⵧcurrent(state: Immutable): Immutable | undefined { +// if no explicit story is requested, +// suggest one from the known stories +function getꓽstoryⵧsuggested(state: Immutable): Immutable | undefined { let candidate: Immutable | undefined = undefined - candidate = state.last_explicitly_activated_story‿uid ? getꓽstoryⵧby_uid(state, state.last_explicitly_activated_story‿uid) : undefined - if (!candidate) { candidate = state.first_encountered_story‿uid ? getꓽstoryⵧby_uid(state, state.first_encountered_story‿uid) : undefined } @@ -36,5 +31,5 @@ function getꓽstoryⵧcurrent(state: Immutable): Immutable | export { getꓽstoryⵧby_uid, - getꓽstoryⵧcurrent, + getꓽstoryⵧsuggested, } diff --git a/stack--2022/4-tools/storypad/src/flux/state--in-mem/types.ts b/stack--2022/4-tools/storypad/src/flux/state--in-mem/types.ts index 0bbcb350..883972f9 100644 --- a/stack--2022/4-tools/storypad/src/flux/state--in-mem/types.ts +++ b/stack--2022/4-tools/storypad/src/flux/state--in-mem/types.ts @@ -1,45 +1,13 @@ -import { FileSystemNodeⳇFolder } from '@offirmo-private/data-structures' -import { Config } from '../../types/config' -import { - Story, isꓽStory, - Meta, -} from '../../types/csf' -///////////////////////////////////////////////// - -export type StoryUId = string -export type FolderUId = string - -export interface StoryEntry { - uid: StoryUId // for convenience when passing the payload around - - story: Story - - meta: Meta | undefined -} -export function isꓽStoryEntry(x: any): x is StoryEntry { - return isꓽStory(x?.story) -} - -export interface StoryFolder { - uid: FolderUId // for convenience when passing the payload around - - isꓽexpandedⵧinitially: boolean -} -export function isꓽStoryFolder(x: any): x is StoryFolder { - return !isꓽStoryEntry(x) // simple for now ;) -} +import { Config } from '../../types/config' +import { StoryUId, StoryTree } from '../types' ///////////////////////////////////////////////// export interface State { config: Config -/* - stories_by_uid: { [k: StoryUId]: StoryEntry } - folders_by_uid: { [k: FolderUId]: StoryFolder } -*/ - last_explicitly_activated_story‿uid: StoryUId | undefined + first_encountered_story‿uid: StoryUId | undefined - tree: FileSystemNodeⳇFolder + tree: StoryTree } diff --git a/stack--2022/4-tools/storypad/src/flux/state--url/consts.ts b/stack--2022/4-tools/storypad/src/flux/state--url/consts.ts index a625ba86..a7a703ce 100644 --- a/stack--2022/4-tools/storypad/src/flux/state--url/consts.ts +++ b/stack--2022/4-tools/storypad/src/flux/state--url/consts.ts @@ -18,6 +18,3 @@ export const QUERYPARAMS = { // globals=theme:dark } - -//const SEP_FOR_UID = 'Ⳇ' // : -//const ROOT_ID = '╣ROOT╠' diff --git a/stack--2022/4-tools/storypad/src/flux/state--url/index.ts b/stack--2022/4-tools/storypad/src/flux/state--url/index.ts new file mode 100644 index 00000000..8f520921 --- /dev/null +++ b/stack--2022/4-tools/storypad/src/flux/state--url/index.ts @@ -0,0 +1,3 @@ +export * from './types' +export * from './selectors' +export * from './reducers' diff --git a/stack--2022/4-tools/storypad/src/flux/state--url/reducers.ts b/stack--2022/4-tools/storypad/src/flux/state--url/reducers.ts index 7d6fc8c1..c5a68c21 100644 --- a/stack--2022/4-tools/storypad/src/flux/state--url/reducers.ts +++ b/stack--2022/4-tools/storypad/src/flux/state--url/reducers.ts @@ -4,14 +4,33 @@ import assert from 'tiny-invariant' import { Immutable } from '@offirmo-private/ts-types' +import { StoryUId } from '../types' +import { getꓽmain_frame_url } from './selectors' +import { QUERYPARAMS } from './consts.ts' + ///////////////////////////////////////////////// -function activateꓽstory(): void { - throw new Error('NIMP activateꓽstory() !') +function init(): void { + console.log('URL=', window.location.href) + + const url‿obj = (new URL(window.location.href)) + ;[...url‿obj.searchParams.keys()] + .filter(k => Object.values(QUERYPARAMS).includes(k)) + .forEach(k => { + console.log(`URL param "${k}" = "${url‿obj.searchParams.get(k)}" found!`) + }) +} + +function requestꓽstory(uid: StoryUId): void { + const new_url = getꓽmain_frame_url(uid) + + history.pushState({}, '', new_url) } ///////////////////////////////////////////////// export { - activateꓽstory + init, + + requestꓽstory, } diff --git a/stack--2022/4-tools/storypad/src/flux/state--url/selectors.ts b/stack--2022/4-tools/storypad/src/flux/state--url/selectors.ts index aa65d6bc..32943448 100644 --- a/stack--2022/4-tools/storypad/src/flux/state--url/selectors.ts +++ b/stack--2022/4-tools/storypad/src/flux/state--url/selectors.ts @@ -6,7 +6,6 @@ import { Url‿str } from '@offirmo-private/ts-types' import { QUERYPARAMS } from './consts' import { StoryUId, RenderMode, isꓽrender_mode } from '../types' -import { getꓽstoryⵧexplicitely_requested‿uid } from '../selectors' import { serializeꓽstory_uid, unserializeꓽstory_uid } from './serialization' @@ -19,7 +18,7 @@ function _getꓽcurrent_urlⵧup_to_pathname(): string { /** return the normalized, state-enriched URL loading this storypad with the given activated story + misc */ -function getꓽmain_frame_url(uid = getꓽstoryⵧexplicitely_requested‿uid()): Url‿str { +function getꓽmain_frame_url(uid?: StoryUId): Url‿str { const sp = new URLSearchParams() if (uid) { sp.set(QUERYPARAMS.story_path, serializeꓽstory_uid(uid)) @@ -31,17 +30,10 @@ function getꓽmain_frame_url(uid = getꓽstoryⵧexplicitely_requested‿uid()) function getꓽstory_frame_url(uid?: StoryUId): Url‿str { return getꓽmain_frame_url(uid) // no difference for now -/* - function getꓽmain_iframe_url(state: Immutable, explicit_uid: StoryUId = getꓽstoryⵧcurrent‿uid(state)): string { - const sp = new URLSearchParams({ - [QUERYPARAMS.story_uid]: explicit_uid, - }) - - return getꓽcurrent_urlⵧcleaned() + '?' + sp.toString()*/ } function getꓽexplicit_render_mode(): RenderMode | undefined { - const url‿obj = (new URL(window.location.href)) + const url‿obj = new URL(window.location.href) const candidate = url‿obj.searchParams.get(QUERYPARAMS.render_mode) if (isꓽrender_mode(candidate)) @@ -51,13 +43,17 @@ function getꓽexplicit_render_mode(): RenderMode | undefined { } function getꓽexplicit_story_uid(): StoryUId | undefined { - const url‿obj = (new URL(window.location.href)) + const url‿obj = new URL(window.location.href) - let candidate = unserializeꓽstory_uid(url‿obj.searchParams.get(QUERYPARAMS.story_path)) + let candidate_raw = url‿obj.searchParams.get(QUERYPARAMS.story_path) + console.log('candidate_raw', candidate_raw) + let candidate = unserializeꓽstory_uid(candidate_raw) if (candidate) return candidate - candidate = unserializeꓽstory_uid(url‿obj.searchParams.get(QUERYPARAMS.story_uid)) + candidate_raw = url‿obj.searchParams.get(QUERYPARAMS.story_uid) + console.log('candidate_raw', candidate_raw) + candidate = unserializeꓽstory_uid(candidate_raw) if (candidate) return candidate diff --git a/stack--2022/4-tools/storypad/src/flux/state--url/types.ts b/stack--2022/4-tools/storypad/src/flux/state--url/types.ts index fe5b42a2..6fbaa96d 100644 --- a/stack--2022/4-tools/storypad/src/flux/state--url/types.ts +++ b/stack--2022/4-tools/storypad/src/flux/state--url/types.ts @@ -3,6 +3,7 @@ ///////////////////////////////////////////////// +// TODO needed?? export type URLSearchParamsⳇName = string export type URLSearchParamsⳇValue = string // or null export interface URLSearchParamsⳇMap { diff --git a/stack--2022/4-tools/storypad/src/flux/types.ts b/stack--2022/4-tools/storypad/src/flux/types.ts index 78076268..81a9f398 100644 --- a/stack--2022/4-tools/storypad/src/flux/types.ts +++ b/stack--2022/4-tools/storypad/src/flux/types.ts @@ -1,6 +1,37 @@ -import { activateꓽstory } from './state--url/reducers.ts' +import { FileSystemNodeⳇFolder } from '@offirmo-private/data-structures' -export * from './state--in-mem/types' +import { + Story, isꓽStory, + Meta, +} from '../types/csf' + +///////////////////////////////////////////////// + +export type StoryUId = string +export interface StoryEntry { + uid: StoryUId // for convenience when passing the payload around + + story: Story + + meta: Meta | undefined +} +export function isꓽStoryEntry(x: any): x is StoryEntry { + return isꓽStory(x?.story) +} + +export type FolderUId = string +export interface StoryFolder { + uid: FolderUId // for convenience when passing the payload around + + isꓽexpandedⵧinitially: boolean +} +export function isꓽStoryFolder(x: any): x is StoryFolder { + return !isꓽStoryEntry(x) // simple for now ;) +} + +export type StoryTree = FileSystemNodeⳇFolder + +///////////////////////////////////////////////// export type RenderMode = 'full' | 'story' export function isꓽrender_mode(x: any): x is RenderMode { diff --git a/stack--2022/4-tools/storypad/src/index.ts b/stack--2022/4-tools/storypad/src/index.ts index 7047b0c2..65a851c7 100644 --- a/stack--2022/4-tools/storypad/src/index.ts +++ b/stack--2022/4-tools/storypad/src/index.ts @@ -33,11 +33,12 @@ export function startꓽstorypad(stories_glob: Immutable, config?: Immutabl asap_but_out_of_immediate_execution(async () => { const is_iframe = ( window.location !== window.parent.location ) - console.groupCollapsed(`Starting storypad… [${is_iframe ? 'SUB frame' : 'TOP frame'}]`) + console.group(`Starting storypad… [${is_iframe ? 'SUB frame' : 'TOP frame'}]`) console.log('config =', config) console.log('glob =', stories_glob) // 1. services + console.groupCollapsed(`Services init...`) // order is important! Timing is non-trivial! assert(Object.keys(initsⵧservices).length > 0, 'no services/init found!') await Object.keys(initsⵧservices).sort().reduce(async (acc, key) => { @@ -49,6 +50,7 @@ export function startꓽstorypad(stories_glob: Immutable, config?: Immutabl logger.trace(`services/init "${key}": done ✅`) logger.groupEnd() }, Promise.resolve()) + console.groupEnd() // 2. flux await initꓽflux(stories_glob, config) diff --git a/stack--2022/4-tools/storypad/src/view/components/manager/index.ts b/stack--2022/4-tools/storypad/src/view/components/manager/index.ts index e9f235e2..df3f2b31 100644 --- a/stack--2022/4-tools/storypad/src/view/components/manager/index.ts +++ b/stack--2022/4-tools/storypad/src/view/components/manager/index.ts @@ -1,11 +1,10 @@ import assert from 'tiny-invariant' import { Url‿str } from '@offirmo-private/ts-types' -import { getꓽstory_frame_url } from '../../../flux/selectors' +import { getꓽstory_frame_url, getꓽstoryⵧcurrent } from '../../../flux/selectors' import { requestꓽstory } from '../../../flux/dispatcher.ts' import renderⵧside_panel from './side-panel' -import renderꓽroot from '../../index.ts' ///////////////////////////////////////////////// @@ -43,7 +42,8 @@ function _renderⵧstory_area() { function _renderⵧstory_frame() { const iframe_elt = document.createElement('iframe') - iframe_elt.src = getꓽstory_frame_url() + const current_story = getꓽstoryⵧcurrent() + iframe_elt.src = getꓽstory_frame_url(current_story?.uid) iframe_elt.id = 'storypad⋄iframe' console.log({iframe_elt}) document.body.appendChild(iframe_elt) diff --git a/stack--2022/4-tools/storypad/src/view/components/manager/side-panel/index.ts b/stack--2022/4-tools/storypad/src/view/components/manager/side-panel/index.ts index 80e4dcdf..bf0f0dbc 100644 --- a/stack--2022/4-tools/storypad/src/view/components/manager/side-panel/index.ts +++ b/stack--2022/4-tools/storypad/src/view/components/manager/side-panel/index.ts @@ -5,7 +5,7 @@ import assert from 'tiny-invariant' import { Immutable, Basename } from '@offirmo-private/ts-types' import { LIB } from '../../../../consts' -import { StoryEntry, StoryFolder, State } from '../../../../flux/types' +import { StoryEntry, StoryFolder, StoryTree } from '../../../../flux/types' import { getꓽtree_root, getꓽconfig, getꓽmain_frame_url, isꓽexpandedⵧinitially } from '../../../../flux/selectors' ///////////////////////////////////////////////// @@ -22,7 +22,7 @@ function render() { console.groupEnd() } -function _append_folder(parent_elt: HTMLElement, treenode: Immutable['tree'], path: Basename[]) { +function _append_folder(parent_elt: HTMLElement, treenode: Immutable, path: Basename[]) { console.group('_append_folder()', path) let details_elt = document.createElement('details')