mirror of
https://github.com/microsoft/terminal.git
synced 2026-07-09 01:56:07 +00:00
## Summary of the Pull Request Adds customization for the New Tab Menu to the settings UI. - Settings Model changes: - The Settings UI generally works by creating a copy of the entire settings model objects on which we apply the changes to. Turns out, we completely left the NewTabMenu out of that process. So I went ahead and implemented it. - `FolderEntry` - `FolderEntry` exposes `Entries()` (used by the new tab menu to figure out what to actually render) and `RawEntries()` (the actual JSON data deserialized into settings model objects). I went ahead and exposed `RawEntries()` since we'll need to apply changes to it to then serialize. - View Model: - `NewTabMenuViewModel` is the main view model that interacts with the page. It maintains the current view of items and applies changes to the settings model. - `NewTabMenuEntryViewModel` and all of the other `_EntryViewModel` classes are wrappers for the settings model NTM entries. - `FolderTreeViewEntry` encapsulates `FolderEntryViewModel`. It allows us to construct a `TreeView` of just folders. - View changes and additions: - Added FontIconGlyph to the SettingContainer - Added a New Tab Menu item to the navigation view - Adding entries: a stack of SettingContainers is used here. We use the new `FontIconGlyph` to make this look nice! - Reordering entries: drag and drop is supported! This might not work in admin mode though, and we can't drag and drop into folders. Buttons were added to make this keyboard accessible. - To move entries into a folder, a button was added which then displays a TreeView of all folders. - Multiple entries can be moved to a folder or deleted at once! - Breadcrumbs are used for folders - When a folder is entered, additional controls are displayed to customize that folder. ## Verification - ✅ a11y pass - ✅ keyboard accessible - scenarios: - ✅ add entries (except actions) - ✅ changes propagated to settings model (aka "saving works") - ✅ reorder entries - ✅ move entries to an existing folder - ✅ delete multiple entries - ✅ delete individual entries - ✅ display entries (including actions) ## Follow-ups - [ ] add support for adding and editing action entries - [ ] when we discard changes or save, it would be cool if we could stay on the same page - [ ] allow customizing the folder entry _before_ adding it (current workaround is to add it, then edit it) - [ ] improve UI for setting icon (reuse UI from #17965)
145 lines
4.9 KiB
C++
145 lines
4.9 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "FolderEntry.h"
|
|
#include "JsonUtils.h"
|
|
#include "TerminalSettingsSerializationHelpers.h"
|
|
|
|
#include "FolderEntry.g.cpp"
|
|
|
|
using namespace Microsoft::Terminal::Settings::Model;
|
|
using namespace winrt::Windows::Foundation::Collections;
|
|
|
|
static constexpr std::string_view NameKey{ "name" };
|
|
static constexpr std::string_view IconKey{ "icon" };
|
|
static constexpr std::string_view EntriesKey{ "entries" };
|
|
static constexpr std::string_view InliningKey{ "inline" };
|
|
static constexpr std::string_view AllowEmptyKey{ "allowEmpty" };
|
|
|
|
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|
{
|
|
FolderEntry::FolderEntry() noexcept :
|
|
FolderEntry{ winrt::hstring{} }
|
|
{
|
|
}
|
|
|
|
FolderEntry::FolderEntry(const winrt::hstring& name) noexcept :
|
|
FolderEntryT<FolderEntry, NewTabMenuEntry>(NewTabMenuEntryType::Folder),
|
|
_Name{ name }
|
|
{
|
|
}
|
|
|
|
Json::Value FolderEntry::ToJson() const
|
|
{
|
|
auto json = NewTabMenuEntry::ToJson();
|
|
|
|
JsonUtils::SetValueForKey(json, NameKey, _Name);
|
|
JsonUtils::SetValueForKey(json, IconKey, _Icon);
|
|
JsonUtils::SetValueForKey(json, EntriesKey, _RawEntries);
|
|
JsonUtils::SetValueForKey(json, InliningKey, _Inlining);
|
|
JsonUtils::SetValueForKey(json, AllowEmptyKey, _AllowEmpty);
|
|
|
|
return json;
|
|
}
|
|
|
|
winrt::com_ptr<NewTabMenuEntry> FolderEntry::FromJson(const Json::Value& json)
|
|
{
|
|
auto entry = winrt::make_self<FolderEntry>();
|
|
|
|
JsonUtils::GetValueForKey(json, NameKey, entry->_Name);
|
|
JsonUtils::GetValueForKey(json, IconKey, entry->_Icon);
|
|
JsonUtils::GetValueForKey(json, EntriesKey, entry->_RawEntries);
|
|
JsonUtils::GetValueForKey(json, InliningKey, entry->_Inlining);
|
|
JsonUtils::GetValueForKey(json, AllowEmptyKey, entry->_AllowEmpty);
|
|
|
|
return entry;
|
|
}
|
|
|
|
// A FolderEntry should only expose the entries to actually render to WinRT,
|
|
// to keep the logic for collapsing/expanding more centralised.
|
|
IVector<Model::NewTabMenuEntry> FolderEntry::Entries() const
|
|
{
|
|
// We filter the full list of entries from JSON to just include the
|
|
// non-empty ones.
|
|
IVector<Model::NewTabMenuEntry> result{ winrt::single_threaded_vector<Model::NewTabMenuEntry>() };
|
|
if (_RawEntries == nullptr)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
for (const auto& entry : _RawEntries)
|
|
{
|
|
if (entry == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (entry.Type())
|
|
{
|
|
case NewTabMenuEntryType::Invalid:
|
|
continue;
|
|
|
|
// A profile is filtered out if it is not valid, so if it was not resolved
|
|
case NewTabMenuEntryType::Profile:
|
|
{
|
|
const auto profileEntry = entry.as<Model::ProfileEntry>();
|
|
if (profileEntry.Profile() == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Any profile collection is filtered out if there are no results
|
|
case NewTabMenuEntryType::RemainingProfiles:
|
|
case NewTabMenuEntryType::MatchProfiles:
|
|
{
|
|
const auto profileCollectionEntry = entry.as<Model::ProfileCollectionEntry>();
|
|
if (profileCollectionEntry.Profiles().Size() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// A folder is filtered out if it has an effective size of 0 (calling
|
|
// this filtering method recursively), and if it is not allowed to be
|
|
// empty, or if it should auto-inline.
|
|
case NewTabMenuEntryType::Folder:
|
|
{
|
|
const auto folderEntry = entry.as<Model::FolderEntry>();
|
|
if (folderEntry.Entries().Size() == 0 && (!folderEntry.AllowEmpty() || folderEntry.Inlining() == FolderEntryInlining::Auto))
|
|
{
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
result.Append(entry);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
Model::NewTabMenuEntry FolderEntry::Copy() const
|
|
{
|
|
auto entry = winrt::make_self<FolderEntry>();
|
|
entry->_Name = _Name;
|
|
entry->_Icon = _Icon;
|
|
entry->_Inlining = _Inlining;
|
|
entry->_AllowEmpty = _AllowEmpty;
|
|
|
|
if (_RawEntries)
|
|
{
|
|
entry->_RawEntries = winrt::single_threaded_vector<Model::NewTabMenuEntry>();
|
|
for (const auto& e : _RawEntries)
|
|
{
|
|
entry->_RawEntries.Append(get_self<NewTabMenuEntry>(e)->Copy());
|
|
}
|
|
}
|
|
return *entry;
|
|
}
|
|
}
|