From 6799f95a2a596dca23bbb6484e07ab04404d91b2 Mon Sep 17 00:00:00 2001 From: Vinzenz Aubry Date: Wed, 13 Sep 2023 21:21:41 -0400 Subject: [PATCH 1/8] init meta page --- .../src/components/MenuTabHeader.vue | 17 +- caster-editor/src/components/Meta.vue | 237 ++++++++++++++++++ caster-editor/src/components/Wysiwyg.vue | 81 ++++++ caster-editor/src/graphql.ts | 10 + caster-editor/src/stores/InterfaceStore.ts | 1 + caster-editor/src/views/GraphDetailView.vue | 10 +- 6 files changed, 347 insertions(+), 9 deletions(-) create mode 100644 caster-editor/src/components/Meta.vue create mode 100644 caster-editor/src/components/Wysiwyg.vue diff --git a/caster-editor/src/components/MenuTabHeader.vue b/caster-editor/src/components/MenuTabHeader.vue index 893716b4..fa888932 100644 --- a/caster-editor/src/components/MenuTabHeader.vue +++ b/caster-editor/src/components/MenuTabHeader.vue @@ -1,3 +1,10 @@ + + - - diff --git a/caster-editor/src/components/Meta.vue b/caster-editor/src/components/Meta.vue new file mode 100644 index 00000000..e5f4c8b7 --- /dev/null +++ b/caster-editor/src/components/Meta.vue @@ -0,0 +1,237 @@ + + + + + + diff --git a/caster-editor/src/components/Wysiwyg.vue b/caster-editor/src/components/Wysiwyg.vue new file mode 100644 index 00000000..53d7907a --- /dev/null +++ b/caster-editor/src/components/Wysiwyg.vue @@ -0,0 +1,81 @@ + + + + + + diff --git a/caster-editor/src/graphql.ts b/caster-editor/src/graphql.ts index a734c295..8153c260 100644 --- a/caster-editor/src/graphql.ts +++ b/caster-editor/src/graphql.ts @@ -1251,6 +1251,11 @@ export type GraphSubscription = { node: { uuid: any }; }>; }>; + aboutText: string; + displayName: string; + endText: string; + startText: string; + templateName: GraphDetailTemplate; }; }; @@ -1882,6 +1887,11 @@ export const GraphDocument = gql` name slugName uuid + aboutText + displayName + endText + startText + templateName edges { uuid inNodeDoor { diff --git a/caster-editor/src/stores/InterfaceStore.ts b/caster-editor/src/stores/InterfaceStore.ts index 3720670c..0880d609 100644 --- a/caster-editor/src/stores/InterfaceStore.ts +++ b/caster-editor/src/stores/InterfaceStore.ts @@ -14,6 +14,7 @@ import { ElMessage } from "element-plus"; export enum Tab { Edit = "Edit", Play = "Play", + Meta = "Meta", } // some hack to avoid diff --git a/caster-editor/src/views/GraphDetailView.vue b/caster-editor/src/views/GraphDetailView.vue index 8c4ab461..5f319a37 100644 --- a/caster-editor/src/views/GraphDetailView.vue +++ b/caster-editor/src/views/GraphDetailView.vue @@ -3,13 +3,14 @@ import { storeToRefs } from "pinia"; import { watch } from "vue"; import { useRoute, useRouter } from "vue-router"; import Graph from "@/components/Graph.vue"; +import Meta from "@/components/Meta.vue"; import Menu from "@/components/Menu.vue"; import NodeEditor from "@/components/NodeEditor.vue"; -import { useInterfaceStore } from "@/stores/InterfaceStore"; +import { useInterfaceStore, Tab } from "@/stores/InterfaceStore"; import { useGraphSubscription } from "@/graphql"; import { ElMessage } from "element-plus"; -const { showNodeEditor, selectedNodeForEditorUuid } = storeToRefs( +const { showNodeEditor, selectedNodeForEditorUuid, tab } = storeToRefs( useInterfaceStore(), ); @@ -40,6 +41,11 @@ watch(graphSubscription.error, () => { + + Date: Wed, 13 Sep 2023 23:19:42 -0400 Subject: [PATCH 2/8] Finished Metadata --- caster-editor/src/components/Meta.vue | 111 +++++++++++++---------- caster-editor/src/components/Wysiwyg.vue | 12 ++- 2 files changed, 69 insertions(+), 54 deletions(-) diff --git a/caster-editor/src/components/Meta.vue b/caster-editor/src/components/Meta.vue index e5f4c8b7..53837266 100644 --- a/caster-editor/src/components/Meta.vue +++ b/caster-editor/src/components/Meta.vue @@ -9,18 +9,10 @@ import { ElFormItem, ElCol, } from "element-plus"; -import { - reactive, - ref, - onMounted, - onDeactivated, - type Ref, - computed, -} from "vue"; -import type { GraphSubscription, Graph } from "@/graphql"; +import { ref, computed } from "vue"; +import type { GraphSubscription } from "@/graphql"; import "@toast-ui/editor/dist/toastui-editor.css"; // Editor's Style -import type { EditorOptions, Editor as EditorType } from "@toast-ui/editor"; -import Editor from "@toast-ui/editor"; + import Wysiwyg from "@/components/Wysiwyg.vue"; import { useInterfaceStore, Tab } from "@/stores/InterfaceStore"; @@ -28,13 +20,8 @@ import { storeToRefs } from "pinia"; const { tab } = storeToRefs(useInterfaceStore()); // props -// const props = defineProps<{ -// graph: GraphSubscription["graph"]; -// }>(); - const props = defineProps<{ - graph: Graph; - showDebug?: boolean; + graph: GraphSubscription["graph"]; }>(); // TODO: Import from graphql @@ -53,63 +40,78 @@ const streamAssignmentOptions = [ }, ]; -const metaForm = reactive({ +const metaForm = ref({ projectName: "", displayName: "", slug: "", streamAssignment: "", - introText: "", + startText: "", aboutText: "", endText: "", }); // cloning needs plugin, so just being redundant for now -const metaFormOriginal = reactive({ +const metaFormOriginal = ref({ projectName: "", displayName: "", slug: "", streamAssignment: "", - introText: "", + startText: "", aboutText: "", endText: "", }); const saveButtonActive = computed(() => { - if (JSON.stringify(metaForm) !== JSON.stringify(metaFormOriginal)) { + if ( + JSON.stringify(metaForm.value) !== JSON.stringify(metaFormOriginal.value) + ) { return true; } else { return false; } }); +const populateData = () => { + // form + metaForm.value.projectName = props.graph.name; + metaForm.value.displayName = props.graph.displayName; + metaForm.value.slug = props.graph.slugName; + metaForm.value.streamAssignment = "one_graph_one_stream"; + metaForm.value.startText = props.graph.startText; + metaForm.value.aboutText = props.graph.aboutText; + metaForm.value.endText = props.graph.endText; + + // original data + metaFormOriginal.value.projectName = props.graph.name; + metaFormOriginal.value.displayName = props.graph.displayName; + metaFormOriginal.value.slug = props.graph.slugName; + metaFormOriginal.value.streamAssignment = "one_graph_one_stream"; + metaFormOriginal.value.startText = props.graph.startText; + metaFormOriginal.value.aboutText = props.graph.aboutText; + metaFormOriginal.value.endText = props.graph.endText; +}; + +// TODO: remove redundancy by abstracting +// somehow I couldn't figure out how to pass the reference to the function +// const updatedMarkdown = (text: string, reference: Ref) => { +// }; + +const updateAbout = (text: string) => { + metaForm.value.aboutText = text; +}; + +const updateEnd = (text: string) => { + metaForm.value.endText = text; +}; + +// TODO: Submit Data const onSubmit = () => { console.log("Submitted"); console.log(metaForm); }; const onCancel = () => { - // tab.value = Tab.Edit; - console.log(JSON.stringify(metaForm)); -}; - -const populateData = () => { - // form - metaForm.projectName = props.graph.name; - metaForm.displayName = props.graph.displayName; - metaForm.slug = props.graph.slugName; - metaForm.streamAssignment = "one_graph_one_stream"; - metaForm.introText = props.graph.startText; - metaForm.aboutText = props.graph.aboutText; - metaForm.endText = props.graph.endText; - - // original data - metaFormOriginal.projectName = props.graph.name; - metaFormOriginal.displayName = props.graph.displayName; - metaFormOriginal.slug = props.graph.slugName; - metaFormOriginal.streamAssignment = "one_graph_one_stream"; - metaFormOriginal.introText = props.graph.startText; - metaFormOriginal.aboutText = props.graph.aboutText; - metaFormOriginal.endText = props.graph.endText; + tab.value = Tab.Edit; }; populateData(); @@ -169,18 +171,30 @@ populateData(); - + - + - + @@ -193,7 +207,7 @@ populateData(); Save - Cancel + Discard @@ -213,7 +227,6 @@ populateData(); overflow-y: scroll; padding: $spacingM; padding-top: $spacingM; - // background-color: red; .el-form { max-width: 740px; diff --git a/caster-editor/src/components/Wysiwyg.vue b/caster-editor/src/components/Wysiwyg.vue index 53d7907a..ffd39754 100644 --- a/caster-editor/src/components/Wysiwyg.vue +++ b/caster-editor/src/components/Wysiwyg.vue @@ -35,16 +35,18 @@ onMounted(() => { autofocus: false, }; - console.log(props.text); - editor.value = new Editor(options); // add events - // editor.value.on("change", () => { - // scriptCellText.value = editor.value?.getMarkdown() || ""; - // }); + editor.value.on("change", () => { + emit("updateText", editor.value?.getMarkdown() || "sampletext"); + }); }); +const emit = defineEmits<{ + (e: "updateText", text: string): void; +}>(); + onDeactivated(() => { if (editor.value) { editor.value.destroy(); From a184355af04e843b7673350213b076e5540c8228 Mon Sep 17 00:00:00 2001 From: Vinzenz Aubry Date: Wed, 13 Sep 2023 23:26:17 -0400 Subject: [PATCH 3/8] missing stream assignment --- caster-editor/src/components/Meta.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/caster-editor/src/components/Meta.vue b/caster-editor/src/components/Meta.vue index 53837266..a26f5b45 100644 --- a/caster-editor/src/components/Meta.vue +++ b/caster-editor/src/components/Meta.vue @@ -76,7 +76,8 @@ const populateData = () => { metaForm.value.projectName = props.graph.name; metaForm.value.displayName = props.graph.displayName; metaForm.value.slug = props.graph.slugName; - metaForm.value.streamAssignment = "one_graph_one_stream"; + //TODO: streamAssignment + metaForm.value.streamAssignment = "missing graph ql"; metaForm.value.startText = props.graph.startText; metaForm.value.aboutText = props.graph.aboutText; metaForm.value.endText = props.graph.endText; @@ -85,7 +86,7 @@ const populateData = () => { metaFormOriginal.value.projectName = props.graph.name; metaFormOriginal.value.displayName = props.graph.displayName; metaFormOriginal.value.slug = props.graph.slugName; - metaFormOriginal.value.streamAssignment = "one_graph_one_stream"; + metaFormOriginal.value.streamAssignment = "missing graph ql"; metaFormOriginal.value.startText = props.graph.startText; metaFormOriginal.value.aboutText = props.graph.aboutText; metaFormOriginal.value.endText = props.graph.endText; From 9c8ea4e38508bc0547a35a933acb76a55993b7fd Mon Sep 17 00:00:00 2001 From: Dennis Scheiba Date: Thu, 14 Sep 2023 14:36:59 +0200 Subject: [PATCH 4/8] add update graph qgl mutations --- caster-back/gencaster/schema.py | 18 ++++ caster-back/operations.gql | 28 ++++++ caster-back/schema.gql | 65 +++++++++++++- caster-back/story_graph/types.py | 41 ++++++--- caster-editor/src/graphql.ts | 142 ++++++++++++++++++++++++++++--- caster-front/src/graphql.ts | 132 +++++++++++++++++++++++++++- 6 files changed, 400 insertions(+), 26 deletions(-) diff --git a/caster-back/gencaster/schema.py b/caster-back/gencaster/schema.py index d8db5834..b1231b21 100644 --- a/caster-back/gencaster/schema.py +++ b/caster-back/gencaster/schema.py @@ -54,6 +54,7 @@ ScriptCell, ScriptCellInputCreate, ScriptCellInputUpdate, + UpdateGraphInput, create_python_highlight_string, ) from stream.exceptions import NoStreamAvailableException @@ -486,6 +487,23 @@ async def add_graph(self, info, graph_input: AddGraphInput) -> Graph: # https://docs.djangoproject.com/en/4.2/ref/models/instances/#django.db.models.Model.arefresh_from_db return await story_graph_models.Graph.objects.aget(uuid=graph.uuid) # type: ignore + @strawberry.mutation + async def update_graph( + self, info, graph_input: UpdateGraphInput, graph_uuid: uuid.UUID + ) -> Graph: + await graphql_check_authenticated(info) + + graph = await story_graph_models.Graph.objects.aget(uuid=graph_uuid) + + for key, value in graph_input.__dict__.items(): + if value == strawberry.UNSET: + continue + graph.__setattr__(key, value) + + await graph.asave() + + return graph # type: ignore + @strawberry.mutation async def add_audio_file(self, info, new_audio_file: AddAudioFile) -> AudioFileUploadResponse: # type: ignore if new_audio_file.file is None or len(new_audio_file.file) == 0: diff --git a/caster-back/operations.gql b/caster-back/operations.gql index e2ef38d9..5e3a9158 100644 --- a/caster-back/operations.gql +++ b/caster-back/operations.gql @@ -208,6 +208,25 @@ subscription node($uuid: UUID!) { } } +fragment GraphMetaData on Graph { + uuid + templateName + startText + slugName + name + endText + displayName + aboutText + streamAssignmentPolicy + publicVisible +} + +query GetGraph($graphUuid:ID!) { + graph(pk: $graphUuid) { + ...GraphMetaData + } +} + mutation CreateGraph($graphInput: AddGraphInput!) { addGraph(graphInput: $graphInput) { name @@ -220,6 +239,15 @@ mutation CreateGraph($graphInput: AddGraphInput!) { } } +mutation UpdateGraph($graphUuid:UUID!, $graphUpdate: UpdateGraphInput!) { + updateGraph( + graphInput: $graphUpdate + graphUuid: $graphUuid + ) { + uuid + } +} + subscription stream($graphUuid: UUID!) { streamInfo(graphUuid: $graphUuid) { __typename diff --git a/caster-back/schema.gql b/caster-back/schema.gql index b3f6906f..227ab87b 100644 --- a/caster-back/schema.gql +++ b/caster-back/schema.gql @@ -47,7 +47,7 @@ input AddGraphInput { publicVisible: Boolean """Manages the stream assignment for this graph""" - streamAssignmentPolicy: String + streamAssignmentPolicy: StreamAssignmentPolicy """ Allows to switch to a different template in the frontend with different connection flows or UI @@ -360,6 +360,14 @@ type Graph { """ templateName: GraphDetailTemplate! + """Manages the stream assignment for this graph""" + streamAssignmentPolicy: StreamAssignmentPolicy! + + """ + If the graph is not public it will not be listed in the frontend, yet it is still accessible via URL + """ + publicVisible: Boolean! + """ Text about the graph which will be displayed at the start of a stream - only if this is set """ @@ -492,6 +500,7 @@ type Mutation { """Deletes a given :class:`~story_graph.models.ScriptCell`.""" deleteScriptCell(scriptCellUuid: UUID!): Void addGraph(graphInput: AddGraphInput!): Graph! + updateGraph(graphInput: UpdateGraphInput!, graphUuid: UUID!): Graph! addAudioFile(newAudioFile: AddAudioFile!): AudioFileUploadResponse! createUpdateStreamVariable(streamVariables: [StreamVariableInput!]!): [StreamVariable!]! createNodeDoor(nodeDoorInput: NodeDoorInputCreate!, nodeUuid: UUID!): NodeDoor! @@ -758,6 +767,13 @@ type Stream { streamPoint: StreamPoint! } +"""An enumeration.""" +enum StreamAssignmentPolicy { + ONE_GRAPH_ONE_STREAM + ONE_USER_ONE_STREAM + DEACTIVATE +} + type StreamInfo { stream: Stream! streamInstruction: StreamInstruction @@ -930,6 +946,53 @@ input UpdateAudioFile { name: String } +""" +A collection of :class:`~Node` and :class:`~Edge`. +This can be considered a score as well as a program as it +has an entry point as a :class:`~Node` and can jump to any +other :class:`~Node`, also allowing for recursive loops/cycles. + +Each node can be considered a little program on its own which can consist +of multiple :class:`~ScriptCell` which can be coded in a variety of +languages which can control the frontend and the audio (by e.g. speaking +on the stream) or setting a background music. + +The story graph is a core concept and can be edited with a native editor. +""" +input UpdateGraphInput { + """Name of the graph""" + name: String + + """Will be used as a display name in the frontend""" + displayName: String + + """ + Text about the graph which will be displayed at the start of a stream - only if this is set + """ + startText: String + + """ + Text about the graph which can be accessed during a stream - only if this is set + """ + aboutText: String + + """Text which will be displayed at the end of a stream""" + endText: String + + """ + If the graph is not public it will not be listed in the frontend, yet it is still accessible via URL + """ + publicVisible: Boolean + + """Manages the stream assignment for this graph""" + streamAssignmentPolicy: StreamAssignmentPolicy + + """ + Allows to switch to a different template in the frontend with different connection flows or UI + """ + templateName: GraphDetailTemplate +} + scalar Upload """ diff --git a/caster-back/story_graph/types.py b/caster-back/story_graph/types.py index 20f19a23..749a8ebd 100644 --- a/caster-back/story_graph/types.py +++ b/caster-back/story_graph/types.py @@ -22,6 +22,7 @@ PlaybackType = strawberry.enum(models.AudioCell.PlaybackChoices) # type: ignore TemplateType = strawberry.enum(models.Graph.GraphDetailTemplate) # type: ignore NodeDoorType = strawberry.enum(models.NodeDoor.DoorType) # type: ignore +StreamAssignmentPolicy = strawberry.enum(models.Graph.StreamAssignmentPolicy) # type: ignore def create_python_highlight_string(e: SyntaxError) -> str: @@ -85,6 +86,8 @@ class Graph: display_name: auto slug_name: auto template_name: TemplateType + stream_assignment_policy: StreamAssignmentPolicy + public_visible: auto start_text: auto about_text: auto end_text: auto @@ -95,6 +98,31 @@ def edges(self) -> List["Edge"]: return models.Edge.objects.filter(out_node_door__node__graph=self) # type: ignore +@strawberry.django.input(models.Graph) +class AddGraphInput: + name: auto + display_name: auto + slug_name: auto + start_text: auto + about_text: auto + end_text: auto + public_visible: auto + stream_assignment_policy: StreamAssignmentPolicy + template_name: TemplateType + + +@strawberry.django.input(model=models.Graph, partial=True) +class UpdateGraphInput: + name: auto + display_name: auto + start_text: auto + about_text: auto + end_text: auto + public_visible: auto + stream_assignment_policy: StreamAssignmentPolicy + template_name: TemplateType + + @strawberry.django.type(models.Node) class Node: uuid: auto @@ -210,16 +238,3 @@ class ScriptCellInputUpdate: cell_code: Optional[str] cell_order: Optional[int] audio_cell: Optional[AudioCellInput] - - -@strawberry.django.input(models.Graph) -class AddGraphInput: - name: auto - display_name: auto - slug_name: auto - start_text: auto - about_text: auto - end_text: auto - public_visible: auto - stream_assignment_policy: auto - template_name: TemplateType diff --git a/caster-editor/src/graphql.ts b/caster-editor/src/graphql.ts index 8153c260..9940e210 100644 --- a/caster-editor/src/graphql.ts +++ b/caster-editor/src/graphql.ts @@ -63,7 +63,7 @@ export type AddGraphInput = { /** Text about the graph which will be displayed at the start of a stream - only if this is set */ startText?: InputMaybe; /** Manages the stream assignment for this graph */ - streamAssignmentPolicy?: InputMaybe; + streamAssignmentPolicy?: InputMaybe; /** Allows to switch to a different template in the frontend with different connection flows or UI */ templateName?: InputMaybe; }; @@ -362,10 +362,14 @@ export type Graph = { /** Name of the graph */ name: Scalars["String"]; nodes: Array; + /** If the graph is not public it will not be listed in the frontend, yet it is still accessible via URL */ + publicVisible: Scalars["Boolean"]; /** Will be used as a URL */ slugName: Scalars["String"]; /** Text about the graph which will be displayed at the start of a stream - only if this is set */ startText: Scalars["String"]; + /** Manages the stream assignment for this graph */ + streamAssignmentPolicy: StreamAssignmentPolicy; /** Allows to switch to a different template in the frontend with different connection flows or UI */ templateName: GraphDetailTemplate; uuid: Scalars["UUID"]; @@ -478,6 +482,7 @@ export type Mutation = { deleteScriptCell?: Maybe; /** Update metadata of an :class:`~stream.models.AudioFile` via a UUID */ updateAudioFile: AudioFile; + updateGraph: Graph; /** * Updates a given :class:`~story_graph.models.Node` which can be used * for renaming or moving it across the canvas. @@ -556,6 +561,12 @@ export type MutationUpdateAudioFileArgs = { uuid: Scalars["UUID"]; }; +/** Mutations for Gencaster via GraphQL. */ +export type MutationUpdateGraphArgs = { + graphInput: UpdateGraphInput; + graphUuid: Scalars["UUID"]; +}; + /** Mutations for Gencaster via GraphQL. */ export type MutationUpdateNodeArgs = { nodeUpdate: NodeUpdate; @@ -858,6 +869,13 @@ export type Stream = { uuid: Scalars["UUID"]; }; +/** An enumeration. */ +export enum StreamAssignmentPolicy { + Deactivate = "DEACTIVATE", + OneGraphOneStream = "ONE_GRAPH_ONE_STREAM", + OneUserOneStream = "ONE_USER_ONE_STREAM", +} + export type StreamInfo = { stream: Stream; streamInstruction?: Maybe; @@ -1037,6 +1055,38 @@ export type UpdateAudioFile = { name?: InputMaybe; }; +/** + * A collection of :class:`~Node` and :class:`~Edge`. + * This can be considered a score as well as a program as it + * has an entry point as a :class:`~Node` and can jump to any + * other :class:`~Node`, also allowing for recursive loops/cycles. + * + * Each node can be considered a little program on its own which can consist + * of multiple :class:`~ScriptCell` which can be coded in a variety of + * languages which can control the frontend and the audio (by e.g. speaking + * on the stream) or setting a background music. + * + * The story graph is a core concept and can be edited with a native editor. + */ +export type UpdateGraphInput = { + /** Text about the graph which can be accessed during a stream - only if this is set */ + aboutText?: InputMaybe; + /** Will be used as a display name in the frontend */ + displayName?: InputMaybe; + /** Text which will be displayed at the end of a stream */ + endText?: InputMaybe; + /** Name of the graph */ + name?: InputMaybe; + /** If the graph is not public it will not be listed in the frontend, yet it is still accessible via URL */ + publicVisible?: InputMaybe; + /** Text about the graph which will be displayed at the start of a stream - only if this is set */ + startText?: InputMaybe; + /** Manages the stream assignment for this graph */ + streamAssignmentPolicy?: InputMaybe; + /** Allows to switch to a different template in the frontend with different connection flows or UI */ + templateName?: InputMaybe; +}; + /** * Users within the Django authentication system are represented by this * model. @@ -1251,11 +1301,6 @@ export type GraphSubscription = { node: { uuid: any }; }>; }>; - aboutText: string; - displayName: string; - endText: string; - startText: string; - templateName: GraphDetailTemplate; }; }; @@ -1317,6 +1362,38 @@ export type NodeSubscription = { }; }; +export type GraphMetaDataFragment = { + uuid: any; + templateName: GraphDetailTemplate; + startText: string; + slugName: string; + name: string; + endText: string; + displayName: string; + aboutText: string; + streamAssignmentPolicy: StreamAssignmentPolicy; + publicVisible: boolean; +}; + +export type GetGraphQueryVariables = Exact<{ + graphUuid: Scalars["ID"]; +}>; + +export type GetGraphQuery = { + graph: { + uuid: any; + templateName: GraphDetailTemplate; + startText: string; + slugName: string; + name: string; + endText: string; + displayName: string; + aboutText: string; + streamAssignmentPolicy: StreamAssignmentPolicy; + publicVisible: boolean; + }; +}; + export type CreateGraphMutationVariables = Exact<{ graphInput: AddGraphInput; }>; @@ -1329,6 +1406,13 @@ export type CreateGraphMutation = { }; }; +export type UpdateGraphMutationVariables = Exact<{ + graphUuid: Scalars["UUID"]; + graphUpdate: UpdateGraphInput; +}>; + +export type UpdateGraphMutation = { updateGraph: { uuid: any } }; + export type StreamSubscriptionVariables = Exact<{ graphUuid: Scalars["UUID"]; }>; @@ -1679,6 +1763,20 @@ export const NodeDoorDetailFragmentDoc = gql` doorType } `; +export const GraphMetaDataFragmentDoc = gql` + fragment GraphMetaData on Graph { + uuid + templateName + startText + slugName + name + endText + displayName + aboutText + streamAssignmentPolicy + publicVisible + } +`; export const StreamInfoFragmentFragmentDoc = gql` fragment StreamInfoFragment on Stream { uuid @@ -1887,11 +1985,6 @@ export const GraphDocument = gql` name slugName uuid - aboutText - displayName - endText - startText - templateName edges { uuid inNodeDoor { @@ -1991,6 +2084,20 @@ export function useNodeSubscription( handler, ); } +export const GetGraphDocument = gql` + query GetGraph($graphUuid: ID!) { + graph(pk: $graphUuid) { + ...GraphMetaData + } + } + ${GraphMetaDataFragmentDoc} +`; + +export function useGetGraphQuery( + options: Omit, "query"> = {}, +) { + return Urql.useQuery({ query: GetGraphDocument, ...options }); +} export const CreateGraphDocument = gql` mutation CreateGraph($graphInput: AddGraphInput!) { addGraph(graphInput: $graphInput) { @@ -2010,6 +2117,19 @@ export function useCreateGraphMutation() { CreateGraphDocument, ); } +export const UpdateGraphDocument = gql` + mutation UpdateGraph($graphUuid: UUID!, $graphUpdate: UpdateGraphInput!) { + updateGraph(graphInput: $graphUpdate, graphUuid: $graphUuid) { + uuid + } + } +`; + +export function useUpdateGraphMutation() { + return Urql.useMutation( + UpdateGraphDocument, + ); +} export const StreamDocument = gql` subscription stream($graphUuid: UUID!) { streamInfo(graphUuid: $graphUuid) { diff --git a/caster-front/src/graphql.ts b/caster-front/src/graphql.ts index a734c295..9940e210 100644 --- a/caster-front/src/graphql.ts +++ b/caster-front/src/graphql.ts @@ -63,7 +63,7 @@ export type AddGraphInput = { /** Text about the graph which will be displayed at the start of a stream - only if this is set */ startText?: InputMaybe; /** Manages the stream assignment for this graph */ - streamAssignmentPolicy?: InputMaybe; + streamAssignmentPolicy?: InputMaybe; /** Allows to switch to a different template in the frontend with different connection flows or UI */ templateName?: InputMaybe; }; @@ -362,10 +362,14 @@ export type Graph = { /** Name of the graph */ name: Scalars["String"]; nodes: Array; + /** If the graph is not public it will not be listed in the frontend, yet it is still accessible via URL */ + publicVisible: Scalars["Boolean"]; /** Will be used as a URL */ slugName: Scalars["String"]; /** Text about the graph which will be displayed at the start of a stream - only if this is set */ startText: Scalars["String"]; + /** Manages the stream assignment for this graph */ + streamAssignmentPolicy: StreamAssignmentPolicy; /** Allows to switch to a different template in the frontend with different connection flows or UI */ templateName: GraphDetailTemplate; uuid: Scalars["UUID"]; @@ -478,6 +482,7 @@ export type Mutation = { deleteScriptCell?: Maybe; /** Update metadata of an :class:`~stream.models.AudioFile` via a UUID */ updateAudioFile: AudioFile; + updateGraph: Graph; /** * Updates a given :class:`~story_graph.models.Node` which can be used * for renaming or moving it across the canvas. @@ -556,6 +561,12 @@ export type MutationUpdateAudioFileArgs = { uuid: Scalars["UUID"]; }; +/** Mutations for Gencaster via GraphQL. */ +export type MutationUpdateGraphArgs = { + graphInput: UpdateGraphInput; + graphUuid: Scalars["UUID"]; +}; + /** Mutations for Gencaster via GraphQL. */ export type MutationUpdateNodeArgs = { nodeUpdate: NodeUpdate; @@ -858,6 +869,13 @@ export type Stream = { uuid: Scalars["UUID"]; }; +/** An enumeration. */ +export enum StreamAssignmentPolicy { + Deactivate = "DEACTIVATE", + OneGraphOneStream = "ONE_GRAPH_ONE_STREAM", + OneUserOneStream = "ONE_USER_ONE_STREAM", +} + export type StreamInfo = { stream: Stream; streamInstruction?: Maybe; @@ -1037,6 +1055,38 @@ export type UpdateAudioFile = { name?: InputMaybe; }; +/** + * A collection of :class:`~Node` and :class:`~Edge`. + * This can be considered a score as well as a program as it + * has an entry point as a :class:`~Node` and can jump to any + * other :class:`~Node`, also allowing for recursive loops/cycles. + * + * Each node can be considered a little program on its own which can consist + * of multiple :class:`~ScriptCell` which can be coded in a variety of + * languages which can control the frontend and the audio (by e.g. speaking + * on the stream) or setting a background music. + * + * The story graph is a core concept and can be edited with a native editor. + */ +export type UpdateGraphInput = { + /** Text about the graph which can be accessed during a stream - only if this is set */ + aboutText?: InputMaybe; + /** Will be used as a display name in the frontend */ + displayName?: InputMaybe; + /** Text which will be displayed at the end of a stream */ + endText?: InputMaybe; + /** Name of the graph */ + name?: InputMaybe; + /** If the graph is not public it will not be listed in the frontend, yet it is still accessible via URL */ + publicVisible?: InputMaybe; + /** Text about the graph which will be displayed at the start of a stream - only if this is set */ + startText?: InputMaybe; + /** Manages the stream assignment for this graph */ + streamAssignmentPolicy?: InputMaybe; + /** Allows to switch to a different template in the frontend with different connection flows or UI */ + templateName?: InputMaybe; +}; + /** * Users within the Django authentication system are represented by this * model. @@ -1312,6 +1362,38 @@ export type NodeSubscription = { }; }; +export type GraphMetaDataFragment = { + uuid: any; + templateName: GraphDetailTemplate; + startText: string; + slugName: string; + name: string; + endText: string; + displayName: string; + aboutText: string; + streamAssignmentPolicy: StreamAssignmentPolicy; + publicVisible: boolean; +}; + +export type GetGraphQueryVariables = Exact<{ + graphUuid: Scalars["ID"]; +}>; + +export type GetGraphQuery = { + graph: { + uuid: any; + templateName: GraphDetailTemplate; + startText: string; + slugName: string; + name: string; + endText: string; + displayName: string; + aboutText: string; + streamAssignmentPolicy: StreamAssignmentPolicy; + publicVisible: boolean; + }; +}; + export type CreateGraphMutationVariables = Exact<{ graphInput: AddGraphInput; }>; @@ -1324,6 +1406,13 @@ export type CreateGraphMutation = { }; }; +export type UpdateGraphMutationVariables = Exact<{ + graphUuid: Scalars["UUID"]; + graphUpdate: UpdateGraphInput; +}>; + +export type UpdateGraphMutation = { updateGraph: { uuid: any } }; + export type StreamSubscriptionVariables = Exact<{ graphUuid: Scalars["UUID"]; }>; @@ -1674,6 +1763,20 @@ export const NodeDoorDetailFragmentDoc = gql` doorType } `; +export const GraphMetaDataFragmentDoc = gql` + fragment GraphMetaData on Graph { + uuid + templateName + startText + slugName + name + endText + displayName + aboutText + streamAssignmentPolicy + publicVisible + } +`; export const StreamInfoFragmentFragmentDoc = gql` fragment StreamInfoFragment on Stream { uuid @@ -1981,6 +2084,20 @@ export function useNodeSubscription( handler, ); } +export const GetGraphDocument = gql` + query GetGraph($graphUuid: ID!) { + graph(pk: $graphUuid) { + ...GraphMetaData + } + } + ${GraphMetaDataFragmentDoc} +`; + +export function useGetGraphQuery( + options: Omit, "query"> = {}, +) { + return Urql.useQuery({ query: GetGraphDocument, ...options }); +} export const CreateGraphDocument = gql` mutation CreateGraph($graphInput: AddGraphInput!) { addGraph(graphInput: $graphInput) { @@ -2000,6 +2117,19 @@ export function useCreateGraphMutation() { CreateGraphDocument, ); } +export const UpdateGraphDocument = gql` + mutation UpdateGraph($graphUuid: UUID!, $graphUpdate: UpdateGraphInput!) { + updateGraph(graphInput: $graphUpdate, graphUuid: $graphUuid) { + uuid + } + } +`; + +export function useUpdateGraphMutation() { + return Urql.useMutation( + UpdateGraphDocument, + ); +} export const StreamDocument = gql` subscription stream($graphUuid: UUID!) { streamInfo(graphUuid: $graphUuid) { From cfea872e61f57ab0f438b242783af97dd9acb525 Mon Sep 17 00:00:00 2001 From: Dennis Scheiba Date: Thu, 14 Sep 2023 14:40:48 +0200 Subject: [PATCH 5/8] adjust graph metadata component to graph mutation --- caster-editor/src/components/Meta.vue | 227 ++++++++++---------- caster-editor/src/views/GraphDetailView.vue | 2 +- 2 files changed, 114 insertions(+), 115 deletions(-) diff --git a/caster-editor/src/components/Meta.vue b/caster-editor/src/components/Meta.vue index a26f5b45..e39eda65 100644 --- a/caster-editor/src/components/Meta.vue +++ b/caster-editor/src/components/Meta.vue @@ -8,120 +8,88 @@ import { ElForm, ElFormItem, ElCol, + ElSwitch, + ElMessage, } from "element-plus"; -import { ref, computed } from "vue"; -import type { GraphSubscription } from "@/graphql"; +import { ref, type Ref, watch } from "vue"; import "@toast-ui/editor/dist/toastui-editor.css"; // Editor's Style - import Wysiwyg from "@/components/Wysiwyg.vue"; -import { useInterfaceStore, Tab } from "@/stores/InterfaceStore"; - -import { storeToRefs } from "pinia"; -const { tab } = storeToRefs(useInterfaceStore()); +import { + useGetGraphQuery, + useUpdateGraphMutation, + StreamAssignmentPolicy, + type UpdateGraphInput, +} from "@/graphql"; -// props const props = defineProps<{ - graph: GraphSubscription["graph"]; + graphUuid: string; }>(); -// TODO: Import from graphql -const streamAssignmentOptions = [ - { - value: "one_graph_one_stream", - label: "Each graph has only one stream", - }, - { - value: "one_user_one_stream", - label: "Each user gets its own stream", - }, - { - value: "deactivate", - label: "No stream assignment", - }, -]; - -const metaForm = ref({ - projectName: "", - displayName: "", - slug: "", - streamAssignment: "", - startText: "", - aboutText: "", - endText: "", +const { executeQuery } = useGetGraphQuery({ + variables: { graphUuid: props.graphUuid }, }); +const updateGraphMutation = useUpdateGraphMutation(); +const { data, error, fetching } = executeQuery(); -// cloning needs plugin, so just being redundant for now -const metaFormOriginal = ref({ - projectName: "", - displayName: "", - slug: "", - streamAssignment: "", - startText: "", - aboutText: "", - endText: "", +watch(error, (errorMsg) => { + ElMessage.error( + `Could not obtain meta data of graph ${props.graphUuid}: ${errorMsg?.message}`, + ); }); -const saveButtonActive = computed(() => { - if ( - JSON.stringify(metaForm.value) !== JSON.stringify(metaFormOriginal.value) - ) { - return true; - } else { - return false; - } +watch(data, (newData) => { + formData.value = newData?.graph ?? {}; }); -const populateData = () => { - // form - metaForm.value.projectName = props.graph.name; - metaForm.value.displayName = props.graph.displayName; - metaForm.value.slug = props.graph.slugName; - //TODO: streamAssignment - metaForm.value.streamAssignment = "missing graph ql"; - metaForm.value.startText = props.graph.startText; - metaForm.value.aboutText = props.graph.aboutText; - metaForm.value.endText = props.graph.endText; - - // original data - metaFormOriginal.value.projectName = props.graph.name; - metaFormOriginal.value.displayName = props.graph.displayName; - metaFormOriginal.value.slug = props.graph.slugName; - metaFormOriginal.value.streamAssignment = "missing graph ql"; - metaFormOriginal.value.startText = props.graph.startText; - metaFormOriginal.value.aboutText = props.graph.aboutText; - metaFormOriginal.value.endText = props.graph.endText; -}; - -// TODO: remove redundancy by abstracting -// somehow I couldn't figure out how to pass the reference to the function -// const updatedMarkdown = (text: string, reference: Ref) => { -// }; - -const updateAbout = (text: string) => { - metaForm.value.aboutText = text; -}; - -const updateEnd = (text: string) => { - metaForm.value.endText = text; -}; - -// TODO: Submit Data -const onSubmit = () => { - console.log("Submitted"); - console.log(metaForm); -}; - -const onCancel = () => { - tab.value = Tab.Edit; +const formData: Ref = ref({}); + +const mutationRuns: Ref = ref(false); + +const onSubmit = async () => { + mutationRuns.value = true; + // formData also contains data that does not belong to the input such as UUID or slugName + // while ts/js does not have a problem with that, graphql has and throws an error, therefore we need + // to provide/copy an explicit mapping here + // see https://stackoverflow.com/questions/75299141/how-to-type-safely-remove-a-property-from-a-typescript-type + // and https://stackoverflow.com/questions/43909566/get-keys-of-a-typescript-interface-as-array-of-strings + const { + name, + displayName, + startText, + aboutText, + endText, + publicVisible, + streamAssignmentPolicy, + templateName, + } = formData.value; + const { error } = await updateGraphMutation.executeMutation({ + graphUuid: props.graphUuid, + graphUpdate: { + name, + displayName, + startText, + aboutText, + endText, + publicVisible, + streamAssignmentPolicy, + templateName, + }, + }); + if (error) { + ElMessage.error(`Failed to update the meta-data: ${error.message}`); + } + mutationRuns.value = false; + executeQuery(); }; - -populateData();