Initial revision, sorts Kerbals in VAB and nowhere else
This commit is contained in:
commit
0542e8be07
7 changed files with 443 additions and 0 deletions
2
.hgignore
Normal file
2
.hgignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Debug
|
||||||
|
Release
|
BIN
.vs/AlphabeticalKerbals/v14/.suo
Normal file
BIN
.vs/AlphabeticalKerbals/v14/.suo
Normal file
Binary file not shown.
22
AlphabeticalKerbals.sln
Normal file
22
AlphabeticalKerbals.sln
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 14
|
||||||
|
VisualStudioVersion = 14.0.25420.1
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlphabeticalKerbals", "AlphabeticalKerbals\AlphabeticalKerbals.csproj", "{D01919EF-056F-4311-84AE-E4E686CA033D}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{D01919EF-056F-4311-84AE-E4E686CA033D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D01919EF-056F-4311-84AE-E4E686CA033D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D01919EF-056F-4311-84AE-E4E686CA033D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D01919EF-056F-4311-84AE-E4E686CA033D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
65
AlphabeticalKerbals/AlphabeticalKerbals.csproj
Normal file
65
AlphabeticalKerbals/AlphabeticalKerbals.csproj
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{D01919EF-056F-4311-84AE-E4E686CA033D}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>AlphabeticalKerbals</RootNamespace>
|
||||||
|
<AssemblyName>AlphabeticalKerbals</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Assembly-CSharp">
|
||||||
|
<HintPath>..\..\..\..\VMShared\KSP_1.3.1\Assembly-CSharp.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Assembly-CSharp-firstpass">
|
||||||
|
<HintPath>..\..\..\..\VMShared\KSP_1.3.1\Assembly-CSharp-firstpass.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
<Reference Include="UnityEngine">
|
||||||
|
<HintPath>..\..\..\..\VMShared\KSP_1.3.1\UnityEngine.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.UI">
|
||||||
|
<HintPath>..\..\..\..\VMShared\KSP_1.3.1\UnityEngine.UI.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="KerbalSorter.cs" />
|
||||||
|
<Compile Include="SceneHooks.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
</Target>
|
||||||
|
-->
|
||||||
|
</Project>
|
193
AlphabeticalKerbals/KerbalSorter.cs
Normal file
193
AlphabeticalKerbals/KerbalSorter.cs
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
using System;
|
||||||
|
using KSP;
|
||||||
|
using KSP.UI;
|
||||||
|
using UnityEngine;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace AlphabeticalKerbals
|
||||||
|
{
|
||||||
|
class KerbalSorter
|
||||||
|
{
|
||||||
|
static void print(string s) { MonoBehaviour.print("AlphabeticalKerbals: " + s); }
|
||||||
|
static public bool Sort_Kerbals()
|
||||||
|
{
|
||||||
|
if (CrewAssignmentDialog.Instance == null)
|
||||||
|
{
|
||||||
|
print("OnEditorCrewOpened has no CrewAssignmentDialog yet...");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UIList avail = CrewAssignmentDialog.Instance.scrollListAvail;
|
||||||
|
|
||||||
|
UIList_QSort(avail, 0, avail.Count-1);
|
||||||
|
|
||||||
|
for (int i = 0; i < avail.Count; i++)
|
||||||
|
{
|
||||||
|
UIListItem li = avail.GetUilistItemAt(i);
|
||||||
|
CrewListItem crew = li.GetComponent<CrewListItem>();
|
||||||
|
print("AFTER SORT = " + crew.GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
print("AlphabeticalKerbals: OnEditorCrewOpened has CrewAssignmentDialog yay!!!");
|
||||||
|
UIList avail = CrewAssignmentDialog.Instance.scrollListAvail;
|
||||||
|
UIListItem first = avail.GetUilistItemAt(0);
|
||||||
|
|
||||||
|
print("AlphabeticalKerbals: Got first item in UIList");
|
||||||
|
|
||||||
|
if (first == null)
|
||||||
|
{
|
||||||
|
//happens on first load
|
||||||
|
print("AlphabeticalKerbals: Uhhh.... first is null?");
|
||||||
|
}
|
||||||
|
if (first.gameObject == null)
|
||||||
|
{
|
||||||
|
print("AlphabeticalKerbals: Uhhh.... gameObject is null?");
|
||||||
|
}
|
||||||
|
Component[] comps = first.gameObject.GetComponents<Component>();
|
||||||
|
print("AlphabeticalKerbals: Uhhh.... got components???");
|
||||||
|
for (int i = 0; i < comps.Length; ++i)
|
||||||
|
{
|
||||||
|
print($"Component {i}: {comps[i].GetType()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
CrewListItem firstdata = first.GetComponent<CrewListItem>();
|
||||||
|
print("AlphabeticalKerbals: Got a component for crew list item");
|
||||||
|
print("AlphabeticalKerbals: Got crew name: " + firstdata.GetName());
|
||||||
|
|
||||||
|
print("AlphabeticalKerbals: Got component list");
|
||||||
|
foreach (Component comp in firstdata)
|
||||||
|
{
|
||||||
|
print("AlphabeticalKerbals: Got a component");
|
||||||
|
print("AlphabeticalKerbals: Component name is " + comp.name);
|
||||||
|
print("AlphabeticalKerbals: Component type is " + comp.GetType().Name);
|
||||||
|
print("AlphabeticalKerbals: Component string is " + comp.ToString());
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return value positive means left > right, return value negative means left < right
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left"></param>
|
||||||
|
/// <param name="right"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
static int UIList_Cmp(UIListItem left, UIListItem right)
|
||||||
|
{
|
||||||
|
if (left == null) { return 1; } // null values are considered greatest, so they are sorted at the end
|
||||||
|
if (right == null) { return -1; } // null values are considered greatest, so they are sorted at the end
|
||||||
|
|
||||||
|
CrewListItem ldata = null;
|
||||||
|
CrewListItem rdata = null;
|
||||||
|
try { ldata = left.GetComponent<CrewListItem>(); } catch { return 1; }
|
||||||
|
try { rdata = right.GetComponent<CrewListItem>(); } catch { return -1; }
|
||||||
|
|
||||||
|
if (ldata == null) { return 1; }
|
||||||
|
if (rdata == null) { return -1; }
|
||||||
|
|
||||||
|
return ldata.GetName().CompareTo(rdata.GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int UIList_Partition(UIList list, int li, int ri)
|
||||||
|
{
|
||||||
|
// Select a random pivot point
|
||||||
|
int pi = UnityEngine.Random.Range(li+1, ri+1);
|
||||||
|
UIListItem pivot = list.GetUilistItemAt(pi);
|
||||||
|
print("Selected pivot is " + pivot.GetComponent<CrewListItem>().GetName() + " at pos " + pi.ToString());
|
||||||
|
|
||||||
|
if (pi < ri)
|
||||||
|
{
|
||||||
|
// Move the pivot to the very end
|
||||||
|
list.SwapItems(pivot, list.GetUilistItemAt(ri));
|
||||||
|
pi = ri;
|
||||||
|
}
|
||||||
|
UIList_DebugPrint(list, li, ri, "Pivot-selection");
|
||||||
|
|
||||||
|
// Iterate over the list and sort above or below pivot as we go
|
||||||
|
int i = li;
|
||||||
|
UIListItem prev = list.GetUilistItemAt(i);
|
||||||
|
|
||||||
|
for (int j = li; j < ri; j++)
|
||||||
|
{
|
||||||
|
UIListItem selected = list.GetUilistItemAt(j);
|
||||||
|
if (UIList_Cmp(selected, pivot) < 1)
|
||||||
|
{
|
||||||
|
print("Pivotname is " + pivot.GetComponent<CrewListItem>().GetName() + " selname is " + selected.GetComponent<CrewListItem>().GetName());
|
||||||
|
UIList_DebugPrint(list, i, j, "Partition-swapping (" + i.ToString() + "," + j.ToString() + "," + UIList_Cmp(selected, pivot) + ")");
|
||||||
|
if (i < j)
|
||||||
|
{
|
||||||
|
list.SwapItems(prev, selected);
|
||||||
|
}
|
||||||
|
UIList_DebugPrint(list, li, ri, "Afterswap i=" + i.ToString());
|
||||||
|
i++;
|
||||||
|
prev = list.GetUilistItemAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
UIListItem pivot_target = list.GetUilistItemAt(i);
|
||||||
|
if (UIList_Cmp(pivot_target, pivot) >= 0)
|
||||||
|
{
|
||||||
|
UIList_DebugPrint(list, li, ri, "Finalswap i=" + i.ToString());
|
||||||
|
list.SwapItems(list.GetUilistItemAt(i), pivot);
|
||||||
|
UIList_DebugPrint(list, li, ri, "Finalswap done i=" + i.ToString());
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ri;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
static void UIList_QSort(UIList list, int li, int ri)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (list == null || list.Count <= 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (li < ri)
|
||||||
|
{
|
||||||
|
print("Before partition from " + li.ToString() + "-" + ri.ToString());
|
||||||
|
UIList_DebugPrint(list, li, ri, "Initial");
|
||||||
|
|
||||||
|
int pi = UIList_Partition(list, li, ri);
|
||||||
|
|
||||||
|
print ("After partition to pivot " + pi.ToString());
|
||||||
|
UIList_DebugPrint(list, li, pi-1, "Left");
|
||||||
|
UIList_DebugPrint(list, pi, pi, "Pivot");
|
||||||
|
UIList_DebugPrint(list, pi+1, ri, "Right");
|
||||||
|
|
||||||
|
UIList_QSort(list, li, pi - 1);
|
||||||
|
UIList_QSort(list, pi + 1, ri);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void UIList_DebugPrint(UIList list, int li, int ri, string msg)
|
||||||
|
{
|
||||||
|
string outmsg = msg + " -- ";
|
||||||
|
|
||||||
|
for (int i = li; i < ri+1; i++)
|
||||||
|
{
|
||||||
|
UIListItem curitem = list.GetUilistItemAt(i);
|
||||||
|
CrewListItem crew = curitem.GetComponent<CrewListItem>();
|
||||||
|
outmsg = outmsg + crew.GetName().Split(' ')[0] + "[" + i.ToString() + "], ";
|
||||||
|
}
|
||||||
|
|
||||||
|
print(outmsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
36
AlphabeticalKerbals/Properties/AssemblyInfo.cs
Normal file
36
AlphabeticalKerbals/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("AlphabeticalKerbals")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("Microsoft")]
|
||||||
|
[assembly: AssemblyProduct("AlphabeticalKerbals")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © Microsoft 2017")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("d01919ef-056f-4311-84ae-e4e686ca033d")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
125
AlphabeticalKerbals/SceneHooks.cs
Normal file
125
AlphabeticalKerbals/SceneHooks.cs
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
using System;
|
||||||
|
using KSP;
|
||||||
|
using KSP.UI;
|
||||||
|
using KSP.UI.Screens;
|
||||||
|
using UnityEngine;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace AlphabeticalKerbals
|
||||||
|
{
|
||||||
|
|
||||||
|
[KSPAddon(KSPAddon.Startup.EditorAny, false)]
|
||||||
|
public class AlphabetVAB : MonoBehaviour
|
||||||
|
{
|
||||||
|
public bool HasBeenSorted;
|
||||||
|
public EditorScreen CurrentEditorScreen;
|
||||||
|
private int sortAttempts;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Module initialization
|
||||||
|
/// </summary>
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
print("AlphabeticalKerbals: AlphabetVAB started!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Module startup
|
||||||
|
/// </summary>
|
||||||
|
public void Awake()
|
||||||
|
{
|
||||||
|
print("AlphabeticalKerbals: Registering VAB events");
|
||||||
|
GameEvents.onEditorLoad.Add(OnShipLoaded);
|
||||||
|
GameEvents.onEditorScreenChange.Add(OnEditorScreenChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Module shutdown
|
||||||
|
/// </summary>
|
||||||
|
public void OnDestroy()
|
||||||
|
{
|
||||||
|
print("AlphabeticalKerbals: Unregistering VAB events");
|
||||||
|
GameEvents.onEditorLoad.Remove(OnShipLoaded);
|
||||||
|
GameEvents.onEditorScreenChange.Remove(OnEditorScreenChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Here when a ship is loaded in the editor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="construct"></param>
|
||||||
|
/// <param name="loadType"></param>
|
||||||
|
private void OnShipLoaded(ShipConstruct construct, CraftBrowserDialog.LoadType loadType)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CurrentEditorScreen = EditorScreen.Parts;
|
||||||
|
HasBeenSorted = false;
|
||||||
|
sortAttempts = 0;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
print("AlphabeticalKerbals: There was an error in OnShipLoaded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Here when Editor Panel is changed
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="construct"></param>
|
||||||
|
private void OnEditorScreenChange(EditorScreen screen)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CurrentEditorScreen = screen;
|
||||||
|
HasBeenSorted = false;
|
||||||
|
sortAttempts = 0;
|
||||||
|
|
||||||
|
if (screen == EditorScreen.Crew)
|
||||||
|
{
|
||||||
|
OnEditorCrewOpened();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
print("AlphabeticalKerbals: There was an error in OnEditorScreenChange");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Here when Editor Crew Panel is opened
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventType"></param>
|
||||||
|
/// <param name="part"></param>
|
||||||
|
private void OnEditorCrewOpened()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (KerbalSorter.Sort_Kerbals())
|
||||||
|
{
|
||||||
|
print("AlphabeticalKerbals: CrewPanel Sorting successful!");
|
||||||
|
HasBeenSorted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
print("AlphabeticalKerbals: There was an error in OnEditorCrewOpened");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
|
||||||
|
public class AlphabetSC : MonoBehaviour
|
||||||
|
{
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
print("AlphabeticalKerbals: AlphabetSC started!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue