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

Allow reordering example sentences #1359

Merged
merged 5 commits into from
Jan 13, 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
55 changes: 51 additions & 4 deletions backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@
}
await Cache.DoUsingNewOrCurrentUOW("Update WritingSystem",
"Revert WritingSystem",
async () =>

Check warning on line 209 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 209 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 209 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 209 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 209 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 209 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)
{
Expand Down Expand Up @@ -935,6 +935,30 @@
lexEntry.SensesOS.Add(lexSense);
}

internal void InsertExampleSentence(ILexSense lexSense, ILexExampleSentence lexExample, BetweenPosition? between = null)
{
var previousExampleId = between?.Previous;
var nextExampleId = between?.Next;

var previousExample = previousExampleId.HasValue ? lexSense.ExamplesOS.FirstOrDefault(s => s.Guid == previousExampleId) : null;
if (previousExample is not null)
{
var insertI = lexSense.ExamplesOS.IndexOf(previousExample) + 1;
lexSense.ExamplesOS.Insert(insertI, lexExample);
return;
}

var nextExample = nextExampleId.HasValue ? lexSense.ExamplesOS.FirstOrDefault(s => s.Guid == nextExampleId) : null;
if (nextExample is not null)
{
var insertI = lexSense.ExamplesOS.IndexOf(nextExample);
lexSense.ExamplesOS.Insert(insertI, lexExample);
return;
}

lexSense.ExamplesOS.Add(lexExample);
}

private void ApplySenseToLexSense(Sense sense, ILexSense lexSense)
{
if (lexSense.MorphoSyntaxAnalysisRA.GetPartOfSpeech()?.Guid != sense.PartOfSpeechId)
Expand Down Expand Up @@ -1067,9 +1091,10 @@
return Task.FromResult(lcmExampleSentence is null ? null : FromLexExampleSentence(senseId, lcmExampleSentence));
}

internal void CreateExampleSentence(ILexSense lexSense, ExampleSentence exampleSentence)
internal void CreateExampleSentence(ILexSense lexSense, ExampleSentence exampleSentence, BetweenPosition? between = null)
{
var lexExampleSentence = LexExampleSentenceFactory.Create(exampleSentence.Id, lexSense);
var lexExampleSentence = LexExampleSentenceFactory.Create(exampleSentence.Id);
InsertExampleSentence(lexSense, lexExampleSentence, between);
UpdateLcmMultiString(lexExampleSentence.Example, exampleSentence.Sentence);
var freeTranslationType = CmPossibilityRepository.GetObject(CmPossibilityTags.kguidTranFreeTranslation);
var translation = CmTranslationFactory.Create(lexExampleSentence, freeTranslationType);
Expand All @@ -1078,7 +1103,7 @@
lexExampleSentence.Reference.get_WritingSystem(0));
}

public async Task<ExampleSentence> CreateExampleSentence(Guid entryId, Guid senseId, ExampleSentence exampleSentence)
public async Task<ExampleSentence> CreateExampleSentence(Guid entryId, Guid senseId, ExampleSentence exampleSentence, BetweenPosition? between = null)
{
if (exampleSentence.Id == default) exampleSentence.Id = Guid.NewGuid();
if (!SenseRepository.TryGetObject(senseId, out var lexSense))
Expand All @@ -1087,7 +1112,7 @@
UndoableUnitOfWorkHelper.DoUsingNewOrCurrentUOW("Create Example Sentence",
"Remove example sentence",
Cache.ServiceLocator.ActionHandler,
() => CreateExampleSentence(lexSense, exampleSentence));
() => CreateExampleSentence(lexSense, exampleSentence, between));
return FromLexExampleSentence(senseId, ExampleSentenceRepository.GetObject(exampleSentence.Id));
}

Expand Down Expand Up @@ -1124,6 +1149,28 @@
return await GetExampleSentence(entryId, senseId, after.Id) ?? throw new NullReferenceException("unable to find example sentence with id " + after.Id);
}

public Task MoveExampleSentence(Guid entryId, Guid senseId, Guid exampleSentenceId, BetweenPosition between)
{
if (!EntriesRepository.TryGetObject(entryId, out var lexEntry))
throw new InvalidOperationException("Entry not found");
if (!SenseRepository.TryGetObject(senseId, out var lexSense))
throw new InvalidOperationException("Sense not found");
if (!ExampleSentenceRepository.TryGetObject(exampleSentenceId, out var lexExample))
throw new InvalidOperationException("Example sentence not found");

ValidateOwnership(lexExample, entryId, senseId);

UndoableUnitOfWorkHelper.DoUsingNewOrCurrentUOW("Move Example sentence",
"Move Example sentence back",
Cache.ServiceLocator.ActionHandler,
() =>
{
// LibLCM treats an insert as a move if the example sentence is already on the sense
InsertExampleSentence(lexSense, lexExample, between);
});
return Task.CompletedTask;
}

public Task DeleteExampleSentence(Guid entryId, Guid senseId, Guid exampleSentenceId)
{
var lexExampleSentence = ExampleSentenceRepository.GetObject(exampleSentenceId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,9 @@ public override required Guid Id
}
}

public override IList<ExampleSentence> ExampleSentences
public override List<ExampleSentence> ExampleSentences
{
get =>
new UpdateListProxy<ExampleSentence>(
sentence => lexboxLcmApi.CreateExampleSentence(sense, sentence),
sentence => lexboxLcmApi.DeleteExampleSentence(sense.Owner.Guid, Id, sentence.Id),
i => new UpdateExampleSentenceProxy(sense.ExamplesOS[i], lexboxLcmApi),
sense.ExamplesOS.Count
);
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
}
4 changes: 3 additions & 1 deletion backend/FwLite/FwLiteProjectSync.Tests/EntrySyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ public async Task CanSyncRandomEntries()
var actual = await _fixture.CrdtApi.GetEntry(after.Id);
actual.Should().NotBeNull();
actual.Should().BeEquivalentTo(after, options => options
.For(e => e.Senses).Exclude(s => s.Order));
.For(e => e.Senses).Exclude(s => s.Order)
.For(e => e.Senses).For(s => s.ExampleSentences).Exclude(s => s.Order)
);
}

[Fact]
Expand Down
3 changes: 2 additions & 1 deletion backend/FwLite/FwLiteProjectSync.Tests/Sena3SyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ private void ShouldAllBeEquivalentTo(Dictionary<Guid, Entry> crdtEntries, Dictio
options => options
.For(e => e.Components).Exclude(c => c.Id)
.For(e => e.ComplexForms).Exclude(c => c.Id)
.For(e => e.Senses).Exclude(s => s.Order),
.For(e => e.Senses).Exclude(s => s.Order)
.For(e => e.Senses).For(s => s.ExampleSentences).Exclude(s => s.Order),
$"CRDT entry {crdtEntry.Id} was synced with FwData");
}
}
Expand Down
7 changes: 7 additions & 0 deletions backend/FwLite/FwLiteProjectSync.Tests/SyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public async Task FirstSyncJustDoesAnImport()
crdtEntries.Should().BeEquivalentTo(fwdataEntries,
options => options
.For(e => e.Senses).Exclude(s => s.Order)
.For(e => e.Senses).For(s => s.ExampleSentences).Exclude(s => s.Order)
.For(e => e.Components).Exclude(c => c.Id)
.For(e => e.ComplexForms).Exclude(c => c.Id));
}
Expand Down Expand Up @@ -153,6 +154,7 @@ await crdtApi.CreateEntry(new Entry()
crdtEntries.Should().BeEquivalentTo(fwdataEntries,
options => options
.For(e => e.Senses).Exclude(s => s.Order)
.For(e => e.Senses).For(s => s.ExampleSentences).Exclude(s => s.Order)
.For(e => e.Components).Exclude(c => c.Id)
.For(e => e.ComplexForms).Exclude(c => c.Id));
}
Expand Down Expand Up @@ -235,6 +237,7 @@ public async Task CreatingAComplexEntryInFwDataSyncsWithoutIssue()
crdtEntries.Should().BeEquivalentTo(fwdataEntries,
options => options
.For(e => e.Senses).Exclude(s => s.Order)
.For(e => e.Senses).For(s => s.ExampleSentences).Exclude(s => s.Order)
.For(e => e.Components).Exclude(c => c.Id)
.For(e => e.ComplexForms).Exclude(c => c.Id));

Expand Down Expand Up @@ -321,6 +324,7 @@ await crdtApi.CreateEntry(new Entry()
crdtEntries.Should().BeEquivalentTo(fwdataEntries,
options => options
.For(e => e.Senses).Exclude(s => s.Order)
.For(e => e.Senses).For(s => s.ExampleSentences).Exclude(s => s.Order)
.For(e => e.Components).Exclude(c => c.Id)
.For(e => e.ComplexForms).Exclude(c => c.Id));
}
Expand Down Expand Up @@ -404,6 +408,7 @@ await crdtApi.CreateEntry(new Entry()
crdtEntries.Should().BeEquivalentTo(fwdataEntries,
options => options
.For(e => e.Senses).Exclude(s => s.Order)
.For(e => e.Senses).For(s => s.ExampleSentences).Exclude(s => s.Order)
.For(e => e.Components).Exclude(c => c.Id)
.For(e => e.ComplexForms).Exclude(c => c.Id));
}
Expand All @@ -430,6 +435,7 @@ public async Task UpdatingAnEntryInEachProjectSyncsAcrossBoth()
crdtEntries.Should().BeEquivalentTo(fwdataEntries,
options => options
.For(e => e.Senses).Exclude(s => s.Order)
.For(e => e.Senses).For(s => s.ExampleSentences).Exclude(s => s.Order)
.For(e => e.Components).Exclude(c => c.Id)
//todo the headword should be changed
.For(e => e.Components).Exclude(c => c.ComponentHeadword)
Expand Down Expand Up @@ -501,6 +507,7 @@ public async Task AddingASenseToAnEntryInEachProjectSyncsAcrossBoth()
crdtEntries.Should().BeEquivalentTo(fwdataEntries,
options => options
.For(e => e.Senses).Exclude(s => s.Order)
.For(e => e.Senses).For(s => s.ExampleSentences).Exclude(s => s.Order)
.For(e => e.Components).Exclude(c => c.Id)
.For(e => e.ComplexForms).Exclude(c => c.Id));
}
Expand Down
10 changes: 8 additions & 2 deletions backend/FwLite/FwLiteProjectSync/DryRunMiniLcmApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,9 @@ public Task RemoveSemanticDomainFromSense(Guid senseId, Guid semanticDomainId)
return api.GetExampleSentence(entryId, senseId, id);
}

public Task<ExampleSentence> CreateExampleSentence(Guid entryId, Guid senseId, ExampleSentence exampleSentence)
public Task<ExampleSentence> CreateExampleSentence(Guid entryId, Guid senseId, ExampleSentence exampleSentence, BetweenPosition? position = null)
{
DryRunRecords.Add(new DryRunRecord(nameof(CreateExampleSentence), $"Create example sentence {exampleSentence.Sentence}"));
DryRunRecords.Add(new DryRunRecord(nameof(CreateExampleSentence), $"Create example sentence {exampleSentence.Sentence} between {position?.Previous} and {position?.Next}"));
return Task.FromResult(exampleSentence);
}

Expand All @@ -278,6 +278,12 @@ public Task<ExampleSentence> UpdateExampleSentence(Guid entryId,
return Task.FromResult(after);
}

public Task MoveExampleSentence(Guid entryId, Guid senseId, Guid exampleId, BetweenPosition between)
{
DryRunRecords.Add(new DryRunRecord(nameof(MoveExampleSentence), $"Move example sentence {exampleId} between {between.Previous} and {between.Next}"));
return Task.CompletedTask;
}

public Task DeleteExampleSentence(Guid entryId, Guid senseId, Guid exampleSentenceId)
{
DryRunRecords.Add(new DryRunRecord(nameof(DeleteExampleSentence), $"Delete example sentence {exampleSentenceId}"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using LcmCrdt;
using Microsoft.JSInterop;
using MiniLcm;
using MiniLcm.Attributes;
using MiniLcm.Models;
using Reinforced.Typings;
using Reinforced.Typings.Ast.Dependency;
Expand Down Expand Up @@ -50,26 +51,25 @@ private static void ConfigureMiniLcmTypes(ConfigurationBuilder builder)
builder.ExportAsThirdParty<MultiString>().WithName("IMultiString").Imports([
new() { From = "$lib/dotnet-types/i-multi-string", Target = "type {IMultiString}" }
]);
builder.ExportAsInterface<Sense>().WithPublicNonStaticProperties(exportBuilder =>
{
if (exportBuilder.Member.Name == nameof(Sense.Order))
{
exportBuilder.Ignore();
}
});
builder.ExportAsInterfaces([
typeof(Entry),
typeof(Sense),
typeof(ExampleSentence),
typeof(WritingSystem),
typeof(WritingSystems),
typeof(PartOfSpeech),
typeof(SemanticDomain),
typeof(ComplexFormType),
typeof(ComplexFormComponent),

typeof(MiniLcmJsInvokable.MiniLcmFeatures),
],
exportBuilder => exportBuilder.WithPublicNonStaticProperties());
exportBuilder => exportBuilder.WithPublicNonStaticProperties(exportBuilder =>
{
if (exportBuilder.Member.GetCustomAttribute<MiniLcmInternalAttribute>() is not null)
{
exportBuilder.Ignore();
}
}));
builder.ExportAsEnum<WritingSystemType>().UseString();
builder.ExportAsInterface<MiniLcmJsInvokable>()
.FlattenHierarchy()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@
{
DerivedType: SetOrderChange<Sense>,
TypeDiscriminator: SetOrderChange:Sense
},
{
DerivedType: SetOrderChange<ExampleSentence>,
TypeDiscriminator: SetOrderChange:ExampleSentence
}
],
IgnoreUnrecognizedTypeDiscriminators: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
Properties:
Id (Guid) Required PK AfterSave:Throw ValueGenerated.OnAdd
DeletedAt (DateTimeOffset?)
Order (double) Required
Reference (string)
SenseId (Guid) Required FK Index
Sentence (MultiString) Required
Expand Down Expand Up @@ -208,7 +209,7 @@
Relational:ColumnType: jsonb
SnapshotId (no field, Guid?) Shadow FK Index
Navigations:
ExampleSentences (IList<ExampleSentence>) Collection ToDependent ExampleSentence
ExampleSentences (List<ExampleSentence>) Collection ToDependent ExampleSentence
Keys:
Id PK
Foreign keys:
Expand Down
3 changes: 3 additions & 0 deletions backend/FwLite/LcmCrdt/Changes/CreateExampleSentenceChange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public CreateExampleSentenceChange(ExampleSentence exampleSentence, Guid senseId
{
exampleSentence.Id = EntityId;
SenseId = senseId;
Order = exampleSentence.Order;
Sentence = exampleSentence.Sentence;
Translation = exampleSentence.Translation;
Reference = exampleSentence.Reference;
Expand All @@ -24,6 +25,7 @@ private CreateExampleSentenceChange(Guid entityId, Guid senseId) : base(entityId
}

public Guid SenseId { get; init; }
public double Order { get; set; }
public MultiString? Sentence { get; set; }
public MultiString? Translation { get; set; }
public string? Reference { get; set; }
Expand All @@ -34,6 +36,7 @@ public override async ValueTask<ExampleSentence> NewEntity(Commit commit, Change
{
Id = EntityId,
SenseId = SenseId,
Order = Order,
Sentence = Sentence ?? new MultiString(),
Translation = Translation ?? new MultiString(),
Reference = Reference,
Expand Down
30 changes: 24 additions & 6 deletions backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

namespace LcmCrdt;

public class CrdtMiniLcmApi(DataModel dataModel, CurrentProjectService projectService, LcmCrdtDbContext dbContext, MiniLcmValidators validators) : IMiniLcmApi

Check warning on line 19 in backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs

View workflow job for this annotation

GitHub Actions / Build FwHeadless / publish-fw-headless

Parameter 'dbContext' is unread.

Check warning on line 19 in backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs

View workflow job for this annotation

GitHub Actions / Build FW Lite and run tests

Parameter 'dbContext' is unread.

Check warning on line 19 in backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Linux

Parameter 'dbContext' is unread.

Check warning on line 19 in backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Mac

Parameter 'dbContext' is unread.

Check warning on line 19 in backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Mac

Parameter 'dbContext' is unread.

Check warning on line 19 in backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Android

Parameter 'dbContext' is unread.

Check warning on line 19 in backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Windows

Parameter 'dbContext' is unread.
{
private Guid ClientId { get; } = projectService.ProjectData.ClientId;
public ProjectData ProjectData => projectService.ProjectData;
Expand Down Expand Up @@ -343,7 +343,7 @@
{
yield return addComplexFormTypeChange;
}
var i = 1;
var senseOrder = 1;
foreach (var sense in entry.Senses)
{
sense.SemanticDomains = sense.SemanticDomains
Expand All @@ -357,10 +357,14 @@
}
if (sense.Order != default) // we don't anticipate this being necessary, so we'll be strict for now
throw new InvalidOperationException("Order should not be provided when creating a sense");
sense.Order = i++;
sense.Order = senseOrder++;
yield return new CreateSenseChange(sense, entry.Id);
var exampleOrder = 1;
foreach (var exampleSentence in sense.ExampleSentences)
{
if (exampleSentence.Order != default) // we don't anticipate this being necessary, so we'll be strict for now
throw new InvalidOperationException("Order should not be provided when creating an example sentence");
exampleSentence.Order = exampleOrder++;
yield return new CreateExampleSentenceChange(exampleSentence, sense.Id);
}
}
Expand All @@ -387,7 +391,7 @@
]);
return await GetEntry(entry.Id) ?? throw new NullReferenceException();

async IAsyncEnumerable<AddEntryComponentChange> ToComplexFormComponents(IList<ComplexFormComponent> complexFormComponents)

Check warning on line 394 in backend/FwLite/LcmCrdt/CrdtMiniLcmApi.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 394 in backend/FwLite/LcmCrdt/CrdtMiniLcmApi.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 394 in backend/FwLite/LcmCrdt/CrdtMiniLcmApi.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 394 in backend/FwLite/LcmCrdt/CrdtMiniLcmApi.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 394 in backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs

View workflow job for this annotation

GitHub Actions / Publish FW Lite app for Android

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 394 in backend/FwLite/LcmCrdt/CrdtMiniLcmApi.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.
{
foreach (var complexFormComponent in complexFormComponents)
{
Expand Down Expand Up @@ -479,10 +483,13 @@
}

yield return new CreateSenseChange(sense, entryId);
foreach (var change in sense.ExampleSentences.Select(sentence =>
new CreateExampleSentenceChange(sentence, sense.Id)))
var exampleOrder = 1;
foreach (var exampleSentence in sense.ExampleSentences)
{
yield return change;
if (exampleSentence.Order != default) // we don't anticipate this being necessary, so we'll be strict for now
throw new InvalidOperationException("Order should not be provided when creating an example sentence");
exampleSentence.Order = exampleOrder++;
yield return new CreateExampleSentenceChange(exampleSentence, sense.Id);
}
}

Expand Down Expand Up @@ -547,9 +554,14 @@

public async Task<ExampleSentence> CreateExampleSentence(Guid entryId,
Guid senseId,
ExampleSentence exampleSentence)
ExampleSentence exampleSentence,
BetweenPosition? between = null)
{
await validators.ValidateAndThrow(exampleSentence);
if (exampleSentence.Order != default) // we don't anticipate this being necessary, so we'll be strict for now
throw new InvalidOperationException("Order should not be provided when creating an example sentence");

exampleSentence.Order = await OrderPicker.PickOrder(ExampleSentences.Where(s => s.SenseId == senseId), between);
await dataModel.AddChange(ClientId, new CreateExampleSentenceChange(exampleSentence, senseId));
return await dataModel.GetLatest<ExampleSentence>(exampleSentence.Id) ?? throw new NullReferenceException();
}
Expand Down Expand Up @@ -583,6 +595,12 @@
return await GetExampleSentence(entryId, senseId, after.Id) ?? throw new NullReferenceException();
}

public async Task MoveExampleSentence(Guid entryId, Guid senseId, Guid exampleId, BetweenPosition between)
{
var order = await OrderPicker.PickOrder(ExampleSentences.Where(s => s.SenseId == senseId), between);
await dataModel.AddChange(ClientId, new Changes.SetOrderChange<ExampleSentence>(exampleId, order));
}

public async Task DeleteExampleSentence(Guid entryId, Guid senseId, Guid exampleSentenceId)
{
await dataModel.AddChange(ClientId, new DeleteChange<ExampleSentence>(exampleSentenceId));
Expand Down
4 changes: 3 additions & 1 deletion backend/FwLite/LcmCrdt/LcmCrdtKernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ public static void ConfigureCrdt(CrdtConfig config)
.Add<RemoveComplexFormTypeChange>()
.Add<SetComplexFormComponentChange>()
.Add<CreateComplexFormType>()
.Add<Changes.SetOrderChange<Sense>>();
.Add<Changes.SetOrderChange<Sense>>()
.Add<Changes.SetOrderChange<ExampleSentence>>()
;
}

public static Type[] AllChangeTypes()
Expand Down
4 changes: 4 additions & 0 deletions backend/FwLite/LcmCrdt/QueryHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ public static class QueryHelpers
public static void ApplySortOrder(this Entry entry)
{
entry.Senses.ApplySortOrder();
foreach (var sense in entry.Senses)
{
sense.ExampleSentences.ApplySortOrder();
}
}

public static void ApplySortOrder<T>(this List<T> items) where T : IOrderable
Expand Down
Loading
Loading