Skip to content

Commit

Permalink
user global store
Browse files Browse the repository at this point in the history
  • Loading branch information
riccardoperra committed Oct 31, 2024
1 parent d275cda commit 9724d48
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 76 deletions.
5 changes: 3 additions & 2 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,17 @@
"@solid-primitives/deep": "^0.2.10",
"@solid-primitives/event-listener": "^2.3.3",
"@solid-primitives/keyed": "^1.2.3",
"@solid-primitives/memo": "^1.3.10",
"@solid-primitives/storage": "^4.2.1",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.14.10",
"@solidjs/router": "^0.15.0",
"@solidjs/start": "^1.0.9",
"@vanilla-extract/css": "^1.16.0",
"@vanilla-extract/dynamic": "^2.1.2",
"@vanilla-extract/recipes": "^0.5.5",
"@vanilla-extract/vite-plugin": "^4.0.17",
"@vinxi/plugin-mdx": "^3.7.2",
"@xyflow/system": "^0.0.43",
"@xyflow/system": "^0.0.44",
"appwrite": "^16.0.2",
"elkjs": "^0.9.3",
"msw": "^2.6.0",
Expand Down
72 changes: 38 additions & 34 deletions packages/app/src/components/Editor/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import * as styles from './EditorHeader.css';
import {EditorStore} from '#editor-store/editor.store';
import {Icon} from '#ui/components/Icon';
import {
Button,
IconButton,
Popover,
PopoverContent,
PopoverTrigger,
} from '@codeui/kit';
import {provideState} from 'statebuilder';
import {EditorUiStore} from '../store/ui.store';
import {createSignal, JSX, type ParentProps, Show, useContext} from 'solid-js';
import {Icon} from '#ui/components/Icon';
import {A, useSubmission} from '@solidjs/router';
import {createSignal, JSX, type ParentProps, Show, useContext} from 'solid-js';
import {provideState} from 'statebuilder';
import {UserStore} from '~/store/user.store';
import {createScratchFork, updateScratch} from '../../../lib/scratchApi';
import {EditorStore} from '#editor-store/editor.store';
import {EditorContext} from '../editor.context';
import {EditorUiStore} from '../store/ui.store';
import * as styles from './EditorHeader.css';

export interface EditorHeaderProps {
showBack: boolean;
Expand Down Expand Up @@ -81,6 +82,7 @@ function EditorHeaderForkButton() {
export function EditorHeader(props: EditorHeaderProps) {
const editorUi = provideState(EditorUiStore);
const editorStore = provideState(EditorStore);
const user = provideState(UserStore);

const isUpdating = useSubmission(updateScratch);

Expand All @@ -103,35 +105,37 @@ export function EditorHeader(props: EditorHeaderProps) {

{props.name}

<div class={styles.headerRightSide}>
<Show
fallback={
<>
<EditorHeaderForkButton />
</>
}
when={editorStore.get.remoteId}
>
{remoteId => (
<form
action={updateScratch.with(
remoteId(),
editorStore.yamlSession.source(),
)}
method={'post'}
>
<Button
type={'submit'}
theme={'primary'}
size={'sm'}
loading={isUpdating.pending}
<Show when={user()}>
<div class={styles.headerRightSide}>
<Show
fallback={
<>
<EditorHeaderForkButton />
</>
}
when={editorStore.get.remoteId}
>
{remoteId => (
<form
action={updateScratch.with(
remoteId(),
editorStore.yamlSession.source(),
)}
method={'post'}
>
Save
</Button>
</form>
)}
</Show>
</div>
<Button
type={'submit'}
theme={'primary'}
size={'sm'}
loading={isUpdating.pending}
>
Save
</Button>
</form>
)}
</Show>
</div>
</Show>
</header>
<div class={styles.subHeader}>
<EditorHeaderActionButton
Expand Down
32 changes: 29 additions & 3 deletions packages/app/src/components/Editor/Header/RepositoryHeaderName.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import {Icon} from '#ui/components/Icon';
import {Link} from '@codeui/kit';
import {A, useParams} from '@solidjs/router';
import {Show} from 'solid-js';
import {headerRepoNavLi, headerRepoNavOl} from './EditorHeader.css';
import {Link} from '@codeui/kit';
import {Icon} from '#ui/components/Icon';

const buildGitHubLink = (
owner: string,
repoName: string,
branch: string,
file: string,
) =>
`https://github.com/${owner}/${repoName}/blob/${branch}/.github/workflows/${file}`;

export function EditorRepositoryHeaderName() {
const params = useParams();
Expand All @@ -16,6 +24,17 @@ export function EditorRepositoryHeaderName() {
filePath,
};
};

const gitHubExternalLink = () => {
const path = resolvedPath();
return buildGitHubLink(
path.owner,
path.repoName,
path.branchName,
path.filePath[path.filePath.length - 1],
);
};

return (
<Show when={resolvedPath()} keyed>
{path => (
Expand All @@ -41,8 +60,15 @@ export function EditorRepositoryHeaderName() {
<Icon name={'arrow_right_alt'} />
</li>
<li class={headerRepoNavLi}>
<Link variant={'underline'} href={''} aria-current={'page'}>
<Link
variant={'underline'}
href={gitHubExternalLink()}
target="_blank"
aria-current={'page'}
>
{path.filePath.join('/')}

<Icon name={'open_in_new'} />
</Link>
</li>
</ol>
Expand Down
4 changes: 3 additions & 1 deletion packages/app/src/components/Editor/editor.context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {createContext, useContext} from 'solid-js';
import type {Models} from 'node-appwrite';
import {Accessor, createContext, useContext} from 'solid-js';

export interface EditorParsedRepository {
owner: string;
Expand All @@ -11,6 +12,7 @@ export interface EditorPageContext {
source: string;
remoteId: string | null;
repository: EditorParsedRepository | null;
user: Accessor<Models.User<any> | undefined | null>;
}

export const EditorContext = createContext<EditorPageContext>();
Expand Down
34 changes: 19 additions & 15 deletions packages/app/src/components/Home/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Button} from '@codeui/kit';
import {
cache,
query,
createAsync,
useSearchParams,
useSubmission,
Expand All @@ -26,8 +26,10 @@ import {RepoCardFallback} from './RepoCard/RepoCardFallback';
import {RepoSearch} from './RepoSearch/RepoSearch';
import {ScratchList} from './ScratchList/ScratchList';
import {OverlayLoader} from '~/ui/components/Loader/Loader';
import {provideState} from 'statebuilder';
import {UserStore} from '~/store/user.store';

export const searchRepo = cache(async (path: string | null) => {
export const searchRepo = query(async (path: string | null) => {
'use server';
if (!path) {
return null;
Expand All @@ -41,7 +43,7 @@ export const searchRepo = cache(async (path: string | null) => {
}, 'repository');

export function Home() {
const user = createAsync(() => loggedInUser(), {deferStream: true});
const user = provideState(UserStore);
const isCreatingScratch = useSubmission(createScratch);
const [params] = useSearchParams();
const repo = createAsync(() => searchRepo(params.repo as string | null));
Expand Down Expand Up @@ -80,19 +82,21 @@ export function Home() {
</Show>
</Suspense>

<div class={choiceSeparator}>Or</div>
<Show when={user()}>
<div class={choiceSeparator}>Or</div>

<form action={createScratch.with()} class={form} method={'post'}>
<Button
loading={isCreatingScratch.pending}
block
theme={'tertiary'}
type={'submit'}
size={'lg'}
>
Create from scratch
</Button>
</form>
<form action={createScratch.with()} class={form} method={'post'}>
<Button
loading={isCreatingScratch.pending}
block
theme={'tertiary'}
type={'submit'}
size={'lg'}
>
Create from scratch
</Button>
</form>
</Show>

<Show when={user()}>
<div class={choiceSeparator}>Your scratches & forks</div>
Expand Down
6 changes: 3 additions & 3 deletions packages/app/src/lib/scratchApi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {action, cache, json, redirect} from '@solidjs/router';
import {Permission, Role, Query, ID, Models} from 'appwrite';
import {action, cache, json, query, redirect} from '@solidjs/router';
import {ID, Models, Permission, Query, Role} from 'appwrite';
import {adjectives, colors, uniqueNamesGenerator} from 'unique-names-generator';
import type {EditorParsedRepository} from '../components/Editor/editor.context';
import {createAdminClient, createSessionClient} from './server/appwrite';
Expand Down Expand Up @@ -163,7 +163,7 @@ export const deleteScratch = action(async scratchId => {
);
}, 'create-scratch');

export const getScratch = cache(async (id: string) => {
export const getScratch = query(async (id: string) => {
'use server';

const {database} = await createAdminClient();
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/lib/session.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {action, cache, redirect} from '@solidjs/router';
import {action, query, redirect} from '@solidjs/router';
import {OAuthProvider} from 'node-appwrite';
import {getHeaders} from 'vinxi/http';
import {createAdminClient} from './server/appwrite';
import {getLoggedInUser, getSession, logoutSession} from './server/session';

export const loggedInUser = cache(async () => {
export const loggedInUser = query(async () => {
'use server';
const session = await getSession();
const userId = session.data.session?.userId;
Expand Down
56 changes: 56 additions & 0 deletions packages/app/src/lib/statebuilder/async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {createLatest} from '@solid-primitives/memo';
import {AccessorWithLatest, createAsync} from '@solidjs/router';
import {createComputed, createSignal, Setter} from 'solid-js';
import {create} from 'statebuilder';

export interface AsyncSignalState<T> {
/**
* The latest value.
*/
(): T;

/**
* The raw signal returned by `createAsync`.
* Note: this accessor will not contain any value written by `set`.
*/
raw: AccessorWithLatest<T | undefined>;

/**
* Write a new value to the store.
*/
set: (setter: Setter<T>) => void;
}
function makeAsyncSignal<T>(
fetcher: (prev: T | undefined) => Promise<T>,
options?: {
name?: string;
initialValue?: T;
deferStream?: boolean;
},
): AsyncSignalState<T | undefined> {
const asyncState = createAsync(fetcher, options);
const [signal, setSignal] = createSignal<T | undefined>(
options?.initialValue,
);

createComputed(() => {
// Sync latest signal retrieved by asyncState in order to be available globally after navigation
const latest = asyncState.latest;
setSignal(() => latest);
});

const latestValue = createLatest([signal, asyncState]);
const state: AsyncSignalState<T | undefined> = Object.assign(latestValue, {
raw: asyncState,
set: setSignal,
});

return state;
}

/**
* Define an async signal.
*
* @experimental TODO port to statebuilder?
*/
export const eDefineAsync = create('asyncSignal', makeAsyncSignal);
16 changes: 11 additions & 5 deletions packages/app/src/routes/editor/[...path].tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import {
cache,
createAsync,
query,
redirect,
type RouteDefinition,
type RouteSectionProps,
} from '@solidjs/router';
import {createEffect} from 'solid-js';
import {provideState, StateProvider} from 'statebuilder';
import {Editor} from '~/components/Editor/Editor';
import {
EditorContext,
type EditorParsedRepository,
} from '~/components/Editor/editor.context';
import {Editor} from '~/components/Editor/Editor';
import {getGithubRepoFileContent} from '~/lib/githubApi';
import {StateProvider} from 'statebuilder';
import {loggedInUser} from '~/lib/session';
import {UserStore} from '~/store/user.store';

const getWorkflowFromUrl = cache(async (path: string) => {
const getWorkflowFromUrl = query(async (path: string) => {
'use server';

const [owner, repoName, branchName, ...filePath] = path.split('/');

return getGithubRepoFileContent(
`${owner}/${repoName}`,
branchName,
Expand Down Expand Up @@ -52,6 +55,8 @@ export default function EditorPage(props: RouteSectionProps) {
getWorkflowFromUrl(props.params.path),
);

const user = provideState(UserStore);

return (
<EditorContext.Provider
value={{
Expand All @@ -62,6 +67,7 @@ export default function EditorPage(props: RouteSectionProps) {
get repository() {
return workflowContent()?.repository ?? null;
},
user,
}}
>
<StateProvider>
Expand Down
Loading

0 comments on commit 9724d48

Please sign in to comment.