mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-28 05:51:06 -04:00
feat(desktop): one Drawing Sets window with Sets and Releases tabs
The drawing sets window now has two tabs. Releases hosts the release list (paging, nests, Undo Release, New Release) as a SetReleasesView control that replaces SetReleasesForm. File > Releases stays as a shortcut that opens the window on that tab. A set's Releases (Sets tab) and a library drawing's Releases (Drawing Library) open the tab filtered to it, with the filter shown above the list and Clear Filter to list every release. Releases re-reads its page whenever the tab is shown again, so a release made on Sets appears. The set editor gets History... for a saved set; the Sets tab keeps its History button until the browser's buttons are rearranged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
49bb7389f7
commit
b376d217dd
13 files changed
+515
-204
No files matched your search
@@ -56,6 +56,9 @@ public sealed class DrawingSetEditSession
|
||||
|
||||
public Guid Id { get; }
|
||||
|
||||
/// <summary>The store the set is read from and saved to; also reads its history.</summary>
|
||||
public IDrawingSetStore Store => _sets;
|
||||
|
||||
public string SavedBy { get; }
|
||||
|
||||
/// <summary>The set as last read or saved; null until a new set is first saved.</summary>
|
||||
|
||||
@@ -66,6 +66,43 @@ public class DrawingSetFormsTests
|
||||
"The new set test did not complete.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Editor_History_ShowsTheSavedSetsRevisions_AndIsHiddenForANewSet()
|
||||
{
|
||||
StaTestThread.Run(
|
||||
() =>
|
||||
{
|
||||
var library = new FakeLibrary();
|
||||
var a = library.Add("SYN-A", "Synthetic Steel", 0.25);
|
||||
var sets = new InMemoryDrawingSets();
|
||||
var created = Run(sets.CreateAsync(new DrawingSetRecord
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Synthetic kit",
|
||||
Members = new[] { new DrawingSetMember { DrawingId = a.Id, Quantity = 2, Drawing = a } },
|
||||
}));
|
||||
var shown = new List<DrawingSetRecord>();
|
||||
|
||||
using (var editor = new EditDrawingSetForm(new DrawingSetEditSession(sets, created, "Synthetic operator"), library, Server))
|
||||
{
|
||||
editor.ShowHistory = shown.Add;
|
||||
editor.Show();
|
||||
var history = Field<Button>(editor, "historyButton");
|
||||
Assert.True(history.Visible);
|
||||
|
||||
history.PerformClick();
|
||||
|
||||
Assert.Equal(created.Id, Assert.Single(shown).Id);
|
||||
}
|
||||
|
||||
using var fresh = new EditDrawingSetForm(new DrawingSetEditSession(sets, null, "Synthetic operator"), library, Server);
|
||||
fresh.Show();
|
||||
Assert.False(Field<Button>(fresh, "historyButton").Visible);
|
||||
},
|
||||
TestTimeout,
|
||||
"The editor history test did not complete.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EditorLibraryPane_ListsEveryDrawing_AddsTheSelectionInListOrder_AndGreysMembers()
|
||||
{
|
||||
|
||||
@@ -293,12 +293,18 @@ public class SetReleaseFormsTests
|
||||
Nests = new[] { Nest("N1", NestStatus.HasBeenCut), Nest("N2", NestStatus.ToBeCut), Nest("N3", NestStatus.Draft, deleted: true) },
|
||||
});
|
||||
|
||||
using var form = new SetReleasesForm(sets, set);
|
||||
using var form = new DrawingSetsForm(sets, new PackageLibrary(), Server);
|
||||
Run(form.ShowReleasesAsync(set));
|
||||
form.Show();
|
||||
var releases = Field<DataGridView>(form, "releaseGrid");
|
||||
var nests = Field<DataGridView>(form, "nestGrid");
|
||||
var view = form.Releases;
|
||||
var releases = Field<DataGridView>(view, "releaseGrid");
|
||||
var nests = Field<DataGridView>(view, "nestGrid");
|
||||
PumpUntil(() => releases.Rows.Count == 1 && nests.Rows.Count == 3, "the releases");
|
||||
|
||||
Assert.True(form.ShowsReleases);
|
||||
Assert.True(Field<TableLayoutPanel>(view, "filterTable").Visible);
|
||||
Assert.Equal("Releases of set 'Synthetic kit'", Field<Label>(view, "filterLabel").Text);
|
||||
Assert.True(releases.Columns["revisionColumn"]!.Visible);
|
||||
Assert.Equal("1 of 2 cut, 1 deleted", releases.Rows[0].Cells["progressColumn"].Value);
|
||||
Assert.Equal("×2", releases.Rows[0].Cells["multiplierColumn"].Value);
|
||||
Assert.Equal(new object[] { "Has Been Cut", "To Be Cut", "Deleted" },
|
||||
@@ -505,21 +511,23 @@ public class SetReleaseFormsTests
|
||||
};
|
||||
var questions = new List<string>();
|
||||
|
||||
using var form = new SetReleasesForm(sets, set);
|
||||
form.ShowMessage = (text, buttons, _) =>
|
||||
using var form = new DrawingSetsForm(sets, new PackageLibrary(), Server);
|
||||
var view = form.Releases;
|
||||
view.ShowMessage = (text, buttons, _) =>
|
||||
{
|
||||
questions.Add(text);
|
||||
return buttons == MessageBoxButtons.YesNo ? DialogResult.Yes : DialogResult.OK;
|
||||
};
|
||||
Run(form.ShowReleasesAsync(set));
|
||||
form.Show();
|
||||
var releases = Field<DataGridView>(form, "releaseGrid");
|
||||
var nests = Field<DataGridView>(form, "nestGrid");
|
||||
var releases = Field<DataGridView>(view, "releaseGrid");
|
||||
var nests = Field<DataGridView>(view, "nestGrid");
|
||||
PumpUntil(() => releases.Rows.Count == 1 && nests.Rows.Count == 2, "the releases");
|
||||
Assert.Equal("×2 (+1 other set)", releases.Rows[0].Cells["multiplierColumn"].Value);
|
||||
Assert.Equal(new object[] { "Created", "Merged (revision 3 → 4)" }, nests.Rows.Cast<DataGridViewRow>().Select(r => r.Cells["changeColumn"].Value));
|
||||
Assert.True(Field<Button>(form, "undoButton").Enabled);
|
||||
Assert.True(Field<Button>(view, "undoButton").Enabled);
|
||||
|
||||
Run(form.UndoSelectedAsync());
|
||||
Run(view.UndoSelectedAsync());
|
||||
|
||||
Assert.Contains("1 nest it created is deleted", questions[0]);
|
||||
Assert.Contains("1 draft it merged into goes back", questions[0]);
|
||||
@@ -529,7 +537,8 @@ public class SetReleaseFormsTests
|
||||
PumpUntil(() => (string)releases.Rows[0].Cells["progressColumn"].Value == "Undone", "the release to read as undone");
|
||||
Assert.Equal(new object[] { "Created; undo deleted it", "Merged (revision 3 → 4); undo left it as it was" },
|
||||
nests.Rows.Cast<DataGridViewRow>().Select(r => r.Cells["changeColumn"].Value));
|
||||
Assert.False(Field<Button>(form, "undoButton").Enabled);
|
||||
Assert.False(Field<Button>(view, "undoButton").Enabled);
|
||||
Assert.Same(set, view.FilterSet);
|
||||
},
|
||||
TestTimeout,
|
||||
"The release undo test did not complete.");
|
||||
@@ -679,17 +688,20 @@ public class SetReleaseFormsTests
|
||||
sets.UndoHandler = (id, request) => new SetReleaseRecord { Id = id, UndoneAt = DateTime.UtcNow };
|
||||
var questions = new List<string>();
|
||||
|
||||
using var form = new SetReleasesForm(sets, a);
|
||||
form.ShowMessage = (text, _, _) => { questions.Add(text); return DialogResult.OK; };
|
||||
using var form = new DrawingSetsForm(sets, library, Server);
|
||||
var view = form.Releases;
|
||||
view.ShowMessage = (text, _, _) => { questions.Add(text); return DialogResult.OK; };
|
||||
Run(form.ShowReleasesAsync(drawing: a));
|
||||
form.Show();
|
||||
var releases = Field<DataGridView>(form, "releaseGrid");
|
||||
var releases = Field<DataGridView>(view, "releaseGrid");
|
||||
PumpUntil(() => releases.Rows.Count == 1, "the releases");
|
||||
|
||||
Assert.Equal("Releases of drawing 'SYN-A'", form.Text);
|
||||
Assert.Equal("Releases of drawing 'SYN-A' on its own", Field<Label>(view, "filterLabel").Text);
|
||||
Assert.Equal("Quantity", releases.Columns["multiplierColumn"]!.HeaderText);
|
||||
Assert.Equal("×4 (+1 set)", releases.Rows[0].Cells["multiplierColumn"].Value);
|
||||
Assert.False(releases.Columns["revisionColumn"]!.Visible);
|
||||
|
||||
Run(form.UndoSelectedAsync());
|
||||
Run(view.UndoSelectedAsync());
|
||||
|
||||
Assert.Equal(release.Id, Assert.Single(sets.UndoRequests).Id);
|
||||
Assert.Contains("deleted (they can be recovered)", Assert.Single(questions));
|
||||
@@ -729,9 +741,10 @@ public class SetReleaseFormsTests
|
||||
Array.Empty<SetReleaseLine>(),
|
||||
new[] { new SetReleaseDrawing { DrawingId = a.Id, DrawingName = a.Name, Quantity = 1 } }));
|
||||
|
||||
using var form = new SetReleasesForm(sets);
|
||||
using var form = new DrawingSetsForm(sets, library, Server);
|
||||
var view = form.Releases;
|
||||
var opened = 0;
|
||||
form.ShowNewRelease = () =>
|
||||
view.ShowNewRelease = () =>
|
||||
{
|
||||
opened++;
|
||||
// The dialog records a release before it closes; a cancelled dialog records none.
|
||||
@@ -740,25 +753,26 @@ public class SetReleaseFormsTests
|
||||
Array.Empty<SetReleaseDrawing>()));
|
||||
return opened == 2;
|
||||
};
|
||||
Run(form.ShowReleasesAsync());
|
||||
form.Show();
|
||||
var releases = Field<DataGridView>(form, "releaseGrid");
|
||||
var releases = Field<DataGridView>(view, "releaseGrid");
|
||||
PumpUntil(() => releases.Rows.Count == 2, "the releases");
|
||||
|
||||
Assert.Equal("Releases", form.Text);
|
||||
Assert.False(Field<TableLayoutPanel>(view, "filterTable").Visible);
|
||||
Assert.False(releases.Columns["revisionColumn"]!.Visible);
|
||||
Assert.Equal("Released", releases.Columns["multiplierColumn"]!.HeaderText);
|
||||
Assert.Equal(new object[] { "SYN-A ×1", "Synthetic kit ×2, Synthetic frame; SYN-A ×4" },
|
||||
releases.Rows.Cast<DataGridViewRow>().Select(r => r.Cells["multiplierColumn"].Value));
|
||||
Assert.Equal("Releases 1-2 of 2", Field<Label>(form, "summaryLabel").Text);
|
||||
Assert.False(Field<Button>(form, "newReleaseButton").Visible);
|
||||
Assert.Equal("Releases 1-2 of 2", Field<Label>(view, "summaryLabel").Text);
|
||||
Assert.False(Field<Button>(view, "newReleaseButton").Visible);
|
||||
|
||||
Run(form.NewReleaseAsync());
|
||||
Run(view.NewReleaseAsync());
|
||||
Assert.Equal(2, releases.Rows.Count);
|
||||
|
||||
Run(form.NewReleaseAsync());
|
||||
Run(view.NewReleaseAsync());
|
||||
PumpUntil(() => releases.Rows.Count == 3, "the re-read releases");
|
||||
Assert.Equal("Synthetic kit", releases.Rows[0].Cells["multiplierColumn"].Value);
|
||||
Assert.Equal("Releases 1-3 of 3", Field<Label>(form, "summaryLabel").Text);
|
||||
Assert.Equal("Releases 1-3 of 3", Field<Label>(view, "summaryLabel").Text);
|
||||
},
|
||||
TestTimeout,
|
||||
"The all-releases view test did not complete.");
|
||||
@@ -828,6 +842,70 @@ public class SetReleaseFormsTests
|
||||
"The empty release test did not complete.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DrawingSetsWindow_ReadsEachTabWhenShown_AndASetsReleasesFilterTheReleasesTab_UntilCleared()
|
||||
{
|
||||
StaTestThread.Run(
|
||||
() =>
|
||||
{
|
||||
var library = new PackageLibrary();
|
||||
var a = library.Add("SYN-A", "Synthetic Steel");
|
||||
var sets = new InMemoryDrawingSets();
|
||||
var kit = CreateSet(sets, "Synthetic kit", (a, 1));
|
||||
var frame = CreateSet(sets, "Synthetic frame", (a, 2));
|
||||
SetReleaseRecord Release(DrawingSetRecord set) => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Sets = new[] { new SetReleaseLine { SetId = set.Id, SetRevision = 1, SetName = set.Name, Quantity = 1 } },
|
||||
ReleasedBy = "Synthetic operator",
|
||||
Nests = new[] { new SetReleaseNest { NestId = Guid.NewGuid(), Nest = new NestRecord { Name = "N1", Status = NestStatus.Draft } } },
|
||||
};
|
||||
sets.Releases.Add(Release(kit));
|
||||
sets.Releases.Add(Release(frame));
|
||||
|
||||
using var form = new DrawingSetsForm(sets, library, Server);
|
||||
var browser = form.Browser;
|
||||
var view = form.Releases;
|
||||
form.Show();
|
||||
var setGrid = Field<DataGridView>(browser, "setGrid");
|
||||
var releases = Field<DataGridView>(view, "releaseGrid");
|
||||
var filter = Field<TableLayoutPanel>(view, "filterTable");
|
||||
PumpUntil(() => setGrid.Rows.Count == 2, "the set list");
|
||||
|
||||
Assert.False(form.ShowsReleases);
|
||||
Assert.False(view.HasLoaded);
|
||||
|
||||
setGrid.CurrentCell = setGrid.Rows.Cast<DataGridViewRow>()
|
||||
.Single(r => (string)r.Cells["nameColumn"].Value == "Synthetic kit").Cells[0];
|
||||
Field<Button>(browser, "releasesButton").PerformClick();
|
||||
PumpUntil(() => releases.Rows.Count == 1, "the set's releases");
|
||||
|
||||
Assert.True(form.ShowsReleases);
|
||||
Assert.Equal(kit.Id, view.FilterSet.Id);
|
||||
Assert.True(filter.Visible);
|
||||
Assert.Equal("Releases of set 'Synthetic kit'", Field<Label>(view, "filterLabel").Text);
|
||||
Assert.Equal("Quantity", releases.Columns["multiplierColumn"]!.HeaderText);
|
||||
|
||||
Field<Button>(view, "clearFilterButton").PerformClick();
|
||||
PumpUntil(() => releases.Rows.Count == 2, "every release");
|
||||
|
||||
Assert.Null(view.FilterSet);
|
||||
Assert.False(filter.Visible);
|
||||
Assert.Equal("Released", releases.Columns["multiplierColumn"]!.HeaderText);
|
||||
Assert.False(releases.Columns["revisionColumn"]!.Visible);
|
||||
|
||||
// A release made meanwhile (as Make Nests on the Sets tab does) shows once the tab is shown again.
|
||||
form.ShowSets();
|
||||
Assert.False(form.ShowsReleases);
|
||||
sets.Releases.Add(Release(frame));
|
||||
Field<TabControl>(form, "tabs").SelectedIndex = 1;
|
||||
PumpUntil(() => releases.Rows.Count == 3, "the re-read releases");
|
||||
Assert.Equal("Releases 1-3 of 3", Field<Label>(view, "summaryLabel").Text);
|
||||
},
|
||||
TestTimeout,
|
||||
"The drawing sets window tabs test did not complete.");
|
||||
}
|
||||
|
||||
private static DrawingSetRecord CreateSet(InMemoryDrawingSets sets, params (DrawingLibraryRecord Drawing, int Quantity)[] members) =>
|
||||
CreateSet(sets, "Synthetic kit", members);
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace OpenNest.Controls
|
||||
/// <summary>
|
||||
/// Browses the server's drawing sets one bounded page at a time through
|
||||
/// <see cref="DrawingSetBrowseSession"/>, and creates, edits, archives or restores them and
|
||||
/// shows their history. <see cref="DrawingSetsForm"/> hosts it to manage and release sets. In pick mode
|
||||
/// shows their history. <see cref="DrawingSetsForm"/> hosts it on its Sets tab to manage and
|
||||
/// release sets, and shows a set's Releases on its Releases tab. In pick mode
|
||||
/// (<see cref="ChooseDrawingSetForm"/>) only active sets are listed, and choosing a set or
|
||||
/// New Set raises <see cref="SetActivated"/> or <see cref="NewSetChosen"/> instead.
|
||||
/// </summary>
|
||||
@@ -38,7 +39,7 @@ namespace OpenNest.Controls
|
||||
/// <summary>Shows a set's revision history; replaced by tests.</summary>
|
||||
internal Action<DrawingSetRecord> ShowHistory { get; set; }
|
||||
|
||||
/// <summary>Shows a set's releases; replaced by tests.</summary>
|
||||
/// <summary>Shows a set's releases; set by the host (Releases is disabled without it) and replaced by tests.</summary>
|
||||
internal Action<DrawingSetRecord> ShowReleases { get; set; }
|
||||
|
||||
/// <summary>Shows the release dialog for a set read with its members; replaced by tests.</summary>
|
||||
@@ -101,11 +102,6 @@ namespace OpenNest.Controls
|
||||
using var form = new DrawingSetHistoryForm(sets, set);
|
||||
form.ShowDialog(FindForm());
|
||||
};
|
||||
ShowReleases = set =>
|
||||
{
|
||||
using var form = new SetReleasesForm(sets, set);
|
||||
form.ShowDialog(FindForm());
|
||||
};
|
||||
ShowRelease = set =>
|
||||
{
|
||||
using var form = new ReleaseSetForm(sets, library, nests, this.serverUrl, set, openNest) { IsNestOpen = isNestOpen };
|
||||
@@ -228,7 +224,7 @@ namespace OpenNest.Controls
|
||||
private void ReleasesButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (SelectedSet is { } set)
|
||||
ShowReleases(set);
|
||||
ShowReleases?.Invoke(set);
|
||||
}
|
||||
|
||||
private async void ReleaseButton_Click(object sender, EventArgs e) => await ReleaseSelectedAsync();
|
||||
@@ -440,7 +436,7 @@ namespace OpenNest.Controls
|
||||
archiveButton.Enabled = ready && selected != null;
|
||||
archiveButton.Text = selected is { Archived: true } ? "Restore" : "Archive";
|
||||
historyButton.Enabled = ready && selected != null;
|
||||
releasesButton.Enabled = ready && selected != null;
|
||||
releasesButton.Enabled = ready && selected != null && ShowReleases != null;
|
||||
releaseButton.Enabled = ready && nests != null && selected is { Archived: false, MemberCount: > 0 };
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+63
-36
@@ -1,6 +1,6 @@
|
||||
namespace OpenNest.Forms
|
||||
namespace OpenNest.Controls
|
||||
{
|
||||
partial class SetReleasesForm
|
||||
partial class SetReleasesView
|
||||
{
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
@@ -19,6 +19,9 @@ namespace OpenNest.Forms
|
||||
{
|
||||
System.Windows.Forms.DataGridViewCellStyle rightAligned = new System.Windows.Forms.DataGridViewCellStyle();
|
||||
layoutTable = new System.Windows.Forms.TableLayoutPanel();
|
||||
filterTable = new System.Windows.Forms.TableLayoutPanel();
|
||||
filterLabel = new System.Windows.Forms.Label();
|
||||
clearFilterButton = new System.Windows.Forms.Button();
|
||||
releaseGrid = new System.Windows.Forms.DataGridView();
|
||||
revisionColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
releasedColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
@@ -41,8 +44,8 @@ namespace OpenNest.Forms
|
||||
summaryLabel = new System.Windows.Forms.Label();
|
||||
newReleaseButton = new System.Windows.Forms.Button();
|
||||
undoButton = new System.Windows.Forms.Button();
|
||||
closeButton = new System.Windows.Forms.Button();
|
||||
layoutTable.SuspendLayout();
|
||||
filterTable.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)releaseGrid).BeginInit();
|
||||
nestsGroup.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)nestGrid).BeginInit();
|
||||
@@ -53,20 +56,59 @@ namespace OpenNest.Forms
|
||||
//
|
||||
layoutTable.ColumnCount = 1;
|
||||
layoutTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
layoutTable.Controls.Add(releaseGrid, 0, 0);
|
||||
layoutTable.Controls.Add(nestsGroup, 0, 1);
|
||||
layoutTable.Controls.Add(bottomTable, 0, 2);
|
||||
layoutTable.Controls.Add(filterTable, 0, 0);
|
||||
layoutTable.Controls.Add(releaseGrid, 0, 1);
|
||||
layoutTable.Controls.Add(nestsGroup, 0, 2);
|
||||
layoutTable.Controls.Add(bottomTable, 0, 3);
|
||||
layoutTable.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
layoutTable.Location = new System.Drawing.Point(0, 0);
|
||||
layoutTable.Name = "layoutTable";
|
||||
layoutTable.Padding = new System.Windows.Forms.Padding(8);
|
||||
layoutTable.RowCount = 3;
|
||||
layoutTable.Padding = new System.Windows.Forms.Padding(0);
|
||||
layoutTable.RowCount = 4;
|
||||
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
layoutTable.Size = new System.Drawing.Size(824, 541);
|
||||
layoutTable.Size = new System.Drawing.Size(984, 541);
|
||||
layoutTable.TabIndex = 0;
|
||||
//
|
||||
// filterTable
|
||||
//
|
||||
filterTable.AutoSize = true;
|
||||
filterTable.ColumnCount = 3;
|
||||
filterTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||
filterTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||
filterTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
filterTable.Controls.Add(filterLabel, 0, 0);
|
||||
filterTable.Controls.Add(clearFilterButton, 1, 0);
|
||||
filterTable.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
filterTable.Margin = new System.Windows.Forms.Padding(0, 0, 0, 6);
|
||||
filterTable.Name = "filterTable";
|
||||
filterTable.RowCount = 1;
|
||||
filterTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
filterTable.TabIndex = 0;
|
||||
//
|
||||
// filterLabel
|
||||
//
|
||||
filterLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||
filterLabel.AutoSize = true;
|
||||
filterLabel.BackColor = System.Drawing.SystemColors.Info;
|
||||
filterLabel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||
filterLabel.ForeColor = System.Drawing.SystemColors.InfoText;
|
||||
filterLabel.Name = "filterLabel";
|
||||
filterLabel.Padding = new System.Windows.Forms.Padding(4, 2, 4, 2);
|
||||
filterLabel.TabIndex = 0;
|
||||
//
|
||||
// clearFilterButton
|
||||
//
|
||||
clearFilterButton.AutoSize = true;
|
||||
clearFilterButton.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
clearFilterButton.Name = "clearFilterButton";
|
||||
clearFilterButton.TabIndex = 1;
|
||||
clearFilterButton.Text = "Clear Filter";
|
||||
clearFilterButton.UseVisualStyleBackColor = true;
|
||||
clearFilterButton.Click += ClearFilterButton_Click;
|
||||
//
|
||||
// releaseGrid
|
||||
//
|
||||
releaseGrid.AllowUserToAddRows = false;
|
||||
@@ -79,7 +121,7 @@ namespace OpenNest.Forms
|
||||
releaseGrid.RowHeadersVisible = false;
|
||||
releaseGrid.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
|
||||
releaseGrid.StandardTab = true;
|
||||
releaseGrid.TabIndex = 0;
|
||||
releaseGrid.TabIndex = 1;
|
||||
releaseGrid.CurrentCellChanged += ReleaseGrid_CurrentCellChanged;
|
||||
//
|
||||
// revisionColumn
|
||||
@@ -142,7 +184,7 @@ namespace OpenNest.Forms
|
||||
nestsGroup.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
nestsGroup.Margin = new System.Windows.Forms.Padding(0, 6, 0, 0);
|
||||
nestsGroup.Name = "nestsGroup";
|
||||
nestsGroup.TabIndex = 1;
|
||||
nestsGroup.TabIndex = 2;
|
||||
nestsGroup.TabStop = false;
|
||||
nestsGroup.Text = "Nests of the selected release";
|
||||
//
|
||||
@@ -206,25 +248,23 @@ namespace OpenNest.Forms
|
||||
// bottomTable
|
||||
//
|
||||
bottomTable.AutoSize = true;
|
||||
bottomTable.ColumnCount = 6;
|
||||
bottomTable.ColumnCount = 5;
|
||||
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||
bottomTable.Controls.Add(previousButton, 0, 0);
|
||||
bottomTable.Controls.Add(nextButton, 1, 0);
|
||||
bottomTable.Controls.Add(summaryLabel, 2, 0);
|
||||
bottomTable.Controls.Add(newReleaseButton, 3, 0);
|
||||
bottomTable.Controls.Add(undoButton, 4, 0);
|
||||
bottomTable.Controls.Add(closeButton, 5, 0);
|
||||
bottomTable.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
bottomTable.Margin = new System.Windows.Forms.Padding(0, 6, 0, 0);
|
||||
bottomTable.Name = "bottomTable";
|
||||
bottomTable.RowCount = 1;
|
||||
bottomTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
bottomTable.TabIndex = 2;
|
||||
bottomTable.TabIndex = 3;
|
||||
//
|
||||
// previousButton
|
||||
//
|
||||
@@ -273,32 +313,17 @@ namespace OpenNest.Forms
|
||||
undoButton.UseVisualStyleBackColor = true;
|
||||
undoButton.Click += UndoButton_Click;
|
||||
//
|
||||
// closeButton
|
||||
//
|
||||
closeButton.AutoSize = true;
|
||||
closeButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
closeButton.Name = "closeButton";
|
||||
closeButton.TabIndex = 5;
|
||||
closeButton.Text = "Close";
|
||||
closeButton.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// SetReleasesForm
|
||||
// SetReleasesView
|
||||
//
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
||||
CancelButton = closeButton;
|
||||
ClientSize = new System.Drawing.Size(1004, 541);
|
||||
Controls.Add(layoutTable);
|
||||
Font = new System.Drawing.Font("Segoe UI", 9.75F);
|
||||
MinimizeBox = false;
|
||||
MinimumSize = new System.Drawing.Size(600, 400);
|
||||
Name = "SetReleasesForm";
|
||||
ShowIcon = false;
|
||||
ShowInTaskbar = false;
|
||||
StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
Text = "Set Releases";
|
||||
Shown += SetReleasesForm_Shown;
|
||||
Name = "SetReleasesView";
|
||||
Size = new System.Drawing.Size(984, 541);
|
||||
layoutTable.ResumeLayout(false);
|
||||
layoutTable.PerformLayout();
|
||||
filterTable.ResumeLayout(false);
|
||||
filterTable.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)releaseGrid).EndInit();
|
||||
nestsGroup.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)nestGrid).EndInit();
|
||||
@@ -310,6 +335,9 @@ namespace OpenNest.Forms
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.TableLayoutPanel layoutTable;
|
||||
private System.Windows.Forms.TableLayoutPanel filterTable;
|
||||
private System.Windows.Forms.Label filterLabel;
|
||||
private System.Windows.Forms.Button clearFilterButton;
|
||||
private System.Windows.Forms.DataGridView releaseGrid;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn revisionColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn releasedColumn;
|
||||
@@ -332,6 +360,5 @@ namespace OpenNest.Forms
|
||||
private System.Windows.Forms.Label summaryLabel;
|
||||
private System.Windows.Forms.Button newReleaseButton;
|
||||
private System.Windows.Forms.Button undoButton;
|
||||
private System.Windows.Forms.Button closeButton;
|
||||
}
|
||||
}
|
||||
@@ -5,29 +5,31 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Controls;
|
||||
using OpenNest.Data;
|
||||
using OpenNest.Forms;
|
||||
|
||||
namespace OpenNest.Forms
|
||||
namespace OpenNest.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// File > Releases: every release on the nest server, newest first, a bounded page at a
|
||||
/// time, each with what it released ("Kit A ×2; Bracket ×4") and its progress ("2 of 3
|
||||
/// cut"), and the nests of the selected release with what the release did to each and
|
||||
/// their current status, or Deleted. Drawing Sets > Releases and Drawings tab >
|
||||
/// Releases show the same view for the releases that include one set, or one library
|
||||
/// drawing on its own. The server joins every nest's current metadata on each read. New
|
||||
/// Release opens the release dialog empty; the list re-reads once a release is recorded.
|
||||
/// Undo Release deletes the nests a release created and restores the drafts it merged
|
||||
/// into, all or nothing; nests changed since can be left as they are.
|
||||
/// The Releases tab of <see cref="DrawingSetsForm"/>: every release on the nest server,
|
||||
/// newest first, a bounded page at a time, each with what it released ("Kit A ×2; Bracket
|
||||
/// ×4") and its progress ("2 of 3 cut"), and the nests of the selected release with what
|
||||
/// the release did to each and their current status, or Deleted. A filter narrows the list
|
||||
/// to the releases that include one set, or one library drawing on its own; the filter
|
||||
/// shows above the list until Clear Filter. The server joins every nest's current metadata
|
||||
/// on each read. New Release opens the release dialog empty; the list re-reads once a
|
||||
/// release is recorded. Undo Release deletes the nests a release created and restores the
|
||||
/// drafts it merged into, all or nothing; nests changed since can be left as they are.
|
||||
/// </summary>
|
||||
public partial class SetReleasesForm : Form
|
||||
public partial class SetReleasesView : UserControl
|
||||
{
|
||||
private const int PageSize = 100;
|
||||
|
||||
private readonly IDrawingSetStore sets;
|
||||
private readonly DrawingSetRecord set;
|
||||
private readonly DrawingLibraryRecord drawing;
|
||||
private readonly int releasedWidth;
|
||||
private readonly DataGridViewCellStyle releasedStyle;
|
||||
private IDrawingSetStore sets;
|
||||
private DrawingSetRecord set;
|
||||
private DrawingLibraryRecord drawing;
|
||||
private SetReleasePage page;
|
||||
private int offset;
|
||||
private int pageRequest;
|
||||
@@ -42,43 +44,31 @@ namespace OpenNest.Forms
|
||||
/// </summary>
|
||||
internal Func<bool> ShowNewRelease { get; set; }
|
||||
|
||||
/// <summary>Every release on the server, newest first.</summary>
|
||||
public SetReleasesForm(IDrawingSetStore sets)
|
||||
: this(sets, all: true)
|
||||
public SetReleasesView()
|
||||
{
|
||||
Text = "Releases";
|
||||
revisionColumn.Visible = false;
|
||||
multiplierColumn.HeaderText = "Released";
|
||||
multiplierColumn.Width = 320;
|
||||
multiplierColumn.DefaultCellStyle = null;
|
||||
}
|
||||
|
||||
public SetReleasesForm(IDrawingSetStore sets, DrawingSetRecord set)
|
||||
: this(sets, all: false)
|
||||
{
|
||||
this.set = set ?? throw new ArgumentNullException(nameof(set));
|
||||
Text = $"Releases of '{set.Name}'";
|
||||
}
|
||||
|
||||
/// <summary>The releases that had <paramref name="drawing"/> on its own, outside any set.</summary>
|
||||
public SetReleasesForm(IDrawingSetStore sets, DrawingLibraryRecord drawing)
|
||||
: this(sets, all: false)
|
||||
{
|
||||
this.drawing = drawing ?? throw new ArgumentNullException(nameof(drawing));
|
||||
Text = $"Releases of drawing '{drawing.Name}'";
|
||||
revisionColumn.Visible = false;
|
||||
multiplierColumn.HeaderText = "Quantity";
|
||||
}
|
||||
|
||||
// The flag only tells the public constructors apart; the public ones set what differs.
|
||||
private SetReleasesForm(IDrawingSetStore sets, bool all)
|
||||
{
|
||||
_ = all;
|
||||
InitializeComponent();
|
||||
this.sets = sets ?? throw new ArgumentNullException(nameof(sets));
|
||||
GridStyle.Apply(releaseGrid);
|
||||
GridStyle.Apply(nestGrid);
|
||||
ShowMessage = (text, buttons, icon) => MessageBox.Show(this, text, Text, buttons, icon);
|
||||
releasedWidth = multiplierColumn.Width;
|
||||
releasedStyle = multiplierColumn.DefaultCellStyle;
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
/// <summary>True after a page of releases has been requested at least once.</summary>
|
||||
public bool HasLoaded { get; private set; }
|
||||
|
||||
/// <summary>The set the list is narrowed to, or null.</summary>
|
||||
public DrawingSetRecord FilterSet => set;
|
||||
|
||||
/// <summary>The drawing the list is narrowed to, or null.</summary>
|
||||
public DrawingLibraryRecord FilterDrawing => drawing;
|
||||
|
||||
public void Initialize(IDrawingSetStore sets)
|
||||
{
|
||||
if (this.sets != null)
|
||||
throw new InvalidOperationException("The releases view is already initialized.");
|
||||
this.sets = sets ?? throw new ArgumentNullException(nameof(sets));
|
||||
ShowMessage = (text, buttons, icon) => MessageBox.Show(FindForm(), text, FindForm()?.Text ?? "Releases", buttons, icon);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -92,18 +82,60 @@ namespace OpenNest.Forms
|
||||
ArgumentNullException.ThrowIfNull(library);
|
||||
ArgumentNullException.ThrowIfNull(nests);
|
||||
ArgumentNullException.ThrowIfNull(openNest);
|
||||
if (sets == null)
|
||||
throw new InvalidOperationException("New Release needs an initialized releases view.");
|
||||
ShowNewRelease = () =>
|
||||
{
|
||||
using var form = new ReleaseSetForm(sets, library, nests, serverUrl, Array.Empty<DrawingSetRecord>(),
|
||||
Array.Empty<SetReleaseDrawingLine>(), openNest)
|
||||
{ IsNestOpen = isNestOpen ?? (_ => false) };
|
||||
form.ShowDialog(this);
|
||||
form.ShowDialog(FindForm());
|
||||
return form.Result?.Release != null;
|
||||
};
|
||||
newReleaseButton.Visible = true;
|
||||
}
|
||||
|
||||
private async void SetReleasesForm_Shown(object sender, EventArgs e) => await LoadPageAsync(0);
|
||||
/// <summary>
|
||||
/// Narrows the list to the releases that include <paramref name="set"/>, or that had
|
||||
/// <paramref name="drawing"/> on its own outside any set; both null lists every release.
|
||||
/// The next read starts at the newest page.
|
||||
/// </summary>
|
||||
public void SetFilter(DrawingSetRecord set, DrawingLibraryRecord drawing)
|
||||
{
|
||||
if (set != null && drawing != null)
|
||||
throw new ArgumentException("Filter by a set or a drawing, not both.");
|
||||
this.set = set;
|
||||
this.drawing = drawing;
|
||||
offset = 0;
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
/// <summary>Re-reads the current page (the first call reads the newest).</summary>
|
||||
public Task RefreshAsync() => LoadPageAsync(offset);
|
||||
|
||||
/// <summary>Lists every release again, from the newest.</summary>
|
||||
internal Task ClearFilterAsync()
|
||||
{
|
||||
SetFilter(null, null);
|
||||
return LoadPageAsync(0);
|
||||
}
|
||||
|
||||
private async void ClearFilterButton_Click(object sender, EventArgs e) => await ClearFilterAsync();
|
||||
|
||||
// Per set the release's revision of it and its quantity; per drawing its quantity; across
|
||||
// all releases, everything each one had.
|
||||
private void ApplyFilter()
|
||||
{
|
||||
var all = set == null && drawing == null;
|
||||
revisionColumn.Visible = set != null;
|
||||
multiplierColumn.HeaderText = all ? "Released" : "Quantity";
|
||||
multiplierColumn.Width = all ? 320 : releasedWidth;
|
||||
multiplierColumn.DefaultCellStyle = all ? null : releasedStyle;
|
||||
filterTable.Visible = !all;
|
||||
filterLabel.Text = set != null ? $"Releases of set '{set.Name}'"
|
||||
: drawing != null ? $"Releases of drawing '{drawing.Name}' on its own"
|
||||
: "";
|
||||
}
|
||||
|
||||
private async void NewReleaseButton_Click(object sender, EventArgs e) => await NewReleaseAsync();
|
||||
|
||||
@@ -192,12 +224,16 @@ namespace OpenNest.Forms
|
||||
/// <summary>Reads one page of releases; a later request discards an earlier one's result.</summary>
|
||||
internal async Task LoadPageAsync(int requestedOffset)
|
||||
{
|
||||
if (sets == null)
|
||||
return;
|
||||
HasLoaded = true;
|
||||
var request = ++pageRequest;
|
||||
var (filterSet, filterDrawing) = (set, drawing);
|
||||
previousButton.Enabled = nextButton.Enabled = false;
|
||||
summaryLabel.Text = "Loading...";
|
||||
try
|
||||
{
|
||||
var loaded = await sets.QueryReleasesAsync(setId: set?.Id, drawingId: drawing?.Id, offset: requestedOffset, limit: PageSize);
|
||||
var loaded = await sets.QueryReleasesAsync(setId: filterSet?.Id, drawingId: filterDrawing?.Id, offset: requestedOffset, limit: PageSize);
|
||||
if (IsDisposed || request != pageRequest)
|
||||
return;
|
||||
|
||||
@@ -212,29 +248,29 @@ namespace OpenNest.Forms
|
||||
// counted; the all-releases view names everything the release had.
|
||||
var revision = 0;
|
||||
string released;
|
||||
if (set == null && drawing == null)
|
||||
if (filterSet == null && filterDrawing == null)
|
||||
{
|
||||
released = Contents(release);
|
||||
}
|
||||
else
|
||||
{
|
||||
int quantity, otherSets, otherDrawings;
|
||||
if (set != null)
|
||||
if (filterSet != null)
|
||||
{
|
||||
var line = release.Sets.First(s => s.SetId == set.Id);
|
||||
var line = release.Sets.First(s => s.SetId == filterSet.Id);
|
||||
(revision, quantity) = (line.SetRevision, line.Quantity);
|
||||
(otherSets, otherDrawings) = (release.Sets.Count - 1, release.Drawings.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
var line = release.Drawings.First(d => d.DrawingId == drawing.Id);
|
||||
var line = release.Drawings.First(d => d.DrawingId == filterDrawing.Id);
|
||||
quantity = line.Quantity;
|
||||
(otherSets, otherDrawings) = (release.Sets.Count, release.Drawings.Count - 1);
|
||||
}
|
||||
var others = new[]
|
||||
{
|
||||
otherSets == 0 ? null : $"{otherSets} {(set != null ? "other " : "")}{(otherSets == 1 ? "set" : "sets")}",
|
||||
otherDrawings == 0 ? null : $"{otherDrawings} {(drawing != null ? "other " : "")}{(otherDrawings == 1 ? "drawing" : "drawings")}",
|
||||
otherSets == 0 ? null : $"{otherSets} {(filterSet != null ? "other " : "")}{(otherSets == 1 ? "set" : "sets")}",
|
||||
otherDrawings == 0 ? null : $"{otherDrawings} {(filterDrawing != null ? "other " : "")}{(otherDrawings == 1 ? "drawing" : "drawings")}",
|
||||
}.Where(o => o != null).ToList();
|
||||
released = others.Count == 0 ? $"×{quantity}" : $"×{quantity} (+{string.Join(", ", others)})";
|
||||
}
|
||||
@@ -255,8 +291,8 @@ namespace OpenNest.Forms
|
||||
releaseGrid.CurrentCell = releaseGrid.Rows[0].Cells[0];
|
||||
|
||||
summaryLabel.Text = loaded.Total == 0
|
||||
? set != null ? "This set has not been released yet."
|
||||
: drawing != null ? "This drawing has not been released on its own yet."
|
||||
? filterSet != null ? "This set has not been released yet."
|
||||
: filterDrawing != null ? "This drawing has not been released on its own yet."
|
||||
: "Nothing has been released yet."
|
||||
: $"Releases {offset + 1:N0}-{offset + loaded.Items.Count:N0} of {loaded.Total:N0}";
|
||||
}
|
||||
@@ -24,8 +24,9 @@ namespace OpenNest.Forms
|
||||
/// selected drawing's details, notes and nesting settings (Cut first, priority, rotation,
|
||||
/// color) on the server as a new revision with the same package. With a set
|
||||
/// store, the drawing grid's menu lists the sets a drawing is in, adds it to a set, lists
|
||||
/// the releases that had it on its own and, once release is enabled, releases it; sets
|
||||
/// themselves are browsed and released in <see cref="DrawingSetsForm"/>.
|
||||
/// the releases that had it on its own (the Releases tab of <see cref="DrawingSetsForm"/>,
|
||||
/// filtered to it) and, once release is enabled, releases it; sets themselves are browsed
|
||||
/// and released in <see cref="DrawingSetsForm"/>.
|
||||
/// The details pane previews the selected revision's geometry, downloaded shortly after
|
||||
/// the selection settles and cached per revision for the dialog's lifetime. The In This
|
||||
/// Nest tab lists the nest's drawings and edits their required quantities in place.
|
||||
@@ -48,6 +49,7 @@ namespace OpenNest.Forms
|
||||
private Task<Drawing> previewLoad;
|
||||
private bool populatingNest;
|
||||
private bool adding;
|
||||
private Action<DrawingSetsForm> enableSetsRelease = _ => { };
|
||||
|
||||
/// <summary>
|
||||
/// Reads the drawing a record's package holds for the preview, or null when the
|
||||
@@ -131,7 +133,9 @@ namespace OpenNest.Forms
|
||||
};
|
||||
ShowReleases = record =>
|
||||
{
|
||||
using var form = new SetReleasesForm(sets, record);
|
||||
using var form = new DrawingSetsForm(sets, library, this.serverUrl);
|
||||
enableSetsRelease(form);
|
||||
_ = form.ShowReleasesAsync(drawing: record);
|
||||
form.ShowDialog(this);
|
||||
};
|
||||
LoadPreview = async (record, token) =>
|
||||
@@ -394,6 +398,7 @@ namespace OpenNest.Forms
|
||||
{ IsNestOpen = isNestOpen ?? (_ => false) };
|
||||
form.ShowDialog(this);
|
||||
};
|
||||
enableSetsRelease = form => form.EnableRelease(nests, openNest, isNestOpen);
|
||||
releaseSeparator.Visible = true;
|
||||
releaseMenuItem.Visible = true;
|
||||
}
|
||||
|
||||
+53
-3
@@ -18,10 +18,17 @@ namespace OpenNest.Forms
|
||||
private void InitializeComponent()
|
||||
{
|
||||
layoutTable = new System.Windows.Forms.TableLayoutPanel();
|
||||
tabs = new System.Windows.Forms.TabControl();
|
||||
setsTab = new System.Windows.Forms.TabPage();
|
||||
browser = new OpenNest.Controls.DrawingSetBrowser();
|
||||
releasesTab = new System.Windows.Forms.TabPage();
|
||||
releasesView = new OpenNest.Controls.SetReleasesView();
|
||||
buttonPanel = new System.Windows.Forms.FlowLayoutPanel();
|
||||
closeButton = new System.Windows.Forms.Button();
|
||||
layoutTable.SuspendLayout();
|
||||
tabs.SuspendLayout();
|
||||
setsTab.SuspendLayout();
|
||||
releasesTab.SuspendLayout();
|
||||
buttonPanel.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
@@ -29,7 +36,7 @@ namespace OpenNest.Forms
|
||||
//
|
||||
layoutTable.ColumnCount = 1;
|
||||
layoutTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
layoutTable.Controls.Add(browser, 0, 0);
|
||||
layoutTable.Controls.Add(tabs, 0, 0);
|
||||
layoutTable.Controls.Add(buttonPanel, 0, 1);
|
||||
layoutTable.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
layoutTable.Location = new System.Drawing.Point(0, 0);
|
||||
@@ -41,6 +48,26 @@ namespace OpenNest.Forms
|
||||
layoutTable.Size = new System.Drawing.Size(984, 601);
|
||||
layoutTable.TabIndex = 0;
|
||||
//
|
||||
// tabs
|
||||
//
|
||||
tabs.Controls.Add(setsTab);
|
||||
tabs.Controls.Add(releasesTab);
|
||||
tabs.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
tabs.Margin = new System.Windows.Forms.Padding(0);
|
||||
tabs.Name = "tabs";
|
||||
tabs.SelectedIndex = 0;
|
||||
tabs.TabIndex = 0;
|
||||
tabs.SelectedIndexChanged += Tabs_SelectedIndexChanged;
|
||||
//
|
||||
// setsTab
|
||||
//
|
||||
setsTab.Controls.Add(browser);
|
||||
setsTab.Name = "setsTab";
|
||||
setsTab.Padding = new System.Windows.Forms.Padding(6);
|
||||
setsTab.TabIndex = 0;
|
||||
setsTab.Text = "Sets";
|
||||
setsTab.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// browser
|
||||
//
|
||||
browser.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
@@ -48,6 +75,22 @@ namespace OpenNest.Forms
|
||||
browser.Name = "browser";
|
||||
browser.TabIndex = 0;
|
||||
//
|
||||
// releasesTab
|
||||
//
|
||||
releasesTab.Controls.Add(releasesView);
|
||||
releasesTab.Name = "releasesTab";
|
||||
releasesTab.Padding = new System.Windows.Forms.Padding(6);
|
||||
releasesTab.TabIndex = 1;
|
||||
releasesTab.Text = "Releases";
|
||||
releasesTab.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// releasesView
|
||||
//
|
||||
releasesView.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
releasesView.Margin = new System.Windows.Forms.Padding(0);
|
||||
releasesView.Name = "releasesView";
|
||||
releasesView.TabIndex = 0;
|
||||
//
|
||||
// buttonPanel
|
||||
//
|
||||
buttonPanel.AutoSize = true;
|
||||
@@ -71,11 +114,11 @@ namespace OpenNest.Forms
|
||||
//
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
||||
CancelButton = closeButton;
|
||||
ClientSize = new System.Drawing.Size(984, 601);
|
||||
ClientSize = new System.Drawing.Size(1024, 641);
|
||||
Controls.Add(layoutTable);
|
||||
Font = new System.Drawing.Font("Segoe UI", 9.75F);
|
||||
MinimizeBox = false;
|
||||
MinimumSize = new System.Drawing.Size(640, 380);
|
||||
MinimumSize = new System.Drawing.Size(720, 440);
|
||||
Name = "DrawingSetsForm";
|
||||
ShowIcon = false;
|
||||
ShowInTaskbar = false;
|
||||
@@ -84,6 +127,9 @@ namespace OpenNest.Forms
|
||||
Shown += DrawingSetsForm_Shown;
|
||||
layoutTable.ResumeLayout(false);
|
||||
layoutTable.PerformLayout();
|
||||
tabs.ResumeLayout(false);
|
||||
setsTab.ResumeLayout(false);
|
||||
releasesTab.ResumeLayout(false);
|
||||
buttonPanel.ResumeLayout(false);
|
||||
buttonPanel.PerformLayout();
|
||||
ResumeLayout(false);
|
||||
@@ -92,7 +138,11 @@ namespace OpenNest.Forms
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.TableLayoutPanel layoutTable;
|
||||
private System.Windows.Forms.TabControl tabs;
|
||||
private System.Windows.Forms.TabPage setsTab;
|
||||
private OpenNest.Controls.DrawingSetBrowser browser;
|
||||
private System.Windows.Forms.TabPage releasesTab;
|
||||
private OpenNest.Controls.SetReleasesView releasesView;
|
||||
private System.Windows.Forms.FlowLayoutPanel buttonPanel;
|
||||
private System.Windows.Forms.Button closeButton;
|
||||
}
|
||||
|
||||
@@ -1,34 +1,91 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Controls;
|
||||
using OpenNest.Data;
|
||||
|
||||
namespace OpenNest.Forms
|
||||
{
|
||||
/// <summary>
|
||||
/// File > Drawing Sets (Database mode): browses, edits and releases the server's drawing
|
||||
/// sets with <see cref="Controls.DrawingSetBrowser"/>. It needs no open nest, since a
|
||||
/// release creates new nests or merges into draft ones and opens each in its own tab.
|
||||
/// File > Drawing Sets and File > Releases (Database mode), one window with two tabs.
|
||||
/// Sets browses, edits and releases the server's drawing sets with
|
||||
/// <see cref="DrawingSetBrowser"/>; Releases lists the releases with
|
||||
/// <see cref="SetReleasesView"/>, and a set's Releases (or a library drawing's) opens that
|
||||
/// tab filtered to it. It needs no open nest, since a release creates new nests or merges
|
||||
/// into draft ones and opens each in its own tab. Each tab reads its list when first shown;
|
||||
/// Releases re-reads its page whenever it is shown again, so a release made on Sets appears.
|
||||
/// </summary>
|
||||
public partial class DrawingSetsForm : Form
|
||||
{
|
||||
internal OpenNest.Controls.DrawingSetBrowser Browser => browser;
|
||||
private readonly IDrawingLibraryStore library;
|
||||
private readonly string serverUrl;
|
||||
private bool shown;
|
||||
|
||||
internal DrawingSetBrowser Browser => browser;
|
||||
|
||||
internal SetReleasesView Releases => releasesView;
|
||||
|
||||
/// <summary>True while the Releases tab is showing.</summary>
|
||||
public bool ShowsReleases => tabs.SelectedTab == releasesTab;
|
||||
|
||||
/// <param name="library">Used by the set editor to pick drawings.</param>
|
||||
public DrawingSetsForm(IDrawingSetStore sets, IDrawingLibraryStore library, string serverUrl)
|
||||
{
|
||||
InitializeComponent();
|
||||
this.library = library ?? throw new ArgumentNullException(nameof(library));
|
||||
this.serverUrl = serverUrl ?? "";
|
||||
browser.Initialize(sets, library, serverUrl);
|
||||
browser.ShowReleases = set => _ = ShowReleasesAsync(set);
|
||||
releasesView.Initialize(sets);
|
||||
Text = $"Drawing Sets — {serverUrl}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows Release: nests are saved through <paramref name="nests"/> and each is opened
|
||||
/// with <paramref name="openNest"/>; a nest <paramref name="isNestOpen"/> reports as open
|
||||
/// in a tab is never merged into.
|
||||
/// Shows Release, and New Release on the Releases tab: nests are saved through
|
||||
/// <paramref name="nests"/> and each is opened with <paramref name="openNest"/>; a nest
|
||||
/// <paramref name="isNestOpen"/> reports as open in a tab is never merged into.
|
||||
/// </summary>
|
||||
public void EnableRelease(INestRepository nests, Action<Nest, NestRecord> openNest, Func<Guid, bool> isNestOpen = null) =>
|
||||
public void EnableRelease(INestRepository nests, Action<Nest, NestRecord> openNest, Func<Guid, bool> isNestOpen = null)
|
||||
{
|
||||
browser.EnableRelease(nests, openNest, isNestOpen);
|
||||
releasesView.EnableRelease(library, nests, serverUrl, openNest, isNestOpen);
|
||||
}
|
||||
|
||||
private async void DrawingSetsForm_Shown(object sender, EventArgs e) => await browser.RefreshAsync();
|
||||
/// <summary>
|
||||
/// Switches to the Releases tab, listing the releases that include <paramref name="set"/>,
|
||||
/// that had <paramref name="drawing"/> on its own, or (both null) every release. Callable
|
||||
/// before the window is shown, to open it on that tab; the page is read once it shows.
|
||||
/// </summary>
|
||||
public Task ShowReleasesAsync(DrawingSetRecord set = null, DrawingLibraryRecord drawing = null)
|
||||
{
|
||||
releasesView.SetFilter(set, drawing);
|
||||
if (ShowsReleases)
|
||||
return shown ? releasesView.RefreshAsync() : Task.CompletedTask;
|
||||
// Selecting the tab reads the page once the window is shown.
|
||||
tabs.SelectedTab = releasesTab;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>Switches to the Sets tab.</summary>
|
||||
public void ShowSets() => tabs.SelectedTab = setsTab;
|
||||
|
||||
private async void DrawingSetsForm_Shown(object sender, EventArgs e)
|
||||
{
|
||||
shown = true;
|
||||
await RefreshSelectedTabAsync();
|
||||
}
|
||||
|
||||
private async void Tabs_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (shown)
|
||||
await RefreshSelectedTabAsync();
|
||||
}
|
||||
|
||||
private Task RefreshSelectedTabAsync()
|
||||
{
|
||||
if (ShowsReleases)
|
||||
return releasesView.RefreshAsync();
|
||||
return browser.HasLoaded ? Task.CompletedTask : browser.RefreshAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-5
@@ -65,6 +65,7 @@ namespace OpenNest.Forms
|
||||
downButton = new System.Windows.Forms.Button();
|
||||
bottomTable = new System.Windows.Forms.TableLayoutPanel();
|
||||
summaryLabel = new System.Windows.Forms.Label();
|
||||
historyButton = new System.Windows.Forms.Button();
|
||||
saveButton = new System.Windows.Forms.Button();
|
||||
cancelButton = new System.Windows.Forms.Button();
|
||||
statusLabel = new System.Windows.Forms.Label();
|
||||
@@ -572,13 +573,15 @@ namespace OpenNest.Forms
|
||||
// bottomTable
|
||||
//
|
||||
bottomTable.AutoSize = true;
|
||||
bottomTable.ColumnCount = 3;
|
||||
bottomTable.ColumnCount = 4;
|
||||
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||
bottomTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||
bottomTable.Controls.Add(summaryLabel, 0, 0);
|
||||
bottomTable.Controls.Add(saveButton, 1, 0);
|
||||
bottomTable.Controls.Add(cancelButton, 2, 0);
|
||||
bottomTable.Controls.Add(historyButton, 1, 0);
|
||||
bottomTable.Controls.Add(saveButton, 2, 0);
|
||||
bottomTable.Controls.Add(cancelButton, 3, 0);
|
||||
bottomTable.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
bottomTable.Margin = new System.Windows.Forms.Padding(0, 6, 0, 0);
|
||||
bottomTable.Name = "bottomTable";
|
||||
@@ -593,12 +596,23 @@ namespace OpenNest.Forms
|
||||
summaryLabel.Name = "summaryLabel";
|
||||
summaryLabel.TabIndex = 0;
|
||||
//
|
||||
// historyButton
|
||||
//
|
||||
historyButton.AutoSize = true;
|
||||
historyButton.Margin = new System.Windows.Forms.Padding(3, 3, 12, 3);
|
||||
historyButton.Name = "historyButton";
|
||||
historyButton.TabIndex = 1;
|
||||
historyButton.Text = "History...";
|
||||
historyButton.UseVisualStyleBackColor = true;
|
||||
historyButton.Visible = false;
|
||||
historyButton.Click += HistoryButton_Click;
|
||||
//
|
||||
// saveButton
|
||||
//
|
||||
saveButton.AutoSize = true;
|
||||
saveButton.Enabled = false;
|
||||
saveButton.Name = "saveButton";
|
||||
saveButton.TabIndex = 1;
|
||||
saveButton.TabIndex = 2;
|
||||
saveButton.Text = "Save";
|
||||
saveButton.UseVisualStyleBackColor = true;
|
||||
saveButton.Click += SaveButton_Click;
|
||||
@@ -608,7 +622,7 @@ namespace OpenNest.Forms
|
||||
cancelButton.AutoSize = true;
|
||||
cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
cancelButton.Name = "cancelButton";
|
||||
cancelButton.TabIndex = 2;
|
||||
cancelButton.TabIndex = 3;
|
||||
cancelButton.Text = "Cancel";
|
||||
cancelButton.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -731,6 +745,7 @@ namespace OpenNest.Forms
|
||||
private System.Windows.Forms.Button downButton;
|
||||
private System.Windows.Forms.TableLayoutPanel bottomTable;
|
||||
private System.Windows.Forms.Label summaryLabel;
|
||||
private System.Windows.Forms.Button historyButton;
|
||||
private System.Windows.Forms.Button saveButton;
|
||||
private System.Windows.Forms.Button cancelButton;
|
||||
private System.Windows.Forms.Label statusLabel;
|
||||
|
||||
@@ -20,8 +20,9 @@ namespace OpenNest.Forms
|
||||
/// selected drawings at the entered quantity, and a drawing already in the set is greyed
|
||||
/// and not added twice. Edit Drawing (a double-click on a member, its right-click menu or
|
||||
/// the button) reads the member's current library record and edits its details there, so
|
||||
/// a drawing without a material or thickness can be fixed in place. Saving adds a revision
|
||||
/// on the server, guarded by the revision the set was read at.
|
||||
/// a drawing without a material or thickness can be fixed in place. History lists a saved
|
||||
/// set's revisions. Saving adds a revision on the server, guarded by the revision the set
|
||||
/// was read at.
|
||||
/// </summary>
|
||||
public partial class EditDrawingSetForm : Form
|
||||
{
|
||||
@@ -39,6 +40,9 @@ namespace OpenNest.Forms
|
||||
/// <summary>Shows Edit Drawing and returns the saved record, or null; replaced by tests.</summary>
|
||||
internal Func<DrawingLibraryEditSession, DrawingLibraryRecord> EditDrawing { get; set; }
|
||||
|
||||
/// <summary>Shows a set's revision history; replaced by tests.</summary>
|
||||
internal Action<DrawingSetRecord> ShowHistory { get; set; }
|
||||
|
||||
/// <summary>The saved revision once the dialog closes with OK; otherwise null.</summary>
|
||||
public DrawingSetRecord Saved { get; private set; }
|
||||
|
||||
@@ -62,6 +66,11 @@ namespace OpenNest.Forms
|
||||
using var form = new EditLibraryDrawingForm(edit);
|
||||
return form.ShowDialog(this) == DialogResult.OK ? form.Saved : null;
|
||||
};
|
||||
ShowHistory = set =>
|
||||
{
|
||||
using var form = new DrawingSetHistoryForm(session.Store, set);
|
||||
form.ShowDialog(this);
|
||||
};
|
||||
|
||||
GridStyle.Apply(memberGrid);
|
||||
GridStyle.Apply(libraryGrid);
|
||||
@@ -80,6 +89,8 @@ namespace OpenNest.Forms
|
||||
Text = session.IsNew
|
||||
? "New Drawing Set"
|
||||
: $"Edit Set '{session.Saved.Name}' — revision {session.Saved.Revision}";
|
||||
// A new set has no history until it is saved, and the dialog closes on save.
|
||||
historyButton.Visible = !session.IsNew;
|
||||
Populate(session.Members.Count > 0 ? 0 : -1);
|
||||
UpdateCommands();
|
||||
}
|
||||
@@ -96,6 +107,12 @@ namespace OpenNest.Forms
|
||||
|
||||
private void MemberGrid_CurrentCellChanged(object sender, EventArgs e) => UpdateCommands();
|
||||
|
||||
private void HistoryButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (session.Saved is { } saved)
|
||||
ShowHistory(saved);
|
||||
}
|
||||
|
||||
private void MemberGrid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
|
||||
{
|
||||
if (e.ColumnIndex != quantityColumn.Index)
|
||||
|
||||
@@ -142,7 +142,12 @@ namespace OpenNest.Forms
|
||||
/// Browses, edits and releases drawing sets; needs no open nest, since a release
|
||||
/// creates or merges into nests and opens each in its own tab.
|
||||
/// </summary>
|
||||
private void DrawingSets_Click(object sender, EventArgs e)
|
||||
private void DrawingSets_Click(object sender, EventArgs e) => ShowDrawingSets(releases: false);
|
||||
|
||||
/// <summary>A shortcut to the Drawing Sets window's Releases tab, listing every release.</summary>
|
||||
private void Releases_Click(object sender, EventArgs e) => ShowDrawingSets(releases: true);
|
||||
|
||||
private void ShowDrawingSets(bool releases)
|
||||
{
|
||||
if (!NestStorage.Settings.IsDatabaseMode)
|
||||
return;
|
||||
@@ -164,35 +169,8 @@ namespace OpenNest.Forms
|
||||
|
||||
using var form = new DrawingSetsForm(sets, library, NestStorage.Settings.ServerUrl);
|
||||
form.EnableRelease(nests, OpenServerNest, IsServerNestOpen);
|
||||
form.ShowDialog(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lists every release on the nest server and starts new ones; needs no open nest, since
|
||||
/// a release creates or merges into nests and opens each in its own tab.
|
||||
/// </summary>
|
||||
private void Releases_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (!NestStorage.Settings.IsDatabaseMode)
|
||||
return;
|
||||
|
||||
IDrawingLibraryStore library;
|
||||
IDrawingSetStore sets;
|
||||
INestRepository nests;
|
||||
try
|
||||
{
|
||||
library = NestStorage.CreateDrawingLibrary();
|
||||
sets = NestStorage.CreateDrawingSetStore();
|
||||
nests = NestStorage.CreateRepository();
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
MessageBox.Show(this, ex.Message, "Releases", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
using var form = new SetReleasesForm(sets);
|
||||
form.EnableRelease(library, nests, NestStorage.Settings.ServerUrl, OpenServerNest, IsServerNestOpen);
|
||||
if (releases)
|
||||
_ = form.ShowReleasesAsync();
|
||||
form.ShowDialog(this);
|
||||
}
|
||||
|
||||
|
||||
+36
-24
@@ -404,7 +404,10 @@ revision; save the nest to keep it.
|
||||
|
||||
**File > Drawing Sets...** (Database mode only) opens the drawing sets window;
|
||||
it needs no open nest, since releasing a set makes new nests or merges into
|
||||
draft ones and opens each in its own tab. It lists sets a page at a time
|
||||
draft ones and opens each in its own tab. The window has two tabs, **Sets** and
|
||||
**Releases** ([below](#releases-tab)); **File > Releases...** is a shortcut that
|
||||
opens the same window on the Releases tab. Each tab reads its list when first
|
||||
shown, and Releases re-reads its page each time it is shown again. Sets lists sets a page at a time
|
||||
(`OpenNest.Data.DrawingSetBrowseSession`): search by name, customer or notes,
|
||||
sort by name, customer, revision or saved time, and **Show archived**. Selecting
|
||||
a set shows its notes.
|
||||
@@ -428,16 +431,19 @@ a set shows its notes.
|
||||
before its first save, and a save that fails without a definite answer stays
|
||||
pending: the next Save reads that id and adopts the stored set (saving any
|
||||
later edit as its next revision) instead of creating it twice. Closing with
|
||||
unsaved edits asks first.
|
||||
unsaved edits asks first. Editing a saved set, the editor's **History...**
|
||||
lists the set's revisions (below).
|
||||
- **Archive** (after confirming) and **Restore** add a revision; nothing is
|
||||
deleted. **History...** lists revisions newest first with the members of the
|
||||
selected one, shown with their drawings' current library details.
|
||||
deleted. **History...** (in the set editor, and on the Sets tab for the
|
||||
selected set) lists revisions newest first with the members of the selected
|
||||
one, shown with their drawings' current library details.
|
||||
|
||||
In the Drawing Library dialog, right-click a drawing for **Used in Sets...** (the sets
|
||||
whose current revision contains it) or **Add to Set...**: choose an active set
|
||||
or **New Set**, and the set editor opens with the drawing added at the entered
|
||||
quantity for review and Save. **Releases...** lists the releases that had the
|
||||
drawing on its own (see below), with Undo Release. When the dialog was opened
|
||||
quantity for review and Save. **Releases...** opens the drawing sets window on
|
||||
its Releases tab, filtered to the releases that had the drawing on its own (see
|
||||
below), with Undo Release. When the dialog was opened
|
||||
from a nest, **Make Nests...** reads the drawing's current record and opens the
|
||||
release dialog with it on its own at the entered quantity; more drawings and
|
||||
sets are added there.
|
||||
@@ -456,7 +462,7 @@ library's latest revision. Anything kept, skipped, archived in the library or
|
||||
newer there than in the nest is listed first, then the set editor opens for
|
||||
review and Save; nothing is stored before then.
|
||||
|
||||
**Make Nests...** (in the drawing sets window) reads the
|
||||
**Make Nests...** (on the drawing sets window's Sets tab) reads the
|
||||
selected active set with its members and opens the release dialog with it listed.
|
||||
More sets are added from **Add a set**: the list opens with the first active sets
|
||||
by name and narrows as you type; Enter or **Add** takes the picked set, or the one
|
||||
@@ -531,24 +537,30 @@ the button becomes **Try Again** and posts the same release again without making
|
||||
the nests again. A dialog
|
||||
runs one release.
|
||||
|
||||
**Releases...** lists the releases that include the selected set, newest first:
|
||||
when, by whom, the set's **Revision**, the **Quantity** of this set (and how many other sets
|
||||
and drawings the release had), the number of nests, progress such as "2 of 3 cut" (deleted
|
||||
nests are not counted) or "Undone", and the notes. The selected release's nests
|
||||
show their **Material**, the **Action** ("Created", "Merged (revision
|
||||
3 → 4)", and what undo did) and current status, or Deleted. **Undo Release...**
|
||||
deletes the nests the release created (they can be recovered) and restores the
|
||||
drafts it merged into. When some nests were changed after the release, it lists
|
||||
them and offers to undo the others, leaving those as they are.
|
||||
<a id="releases-tab"></a>
|
||||
The **Releases** tab lists every release on the server, newest first, 100 at a
|
||||
time (**< Newer** / **Older >**): when, by whom, a **Released** column naming what
|
||||
each one had ("Frame A ×2, Frame B; Bracket ×4": sets with their quantity past
|
||||
one, then drawings on their own with their quantities), the number of nests,
|
||||
progress such as "2 of 3 cut" (deleted nests are not counted) or "Undone", and the
|
||||
notes. The selected release's nests show their **Material**, the **Action**
|
||||
("Created", "Merged (revision 3 → 4)", and what undo did) and current status, or
|
||||
Deleted. **Undo Release...** deletes the nests the release created (they can be
|
||||
recovered) and restores the drafts it merged into. When some nests were changed
|
||||
after the release, it lists them and offers to undo the others, leaving those as
|
||||
they are. **New Release...** opens the release dialog empty; sets are added by
|
||||
name and drawings with Add Drawings, and once the dialog records a release the
|
||||
list re-reads from the newest.
|
||||
|
||||
**File > Releases...** lists every release on the server the same way, newest
|
||||
first, with a **Released** column naming what each one had ("Frame A ×2, Frame B;
|
||||
Bracket ×4": sets with their quantity past one, then drawings on their own with
|
||||
their quantities) in place of the revision and quantity, and Undo Release.
|
||||
**New Release...** opens the release dialog empty; sets are added by name and
|
||||
drawings with Add Drawings, and once the dialog records a release the list
|
||||
re-reads from the newest. Like Drawing Sets, it is enabled in database mode
|
||||
only.
|
||||
A filter narrows the tab to one set or one drawing, shown above the list
|
||||
("Releases of set 'Frame A'") until **Clear Filter** lists every release again:
|
||||
|
||||
- **Releases...** on the Sets tab filters to the releases that include the
|
||||
selected set, with the set's **Revision** and the **Quantity** of this set (and
|
||||
how many other sets and drawings the release had) in place of Released.
|
||||
- **Releases...** in the Drawing Library's right-click menu opens the window
|
||||
filtered to the releases that had that drawing on its own, with its
|
||||
**Quantity** (and how many sets and other drawings the release had).
|
||||
|
||||
**Nest > Edit** (Nest Info) shows, beside its buttons, the newest release that
|
||||
made or merged into a server nest ("Released from set 'Frame A' revision 3 ×2 on
|
||||
|
||||
Reference in new issue
Block a user