-
Notifications
You must be signed in to change notification settings - Fork 0
Implement subject creation with existing schema #477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
232c84b
9d7e707
74fb2ca
7bacb4b
2816eb8
44baa7e
88be3cc
c77c2f2
4e1953d
c451fb9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| <template> | ||
| <div class="ext-neowiki-schema-lookup"> | ||
| <CdxLookup | ||
| ref="lookupRef" | ||
| v-model:selected="selectedSchema" | ||
| :menu-items="menuItems" | ||
| :start-icon="cdxIconSearch" | ||
| :placeholder="$i18n( 'neowiki-subject-creator-schema-search-placeholder' ).text()" | ||
| @input="onLookupInput" | ||
| @update:selected="onSchemaSelected" | ||
| /> | ||
| </div> | ||
| </template> | ||
|
|
||
| <script setup lang="ts"> | ||
| import { ref } from 'vue'; | ||
| import { CdxLookup } from '@wikimedia/codex'; | ||
| import { cdxIconSearch } from '@wikimedia/codex-icons'; | ||
| import type { MenuItemData } from '@wikimedia/codex'; | ||
| import { useSchemaStore } from '@/stores/SchemaStore.ts'; | ||
|
|
||
| const emit = defineEmits<{ | ||
| 'select': [ schemaName: string ]; | ||
| }>(); | ||
|
|
||
| const schemaStore = useSchemaStore(); | ||
| const selectedSchema = ref<string | null>( null ); | ||
| const menuItems = ref<MenuItemData[]>( [] ); | ||
| const lookupRef = ref<InstanceType<typeof CdxLookup> | null>( null ); | ||
|
|
||
| async function onLookupInput( value: string ): Promise<void> { | ||
| if ( !value ) { | ||
| menuItems.value = []; | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const schemaNames = await schemaStore.searchAndFetchMissingSchemas( value ); | ||
| menuItems.value = schemaNames.map( ( name ) => ( { | ||
| label: name, | ||
| value: name | ||
| } ) ); | ||
| } catch ( error ) { | ||
| console.error( 'Error searching schemas:', error ); | ||
| menuItems.value = []; | ||
| } | ||
| } | ||
|
|
||
| function onSchemaSelected( schemaName: string ): void { | ||
| if ( schemaName ) { | ||
| emit( 'select', schemaName ); | ||
| } | ||
| } | ||
|
|
||
| function focus(): void { | ||
| // CdxLookup component does not expose a focus method, | ||
| // so we need to find the input element and focus it directly. | ||
| const input = ( lookupRef.value?.$el as HTMLElement )?.querySelector( 'input' ); | ||
| input?.focus(); | ||
| } | ||
|
|
||
| defineExpose( { focus } ); | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| <template> | ||
| <div class="ext-neowiki-subject-creator"> | ||
| <p> | ||
| {{ $i18n( 'neowiki-subject-creator-schema-title' ).text() }} | ||
| </p> | ||
|
|
||
| <CdxToggleButtonGroup | ||
| v-model="selectedValue" | ||
| class="ext-neowiki-subject-creator-schema-options" | ||
| :buttons="buttons" | ||
| /> | ||
|
|
||
| <div | ||
| v-if="selectedValue === 'existing'" | ||
| class="ext-neowiki-subject-creator-existing" | ||
| > | ||
| <SchemaLookup | ||
| ref="schemaLookupRef" | ||
| @select="onSchemaSelected" | ||
| /> | ||
| </div> | ||
|
|
||
| <div v-if="selectedValue === 'new'"> | ||
| TODO: New schema UI | ||
| </div> | ||
| </div> | ||
| </template> | ||
|
|
||
| <script setup lang="ts"> | ||
| import { ref, watch, nextTick, onMounted } from 'vue'; | ||
| import { CdxToggleButtonGroup } from '@wikimedia/codex'; | ||
| import { cdxIconSearch, cdxIconAdd } from '@wikimedia/codex-icons'; | ||
| import type { ButtonGroupItem } from '@wikimedia/codex'; | ||
| import { Subject } from '@/domain/Subject.ts'; | ||
| import SchemaLookup from '@/components/SubjectCreator/SchemaLookup.vue'; | ||
|
|
||
| const emit = defineEmits<{ | ||
| 'draft': [ subject: Subject ]; | ||
| }>(); | ||
|
|
||
| const selectedValue = ref( 'existing' ); | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const schemaLookupRef = ref<any | null>( null ); | ||
|
|
||
| const buttons = [ | ||
| { | ||
| value: 'existing', | ||
| label: mw.msg( 'neowiki-subject-creator-existing-schema' ), | ||
| icon: cdxIconSearch | ||
| }, | ||
| { | ||
| value: 'new', | ||
| label: mw.msg( 'neowiki-subject-creator-new-schema' ), | ||
| icon: cdxIconAdd | ||
| } | ||
| ] as ButtonGroupItem[]; | ||
|
|
||
| onMounted( () => { | ||
| focusSchemaLookup( selectedValue.value ); | ||
| } ); | ||
|
|
||
| watch( selectedValue, focusSchemaLookup ); | ||
|
|
||
| async function focusSchemaLookup( newValue: string ): Promise<void> { | ||
| await nextTick(); | ||
| if ( newValue === 'existing' && schemaLookupRef.value ) { | ||
| schemaLookupRef.value.focus(); | ||
| } | ||
| } | ||
|
|
||
| async function onSchemaSelected( schemaName: string ): Promise<void> { | ||
| if ( !schemaName ) { | ||
| return; | ||
| } | ||
|
|
||
| const subjectLabel = mw.config.get( 'wgTitle' ); | ||
| if ( typeof subjectLabel !== 'string' ) { | ||
| mw.notify( 'Error preparing subject: No subject label found', { type: 'error' } ); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const subject = Subject.createNew( subjectLabel, schemaName ); | ||
| emit( 'draft', subject ); | ||
| } catch ( error ) { | ||
| console.error( 'Error preparing subject:', error ); | ||
| mw.notify( 'Error preparing subject: ' + String( error ), { type: 'error' } ); | ||
| } | ||
| } | ||
| </script> | ||
|
|
||
| <style lang="less"> | ||
| @import ( reference ) '@wikimedia/codex-design-tokens/theme-wikimedia-ui.less'; | ||
|
|
||
| .ext-neowiki-subject-creator { | ||
| &-schema-options.cdx-toggle-button-group { | ||
| width: inherit; | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
|
|
||
| .cdx-toggle-button { | ||
| flex-grow: 1; | ||
| } | ||
| } | ||
|
|
||
| &-existing { | ||
| margin-top: @spacing-100; | ||
| } | ||
| } | ||
| </style> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,8 @@ | ||
| import { SubjectId } from '@/domain/SubjectId'; | ||
| import { StatementList } from '@/domain/StatementList'; | ||
| import type { SubjectLookup } from '@/domain/SubjectLookup'; | ||
| import type { SchemaName } from '@/domain/Schema'; | ||
| import type { SubjectMap } from '@/domain/SubjectMap'; | ||
| import type { SubjectId } from '@/domain/SubjectId'; | ||
| import type { StatementList } from '@/domain/StatementList'; | ||
| import type { PropertyName } from '@/domain/PropertyDefinition'; | ||
| import type { Value } from '@/domain/Value'; | ||
|
|
||
|
|
@@ -16,6 +16,17 @@ export class Subject { | |
| ) { | ||
| } | ||
|
|
||
| public static createNew( label: string, schemaName: SchemaName ): Subject { | ||
| // TODO: The dummy ID is a temporary workaround. | ||
| // Should we make ID optional in Subject or create a separate NewSubject DTO? | ||
| return new Subject( | ||
| new SubjectId( 's11111111111111' ), | ||
|
Comment on lines
+20
to
+23
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to think about this more. The returned object must be compatible with If
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, more thinking is needed here. Currently we generate IDs on the backend. Some relevant TS: export interface SubjectRepository extends SubjectLookup {
createMainSubject(
pageId: number,
label: string,
schemaName: SchemaName,
statements: StatementList
): Promise<SubjectId>;SubjectEditor.vue: const props = defineProps<{
schemaStatements: StatementList;
schemaProperties: PropertyDefinitionList;
}>();TL;DR: A Subject is not required in SubjectEditor or in the call to the API that creates a Subject. |
||
| label, | ||
| schemaName, | ||
| new StatementList( [] ), | ||
| ); | ||
malberts marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| public getId(): SubjectId { | ||
| return this.id; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call this
SchemaSelectoror similar. It's different from theLookupservices we have, and the point of the component is to let the user choose a subject. PerhapsSubjectChooser?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume the last one was meant to be
SchemaChooser.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was picked to align with what Codex called the
Lookupcomponent.SchemaChoosersounds fine to me too. What do you think?