Skip to content

Commit

Permalink
feat: Variants
Browse files Browse the repository at this point in the history
  • Loading branch information
areknawo committed Jul 15, 2023
1 parent 569ed8e commit 04d0dc2
Show file tree
Hide file tree
Showing 41 changed files with 1,302 additions and 221 deletions.
45 changes: 38 additions & 7 deletions apps/backend/collaboration/src/writing.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { publicPlugin, getContentsCollection } from "@vrite/backend";
import { publicPlugin, getContentsCollection, getContentVariantsCollection } from "@vrite/backend";
import { Server } from "@hocuspocus/server";
import { Database } from "@hocuspocus/extension-database";
import { ObjectId, Binary } from "mongodb";
Expand All @@ -7,6 +7,7 @@ import { unauthorized } from "@vrite/backend/src/lib/errors";

const writingPlugin = publicPlugin(async (fastify) => {
const contentsCollection = getContentsCollection(fastify.mongo.db!);
const contentVariantsCollection = getContentVariantsCollection(fastify.mongo.db!);
const server = Server.configure({
port: fastify.config.PORT,
address: fastify.config.HOST,
Expand Down Expand Up @@ -38,24 +39,54 @@ const writingPlugin = publicPlugin(async (fastify) => {
return null;
}

const articleContent = await contentsCollection.findOne({
contentPieceId: new ObjectId(documentName)
const [contentPieceId, variantId] = documentName.split(":");

if (variantId) {
const contentVariant = await contentVariantsCollection.findOne({
contentPieceId: new ObjectId(contentPieceId),
variantId: new ObjectId(variantId)
});

if (contentVariant && contentVariant.content) {
return new Uint8Array(contentVariant.content.buffer);
}
}

const content = await contentsCollection.findOne({
contentPieceId: new ObjectId(contentPieceId)
});

if (articleContent && articleContent.content) {
return new Uint8Array(articleContent.content.buffer);
if (content && content.content) {
return new Uint8Array(content.content.buffer);
}

return null;
},
store({ documentName, state }) {
store({ documentName, state, ...details }) {
const [contentPieceId, variantId] = documentName.split(":");

if (documentName.startsWith("workspace:")) {
return;
}

if (state) {
if (variantId) {
if (!(details as { update?: any }).update) {
return;
}

return contentVariantsCollection?.updateOne(
{
contentPieceId: new ObjectId(contentPieceId),
variantId: new ObjectId(variantId)
},
{ $set: { content: new Binary(state) } },
{ upsert: true }
);
}

return contentsCollection?.updateOne(
{ contentPieceId: new ObjectId(documentName) },
{ contentPieceId: new ObjectId(contentPieceId) },
{ $set: { content: new Binary(state) } },
{ upsert: true }
);
Expand Down
17 changes: 8 additions & 9 deletions apps/web/src/context/authenticated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface AuthenticatedContextValue {
membership: Accessor<App.WorkspaceMembership | null>;
workspace: Accessor<Omit<App.Workspace, "contentGroups"> | null>;
workspaceSettings: Accessor<App.WorkspaceSettings | null>;
role: Accessor<App.Role | null>;
role: Accessor<App.ExtendedRole<"baseType"> | null>;
deletedTags: Accessor<string[]>;
currentWorkspaceId: Accessor<string | null>;
}
Expand Down Expand Up @@ -75,13 +75,12 @@ const AuthenticatedContextProvider: ParentComponent = (props) => {
>(currentWorkspaceId, () => client.workspaceSettings.get.query(), {
initialValue: null
});
const [role, { mutate: setRole }] = createResource<App.Role | null, string | null>(
currentWorkspaceId,
() => client.roles.get.query(),
{
initialValue: null
}
);
const [role, { mutate: setRole }] = createResource<
App.ExtendedRole<"baseType"> | null,
string | null
>(currentWorkspaceId, () => client.roles.get.query(), {
initialValue: null
});
const loading = (): boolean => {
return (
currentWorkspaceId.loading ||
Expand Down Expand Up @@ -235,7 +234,7 @@ const useAuthenticatedContext = (): AuthenticatedContextValue => {
const hasPermission = (permission: App.Permission): boolean => {
const { role } = useAuthenticatedContext();

return role()?.permissions.includes(permission) || false;
return role()?.permissions.includes(permission) || role()?.baseType === "admin" || false;
};

export { AuthenticatedContextProvider, useAuthenticatedContext, hasPermission };
19 changes: 13 additions & 6 deletions apps/web/src/context/cache/content-pieces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ const useContentPieces = (contentGroupId: string): UseContentPieces => {
const contentPiecesChanges = client.contentPieces.changes.subscribe(
{ contentGroupId },
{
onData({ action, data }) {
onData(value) {
const { action, data } = value;

switch (action) {
case "delete":
setState("contentPieces", (contentPieces) => {
Expand All @@ -51,11 +53,16 @@ const useContentPieces = (contentGroupId: string): UseContentPieces => {
setState("contentPieces", (contentPieces) => [data, ...contentPieces]);
break;
case "update":
setState(
"contentPieces",
state.contentPieces.findIndex((contentPiece) => contentPiece.id === data.id),
(contentPiece) => ({ ...contentPiece, ...data })
);
if (!("variantId" in value)) {
setState(
"contentPieces",
state.contentPieces.findIndex((contentPiece) => contentPiece.id === data.id),
(contentPiece) => {
return { ...contentPiece, ...data };
}
);
}

break;
case "move":
setState("contentPieces", (contentPieces) => {
Expand Down
26 changes: 1 addition & 25 deletions apps/web/src/context/cache/index.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,19 @@
import { UseContentGroups, useContentGroups } from "./content-groups";
import { UseContentPieces, useContentPieces } from "./content-pieces";
import { UseOpenedContentPiece, useOpenedContentPiece } from "./opened-content-piece";
import { ParentComponent, createContext, createEffect, on, onCleanup, useContext } from "solid-js";
import { ParentComponent, createContext, useContext } from "solid-js";

interface CacheContextData {
useContentGroups(): UseContentGroups;
useOpenedContentPiece(): UseOpenedContentPiece;
useContentPieces(contentGroupId: string): UseContentPieces;
}

const CacheContext = createContext<CacheContextData>();
const CacheContextProvider: ParentComponent = (props) => {
const useContentGroupsCache = useContentGroups();
const useOpenedContentPieceCache = useOpenedContentPiece();

let useContentPiecesCache: Record<string, UseContentPieces> = {};

createEffect(
on(useContentGroupsCache.contentGroups, (contentGroups) => {
contentGroups.forEach(({ id }) => {
useContentPiecesCache[id] = useContentPiecesCache[id] || useContentPieces(id);
});
onCleanup(() => {
useContentPiecesCache = {};
});
})
);

return (
<CacheContext.Provider
value={{
useOpenedContentPiece() {
return useOpenedContentPieceCache;
},
useContentGroups() {
return useContentGroupsCache;
},
useContentPieces(id: string) {
return useContentPieces(id);
}
}}
>
Expand Down
46 changes: 29 additions & 17 deletions apps/web/src/context/cache/opened-content-piece.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { createSignal, createEffect, on, onCleanup } from "solid-js";
import { createSignal, createEffect, on, onCleanup, Accessor } from "solid-js";
import { createStore } from "solid-js/store";
import { useAuthenticatedContext } from "#context/authenticated";
import { useClientContext, App } from "#context/client";
import { useUIContext } from "#context/ui";

interface UseOpenedContentPiece {
activeVariant: Accessor<App.Variant | null>;
setContentPiece<
K extends keyof App.ExtendedContentPieceWithAdditionalData<"locked" | "coverWidth">
>(
keyOrObject: K | Partial<App.ExtendedContentPieceWithAdditionalData<"locked" | "coverWidth">>,
value?: App.ExtendedContentPieceWithAdditionalData<"locked" | "coverWidth">[K]
): void;
loading(): boolean;
setActiveVariant(variant: App.Variant | null): void;
contentPiece(): App.ExtendedContentPieceWithAdditionalData<"locked" | "coverWidth"> | null;
}

const useOpenedContentPiece = (): UseOpenedContentPiece => {
const [activeVariant, setActiveVariant] = createSignal<App.Variant | null>(null);
const { deletedTags } = useAuthenticatedContext();
const { profile } = useAuthenticatedContext();
const { storage, setStorage } = useUIContext();
Expand All @@ -32,7 +35,8 @@ const useOpenedContentPiece = (): UseOpenedContentPiece => {
if (contentPieceId) {
try {
const contentPiece = await client.contentPieces.get.query({
id: contentPieceId
id: contentPieceId,
variant: activeVariant()?.id
});

setState({ contentPiece });
Expand All @@ -48,9 +52,11 @@ const useOpenedContentPiece = (): UseOpenedContentPiece => {

createEffect(
on(
() => storage().contentPieceId,
(contentPieceId, previousContentPieceId) => {
if (contentPieceId !== previousContentPieceId) {
[() => storage().contentPieceId, () => activeVariant()?.id || ""],
([contentPieceId, variantId], previous) => {
const [previousContentPieceId, previousVariantId] = previous || [];

if (contentPieceId !== previousContentPieceId || variantId !== previousVariantId) {
fetchContentPiece();
}

Expand All @@ -69,22 +75,26 @@ const useOpenedContentPiece = (): UseOpenedContentPiece => {
contentGroupId
},
{
onData({ data, action, userId = "" }) {
onData(value) {
const { data, action, userId = "" } = value;

if (action === "update") {
const { tags, members, ...updateData } = data;
const update: Partial<
App.ExtendedContentPieceWithAdditionalData<"locked" | "coverWidth">
> = { ...updateData };
if (!("variantId" in value) || value.variantId === activeVariant()?.id) {
const { tags, members, ...updateData } = data;
const update: Partial<
App.ExtendedContentPieceWithAdditionalData<"locked" | "coverWidth">
> = { ...updateData };

if (data.members && userId !== profile()?.id) {
update.members = data.members.filter((member) => member.id !== profile()?.id);
}
if (data.members && userId !== profile()?.id) {
update.members = data.members.filter((member) => member.id !== profile()?.id);
}

if (data.tags && userId !== profile()?.id) {
update.tags = data.tags.filter((tag) => !deletedTags().includes(tag.id));
}
if (data.tags && userId !== profile()?.id) {
update.tags = data.tags.filter((tag) => !deletedTags().includes(tag.id));
}

setState("contentPiece", update);
setState("contentPiece", update);
}
} else if (action === "delete") {
setState("contentPiece", null);
setStorage((storage) => ({ ...storage, contentPieceId: undefined }));
Expand Down Expand Up @@ -125,6 +135,8 @@ const useOpenedContentPiece = (): UseOpenedContentPiece => {

return {
loading,
activeVariant,
setActiveVariant,
contentPiece: () => state.contentPiece,
setContentPiece: (keyOrObject, value) => {
if (typeof keyOrObject === "string" && value) {
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/context/ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface StorageData {
}
interface ReferencesData {
editedContentPiece?: App.ExtendedContentPieceWithAdditionalData<"locked">;
activeVariant?: App.Variant;
provider?: HocuspocusProvider;
editor?: SolidEditor;
}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/layout/secured-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const SecuredLayout: ParentComponent = (props) => {
<SidebarMenu />
</Show>
<div
class="flex flex-col flex-1 md:h-full overflow-hidden"
class="flex flex-col flex-1 md:h-full overflow-visible"
id="main-scrollable-container"
>
<div class="flex flex-1 h-full">
Expand Down
9 changes: 5 additions & 4 deletions apps/web/src/layout/side-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ const SidePanel: Component = () => {
return (
<div
class={clsx(
"fixed z-20 top-0 left-0 !lt-md:w-full md:relative h-[calc(100%-3.25rem-env(safe-area-inset-bottom,0px))] md:h-full border-gray-200 dark:border-gray-700 bg-gray-100 dark:bg-gray-800",
"fixed top-0 left-0 !lt-md:w-full md:relative h-[calc(100%-3.25rem-env(safe-area-inset-bottom,0px))] md:h-full border-gray-200 dark:border-gray-700 bg-gray-100 dark:bg-gray-800",
"transition-transform ease-out duration-350",
sidePanelEnabled() ? "flex" : "hidden",
collapsed() && "translate-y-[100vh] md:translate-y-0",
!collapsed() && "md:border-r-2"
collapsed() && "translate-y-[100vh] md:translate-y-0 z-50",
!collapsed() && "md:border-r-2 z-20"
)}
style={{
"width": `${storage().sidePanelWidth || 0}px`,
Expand Down Expand Up @@ -124,7 +124,8 @@ const SidePanel: Component = () => {
>
<div
class={clsx(
"w-1 -ml-0.25 fixed h-full bg-gradient-to-tr transition-opacity duration-350",
"w-1 fixed h-full bg-gradient-to-tr transition-opacity duration-350",
collapsed() ? "-ml-0.75" : "-ml-0.25",
dragging() || handleHover() ? "opacity-100" : "opacity-0"
)}
></div>
Expand Down
Loading

0 comments on commit 04d0dc2

Please sign in to comment.