Skip to content
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

add a dev only writing system editor #1415

Merged
merged 7 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,15 @@
};
}

public Task<WritingSystem> GetWritingSystem(WritingSystemId id, WritingSystemType type)
public async Task<WritingSystem> GetWritingSystem(WritingSystemId id, WritingSystemType type)
{
throw new NotImplementedException();
var writingSystems = await GetWritingSystems();
return type switch
{
WritingSystemType.Vernacular => writingSystems.Vernacular.FirstOrDefault(ws => ws.WsId == id),
WritingSystemType.Analysis => writingSystems.Analysis.FirstOrDefault(ws => ws.WsId == id),
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
} ?? throw new NullReferenceException($"unable to find writing system with id {id}");
}

internal void CompleteExemplars(WritingSystems writingSystems)
Expand Down Expand Up @@ -206,15 +212,14 @@
}
await Cache.DoUsingNewOrCurrentUOW("Update WritingSystem",
"Revert WritingSystem",
async () =>

Check warning on line 215 in backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs

View workflow job for this annotation

GitHub Actions / Build FwHeadless / publish-fw-headless

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 215 in backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs

View workflow job for this annotation

GitHub Actions / Build FW Lite and run tests

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 215 in backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Linux

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 215 in backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Mac

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 215 in backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Mac

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 215 in backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Windows

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
var updateProxy = new UpdateWritingSystemProxy(lcmWritingSystem, this)
var updateProxy = new UpdateWritingSystemProxy(lcmWritingSystem)
{
Id = Guid.Empty,
Type = type,
};
update.Apply(updateProxy);
updateProxy.CommitUpdate(Cache);
});
return await GetWritingSystem(id, type);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,60 +9,43 @@ namespace FwDataMiniLcmBridge.Api.UpdateProxy;

public record UpdateWritingSystemProxy : WritingSystem
{
private readonly CoreWritingSystemDefinition _origLcmWritingSystem;
private readonly CoreWritingSystemDefinition _workingLcmWritingSystem;
private readonly FwDataMiniLcmApi _lexboxLcmApi;
private readonly CoreWritingSystemDefinition _lcmWritingSystem;

[SetsRequiredMembers]
public UpdateWritingSystemProxy(CoreWritingSystemDefinition lcmWritingSystem, FwDataMiniLcmApi lexboxLcmApi)
public UpdateWritingSystemProxy(CoreWritingSystemDefinition lcmWritingSystem)
{
_origLcmWritingSystem = lcmWritingSystem;
_workingLcmWritingSystem = new CoreWritingSystemDefinition(lcmWritingSystem, cloneId: true);
base.Abbreviation = Abbreviation = _origLcmWritingSystem.Abbreviation ?? "";
base.Name = Name = _origLcmWritingSystem.LanguageName ?? "";
base.Font = Font = _origLcmWritingSystem.DefaultFontName ?? "";
_lexboxLcmApi = lexboxLcmApi;
}

public void CommitUpdate(LcmCache cache)
{
if (_workingLcmWritingSystem.Id == _origLcmWritingSystem.Id)
{
cache.ServiceLocator.WritingSystemManager.Set(_workingLcmWritingSystem);
}
else
{
// Changing the ID of a writing system requires LCM to do a lot of work, so only go through that process if absolutely required
WritingSystemServices.MergeWritingSystems(cache, _workingLcmWritingSystem, _origLcmWritingSystem);
}
_lcmWritingSystem = lcmWritingSystem;
base.Abbreviation = Abbreviation = _lcmWritingSystem.Abbreviation ?? "";
base.Name = Name = _lcmWritingSystem.LanguageName ?? "";
base.Font = Font = _lcmWritingSystem.DefaultFontName ?? "";
}

public override required WritingSystemId WsId
{
get => _workingLcmWritingSystem.Id;
set => _workingLcmWritingSystem.Id = value;
get => _lcmWritingSystem.Id;
set => throw new NotSupportedException("Changing the ID of a writing system is not supported");
}

public override required string Name
{
get => _workingLcmWritingSystem.LanguageName;
get => _lcmWritingSystem.LanguageName;
set { } // Silently do nothing; name should be derived from WsId at all times, so if the name should change then so should the WsId
}

public override required string Abbreviation
{
get => _workingLcmWritingSystem.Abbreviation;
set => _workingLcmWritingSystem.Abbreviation = value;
get => _lcmWritingSystem.Abbreviation;
set => _lcmWritingSystem.Abbreviation = value;
}

public override required string Font
{
get => _workingLcmWritingSystem.DefaultFontName;
get => _lcmWritingSystem.DefaultFontName;
set
{
if (value != _workingLcmWritingSystem.DefaultFontName)
if (value != _lcmWritingSystem.DefaultFontName)
{
_workingLcmWritingSystem.DefaultFont = new FontDefinition(value);
_lcmWritingSystem.DefaultFont = new FontDefinition(value);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ private static void ConfigureMiniLcmTypes(ConfigurationBuilder builder)
exportBuilder.Ignore();
}
}));
builder.ExportAsEnum<WritingSystemType>().UseString();
builder.ExportAsEnum<WritingSystemType>();
builder.ExportAsInterface<MiniLcmJsInvokable>()
.FlattenHierarchy()
.WithPublicProperties()
Expand Down
23 changes: 23 additions & 0 deletions backend/FwLite/LcmCrdt/CrdtProjectsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
return GetProject(name);
}

IMiniLcmApi IProjectProvider.OpenProject(IProjectIdentifier project, bool saveChangesOnDispose = true)

Check warning on line 27 in backend/FwLite/LcmCrdt/CrdtProjectsService.cs

View workflow job for this annotation

GitHub Actions / Build FwHeadless / publish-fw-headless

The default value specified for parameter 'saveChangesOnDispose' will have no effect because it applies to a member that is used in contexts that do not allow optional arguments

Check warning on line 27 in backend/FwLite/LcmCrdt/CrdtProjectsService.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Linux

The default value specified for parameter 'saveChangesOnDispose' will have no effect because it applies to a member that is used in contexts that do not allow optional arguments

Check warning on line 27 in backend/FwLite/LcmCrdt/CrdtProjectsService.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Mac

The default value specified for parameter 'saveChangesOnDispose' will have no effect because it applies to a member that is used in contexts that do not allow optional arguments

Check warning on line 27 in backend/FwLite/LcmCrdt/CrdtProjectsService.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Mac

The default value specified for parameter 'saveChangesOnDispose' will have no effect because it applies to a member that is used in contexts that do not allow optional arguments

Check warning on line 27 in backend/FwLite/LcmCrdt/CrdtProjectsService.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Android

The default value specified for parameter 'saveChangesOnDispose' will have no effect because it applies to a member that is used in contexts that do not allow optional arguments

Check warning on line 27 in backend/FwLite/LcmCrdt/CrdtProjectsService.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Windows

The default value specified for parameter 'saveChangesOnDispose' will have no effect because it applies to a member that is used in contexts that do not allow optional arguments
{
//todo not sure if we should implement this, it's mostly there for the FwData version
throw new NotImplementedException();
Expand Down Expand Up @@ -150,6 +150,18 @@
]
});

await lexboxApi.CreateWritingSystem(WritingSystemType.Vernacular,
new()
{
Id = Guid.NewGuid(),
Type = WritingSystemType.Vernacular,
WsId = "de",
Name = "German",
Abbreviation = "de",
Font = "Arial",
Exemplars = WritingSystem.LatinExemplars
});

await lexboxApi.CreateWritingSystem(WritingSystemType.Vernacular,
new()
{
Expand All @@ -173,5 +185,16 @@
Font = "Arial",
Exemplars = WritingSystem.LatinExemplars
});
await lexboxApi.CreateWritingSystem(WritingSystemType.Analysis,
new()
{
Id = Guid.NewGuid(),
Type = WritingSystemType.Analysis,
WsId = "fr",
Name = "French",
Abbreviation = "fr",
Font = "Arial",
Exemplars = WritingSystem.LatinExemplars
});
}
}
11 changes: 11 additions & 0 deletions backend/FwLite/MiniLcm.Tests/WritingSystemTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,15 @@ await Api.CreateWritingSystem(WritingSystemType.Vernacular,
});
await action.Should().ThrowAsync<DuplicateObjectException>();
}

[Fact]
public async Task UpdateExistingWritingSystem_Works()
{
var writingSystems = await Api.GetWritingSystems();
var writingSystem = writingSystems.Vernacular.First();
var original = writingSystem.Copy();
writingSystem.Abbreviation = "New Abbreviation";
var updatedWritingSystem = await Api.UpdateWritingSystem(original, writingSystem);
updatedWritingSystem.Abbreviation.Should().Be("New Abbreviation");
}
}
8 changes: 5 additions & 3 deletions backend/FwLite/MiniLcm/Models/WritingSystem.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
namespace MiniLcm.Models;
using System.Text.Json.Serialization;

public record WritingSystem: IObjectWithId
namespace MiniLcm.Models;

public record WritingSystem: IObjectWithId<WritingSystem>
{
public required Guid Id { get; set; }
public virtual required WritingSystemId WsId { get; set; }
Expand All @@ -26,7 +28,7 @@ public void RemoveReference(Guid id, DateTimeOffset time)
{
}

public IObjectWithId Copy()
public WritingSystem Copy()
{
return new WritingSystem
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// the code is regenerated.

export enum WritingSystemType {
Vernacular = "Vernacular",
Analysis = "Analysis"
Vernacular = 0,
Analysis = 1
}
/* eslint-enable */
2 changes: 1 addition & 1 deletion frontend/viewer/src/lib/entry-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const writingSystems: IWritingSystems = {
'abbreviation': 'Por',
'font': '???',
'exemplars': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
'type': WritingSystemType.Vernacular,
'type': WritingSystemType.Analysis,
'order': 1
}
],
Expand Down
13 changes: 11 additions & 2 deletions frontend/viewer/src/lib/layout/AppBarMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import AboutDialog from '$lib/about/AboutDialog.svelte';
import ActivityView from '$lib/activity/ActivityView.svelte';
import {useFeatures} from '$lib/services/feature-service';
import {mdiDotsVertical, mdiEyeSettingsOutline, mdiHistory, mdiInformationVariantCircle} from '@mdi/js';
import {mdiDotsVertical, mdiEyeSettingsOutline, mdiHistory, mdiInformationVariantCircle, mdiNoteEdit} from '@mdi/js';
import {createEventDispatcher} from 'svelte';
import {Button, MenuItem, ResponsiveMenu, Toggle} from 'svelte-ux';
import {asScottyPortal} from './Scotty.svelte';
import {useProjectViewState} from '$lib/services/project-view-state-service';
import WritingSystemDialog from '$lib/writing-system/WritingSystemDialog.svelte';
import DevContent from '$lib/layout/DevContent.svelte';

const dispatch = createEventDispatcher<{
showOptionsDialog: void;
Expand All @@ -20,6 +22,7 @@

let activityViewOpen = false;
let aboutDialogOpen = false;
let wsEditDialogOpen = false;
</script>

<!-- #key prevents rendering ugly delayed state updates -->
Expand All @@ -41,6 +44,11 @@
<MenuItem icon={mdiInformationVariantCircle} on:click={() => aboutDialogOpen = true}>About</MenuItem>
</div>
{/if}
<DevContent>
<MenuItem icon={mdiNoteEdit} on:click={() => wsEditDialogOpen = true}>
Edit WS
</MenuItem>
</DevContent>
</button>
</ResponsiveMenu>
</Button>
Expand All @@ -50,11 +58,12 @@
{#if $features.history}
<ActivityView bind:open={activityViewOpen} {projectName} />
{/if}

{#if about}
<AboutDialog bind:open={aboutDialogOpen} text={about} />
{/if}

<WritingSystemDialog bind:open={wsEditDialogOpen}/>

<style lang="postcss" global>
.app-bar-menu .MenuItem {
@apply justify-start;
Expand Down
4 changes: 4 additions & 0 deletions frontend/viewer/src/lib/services/service-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export function useLexboxApi(): IMiniLcmJsInvokable {
return window.lexbox.ServiceProvider.getService(DotnetService.MiniLcmApi);
}

export function useMiniLcmApi(): IMiniLcmJsInvokable {
return window.lexbox.ServiceProvider.getService(DotnetService.MiniLcmApi);
}

export function useProjectsService(): ICombinedProjectsService {
return window.lexbox.ServiceProvider.getService(DotnetService.CombinedProjectsService);
}
Expand Down
16 changes: 15 additions & 1 deletion frontend/viewer/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {IEntry, IExampleSentence, ISense} from '$lib/dotnet-types';
import type {IEntry, IExampleSentence, ISense, IWritingSystem, WritingSystemType} from '$lib/dotnet-types';

export function randomId(): string {
return crypto.randomUUID();
Expand Down Expand Up @@ -48,3 +48,17 @@ export function defaultExampleSentence(senseId: string): IExampleSentence {
reference: '',
};
}

export function defaultWritingSystem(type: WritingSystemType): IWritingSystem {
return {
id: randomId(),
wsId: 'en',
name: 'English',
abbreviation: 'en',
font: 'Arial',
exemplars: [],
order: 0,
deletedAt: undefined,
type
};
}
55 changes: 55 additions & 0 deletions frontend/viewer/src/lib/writing-system/WritingSystemDialog.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<script lang="ts">
import {Button, Dialog, MenuItem, SelectField} from 'svelte-ux';
import WritingSystemEditor from '$lib/writing-system/WritingSystemEditor.svelte';
import {useWritingSystemService} from '$lib/writing-system-service';
import {type IWritingSystem, WritingSystemType} from '$lib/dotnet-types';
import {defaultWritingSystem} from '$lib/utils';
import {mdiNoteEdit} from '@mdi/js';

export let open = false;
let loading = false;
const writingSystemService = useWritingSystemService();
let vernacular = writingSystemService.vernacular;
let analysis = writingSystemService.analysis;
let options: Array<{ value: IWritingSystem, label: string, group: 'Vernacular' | 'Analysis' }> = [];
let newVernacular = defaultWritingSystem(WritingSystemType.Vernacular);
let newAnalysis = defaultWritingSystem(WritingSystemType.Analysis);
$: options = [
...vernacular.map(ws => mapOption(ws)),
{value: newVernacular, label: 'New Vernacular Writing System', group: 'Vernacular'},
...analysis.map(ws => mapOption(ws)),
{value: newAnalysis, label: 'New Analysis Writing System', group: 'Analysis'},
];

function mapOption(ws: IWritingSystem): {
value: IWritingSystem,
label: string,
group: 'Vernacular' | 'Analysis'
} {
return {value: ws, label: `${ws.name} (${ws.wsId})`, group: ws.type === WritingSystemType.Vernacular ? 'Vernacular' : 'Analysis'};
}

let selectedOption: IWritingSystem = vernacular[0] ?? analysis[0];

function onCreate(_writingSystem: IWritingSystem) {
//todo need a better way to invalidate the writing system service
location.reload();
}
function onChange(_writingSystem: IWritingSystem) {
//todo need a better way to invalidate the writing system service
location.reload();
}
</script>

<Dialog bind:open={open} {loading} persistent={loading} style="height: auto">
<div slot="title">Edit Writing Systems</div>
<SelectField label="Writing System" bind:value={selectedOption} {options} clearable={false}/>
<WritingSystemEditor
writingSystem={selectedOption}
newWs={selectedOption === newVernacular || selectedOption === newAnalysis}
on:create={(e) => onCreate(e.detail.writingSystem)}
on:change={(e) => onChange(e.detail.writingSystem)}/>
<div slot="actions">
<Button on:click={() => open = false}>Close</Button>
</div>
</Dialog>
43 changes: 43 additions & 0 deletions frontend/viewer/src/lib/writing-system/WritingSystemEditor.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script lang="ts">
import type {IWritingSystem} from '$lib/dotnet-types';
import {Button} from 'svelte-ux';
import CrdtTextField from '$lib/entry-editor/inputs/CrdtTextField.svelte';
import {useMiniLcmApi} from '$lib/services/service-provider';
import {createEventDispatcher} from 'svelte';

const dispatch = createEventDispatcher<{
change: { writingSystem: IWritingSystem },
create: { writingSystem: IWritingSystem }
}>();
const miniLcmApi = useMiniLcmApi();
export let writingSystem: IWritingSystem;
$: initialWs = JSON.parse(JSON.stringify(writingSystem));
function updateInitialWs() {
// eslint-disable-next-line svelte/no-reactive-reassign
initialWs = JSON.parse(JSON.stringify(writingSystem));
}
export let newWs: boolean = false;
async function onChange() {
if (newWs) return;
await miniLcmApi.updateWritingSystem(initialWs, writingSystem);
updateInitialWs();
dispatch('change', {writingSystem});
}

async function createNew() {
await miniLcmApi.createWritingSystem(writingSystem.type, writingSystem);
dispatch('create', {writingSystem});
}
</script>
<form class="flex flex-col gap-2 p-2">
<CrdtTextField label="Language Code" readonly={!newWs} on:change={() => onChange()} bind:value={writingSystem.wsId}/>
<!-- todo changing the name for FieldWorks writing systems is not yet supported-->
<CrdtTextField label="Name" readonly={!newWs} on:change={() => onChange()} bind:value={writingSystem.name}/>
<CrdtTextField label="Abbreviation" on:change={() => onChange()} bind:value={writingSystem.abbreviation}/>
<CrdtTextField label="Font" on:change={() => onChange()} bind:value={writingSystem.font}/>
{#if newWs}
<Button variant="outline" on:click={createNew}>Create new Writing System</Button>
{:else}
<span>Changes are saved automatically</span>
{/if}
</form>
Loading